/** \file turnout.c * Turnout drawing */ /* XTrkCad - Model Railroad CAD * Copyright (C) 2005 Dave Bullis * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include "common-ui.h" #include "compound.h" #include "custom.h" #include "draw.h" #include "track.h" /* Draw turnout data */ /** * The types of turnouts that get enhanced drawing methods */ enum dtoType { DTO_INVALID, DTO_NORMAL, DTO_THREE, DTO_WYE, DTO_CURVED, DTO_XING, DTO_XNG9, DTO_SSLIP, DTO_DSLIP, DTO_LCROSS, DTO_RCROSS, DTO_DCROSS }; // Define to plot control points (DTO_NORMAL, DTO_CURVED, DTO_XING, DTO_LCROSS) // #define DTO_DEBUG DTO_XING #define DTO_DIM 4 // Maximum number of paths #define DTO_SEGS 24 // Maximum number of control points static struct DrawToData_t { TRKINX_T index; enum dtoType toType; track_p trk; tieData_t td; int bridge; int roadbed; int endCnt; int pathCnt; int routeCnt; int strCnt; int crvCnt; int rgtCnt; int lftCnt; int strPath; int str2Path; int crvPath; int crv2Path; int origCnt; int origins[DTO_DIM]; coOrd midPt; struct extraDataCompound_t* xx; } dtod; struct DrawTo_t { int n; trkSeg_p trkSeg[DTO_SEGS]; coOrd base[DTO_SEGS]; coOrd baseLast; DIST_T dy[DTO_SEGS]; ANGLE_T angle; ANGLE_T crvAngle; coOrd pts[DTO_SEGS]; coOrd ptsLast; char type; }; static struct DrawTo_t dto[DTO_DIM]; /**************************************** * * Draw Turnout Roadbed * */ int roadbedOnScreen = 0; void DrawTurnoutRoadbedSide(drawCmd_p d, wDrawColor color, coOrd orig, ANGLE_T angle, trkSeg_p sp, ANGLE_T side, int first, int last) { segProcData_t data; if (last <= first) { return; } data.drawRoadbedSide.first = first; data.drawRoadbedSide.last = last; data.drawRoadbedSide.side = side; data.drawRoadbedSide.roadbedWidth = roadbedWidth; data.drawRoadbedSide.rbw = (wDrawWidth)floor(roadbedLineWidth * (d->dpi / d->scale) + 0.5); data.drawRoadbedSide.orig = orig; data.drawRoadbedSide.angle = angle; data.drawRoadbedSide.color = color; data.drawRoadbedSide.d = d; SegProc(SEGPROC_DRAWROADBEDSIDE, sp, &data); } static void ComputeAndDrawTurnoutRoadbedSide( drawCmd_p d, wDrawColor color, coOrd orig, ANGLE_T angle, trkSeg_p segPtr, int segCnt, int segInx, ANGLE_T side) { unsigned long res, res1; int b0, b1; res = ComputeTurnoutRoadbedSide(segPtr, segCnt, segInx, side, roadbedWidth); if (res == 0L) { } else if (res == 0xFFFFFFFF) { DrawTurnoutRoadbedSide(d, color, orig, angle, &segPtr[segInx], side, 0, 32); } else { for (b0 = 0, res1 = 0x00000001; res1 && (res1 & res); b0++, res1 <<= 1); for (b1 = 32, res1 = 0x80000000; res1 && (res1 & res); b1--, res1 >>= 1); DrawTurnoutRoadbedSide(d, color, orig, angle, &segPtr[segInx], side, 0, b0); DrawTurnoutRoadbedSide(d, color, orig, angle, &segPtr[segInx], side, b1, 32); } } static void DrawTurnoutRoadbed( drawCmd_p d, wDrawColor color, coOrd orig, ANGLE_T angle, trkSeg_p segPtr, int segCnt) { int inx, trkCnt = 0, segInx = 0; for (inx = 0; inx < segCnt; inx++) { if (IsSegTrack(&segPtr[inx])) { segInx = inx; trkCnt++; if (trkCnt > 1) { break; } } } if (trkCnt == 0) { return; } if (trkCnt == 1) { DrawTurnoutRoadbedSide(d, color, orig, angle, &segPtr[segInx], +90, 0, 32); DrawTurnoutRoadbedSide(d, color, orig, angle, &segPtr[segInx], -90, 0, 32); } else { for (inx = 0; inx < segCnt; inx++) { if (IsSegTrack(&segPtr[inx])) { ComputeAndDrawTurnoutRoadbedSide(d, color, orig, angle, segPtr, segCnt, inx, +90); ComputeAndDrawTurnoutRoadbedSide(d, color, orig, angle, segPtr, segCnt, inx, -90); } } } } /**************************************** * * TURNOUT DRAWING * */ /** * Get the paths from the turnout definition. Puts the results into static dto structure. * Curved segments are broken up into short sections of the lesser of 5 degrees or 5 * tie spacing. * * \param trk track_p pointer to a track * \param xx pointer to the extraDataCompound struct * * \returns the number of paths */ int GetTurnoutPaths(track_p trk, struct extraDataCompound_t* xx) { wIndex_t segInx; wIndex_t segEP; int i; ANGLE_T a0, a1, aa0, aa1; DIST_T r, len; coOrd p0, p1; PATHPTR_T pp; dtod.trk = trk; dtod.index = GetTrkIndex( trk ); dtod.xx = xx; dtod.td = GetTrkTieData( trk ); int pathCnt = 0, routeCnt = 0; for (i = 0; i < DTO_DIM; i++) { dto[i].n = 0; } // Validate that the first segment starts at (0, 0) // and if STR p1.y == 0, if CRV angle == 0 or angle == 180 GetSegInxEP(1, &segInx, &segEP); trkSeg_p segPtr = &xx->segs[segInx]; switch (segPtr->type) { case SEG_STRTRK: p0 = segPtr->u.l.pos[0]; p1 = segPtr->u.l.pos[1]; if ((FindDistance(p0, zero) > EPSILON) || (fabs(p1.y) > EPSILON)) { return -1; } break; case SEG_CRVTRK: r = fabs(segPtr->u.c.radius); a0 = segPtr->u.c.a0; a1 = segPtr->u.c.a1; if (segPtr->u.c.radius > 0) { aa0 = a0; } else { aa0 = a0 + a1; } PointOnCircle(&p0, segPtr->u.c.center, r, aa0); if ((FindDistance(p0, zero) > EPSILON) || ((fabs(aa0 - 180) > EPSILON) && (fabs(aa0) > EPSILON))) { return -1; } break; } pp = GetPaths(trk); while (pp[0]) { pp += strlen((char*)pp) + 1; ANGLE_T angle = 0; while (pp[0]) { if (pathCnt < DTO_DIM) { dto[pathCnt].type = 'S'; while (pp[0]) { GetSegInxEP(pp[0], &segInx, &segEP); // trkSeg_p segPtr = &xx->segs[segInx]; switch (segPtr->type) { case SEG_STRTRK: p0 = segPtr->u.l.pos[0]; p1 = segPtr->u.l.pos[1]; wIndex_t n = dto[pathCnt].n; dto[pathCnt].trkSeg[n] = segPtr; dto[pathCnt].base[n] = p0; n++; dto[pathCnt].trkSeg[n] = segPtr; dto[pathCnt].base[n] = p1; // n++; dto[pathCnt].n = n; if (n >= DTO_SEGS - 1) { return -1; } break; case SEG_CRVTRK: r = fabs(segPtr->u.c.radius); dto[pathCnt].type = segPtr->u.c.center.y < 0 ? 'R' : 'L'; a0 = segPtr->u.c.a0; a1 = segPtr->u.c.a1; angle += a1; len = D2R(a1) * r; // Every 5 degrees or 5 * tie spacing int cnt = (int)floor(a1 / 5.0); int cnt2 = (int)floor(len / 5 / dtod.td.spacing); if (cnt2 > cnt) { cnt = cnt2; } if (cnt <= 0) { cnt = 1; } aa1 = a1 / cnt; if (dto[pathCnt].type == 'R') { aa0 = a0; } else { aa0 = a0 + a1; aa1 = -aa1; } PointOnCircle(&p0, segPtr->u.c.center, r, aa0); n = dto[pathCnt].n; dto[pathCnt].trkSeg[n] = segPtr; dto[pathCnt].base[n] = p0; n++; dto[pathCnt].n = n; while (cnt > 0) { aa0 += aa1; PointOnCircle(&p0, segPtr->u.c.center, r, aa0); // n = dto[pathCnt].n; dto[pathCnt].trkSeg[n] = segPtr; dto[pathCnt].base[n] = p0; n++; if (n >= DTO_SEGS - 1) { return -1; } cnt--; } n--; // remove that last point count dto[pathCnt].n = n; } pp++; } // Include the last point dto[pathCnt].crvAngle = angle; dto[pathCnt].n++; } pathCnt++; if (pathCnt > DTO_DIM) { return -1; } pp++; } routeCnt++; pp++; } dtod.pathCnt = pathCnt; dtod.routeCnt = routeCnt; dtod.endCnt = GetTrkEndPtCnt( trk ); // Guard value: n < DTO_SEGS - 2 for (i = 0; i < pathCnt; i++) { dto[i].pts[dto[i].n].x = DIST_INF; } return pathCnt; } /** * Sets the turnout type if compatible with enhanced drawing methods. The data is * from the path data saved in dtod and dto by GetTurnoutPaths. The turnout type is * stored in the dtod.toType. DTO_INVALID (0) if the enhanced methods cannot handle * it. */ void GetTurnoutType() { dtod.strPath = -1; dtod.str2Path = -1; dtod.crvPath = -1; dtod.crv2Path = -1; dtod.toType = DTO_INVALID; int strCnt = 0, crvCnt = 0, lftCnt = 0, rgtCnt = 0; // enum dtoType toType = DTO_INVALID; int i, j; // Count path origins dtod.origCnt = 1; dtod.origins[0] = 0; for (i = 1; i < dtod.pathCnt; i++) { int eq = 0; for (j = 0; j < i; j++) { if (CoOrdEqual(dto[dtod.origins[j]].base[0], dto[i].base[0])) { eq++; break; } } if (eq == 0) { if (dtod.origCnt < DTO_DIM) { dtod.origins[dtod.origCnt] = i; } dtod.origCnt++; } if (dtod.origCnt >= DTO_DIM) { return; } } // Determine the path type for (i = 0; i < dtod.pathCnt; i++) { switch (dto[i].type) { case 'S': strCnt++; if (strCnt == 1) { dtod.strPath = i; } else { dtod.str2Path = i; } break; case 'L': lftCnt++; crvCnt++; if (crvCnt == 1) { dtod.crvPath = i; } else { dtod.crv2Path = i; } break; case 'R': rgtCnt++; crvCnt++; if (crvCnt == 1) { dtod.crvPath = i; } else { dtod.crv2Path = i; } break; } } dtod.strCnt = strCnt; dtod.crvCnt = crvCnt; dtod.lftCnt = lftCnt; dtod.rgtCnt = rgtCnt; // Normal two- or three-way turnout, or a curved turnout if (dtod.origCnt == 1) { if (dtod.pathCnt == 2) { if (strCnt == 1 && crvCnt == 1) { dtod.toType = DTO_NORMAL; } else if ((strCnt == 0) && ((lftCnt == 2) || (rgtCnt == 2))) { // Assumes outer curve is [0] and inner is [1] if ((dto[0].crvAngle <= 20) && (dto[1].crvAngle - dto[0].crvAngle <= 15)) { dtod.toType = DTO_CURVED; } } else if (lftCnt == 1 && rgtCnt == 1) { dtod.toType = DTO_WYE; } } else if ((dtod.pathCnt == 3) && (strCnt == 1) && (lftCnt == 1) && (rgtCnt == 1)) { dtod.toType = DTO_THREE; } } else // Crossing, single- and double-slip if ((dtod.origCnt == 2) && (dtod.endCnt == 4) && strCnt == 2) { ANGLE_T a0, a1, a2; a1 = FindAngle(dto[dtod.strPath].base[0], dto[dtod.strPath].base[1]); a2 = FindAngle(dto[dtod.str2Path].base[0], dto[dtod.str2Path].base[1]); // Swap the ends of the strPath if large angle if((a1 > 180.0) && (dto[dtod.strPath].n == 2)) { coOrd tmp = dto[dtod.strPath].base[0]; dto[dtod.strPath].base[0] = dto[dtod.strPath].base[1]; dto[dtod.strPath].base[1] = tmp; i = dto[dtod.strPath].n - 1; tmp = dto[dtod.strPath].pts[0]; dto[dtod.strPath].pts[0] = dto[dtod.strPath].pts[i]; dto[dtod.strPath].pts[i] = tmp; a1 = a1 - 180.0; dto[dtod.strPath].angle = a1; } // Swap the ends of the str2Path if large angle if((a2 > 180.0) && (dto[dtod.str2Path].n == 2)) { coOrd tmp = dto[dtod.str2Path].base[0]; dto[dtod.str2Path].base[0] = dto[dtod.str2Path].base[1]; dto[dtod.str2Path].base[1] = tmp; i = dto[dtod.str2Path].n - 1; tmp = dto[dtod.str2Path].pts[0]; dto[dtod.str2Path].pts[0] = dto[dtod.str2Path].pts[i]; dto[dtod.str2Path].pts[i] = tmp; a2 = a2 - 180.0; dto[dtod.str2Path].angle = a2; } a0 = DifferenceBetweenAngles(a1, a2); if(a0 < 0) { int tmp = dtod.strPath; dtod.strPath = dtod.str2Path; dtod.str2Path = tmp; a0 = NormalizeAngle(-a0); } if ((a0 > 90.0) || (a0 < 0.0)) { return; } coOrd p1 = dto[dtod.strPath].base[0]; coOrd p2 = dto[dtod.str2Path].base[0]; coOrd pos = zero; int intersect = FindIntersection(&pos, p1, a1, p2, a2); if (intersect) { if(strCnt == 2 && dtod.pathCnt == 2) { if((a0 <= 61) && (a0 >= -61)) { dtod.toType = DTO_XING; } else { dtod.toType = DTO_XNG9; } } else if(dtod.pathCnt == 3 && (lftCnt == 1 || rgtCnt == 1)) { dtod.toType = DTO_SSLIP; } else if(dtod.pathCnt == 4 && lftCnt == 1 && rgtCnt == 1) { dtod.toType = DTO_DSLIP; } } // No intersect, it could be a crossover else if (strCnt == 2) { if (dtod.pathCnt == 4 && lftCnt == 1 && rgtCnt == 1) { dtod.toType = DTO_DCROSS; } else if(dtod.pathCnt == 3) { // Perverse test because the cross paths go Left then Right, for example if(lftCnt == 1) { dtod.toType = DTO_RCROSS; } else if(rgtCnt == 1) { dtod.toType = DTO_LCROSS; } else { dtod.toType = DTO_INVALID; } } } } } #if 0 /** * Draw Layout lines and points * * \param d The drawing object * \param scaleInx The layout/track scale index */ static void DrawDtoLayout( drawCmd_p d, SCALEINX_T scaleInx ) { #ifdef DTO_DEBUG // Draw the points and lines from dto double r = dtod.td->width / 2; // if (r < 1) r = 1; int i, j; for (i = 0; i < DTO_DIM; i++) { for (j = 0; j < dto[i].n; j++) { DrawFillCircle(d, dto[i].pts[j], r, drawColorPurple); if (j < dto[i].n - 1) { DrawLine(d, dto[i].pts[j], dto[i].pts[j + 1], 0, drawColorPurple); } } } #endif } #endif /** * Use the coOrds to build a polygon and draw the bridge fill. Note that the coordinates are * passed as pairs, and rearranged into a polygon with the 1,2,4,3 order. * * \param d The drawing object * \param c The color * \param b1 The first coordinate * \param b2 The second coordinate * \param b3 The third coordinate * \param b4 The fourth coordinate */ static void DrawFill( drawCmd_p d, wDrawColor c, coOrd b1, coOrd b2, coOrd b3, coOrd b4 ) { coOrd p[4] = {b1, b2, b4, b3}; DrawPoly(d,4,p,NULL,c,0,DRAW_FILL ); } /** * Draw Bridge parapets and background or Roadbed for a turnout * * \param d The drawing object * \param fillType 0 for bridge, 1 for roadbed * \param path1 The first path * \param path2 The second path */ static void DrawTurnoutFill( drawCmd_p d, int fillType, int path1, int path2 ) { DIST_T trackGauge = GetTrkGauge(dtod.trk); wDrawWidth width2 = (wDrawWidth)round((2.0 * d->dpi) / BASE_DPI); if (d->options&DC_PRINT) { width2 = (wDrawWidth)round(d->dpi / BASE_DPI); } wDrawColor color = (fillType==0?bridgeColor:roadbedColor); double fillWidth = 1.5; coOrd b1,b2,b3,b4,b5,b6; ANGLE_T angle = dtod.xx->angle,a = 0.0; int i,j,i1,i2; i1 = path1; i2 = path2; if(dto[i1].base[dto[i1].n - 1].y < dto[i2].base[dto[i2].n - 1].y) { i1 = path2; i2 = path1; // a = -a; } if(dtod.toType == DTO_THREE) { i = dtod.strPath; DIST_T dy = fabs(dto[i].dy[0]) + trackGauge * fillWidth; b1 = dto[i].pts[0]; Translate(&b3,b1,(angle + a),dy); b1 = dto[i].pts[dto[i].n - 1]; Translate(&b4,b1,(angle + a),dy); b2 = dto[i].pts[0]; Translate(&b5,b2,(angle + a),-dy); b2 = dto[i].pts[dto[i].n - 1]; Translate(&b6,b2,(angle + a),-dy); // Draw the background DrawFill(d,color,b3,b4,b5,b6); } for(i = i1; 1; i = i2,a = 180.0) { DIST_T dy = fabs(dto[i].dy[0]) + trackGauge * fillWidth; b1 = dto[i].pts[0]; Translate(&b3,b1,(angle + a),dy); Translate(&b5,b1,(angle + a),-(dy * 0.75)); for(j = 1; j < dto[i].n; j++) { dy = fabs(dto[i].dy[j]) + trackGauge * fillWidth; b2 = dto[i].pts[j]; Translate(&b4,b2,(angle + a),dy); Translate(&b6,b2,(angle + a),-(dy * 0.75)); // Draw the background DrawFill(d,color,b3,b4,b5,b6); // Draw the bridge edge if(fillType==0) { DrawLine( d,b3,b4,width2,drawColorBlack ); } b1 = b2; b3 = b4; b5 = b6; } if(i == i2) { break; } } EPINX_T ep; coOrd p; track_p trk1; coOrd p0,p1; // Bridge parapet ends if(fillType==0) { for(ep = 0; ep < 3; ep++) { trk1 = GetTrkEndTrk(dtod.trk,ep); if((trk1) && (!GetTrkBridge(trk1))) { p = GetTrkEndPos(dtod.trk,ep); a = GetTrkEndAngle(dtod.trk,ep) + 90.0; int i = (dtod.lftCnt > 0) && (dtod.rgtCnt == 0) ? 2 : 1; if(ep != i) { Translate(&p0,p,a,trackGauge * 1.5); Translate(&p1,p0,a - 45.0,trackGauge * 1.5); DrawLine(d,p0,p1,width2,drawColorBlack); } if(ep != (3 - i)) { Translate(&p0,p,a,-trackGauge * 1.5); Translate(&p1,p0,a + 45.0,-trackGauge * 1.5); DrawLine(d,p0,p1,width2,drawColorBlack); } } } } } /** * Draw Bridge parapets and background or Roadbed for a cross-over * * \param d The drawing object * \param fillType 0 for bridge, 1 for roadbed * \param path1 The first path, straight * \param path2 The second path, straight */ static void DrawCrossFill( drawCmd_p d, int fillType, int path1, int path2 ) { DIST_T trackGauge = GetTrkGauge(dtod.trk); wDrawWidth width2 = (wDrawWidth)round((2.0 * d->dpi)/BASE_DPI); if (d->options&DC_PRINT) { width2 = (wDrawWidth)round(d->dpi / BASE_DPI); } wDrawColor color = (fillType==0?bridgeColor:roadbedColor); double fillWidth = 1.5; coOrd b1, b2, b3, b4, b5, b6; ANGLE_T angle = dtod.xx->angle, a = 0.0; int i1, i2; i1 = path1; i2 = path2; if(dto[i1].base[dto[i1].n - 1].y < dto[i2].base[dto[i2].n - 1].y) { i1 = path2; i2 = path1; // a = -a; } DIST_T dy = fabs(dto[i1].dy[0]) + trackGauge * fillWidth; b1 = dto[i1].pts[0]; Translate(&b3,b1,(angle + a),dy); b1 = dto[i1].pts[dto[i1].n-1]; Translate(&b4,b1,(angle + a),dy); b2 = dto[i2].pts[0]; Translate(&b5,b2,(angle + a),-dy); b2 = dto[i2].pts[dto[i2].n-1]; Translate(&b6,b2,(angle + a),-dy); // Draw the background DrawFill(d, color, b3, b4, b5, b6); // Draw the bridge edges if(fillType == 0) { DrawLine(d,b3,b4,width2,drawColorBlack); DrawLine(d,b5,b6,width2,drawColorBlack); } EPINX_T ep; coOrd p; track_p trk1; coOrd p0,p1; // Bridge parapet ends if(fillType==0) { for(ep = 0; ep < 4; ep++) { trk1 = GetTrkEndTrk(dtod.trk,ep); if((trk1) && (!GetTrkBridge(trk1))) { p = GetTrkEndPos(dtod.trk,ep); a = GetTrkEndAngle(dtod.trk,ep) + 90.0; if((ep == 1) || (ep == 2)) { Translate(&p0,p,a,trackGauge * 1.5); Translate(&p1,p0,a - 45.0,trackGauge * 1.5); DrawLine(d,p0,p1,width2,drawColorBlack); } if((ep == 0) || (ep == 3)) { Translate(&p0,p,a,-trackGauge * 1.5); Translate(&p1,p0,a + 45.0,-trackGauge * 1.5); DrawLine(d,p0,p1,width2,drawColorBlack); } } } } } /** * Draw Bridge parapets and background or Roadbed for a crossing * * \param d The drawing object * \param fillType 0 for bridge, 1 for roadbed * \param path1 The first path * \param path2 The second path */ static void DrawXingFill( drawCmd_p d, int fillType, int path1, int path2 ) { DIST_T trackGauge = GetTrkGauge(dtod.trk); wDrawWidth width2 = (wDrawWidth)round((2.0 * d->dpi)/BASE_DPI); if (d->options&DC_PRINT) { width2 = (wDrawWidth)round(d->dpi / BASE_DPI); } double fillWidth = 1.5; coOrd b0, b1, b2, b3, b4, b5, b6; int i, j, i1, i2; i1 = dtod.strPath; i2 = dtod.str2Path; // Fill both straight sections wDrawWidth width3 = (wDrawWidth)round(trackGauge * 2 * fillWidth * d->dpi / d->scale); wDrawColor color = (fillType==0?bridgeColor:roadbedColor); b1 = dto[i1].pts[0]; b2 = dto[i1].pts[dto[i1].n-1]; DrawLine(d,b1,b2,width3,color); b1 = dto[i2].pts[0]; b2 = dto[i2].pts[dto[i1].n-1]; DrawLine(d,b1,b2,width3,color); i1 = path1; i2 = path2; if(dto[i1].base[dto[i1].n - 1].y < dto[i2].base[dto[i2].n - 1].y) { i1 = path2; i2 = path1; } // Handle curved sections for slips BOOL_T hasLeft = 0, hasRgt = 0; ANGLE_T angle = dtod.xx->angle, a = 0.0; for(i = i1; 1; i = i2,a = 180.0) { DIST_T dy = fabs(dto[i].dy[0]) + trackGauge * fillWidth; b1 = dto[i].pts[0]; Translate(&b3,b1,(angle + a),dy); Translate(&b5,b1,(angle + a),-(dy * 0.75)); if(dto[i].type != 'S') { if(dto[i].type == 'L') { hasLeft = 1; } else if(dto[i].type == 'R') { hasRgt = 1; } for(j = 1; j < dto[i].n; j++) { dy = fabs(dto[i].dy[j]) + trackGauge * fillWidth; b2 = dto[i].pts[j]; Translate(&b4,b2,(angle + a),dy); Translate(&b6,b2,(angle + a),-(dy * 0.75)); // Draw the background DrawFill(d,color,b3,b4,b5,b6); // Draw the bridge edge if(fillType==0) { DrawLine( d,b3,b4,width2,drawColorBlack ); } b1 = b2; b3 = b4; b5 = b6; } } if(i == i2) { break; } } if(dtod.strPath >= 0 && dtod.str2Path >= 0) { i1 = dtod.strPath; i2 = dtod.str2Path; if(!hasRgt&&fillType==0) { DIST_T dy = trackGauge * 1.5; ANGLE_T a1, a2; b1 = dto[i1].pts[0]; a1 = dto[i1].angle + 90; Translate(&b3,b1,a1,dy); b2 = dto[i2].pts[dto[i2].n - 1]; a2 = dto[i2].angle + 90; Translate(&b4,b2,a2,dy); FindIntersection(&b0, b3, a1-90.0, b4, a2-90.0); // Draw the bridge edge DrawLine(d,b3,b0,width2,drawColorBlack); DrawLine(d,b0,b4,width2,drawColorBlack); } if(!hasLeft&&fillType==0) { DIST_T dy = trackGauge * 1.5; ANGLE_T a1, a2; b1 = dto[i2].pts[0]; a1 = dto[i2].angle - 90; Translate(&b3,b1,a1,dy); b2 = dto[i1].pts[dto[i1].n - 1]; a2 = dto[i1].angle - 90; Translate(&b4,b2,a2,dy); FindIntersection(&b0, b3, a1+90.0, b4, a2+90.0); // Draw the bridge edge DrawLine(d,b3,b0,width2,drawColorBlack); DrawLine(d,b0,b4,width2,drawColorBlack); } if(dtod.toType == DTO_XNG9 && fillType==0) { DIST_T dy = trackGauge * 1.5; ANGLE_T a1, a2; b1 = dto[i1].pts[dto[i1].n - 1]; a1 = dto[i1].angle + 90; Translate(&b3,b1,a1,dy); b2 = dto[i2].pts[dto[i2].n - 1]; a2 = dto[i2].angle - 90; Translate(&b4,b2,a2,dy); FindIntersection(&b0, b3, a1-90.0, b4, a2+90.0); // Draw the bridge edge DrawLine(d,b3,b0,width2,drawColorBlack); DrawLine(d,b0,b4,width2,drawColorBlack); b1 = dto[i1].pts[0]; a1 = dto[i1].angle - 90; Translate(&b3,b1,a1,dy); b2 = dto[i2].pts[0]; a2 = dto[i2].angle + 90; Translate(&b4,b2,a2,dy); FindIntersection(&b0, b3, a1+90.0, b4, a2-90.0); // Draw the bridge edge DrawLine(d,b3,b0,width2,drawColorBlack); DrawLine(d,b0,b4,width2,drawColorBlack); } } // Bridge wings EPINX_T ep; coOrd p; track_p trk1; coOrd p0,p1; if(fillType==0) { for(ep = 0; ep < 4; ep++) { trk1 = GetTrkEndTrk(dtod.trk,ep); if((trk1) && (!GetTrkBridge(trk1))) { p = GetTrkEndPos(dtod.trk,ep); a = GetTrkEndAngle(dtod.trk,ep) + 90.0; if((dtod.toType == DTO_XNG9) || (ep == 2) || (ep == 3)) { Translate(&p0,p,a,trackGauge * 1.5); Translate(&p1,p0,a - 45.0,trackGauge * 1.5); DrawLine(d,p0,p1,width2,drawColorBlack); } if((dtod.toType == DTO_XNG9) || (ep == 0) || (ep == 1)) { Translate(&p0,p,a,-trackGauge * 1.5); Translate(&p1,p0,a + 45.0,-trackGauge * 1.5); DrawLine(d,p0,p1,width2,drawColorBlack); } } } } } /** * Init Normal Turnout data structure * Calculates the dy value of each segment * Sets pts values REORIGIN base to actual position and angle * Save often used last base and last point coOrd */ static void DrawDtoInit() { struct extraDataCompound_t* xx = dtod.xx; coOrd p1; int i, j; for(i = 0; i < DTO_DIM; i++) { int n = dto[i].n; for(j = 0; j < n; j++) { REORIGIN(p1,dto[i].base[j],xx->angle,xx->orig); dto[i].pts[j] = p1; if(j < n - 1) { dto[i].dy[j] = (dto[i].base[j + 1].y - dto[i].base[j].y) / (dto[i].base[j + 1].x - dto[i].base[j].x); } } dto[i].ptsLast = dto[i].pts[n - 1]; dto[i].baseLast = dto[i].base[n - 1]; } } /** * Draw Normal (Single Origin) Turnout Bridge and Ties. Uses the static dto and dtod structures. * * \param d The drawing object * \param scaleInx The layout/track scale index * \param color The tie color. If black the color is read from the global tieColor. */ static void DrawNormalTurnout( drawCmd_p d, SCALEINX_T scaleInx, BOOL_T omitTies, wDrawColor color) { DIST_T len; coOrd pos; int cnt; ANGLE_T angle; coOrd s1, s2, p1, p2, q1, q2; int p0, q0; // int s0; ANGLE_T a0; if (color == wDrawColorBlack) { color = tieColor; } // DIST_T trackGauge = GetTrkGauge(dtod.trk); DrawDtoInit(); // draw the points #ifdef DTO_DEBUG if (DTO_DEBUG == DTO_NORMAL) { DrawDtoLayout(d, scaleInx); } #endif int strPath = dtod.strPath, othPath = 0, secPath = 1; int toType = dtod.toType; // int first = 1; switch (toType) { case DTO_NORMAL: othPath = 1 - strPath; secPath = strPath; break; case DTO_WYE: // strPath = 2; othPath = 0; secPath = 1; break; case DTO_THREE: switch (strPath) { case 0: othPath = 1; secPath = 2; break; case 1: othPath = 0; secPath = 2; break; case 2: othPath = 0; secPath = 1; break; } break; } if(dtod.bridge) { DrawTurnoutFill(d,0,othPath,secPath); } else if(dtod.roadbed) { DrawTurnoutFill( d,1,othPath,secPath ); } if (omitTies) { return; } // Straight vector for tie angle if (toType == DTO_WYE) { s1 = dto[othPath].pts[0]; s2 = MidPtCoOrd(dto[othPath].ptsLast, dto[secPath].ptsLast); } else { s1 = dto[strPath].pts[0]; s2 = dto[strPath].ptsLast; } // Diverging vector(s) p1 = dto[othPath].pts[0]; p2 = dto[othPath].ptsLast; q1 = dto[secPath].pts[0]; q2 = dto[secPath].ptsLast; len = FindDistance(s1, s2); angle = FindAngle(s1, s2); // The straight segment cnt = (int)floor(len / dtod.td.spacing + 0.5); if (cnt > 0) { int pn = dto[othPath].n; int qn = dto[secPath].n; DIST_T dx = len / cnt; /*s0 =*/ p0 = q0 = 0; DIST_T tdlen = dtod.td.length; DIST_T tdmax = (toType == DTO_WYE) ? 2.0 * tdlen : 2.5 * tdlen; DIST_T tdwid = dtod.td.width; DIST_T px = len, dlenx = dx / 2; cnt = cnt > 1 ? cnt - 1 : 1; for (px = dlenx; cnt; cnt--, px += dx) { if (px >= dto[othPath].base[p0 + 1].x) { p0++; } if (px >= dto[secPath].base[q0 + 1].x) { q0++; } if (p0 >= pn || q0 >= qn) { break; } if ((px + dx >= dto[othPath].baseLast.x) || (px + dx >= dto[secPath].baseLast.x)) { break; } DIST_T dy1 = dto[othPath].base[p0].y + (px - dto[othPath].base[p0].x) * dto[othPath].dy[p0]; DIST_T dy2 = dto[secPath].base[q0].y + (px - dto[secPath].base[q0].x) * dto[secPath].dy[q0]; tdlen = dtod.td.length + fabs(dy1) + fabs(dy2); if (tdlen > tdmax) { break; } DIST_T dy = dy1 + dy2; Translate(&pos, s1, angle, px); Translate(&pos, pos, (angle - 90.0), dy / 2); DrawTie(d, pos, angle, tdlen, tdwid, color, tieDrawMode == TIEDRAWMODE_SOLID); } // Asymmetric? Use longer ties for remaining two tracks (strPath, othPath) DIST_T sx = px; // Save these values for second code block int s0 = p0; if((dtod.toType == DTO_THREE) && (px + dx >= dto[secPath].baseLast.x)) { for ( ; cnt; cnt--, px += dx) { if (px >= dto[othPath].base[p0 + 1].x) { p0++; } // if (px >= dto[secPath].base[q0 + 1].x) q0++; if (p0 >= pn) { break; } if (px + dx >= dto[othPath].baseLast.x) { break; } DIST_T dy1 = dto[othPath].base[p0].y + (px - dto[othPath].base[p0].x) * dto[othPath].dy[p0]; tdlen = dtod.td.length + fabs(dy1); if (tdlen > tdmax) { break; } DIST_T dy = dy1; Translate(&pos, s1, angle, px); Translate(&pos, pos, (angle - 90.0), dy / 2); DrawTie(d, pos, angle, tdlen, tdwid, color, tieDrawMode == TIEDRAWMODE_SOLID); } } // Draw remaining ties, if any if (px + dx < dto[othPath].baseLast.x) { p1 = dto[othPath].pts[p0]; p2 = dto[othPath].ptsLast; angle = FindAngle(p1, p2); a0 = FindAngle(dto[othPath].base[p0], dto[othPath].baseLast); DIST_T lenr = (dto[othPath].baseLast.x - px + dlenx) / cos(D2R(90.0 - a0)); Translate(&p1, p2, angle, -lenr); DrawStraightTies(d, dtod.td, p1, p2, color); } else { p1 = dto[othPath].pts[pn - 2]; a0 = FindAngle(p1, p2); Translate(&pos, p2, a0, -dx / 2); DrawTie(d, pos, a0, dtod.td.length, tdwid, color, tieDrawMode == TIEDRAWMODE_SOLID); } // Restore saved values if(dtod.toType == DTO_THREE) { px = sx; p0 = s0; } // Asymmetric? Use longer ties for remaining two tracks (strPath, secPath) if((dtod.toType == DTO_THREE) && (px + dx >= dto[othPath].baseLast.x)) { for ( ; cnt; cnt--, px += dx) { // if (px >= dto[othPath].base[p0 + 1].x) p0++; if (px >= dto[secPath].base[q0 + 1].x) { q0++; } if (q0 >= qn) { break; } if (px + dx >= dto[secPath].baseLast.x) { break; } DIST_T dy1 = dto[secPath].base[q0].y + (px - dto[secPath].base[q0].x) * dto[secPath].dy[q0]; tdlen = dtod.td.length + fabs(dy1); if (tdlen > tdmax) { break; } DIST_T dy = dy1; Translate(&pos, s1, angle, px); Translate(&pos, pos, (angle - 90.0), dy / 2); DrawTie(d, pos, angle, tdlen, tdwid, color, tieDrawMode == TIEDRAWMODE_SOLID); } } if (px + dx < dto[secPath].baseLast.x) { q1 = dto[secPath].pts[q0]; q2 = dto[secPath].ptsLast; angle = FindAngle(q1, q2); a0 = FindAngle(dto[secPath].base[q0], dto[secPath].baseLast); DIST_T lenr = (dto[secPath].baseLast.x - px + dlenx) / cos(D2R(90.0 - a0)); Translate(&q1, q2, angle, -lenr); DrawStraightTies(d, dtod.td, q1, q2, color); } else { q1 = dto[secPath].pts[qn - 2]; a0 = FindAngle(q1, q2); Translate(&pos, q2, a0, -dx / 2); DrawTie(d, pos, a0, dtod.td.length, tdwid, color, tieDrawMode == TIEDRAWMODE_SOLID); } // Final ties at end if (dtod.toType == DTO_THREE) { int n = (int)(dto[strPath].baseLast.x); if (px + dx < len) { angle = FindAngle(s1, s2); DIST_T lenr = len - px + dlenx; Translate(&s1, s2, angle, -lenr); DrawStraightTies(d, dtod.td, s1, s2, color); } else { n = dto[strPath].n; s1 = dto[strPath].pts[n - 2]; a0 = FindAngle(s1, s2); Translate(&pos, s2, a0, -dx / 2); DrawTie(d, pos, a0, dtod.td.length, tdwid, color, tieDrawMode == TIEDRAWMODE_SOLID); } } } } /** * Draw Curved (Single Origin) Turnout Bridge and Ties. Uses the static dto and dtod structures. * * \param d The drawing object * \param scaleInx The layout/track scale index * \param color The tie color. If black the color is read from the global tieColor. */ static void DrawCurvedTurnout( drawCmd_p d, SCALEINX_T scaleInx, BOOL_T omitTies, wDrawColor color) { DIST_T len, r; coOrd pos; int cnt; ANGLE_T angle, dang; coOrd center; coOrd p1, p2, q1, q2; ANGLE_T a0, a1, a2; struct extraDataCompound_t* xx = dtod.xx; if (color == wDrawColorBlack) { color = tieColor; } DrawDtoInit(); // draw the points #ifdef DTO_DEBUG if (DTO_DEBUG == DTO_CURVED) { DrawDtoLayout(d, scaleInx); } #endif int othPath = 0, secPath = 1; // int toType = dtod.toType; if(dtod.bridge) { DrawTurnoutFill(d,0,othPath,secPath); } else if(dtod.roadbed) { DrawTurnoutFill(d,1,othPath,secPath); } if (omitTies) { return; } // Save the ending coordinates coOrd othEnd = zero, secEnd = zero; trkSeg_p trk; DIST_T tdlen = dtod.td.length, tdmax = tdlen * 2.5; DIST_T tdspc = dtod.td.spacing, tdspc2 = tdspc / 2.0; DIST_T tdwid = dtod.td.width; // double rdot = tdwid / 2; int pn = dto[othPath].n; int qn = dto[secPath].n; int p0 = 0, q0 = 0; DIST_T px = 0, qx = 0, dy = 0, dy1 = 0, dy2 = 0; double cosAdj = 1.0; angle = 0; px = tdspc2; qx = tdspc2; int segs = max(dto[othPath].n, dto[secPath].n); for (; segs > 0; segs--) { if (px >= dto[othPath].base[p0 + 1].x) { p0++; } if (qx >= dto[secPath].base[q0 + 1].x) { q0++; } if ((p0 >= pn - 1) || (q0 >= qn - 1)) { break; } trk = dto[othPath].trkSeg[p0]; if (trk->type == SEG_CRVTRK) { center = trk->u.c.center; r = fabs(trk->u.c.radius); a0 = NormalizeAngle(trk->u.c.a0 + dtod.xx->angle); a1 = trk->u.c.a1; pos = center; REORIGIN(center, pos, xx->angle, xx->orig); len = r * D2R(a1); cnt = (int)floor(len / tdspc + 0.5); if (len - tdspc * cnt >= tdspc2) { cnt++; } DIST_T tdlen = dtod.td.length; // DIST_T dx = len / cnt, dx2 = dx / 2; if (cnt != 0) { dang = (len / cnt) * 360 / (2 * M_PI * r); DIST_T dx = len / cnt, dx2 = dx / 2; if (dto[othPath].type == 'R') { a2 = a0 + dang / 2; } else { a2 = a0 + a1 - dang / 2; dang = -dang; } angle += fabs(dang / 2); cosAdj = fabs(cos(D2R(angle))); px += dx2 * cosAdj; qx += dx2 * cosAdj; for (; cnt; cnt--, a2 += dang, angle += dang) { if (px >= dto[othPath].base[p0 + 1].x) { p0++; } if (qx >= dto[secPath].base[q0 + 1].x) { q0++; } if ((p0 >= pn - 1) || (q0 >= qn - 1)) { break; } coOrd e1, e2; PointOnCircle(&e1, center, r, a2); q1 = dto[secPath].pts[q0]; q2 = dto[secPath].pts[q0 + 1]; FindIntersection(&e2, e1, a2, q1, FindAngle(q1, q2)); dy = FindDistance(e1, e2); DIST_T tlen = tdlen + dy; if (tlen > tdmax) { break; } Translate(&pos, e1, a2, -dy / 2); DrawTie(d, pos, angle + xx->angle + 90, tlen, tdwid, color, tieDrawMode == TIEDRAWMODE_SOLID); // Assures that these ends are the last point drawn before break othEnd = e1; secEnd = e2; cosAdj = fabs(cos(D2R(angle))); if (cnt > 1) { px += dx * cosAdj; qx += dx * cosAdj; } else { px += dx2 * cosAdj; qx += dx2 * cosAdj; } } } } else { cosAdj = fabs(cos(D2R(angle))); p1 = dto[othPath].base[p0]; p2 = dto[othPath].base[p0 + 1]; len = FindDistance(p1, p2); cnt = (int)floor(len / tdspc + 0.6); if (cnt > 0) { DIST_T dx = len / cnt/*, dx2 = dx / 2*/; for (; cnt; cnt--) { if (px >= dto[othPath].base[p0 + 1].x) { p0++; } if (qx >= dto[secPath].base[q0 + 1].x) { q0++; } if ((p0 >= pn - 1) || (q0 >= qn - 1)) { break; } p1 = dto[othPath].base[p0]; p2 = dto[othPath].base[p0 + 1]; if ((px >= dto[othPath].baseLast.x) || (qx >= dto[secPath].baseLast.x)) { break; } dy1 = dto[secPath].base[q0].y + (qx - dto[secPath].base[q0].x) * dto[secPath].dy[q0]; dy2 = dto[othPath].base[p0].y + (px - dto[othPath].base[p0].x) * dto[othPath].dy[p0]; dy = dy1 - dy2; DIST_T tlen = tdlen + fabs(cosAdj * dy); if (tlen > tdmax) { break; } q1 = dto[secPath].pts[q0]; q2 = dto[secPath].pts[q0 + 1]; a1 = FindAngle(q1, q2); DIST_T xlen = qx - dto[secPath].base[q0].x; Translate(&pos, q1, a1, xlen); secEnd = pos; q1 = dto[othPath].pts[p0]; q2 = dto[othPath].pts[p0 + 1]; a1 = FindAngle(q1, q2); xlen = px - dto[othPath].base[p0].x; Translate(&pos, q1, a1, xlen); othEnd = pos; Translate(&pos, pos, (a1 - 90.0), dy / 2); DrawTie(d, pos, a1, tlen, tdwid, color, tieDrawMode == TIEDRAWMODE_SOLID); cosAdj = fabs(cos(D2R(angle))); px += dx * cosAdj; qx += dx * cosAdj; } } else { break; } } } #ifdef DTO_DEBUG if (DTO_DEBUG == DTO_CURVED) { DrawFillCircle(d, othEnd, rdot, drawColorGreen); DrawFillCircle(d, secEnd, rdot, drawColorGreen); DrawFillCircle(d, dto[othPath].pts[p0], rdot, drawColorBlue); DrawFillCircle(d, dto[secPath].pts[q0], rdot, drawColorBlue); } #endif // Draw remaining ties, if any p1 = othEnd; p2 = dto[othPath].ptsLast; a0 = FindAngle(p1, p2); len = FindDistance(p1, p2); if (len >= 2 * tdspc) { Translate(&p1, p1, a0, tdspc2); DrawStraightTies(d, dtod.td, p1, p2, color); } else if (len > tdspc2) { Translate(&p2, p2, a0, -tdspc2); DrawTie(d, p2, a0, dtod.td.length, tdwid, color, tieDrawMode == TIEDRAWMODE_SOLID); } q1 = secEnd; q2 = dto[secPath].ptsLast; a0 = FindAngle(q1, q2); len = FindDistance(q1, q2); if (len >= 2 * tdspc) { Translate(&q1, q1, a0, tdspc2); DrawStraightTies(d, dtod.td, q1, q2, color); } else if (len > tdspc2) { Translate(&q2, q2, a0, -tdspc2); DrawTie(d, q2, a0, dtod.td.length, tdwid, color, tieDrawMode == TIEDRAWMODE_SOLID); } } /** * Draw Crossing and Slip Turnout Bridge and Ties - Uses the static dto and dtod structures. * * \param d The drawing object * \param scaleInx The layout/track scale index * \param color The tie color. If black the color is read from the global tieColor. */ static void DrawXingTurnout( drawCmd_p d, SCALEINX_T scaleInx, BOOL_T omitTies, wDrawColor color) { DIST_T len; coOrd pos; int cnt; ANGLE_T cAngle; if (color == wDrawColorBlack) { color = tieColor; } coOrd c1, c2, s1, s2, p1, p2, q1; int p0, q0; ANGLE_T a0, a1, a2; int strPath = dtod.strPath, str2Path = dtod.str2Path; struct extraDataCompound_t* xx = dtod.xx; DrawDtoInit(); dto[strPath].angle = FindAngle(dto[strPath].pts[0], dto[strPath].ptsLast); dto[str2Path].angle = FindAngle(dto[str2Path].pts[0], dto[str2Path].ptsLast); int othPath = strPath, secPath = str2Path; int toType = dtod.toType; int i, j; switch (toType) { case DTO_XING: case DTO_XNG9: break; case DTO_SSLIP: for (i = 0; i < dtod.pathCnt; i++) { if (dto[i].type == 'L' || dto[i].type == 'R') { secPath = i; break; } } break; case DTO_DSLIP: for (i = 0; i < dtod.pathCnt; i++) { if (dto[i].type == 'L') { othPath = i; } else if (dto[i].type == 'R') { secPath = i; } } break; } if(dtod.bridge) { DrawXingFill(d,0,othPath,secPath); } else if(dtod.roadbed) { DrawXingFill(d,1,othPath,secPath); } // draw the points #ifdef DTO_DEBUG if (DTO_DEBUG == DTO_XING) { DrawDtoLayout(d, scaleInx); } #endif if (omitTies) { return; } DIST_T tdlen = dtod.td.length, tdmax = 2.0 * tdlen; DIST_T tdwid = dtod.td.width; DIST_T tdspc = dtod.td.spacing, tdspc2 = tdspc / 2; // Midpoint p1 = dto[strPath].pts[0]; a1 = dto[strPath].angle; q1 = dto[str2Path].pts[0]; a2 = dto[str2Path].angle; FindIntersection(&pos, p1, a1, q1, a2); dtod.midPt = pos; #ifdef DTO_DEBUG if(DTO_DEBUG == DTO_XING) { double r = td->width / 2; DrawFillCircle(d,p1,r,drawColorPurple); DrawFillCircle(d,q1,r,drawColorPurple); DrawFillCircle(d,dtod.midPt,r,drawColorPurple); } #endif // Tie length adjust double dAngle = fabs(DifferenceBetweenAngles(a1, a2)); double magic = 1.0; // Short circuit the complex code for this simple case if (toType == DTO_XNG9) { p1 = dto[strPath].pts[0]; p2 = dto[strPath].ptsLast; DrawStraightTies(d, dtod.td, p1, p2, color); p1 = dto[str2Path].pts[0]; p2 = dto[str2Path].ptsLast; // Omit the center ties magic = 1 / cos(D2R(90 - dAngle)); DIST_T tdadj = (tdlen / 2) * magic; DIST_T tdadj2 = tdspc2 * magic; dAngle = (dAngle - 90) / 2; Translate(&pos, dtod.midPt, a2, -tdadj - tdadj2); DrawTie(d, pos, a2 - dAngle, tdlen, tdwid, color, tieDrawMode == TIEDRAWMODE_SOLID); Translate(&pos, dtod.midPt, a2, -tdadj - tdspc); DrawStraightTies(d, dtod.td, p1, pos, color); Translate(&pos, dtod.midPt, a2, tdadj + tdadj2); DrawTie(d, pos, a2 - dAngle, tdlen, tdwid, color, tieDrawMode == TIEDRAWMODE_SOLID); Translate(&pos, dtod.midPt, a2, tdadj + tdspc); DrawStraightTies(d, dtod.td, pos, p2, color); return; } // Straight vector for tie angle s1 = MidPtCoOrd(dto[strPath].base[0], dto[str2Path].base[0]); s2 = MidPtCoOrd(dto[strPath].baseLast, dto[str2Path].baseLast); // Rotate base coordinates so that the tie line is aligned with x-axis and origin is at zero cAngle = FindAngle(s1, s2); for (i = 0; i < DTO_DIM; i++) for (j = 0; j < dto[i].n; j++) { dto[i].base[j].x -= s1.x; dto[i].base[j].y -= s1.y; Rotate(&dto[i].base[j], zero, (90.0 - cAngle)); } for (i = 0; i < DTO_DIM; i++) { for (j = 0; j < dto[i].n - 1; j++) { dto[i].dy[j] = (dto[i].base[j + 1].y - dto[i].base[j].y) / (dto[i].base[j + 1].x - dto[i].base[j].x); } if (dto[i].type == 'S') { dto[i].angle = FindAngle(dto[i].pts[0], dto[i].ptsLast); } } // Tie center line in drawing coordinates REORIGIN(c1, s1, xx->angle, xx->orig); REORIGIN(c2, s2, xx->angle, xx->orig); cAngle = FindAngle(c1, c2); int pn = dto[othPath].n; int qn = dto[secPath].n; // Tie length adjust magic = 1 / cos(0.5 * D2R(dAngle)); // Extra ties length adjust double magic2 = 1.0 / cos(0.5 * D2R(dAngle)); // Draw right half len = FindDistance(dtod.midPt, c2); cnt = (int)floor(len / dtod.td.spacing + 0.5); if (cnt <= 0) { return; } DIST_T dx = len / cnt; p0 = q0 = 0; DIST_T dx2 = dx / 2; DIST_T px = len + dx2; DIST_T lenx = 0; while (p0 < pn && px > dto[othPath].base[p0 + 1].x) { p0++; } while (q0 < qn && px > dto[secPath].base[q0 + 1].x) { q0++; } while (p0 < pn && q0 < qn) { if (px > dto[othPath].base[p0 + 1].x) { p0++; } if (px > dto[secPath].base[q0 + 1].x) { q0++; } if (p0 >= pn || q0 >= qn) { break; } // Dont use baseLast, as base coOrds have been rotated if ((px + dx >= dto[othPath].base[pn - 1].x) || (px + dx >= dto[secPath].base[qn - 1].x)) { break; } DIST_T dy1 = dto[othPath].base[p0].y + (px - dto[othPath].base[p0].x) * dto[othPath].dy[p0]; DIST_T dy2 = dto[secPath].base[q0].y + (px - dto[secPath].base[q0].x) * dto[secPath].dy[q0]; tdlen = (dtod.td.length + fabs(dy1) + fabs(dy2)) * magic; if(tdlen > tdmax) { if(dAngle >= 30) { DIST_T dy = (dy1 + dy2) / 2; Translate(&pos,dtod.midPt,cAngle,px - len); Translate(&pos,pos,(cAngle - 90.0),dy); DrawTie(d,pos,cAngle,tdlen - dtod.td.length * magic,tdwid,color, tieDrawMode == TIEDRAWMODE_SOLID); lenx += dx2 * magic2; } break; } DIST_T dy = (dy1 + dy2) / 2; Translate(&pos, dtod.midPt, cAngle, px - len); Translate(&pos, pos, (cAngle - 90.0), dy); DrawTie(d, pos, cAngle, tdlen, tdwid, color, tieDrawMode == TIEDRAWMODE_SOLID); px += dx; lenx += dx; } p1 = dtod.midPt; p2 = dto[strPath].ptsLast; DIST_T lenr = FindDistance(p1, p2) - lenx * magic2; a0 = dto[strPath].angle; if (lenr > dx) { Translate(&pos, p2, a0, -lenr); DrawStraightTies(d, dtod.td, pos, p2, color); } else { Translate(&pos, p2, a0, -dx2); DrawTie(d, pos, a0, dtod.td.length, tdwid, color, tieDrawMode == TIEDRAWMODE_SOLID); } // p1 = dtod.midPt; p2 = dto[str2Path].ptsLast; lenr = FindDistance(p1, p2) - lenx * magic2; a0 = dto[str2Path].angle; if (lenr > dx) { Translate(&pos, p2, a0, -lenr); DrawStraightTies(d, dtod.td, pos, p2, color); } else { Translate(&pos, p2, a0, -dx2); DrawTie(d, pos, a0, dtod.td.length, tdwid, color, tieDrawMode == TIEDRAWMODE_SOLID); } // Draw left half // Change the straight path used if (dtod.toType == DTO_SSLIP) { othPath = str2Path; } len = FindDistance(c1, dtod.midPt); cnt = (int)floor(len / dtod.td.spacing + 0.5); if (cnt <= 0) { return; } p0 = q0 = 0; tdlen = dtod.td.length; dx = len / cnt; dx2 = dx / 2; px = len - dx2; lenx = 0; while (p0 < pn && px > dto[othPath].base[p0 + 1].x) { p0++; } while (q0 < qn && px > dto[secPath].base[q0 + 1].x) { q0++; } while (p0 >= 0 && q0 >= 0) { if (px < dto[othPath].base[p0].x) { p0--; } if (px < dto[secPath].base[q0].x) { q0--; } if (p0 < 0 || q0 < 0) { break; } if ((px - dx < dto[othPath].base[0].x) || (px - dx < dto[secPath].base[0].x)) { break; } DIST_T dy1 = dto[othPath].base[p0].y + (px - dto[othPath].base[p0].x) * dto[othPath].dy[p0]; DIST_T dy2 = dto[secPath].base[q0].y + (px - dto[secPath].base[q0].x) * dto[secPath].dy[q0]; tdlen = (dtod.td.length + fabs(dy1) + fabs(dy2)) * magic; if(tdlen > tdmax) { if(dAngle >= 30) { DIST_T dy = (dy1 + dy2) / 2; Translate(&pos,dtod.midPt,cAngle,px - len); Translate(&pos,pos,(cAngle - 90.0),dy); DrawTie(d,pos,cAngle,tdlen - dtod.td.length * magic,tdwid,color, tieDrawMode == TIEDRAWMODE_SOLID); lenx += dx2 * magic2; } break; } DIST_T dy = (dy1 + dy2) / 2; Translate(&pos, dtod.midPt, cAngle, px - len); Translate(&pos, pos, (cAngle - 90.0), dy); DrawTie(d, pos, cAngle, tdlen, tdwid, color, tieDrawMode == TIEDRAWMODE_SOLID); px -= dx; lenx += dx; } p1 = dto[strPath].pts[0]; p2 = dtod.midPt; a0 = dto[strPath].angle; lenr = FindDistance(p1, p2) - lenx * magic2; if (lenr > dx) { Translate(&pos, p1, a0, lenr); DrawStraightTies(d, dtod.td, p1, pos, color); } else { Translate(&pos, p1, a0, dx2); DrawTie(d, pos, a0, dtod.td.length, tdwid, color, tieDrawMode == TIEDRAWMODE_SOLID); } p1 = dto[str2Path].pts[0]; // p2 = dtod.midPt; a0 = dto[str2Path].angle; lenr = FindDistance(p1, p2) - lenx * magic2; if (lenr > dx) { Translate(&pos, p1, a0, lenr); DrawStraightTies(d, dtod.td, p1, pos, color); } else { Translate(&pos, p1, a0, dx2); DrawTie(d, pos, a0, dtod.td.length, tdwid, color, tieDrawMode == TIEDRAWMODE_SOLID); } } /** * Draw Crossover (Two Origin) Turnout Bridge and Ties. Uses the static dto and dtod structures. * * \param d The drawing object * \param scaleInx The layout/track scale index * \param color The tie color. If black the color is read from the global tieColor. */ static void DrawCrossTurnout( drawCmd_p d, SCALEINX_T scaleInx, BOOL_T omitTies, wDrawColor color) { DIST_T len, dx; coOrd pos; int cnt; ANGLE_T angle; if (color == wDrawColorBlack) { color = tieColor; } // struct extraDataCompound_t* xx = dtod.xx; DrawDtoInit(); // draw the points #ifdef DTO_DEBUG if (DTO_DEBUG == DTO_LCROSS) { DrawDtoLayout(d, scaleInx); } #endif int strPath = dtod.strPath, str2Path = dtod.str2Path; // Bad assumption int othPath = 2, secPath = 2; if (dtod.pathCnt == 4) { secPath = 3; } dto[strPath].angle = FindAngle(dto[strPath].pts[0], dto[strPath].ptsLast); dto[str2Path].angle = FindAngle(dto[str2Path].pts[0], dto[str2Path].ptsLast); if(dtod.bridge) { DrawCrossFill(d,0,strPath,str2Path); } else if(dtod.roadbed) { DrawCrossFill(d,1,strPath,str2Path); } if (omitTies) { return; } coOrd s1, s2, t1; // coOrd t2, p1, p2, q1, q2; int s0, t0, p0, q0; int sn = dto[strPath].n; int tn = dto[str2Path].n; int pn = dto[othPath].n; int qn = dto[secPath].n; s1 = dto[strPath].pts[0]; s2 = dto[strPath].ptsLast; t1 = dto[str2Path].pts[0]; // t2 = dto[str2Path].ptsLast; angle = dto[strPath].angle; // p1 = dto[othPath].base[0]; // p2 = dto[othPath].baseLast; // q1 = dto[secPath].base[0]; // q2 = dto[secPath].baseLast; len = FindDistance(s1, s2); angle = dto[strPath].angle; cnt = (int)floor(len / dtod.td.spacing + 0.5); if (cnt > 0) { DIST_T px = 0; DIST_T dy, dy1, dy2; int cflag = 0; dy = dto[str2Path].base[0].y - dto[strPath].base[0].y; dx = len / cnt; s0 = t0 = p0 = q0 = 0; DIST_T tdlen = dtod.td.length; DIST_T tdwid = dtod.td.width; DIST_T dlenx = dx / 2; DIST_T px1 = len / 2 - dlenx * 5, px2 = len / 2 + dlenx * 4; for (px = dlenx; cnt; cnt--, px += dx) { if (px >= dto[strPath].base[s0 + 1].x) { s0++; } if (px >= dto[str2Path].base[t0 + 1].x) { t0++; } if (px >= dto[othPath].base[p0 + 1].x) { p0++; } if (px >= dto[secPath].base[q0 + 1].x) { q0++; } if (s0 >= sn || t0 >= tn || p0 >= pn || q0 >= qn) { break; } if ((px >= dto[strPath].baseLast.x) || (px >= dto[str2Path].baseLast.x)) { break; } dy1 = dy2 = 0; cflag = 0; if (px < px1) { switch (dtod.toType) { case DTO_DCROSS: dy1 = dto[othPath].base[p0].y + (px - dto[othPath].base[p0].x) * dto[othPath].dy[p0]; dy2 = dy - dto[secPath].base[q0].y - (px - dto[secPath].base[q0].x) * dto[secPath].dy[q0]; break; case DTO_LCROSS: dy1 = dto[othPath].base[p0].y + (px - dto[othPath].base[p0].x) * dto[othPath].dy[p0]; dy2 = 0; break; case DTO_RCROSS: dy1 = 0; dy2 = dy - dto[secPath].base[q0].y - (px - dto[secPath].base[q0].x) * dto[secPath].dy[q0]; break; default: break; } } else if (px < px2) { dy1 = (dto[str2Path].base[s0].y - dto[strPath].base[t0].y); dy2 = 0; cflag = 1; } else { switch (dtod.toType) { case DTO_DCROSS: dy1 = dto[secPath].base[q0].y + (px - dto[secPath].base[q0].x) * dto[secPath].dy[q0]; dy2 = dy - dto[othPath].base[p0].y - (px - dto[othPath].base[p0].x) * dto[othPath].dy[p0]; break; case DTO_LCROSS: dy1 = 0; dy2 = dy - dto[secPath].base[q0].y - (px - dto[secPath].base[q0].x) * dto[secPath].dy[q0]; break; case DTO_RCROSS: dy1 = dto[othPath].base[p0].y + (px - dto[othPath].base[p0].x) * dto[othPath].dy[p0]; dy2 = 0; break; default: break; } } if (fabs(dy1) + fabs(dy2) >= dy) { dy1 = (dto[str2Path].base[s0].y - dto[strPath].base[t0].y); dy2 = 0; cflag = 1; } tdlen = dtod.td.length + fabs(dy1); Translate(&pos, s1, angle, px); Translate(&pos, pos, (angle - 90.0), dy1 / 2); DrawTie(d, pos, angle, tdlen, tdwid, color, tieDrawMode == TIEDRAWMODE_SOLID); if (!cflag) { tdlen = dtod.td.length + fabs(dy2); Translate(&pos, t1, angle, px); Translate(&pos, pos, (angle - 90.0), -dy2 / 2); DrawTie(d, pos, angle, tdlen, tdwid, color, tieDrawMode == TIEDRAWMODE_SOLID); } } return; // Draw remaining ties, if any // Currently by definition, there won't be any /* if (px + dx < dto[strPath].baseLast.x) { p1 = dto[strPath].pts[p0]; p2 = dto[strPath].ptsLast; angle = FindAngle(p1, p2); a0 = FindAngle(dto[strPath].base[p0], dto[strPath].baseLast); DIST_T lenr = (dto[strPath].baseLast.x - px + dlenx) / cos(D2R(90.0 - a0)); Translate(&p1, p2, angle, -lenr); DrawStraightTies(d, dtod.td, p1, p2, color); } else { p1 = dto[strPath].pts[pn - 2]; a0 = FindAngle(p1, p2); Translate(&pos, p2, a0, -dx / 2); DrawTie(d, pos, a0, td->length, tdwid, color, tieDrawMode == TIEDRAWMODE_SOLID); } if (px + dx < dto[str2Path].baseLast.x) { q1 = dto[str2Path].pts[q0]; q2 = dto[str2Path].ptsLast; angle = FindAngle(q1, q2); a0 = FindAngle(dto[str2Path].base[q0], dto[str2Path].baseLast); DIST_T lenr = (dto[str2Path].baseLast.x - px + dlenx) / cos(D2R(90.0 - a0)); Translate(&q1, q2, angle, -lenr); DrawStraightTies(d, dtod.td, q1, q2, color); } else { q1 = dto[str2Path].pts[qn - 2]; a0 = FindAngle(q1, q2); Translate(&pos, q2, a0, -dx / 2); DrawTie(d, pos, a0, td->length, tdwid, color, tieDrawMode == TIEDRAWMODE_SOLID); } */ } } /** * Draw all turnout components: ties, rail, roadbed, etc. * * The turnout is checked to see if the enhanced methods can be used. * If so the ties are drawn and the TB_NOTIES bit is set so that the * rails and such are drawn on top of the ties. Similarly the bridge * and roadbed bits are cleared so the enhanced method can be used. * Those bits are restored to the previous state before return. * * \param trk Pointer to the track object * \param d The drawing object * \param color The turnout color. */ EXPORT void DrawTurnout( track_p trk, drawCmd_p d, wDrawColor color) { struct extraDataCompound_t* xx = GET_EXTRA_DATA(trk, T_TURNOUT, extraDataCompound_t); wIndex_t i; long widthOptions = 0; SCALEINX_T scaleInx = GetTrkScale(trk); BOOL_T omitTies = !DoDrawTies(d, trk) || !DrawTwoRails(d,1) || ((d->options & DC_SIMPLE) != 0); // || (scaleInx == 0); widthOptions = DTS_LEFT | DTS_RIGHT; // Save these values int noTies = GetTrkNoTies(trk); int bridge = GetTrkBridge(trk); int roadbed = GetTrkRoadbed(trk); long skip = 0; /** @prefs [Preference] NormalTurnoutDraw=1 to skip enhanced drawing methods */ wPrefGetInteger("Preference", "NormalTurnoutDraw", (long *) &skip, 0); int pathCnt = (skip == 0 ? GetTurnoutPaths(trk, xx) : 0); if ( (pathCnt > 1) && (pathCnt <= DTO_DIM) && ( GetTrkEndPtCnt( trk ) <= 4) && (xx->special == TOnormal) ) { dtod.bridge = bridge; dtod.roadbed = roadbed; // int strPath = -1; GetTurnoutType(); if (dtod.toType != DTO_INVALID) { switch (dtod.toType) { case DTO_NORMAL: case DTO_THREE: case DTO_WYE: DrawNormalTurnout(d, scaleInx, omitTies, color); break; case DTO_CURVED: DrawCurvedTurnout(d, scaleInx, omitTies, color); break; case DTO_XING: case DTO_XNG9: case DTO_SSLIP: case DTO_DSLIP: DrawXingTurnout(d, scaleInx, omitTies, color); break; case DTO_LCROSS: case DTO_RCROSS: case DTO_DCROSS: DrawCrossTurnout(d, scaleInx, omitTies, color); break; default: break; } // Ignore these settings SetTrkNoTies(trk, 1); ClrTrkBits( trk,TB_BRIDGE ); ClrTrkBits(trk, TB_ROADBED); } } // Begin standard DrawTurnout code to draw rails or centerline // no curve center for turnouts, leave centerline for sectional curved long opts = widthOptions | (xx->segCnt > 1 ? DTS_NOCENTER : 0); DrawSegsO(d, trk, xx->orig, xx->angle, xx->segs, xx->segCnt, GetTrkGauge(trk), color, opts); for (i = 0; i < GetTrkEndPtCnt(trk); i++) { DrawEndPt(d, trk, i, color); } if ((d->options & DC_SIMPLE) == 0 && (labelWhen == 2 || (labelWhen == 1 && (d->options & DC_PRINT))) && labelScale >= d->scale && (GetTrkBits(trk) & TB_HIDEDESC) == 0) { DrawCompoundDescription(trk, d, color); if (!xx->handlaid) { LabelLengths(d, trk, color); } } if (roadbedWidth > GetTrkGauge(trk) && DrawTwoRails( d, 1 ) && ( (d->options & DC_PRINT) || roadbedOnScreen ) ) { DrawTurnoutRoadbed(d, color, xx->orig, xx->angle, xx->segs, xx->segCnt); } // Restore these settings if (noTies == 0) { ClrTrkBits(trk, TB_NOTIES); } if (bridge) { SetTrkBits(trk, TB_BRIDGE); } if (roadbed) { SetTrkBits(trk, TB_ROADBED); } }