/** \file ccornu.c * Cornu Command. Draw or modify a Cornu Easement Track. */ /* XTrkCad - Model Railroad CAD * * Cornu curves are a family of mathematically defined curves that define spirals that Euler spirals and elastica come from. * * They have the useful property for us that curvature increases linearly along the curve which * means the acceleration towards the center of the curve also increases evenly. Railways have long understood that * smoothly changing the radius is key to passenger comfort and reduced derailments. The railway versions of these * curves were called variously called easements, Talbot or Euler spirals. * * In XTrackCAD often want to change radius smoothly between two tracks whose end position, angle and curvature are known. * * Finding the right part(s) of the Cornu to fit the gap (if one is available) is mathematically complex, * but fortunately Raph Levien published a PhD thesis on this together with his mathematical libraries as * open source. He was doing work on font design where the Cornu shapes make beautiful smooth fonts. He * was faced with the reality, though, that graphics packages do not include these shapes as native objects * and so his solution was to produce a set of Bezier curves that approximate the solution. * * We already have a tool that can produce a set of arcs and straight line to approximate a Bezier - so that in the end * for an easement track we will have an array of Bezier, each of which is an array of Arcs and Lines. The tool will * use the approximations for most of its work. * * The inputs for the Cornu are the end points, angles and radii. To match the Cornu algorithm's expectations we input * these as a set of knots (points on lines). One point is always the desired end point and the other two are picked * direct the code to derive the other two end conditions. By specifying that the desired end point is a "one-way" * knot we ensure that the result has smooth ends of either zero or fixed radius. * * When reading back the output, we simply ignore the results before the first end point knot and after the last. * * Because we are mathematically deriving the output, we can alter the end conditions and recalculate. This allows * support of modify for Cornu Easements and also movement of tracks that are connected via a Cornu easement to another track. * * Note that unlike the existing Easements in XTrkCAD, the degree of sharpness (the rate of change of curvature) * is derived not defined. By adjusting the ends, one can have an infinite set of sharpness solutions. * * Cornu will not find a solution for every set of input conditions, or may propose one that is impractical such as * huge loops or tiny curves. These are mathematically correct, but not useful. In these cases the answer is to change the * end conditions (more space between the ends, different angles or different radii). * * Note that every time we change the Cornu end points we have to recalculate the Bezier approximation, * which recalculates the arc approximations, but that still means that the majority of the time we are using the approximation. * * Cornus do not have cusps, but can result in smooth loops. If there is too much looping, the code will reject the easement. * * This program is built and founded upon Raph Levien's seminal work and relies on an adaptation of his Cornu library. * As with the Bezier work, it also relies on the pages about Bezier curves that PoMax put up at https://pomax.github.io/bezierinfo * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "track.h" #include "spiro.h" #include "spiroentrypoints.h" #include "bezctx_xtrkcad.h" #include "draw.h" #include "ccurve.h" #include "ccornu.h" #include "tcornu.h" #include "tbezier.h" #include "cstraigh.h" #include "drawgeom.h" #include "cjoin.h" #include "i18n.h" #include "common.h" #include "utility.h" #include "math.h" #include "param.h" #include "layout.h" #include "cundo.h" #include "messages.h" #include "cselect.h" #include "fileio.h" #include extern drawCmd_t tempD; extern TRKTYP_T T_BEZIER; extern TRKTYP_T T_CORNU; typedef struct { coOrd end_center; coOrd end_curve; DIST_T mid_disp; BOOL_T end_valid; BOOL_T angle_selected; BOOL_T radius_selected; BOOL_T last_selected; ANGLE_T arc_angle; } endHandle; /* * STATE INFO */ enum Cornu_States { NONE, POS_1, LOC_2, POS_2, PICK_POINT, POINT_PICKED, TRACK_SELECTED }; typedef enum {CORNU_MODIFY, CORNU_CREATE} cornuCmdType_e; static struct { enum Cornu_States state; coOrd pos[2]; int number_of_points; int selectEndPoint; int selectMidPoint; int selectEndHandle; int prevSelected; int prevEndPoint; DIST_T width; track_p trk[2]; EPINX_T ep[2]; DIST_T radius[2]; ANGLE_T angle[2]; ANGLE_T arcA0[2]; ANGLE_T arcA1[2]; coOrd center[2]; curveType_e trackType[2]; BOOL_T extend[2]; trkSeg_t extendSeg[2]; trkSeg_t ep1Segs[11]; int ep1Segs_da_cnt; trkSeg_t ep2Segs[11]; int ep2Segs_da_cnt; dynArr_t crvSegs_da; int crvSegs_da_cnt; trkSeg_t trk1Seg; trkSeg_t trk2Seg; track_p selectTrack; DIST_T minRadius; BOOL_T circleorHelix[2]; DIST_T trackGauge; int cmdType; dynArr_t midSegs; dynArr_t mid_points; dynArr_t tracks; BOOL_T ends[2]; endHandle endHandle[2]; bezctx * bezc; cornuCmdType_e commandType; } Da; static trkSeg_p curCornu; static wIndex_t cornuHotBarCmdInx; static struct { trkSeg_t st; trkSeg_t back; trkSeg_t txt; int count; } hotB; static char * CmdCornuHotBarProc( hotBarProc_e op, void * data, drawCmd_p d, coOrd * origP ) { trkSeg_p trkseg = &hotB.st; switch ( op ) { case HB_SELECT: CmdCornu( C_CANCEL, zero ); curCornu = trkseg; DoCommandB( (void*)(intptr_t)cornuHotBarCmdInx ); return NULL; case HB_LISTTITLE: sprintf(message,_("%s FlexTrack"),GetScaleName(GetLayoutCurScale())); return message; case HB_BARTITLE: sprintf(message,_("%s FlexTrack"),GetScaleName(GetLayoutCurScale())); return message; case HB_FULLTITLE: sprintf(message,_("%s FlexTrack"),GetScaleName(GetLayoutCurScale())); return message; case HB_DRAW: DrawSegs( d, *origP, 0.0, trkseg, hotB.count, trackGauge, wDrawColorBlack ); return NULL; } return NULL; } static pts_t pts[4]; EXPORT void AddHotBarCornu( void ) { hotB.st.type = SEG_STRTRK; hotB.st.color = wDrawColorBlack; hotB.st.u.l.pos[0] = zero; DIST_T ratio = 75.0/curScaleRatio; Translate(&hotB.st.u.l.pos[1],zero,45.0,15.0*ratio); hotB.st.u.l.angle = 45.0; pts[0].pt_type = wPolyLineStraight; pts[0].pt.x = 1.0*ratio; pts[0].pt.y = 5.0*ratio; pts[1].pt_type = wPolyLineStraight; pts[1].pt.x = 1.0*ratio; pts[1].pt.y = 8.0*ratio; pts[2].pt_type = wPolyLineStraight; pts[2].pt.x = 13.0*ratio; pts[2].pt.y = 8.0*ratio; pts[3].pt_type = wPolyLineStraight; pts[3].pt.x = 13.0*ratio; pts[3].pt.y = 5.0*ratio; hotB.back.type = SEG_FILPOLY; hotB.back.color = wDrawColorWhite; hotB.back.u.p.orig.x = 0.0; hotB.back.u.p.orig.y = 0.0; hotB.back.u.p.cnt = 4; hotB.back.u.p.angle = 0.0; hotB.back.u.p.polyType = RECTANGLE; hotB.back.u.p.pts = &pts[0]; hotB.txt.type = SEG_TEXT; hotB.txt.color = wDrawColorBlack; hotB.txt.u.t.pos.x = 1.0*ratio; hotB.txt.u.t.pos.y = 5.0*ratio; hotB.txt.u.t.boxed = TRUE; hotB.txt.u.t.string = MyStrdup(_(" FLEX ")); hotB.txt.u.t.fontP = NULL; hotB.txt.u.t.fontSize = 160.0*ratio; hotB.txt.u.t.angle = 0.0; char * label = MyMalloc(256); sprintf(label,_("%s FlexTrack"),GetScaleName(GetLayoutCurScale())); coOrd end; end = hotB.st.u.l.pos[1]; //end.x = 21.25; //end.y = 21.25; hotB.count = 3; //hotB.st.u.l.pos[1] = end; AddHotBarElement( label, end, zero, TRUE, TRUE, curBarScale>0?curBarScale:-1, &hotB, CmdCornuHotBarProc ); } int createMidPoint(dynArr_t * ap, coOrd pos0, //end on curve BOOL_T point_selected, BOOL_T point_selectable, BOOL_T track_modifyable ) { DIST_T d, w; d = tempD.scale*0.25; w = tempD.scale/tempD.dpi; /*double width*/ DYNARR_APPEND(trkSeg_t,*ap,1); trkSeg_p sp = &DYNARR_LAST(trkSeg_t,*ap); sp->u.c.center = pos0; sp->u.c.a0 = 0.0; sp->u.c.a1 = 360.0; sp->u.c.radius = d/2; sp->type = point_selected?SEG_FILCRCL:SEG_CRVLIN; sp->width = w; sp->color = drawColorBlack; return 1; } /** * Draw a EndPoint. * A Cornu end Point has a filled circle surrounded by another circle for endpoint */ int createEndPoint( trkSeg_t sp[], //seg pointer for up to 2 trkSegs (ends and line) coOrd pos0, //end on curve BOOL_T point_selected, BOOL_T point_selectable, BOOL_T track_modifyable, BOOL_T track_present, ANGLE_T angle, DIST_T radius, coOrd centert, endHandle * endHandle ) { DIST_T d, w; int num =0; d = tempD.scale*0.25; w = tempD.scale/tempD.dpi; /*double width*/ num = 1; if (point_selectable) { sp[1].u.c.center = pos0; sp[1].u.c.a0 = 0.0; sp[1].u.c.a1 = 360.0; sp[1].u.c.radius = d/2; sp[1].type = SEG_CRVLIN; sp[1].width = w; sp[1].color = point_selected?drawColorBlue:drawColorRed; num = 2; } sp[0].u.c.center = pos0; sp[0].u.c.a0 = 0.0; sp[0].u.c.a1 = 360.0; sp[0].width = w; sp[0].u.c.radius = d/4; sp[0].color = point_selected?drawColorBlue:drawColorRed; if (track_modifyable) sp[0].type = SEG_CRVLIN; else sp[0].type = SEG_FILCRCL; if (!track_present && endHandle ) { endHandle->end_center = zero; endHandle->end_curve = zero; endHandle->end_valid = TRUE; endHandle->mid_disp = 0.0; DIST_T end_length = 20*trackGauge; Translate(&endHandle->end_curve,pos0,angle,end_length); Translate(&endHandle->end_center,pos0,angle,end_length/2); if (radius>0.0) { ANGLE_T a1 = R2D(end_length/radius); if (DifferenceBetweenAngles(angle,FindAngle(centert,pos0))>0.0) { a1 = -a1; } PointOnCircle( &endHandle->end_curve, centert,radius,NormalizeAngle(FindAngle(centert,pos0)+a1)); PointOnCircle( &endHandle->end_center,centert,radius,NormalizeAngle(FindAngle(centert,pos0)+(a1/2.0))); coOrd cm; cm = endHandle->end_center; ANGLE_T a = FindAngle(endHandle->end_curve,pos0); Rotate(&cm,endHandle->end_curve,-a ); endHandle->mid_disp = cm.x-endHandle->end_curve.x; curveData_t curveData; PlotCurve(crvCmdFromCenter,pos0,endHandle->end_center, endHandle->end_curve, &curveData, FALSE); if (curveData.type == curveTypeStraight) { coOrd pos_line[2]; Translate(&pos_line[0],pos0,FindAngle(pos0,endHandle->end_curve)+90,trackGauge/2); Translate(&pos_line[1],endHandle->end_curve,FindAngle(pos0,endHandle->end_curve)+90,trackGauge/2); sp[num].type = SEG_STRLIN; sp[num].width = w; sp[num].u.l.pos[0] = pos_line[0]; sp[num].u.l.pos[1] = pos_line[1]; sp[num].color = (endHandle->last_selected||endHandle->radius_selected)?drawColorBlue:drawColorRed; num++; Translate(&pos_line[0],pos0,FindAngle(pos0,endHandle->end_curve)-90,trackGauge/2); Translate(&pos_line[1],endHandle->end_curve,FindAngle(pos0,endHandle->end_curve)-90,trackGauge/2); sp[num].type = SEG_STRLIN; sp[num].width = w; sp[num].u.l.pos[0] = pos_line[0]; sp[num].u.l.pos[1] = pos_line[1]; sp[num].color = (endHandle->last_selected||endHandle->radius_selected)?drawColorBlue:drawColorRed; num++; pos_line[0]= pos0; Translate(&pos_line[1],pos0,-FindAngle(pos0,endHandle->end_curve),end_length); sp[num].type = SEG_STRLIN; sp[num].width = w; sp[num].u.l.pos[0] = pos_line[0]; sp[num].u.l.pos[1] = pos_line[1]; sp[num].color = drawColorRed; num++; } else { DIST_T pos_rad; pos_rad = radius+trackGauge/2; sp[num].type = SEG_CRVLIN; sp[num].width = w; sp[num].u.c.center = centert; sp[num].u.c.radius = pos_rad; ANGLE_T an0 = FindAngle(centert,pos0); ANGLE_T an1 = FindAngle(centert,endHandle->end_curve); if (DifferenceBetweenAngles(an0,an1)>0) { sp[num].u.c.a1 = DifferenceBetweenAngles(an0,an1); sp[num].u.c.a0 = an0; } else { sp[num].u.c.a1 = -DifferenceBetweenAngles(an0,an1); sp[num].u.c.a0 = an1; } endHandle->arc_angle = sp[num].u.c.a1; sp[num].color = (endHandle->last_selected||endHandle->radius_selected)?drawColorBlue:drawColorRed; num++; pos_rad = radius-trackGauge/2; sp[num].type = SEG_CRVLIN; sp[num].width = w; sp[num].u.c.center = centert; sp[num].u.c.radius = pos_rad; sp[num].u.c.a1 = sp[num-1].u.c.a1; sp[num].u.c.a0 = sp[num-1].u.c.a0; sp[num].color = (endHandle->last_selected||endHandle->radius_selected)?drawColorBlue:drawColorRed; num++; } } else { coOrd pos_line[2]; Translate(&pos_line[0],pos0,FindAngle(pos0,endHandle->end_curve)+90,trackGauge/2); Translate(&pos_line[1],endHandle->end_curve,FindAngle(pos0,endHandle->end_curve)+90,trackGauge/2); sp[num].type = SEG_STRLIN; sp[num].width = w; sp[num].u.l.pos[0] = pos_line[0]; sp[num].u.l.pos[1] = pos_line[1]; sp[num].color = (endHandle->last_selected||endHandle->radius_selected)?drawColorBlue:drawColorRed; num++; Translate(&pos_line[0],pos0,FindAngle(pos0,endHandle->end_curve)-90,trackGauge/2); Translate(&pos_line[1],endHandle->end_curve,FindAngle(pos0,endHandle->end_curve)-90,trackGauge/2); sp[num].type = SEG_STRLIN; sp[num].width = w; sp[num].u.l.pos[0] = pos_line[0]; sp[num].u.l.pos[1] = pos_line[1]; sp[num].color = (endHandle->last_selected||endHandle->radius_selected)?drawColorBlue:drawColorRed; num++; } coOrd pos_line[2]; pos_line[0]= pos0; Translate(&pos_line[1],pos0,angle+180,end_length); sp[num].type = SEG_STRLIN; sp[num].width = w; sp[num].u.l.pos[0] = pos_line[0]; sp[num].u.l.pos[1] = pos_line[1]; sp[num].color = drawColorRed; num++; sp[num].type = SEG_CRVLIN; sp[num].u.c.center = endHandle->end_curve; sp[num].u.c.a0 = 0.0; sp[num].u.c.a1 = 360.0; sp[num].width = w; sp[num].u.c.radius = d/4; sp[num].color = endHandle->angle_selected?drawColorBlue:drawColorRed; num++; if (radius<=0.0) DrawArrowHeads(&sp[num],endHandle->end_center,angle+90.0,TRUE,endHandle->radius_selected?drawColorBlue:drawColorRed); else DrawArrowHeads(&sp[num],endHandle->end_center,FindAngle(centert,endHandle->end_center),TRUE,endHandle->radius_selected?drawColorBlue:drawColorRed); num=num+5; } else if (endHandle) { endHandle->end_valid=FALSE; } return num; } static dynArr_t anchors_da; #define anchors(N) DYNARR_N(trkSeg_t,anchors_da,N) static void CreateCornuEndAnchor(coOrd p, wBool_t lock) { DIST_T d = tempD.scale*0.15; DYNARR_APPEND(trkSeg_t,anchors_da,1); int i = anchors_da.cnt-1; anchors(i).type = lock?SEG_FILCRCL:SEG_CRVLIN; anchors(i).color = wDrawColorBlue; anchors(i).u.c.center = p; anchors(i).u.c.radius = d/2; anchors(i).u.c.a0 = 0.0; anchors(i).u.c.a1 = 360.0; anchors(i).width = 0; DYNARR_APPEND(trkSeg_t,anchors_da,1); i = anchors_da.cnt-1; anchors(i).type = SEG_CRVLIN; anchors(i).color = wDrawColorBlue; anchors(i).u.c.center = p; anchors(i).u.c.radius = d; anchors(i).u.c.a0 = 0.0; anchors(i).u.c.a1 = 360.0; anchors(i).width = 0; } static void CreateCornuExtendAnchor(coOrd p, ANGLE_T a, wBool_t selected) { DYNARR_SET(trkSeg_t,anchors_da,anchors_da.cnt+5); DrawArrowHeads(&DYNARR_N(trkSeg_t,anchors_da,anchors_da.cnt-5),p,a,FALSE,wDrawColorBlue); } static void CreateCornuAnchor(coOrd p, wBool_t open) { DIST_T d = tempD.scale*0.15; DYNARR_APPEND(trkSeg_t,anchors_da,1); int i = anchors_da.cnt-1; anchors(i).type = open?SEG_CRVLIN:SEG_FILCRCL; anchors(i).color = wDrawColorBlue; anchors(i).u.c.center = p; anchors(i).u.c.radius = d/2; anchors(i).u.c.a0 = 0.0; anchors(i).u.c.a1 = 360.0; anchors(i).width = 0; } /* * Add element to DYNARR pointed to by caller from segment handed in */ void addSegCornu(dynArr_t * const array_p, trkSeg_p seg) { trkSeg_p s; DYNARR_APPEND(trkSeg_t, * array_p, 10); //Adds 1 to cnt s = &DYNARR_N(trkSeg_t,* array_p,array_p->cnt-1); s->type = seg->type; s->bezSegs.max = 0; s->bezSegs.cnt = 0; if (s->bezSegs.ptr) MyFree(s->bezSegs.ptr); s->bezSegs.ptr = NULL; s->color = seg->color; s->width = seg->width; if ((s->type == SEG_BEZLIN || s->type == SEG_BEZTRK) && seg->bezSegs.cnt) { s->u.b.angle0 = seg->u.b.angle0; //Copy all the rest s->u.b.angle3 = seg->u.b.angle3; s->u.b.length = seg->u.b.length; s->u.b.minRadius = seg->u.b.minRadius; for (int i=0;i<4;i++) s->u.b.pos[i] = seg->u.b.pos[i]; s->u.b.radius0 = seg->u.b.radius3; for (int i = 0; ibezSegs.cnt; i++) { addSegCornu(&s->bezSegs, (((trkSeg_p)seg->bezSegs.ptr) + i)); //recurse for copying embedded Beziers as in Cornu joint } } else { s->u = seg->u; } } EXPORT void SetKnots(spiro_cp knots[], coOrd posk[], char type[], int count) { for (int i = 0; i < count; i++) { knots[i].x = posk[i].x; knots[i].y = posk[i].y; knots[i].ty = type[i]; } } typedef struct { coOrd pos; char ty; } points_t; // Take in extra points within Cornu // G2 (position only k1'' = k2'' = 0); Also Cornu <-> Cornu // G4 (position only - splitable for Cornu - a G4 point) k1''= k2'' BOOL_T CallCornuM(dynArr_t extra_points, BOOL_T end[2], coOrd pos[2], cornuParm_t * cp, dynArr_t * array_p, BOOL_T spots) { array_p->cnt = 0; //Create LH knots //Find remote end point of track, create start knot int ends[2]; ends[0] = (end[0]?2:0); ends[1] = (end[0]?3:1)+extra_points.cnt; spiro_cp * knots; coOrd * posk; char * type; posk = MyMalloc((6+extra_points.cnt)*sizeof(coOrd)); knots = MyMalloc((6+extra_points.cnt)*sizeof(spiro_cp)); type = MyMalloc((6+extra_points.cnt)*sizeof(char)); BOOL_T back; ANGLE_T angle1; if (Da.bezc) free(Da.bezc); Da.bezc = new_bezctx_xtrkcad(array_p,ends,spots,tempD.scale*0.15/4); coOrd pos0 = pos[0]; if (end[0]) { type[0] = SPIRO_OPEN_CONTOUR; type[1] = SPIRO_G2; type[2] = SPIRO_RIGHT; if (cp->radius[0] == 0.0) { Translate(&posk[0],pos0,cp->angle[0],10); Translate(&posk[1],pos0,cp->angle[0],5); } else { angle1 = FindAngle(cp->center[0],pos[0]); if (NormalizeAngle(angle1 - cp->angle[0])<180) back = TRUE; else back = FALSE; posk[0] = pos[0]; Rotate(&posk[0],cp->center[0],(back)?-10:10); posk[1] = pos[0]; Rotate(&posk[1],cp->center[0],(back)?-5:5); } posk[2] = pos[0]; } else { type[0] = SPIRO_OPEN_CONTOUR; posk[0] = pos[0]; } for (int i=0;iradius[1] == 0.0) { Translate(&posk[(end[0]?3:1)+extra_points.cnt+1],pos1,cp->angle[1],5); Translate(&posk[(end[0]?3:1)+extra_points.cnt+2],pos1,cp->angle[1],10); } else { angle1 = FindAngle(cp->center[1],pos[1]); if (NormalizeAngle(angle1 - cp->angle[1])>180) back = TRUE; else back = FALSE; posk[(end[0]?3:1)+extra_points.cnt+1] = pos[1]; Rotate(&posk[(end[0]?3:1)+extra_points.cnt+1],cp->center[1],(back)?5:-5); posk[(end[0]?3:1)+extra_points.cnt+2] = pos[1]; Rotate(&posk[(end[0]?3:1)+extra_points.cnt+2],cp->center[1],(back)?10:-10); } } else { type[(end[0]?3:1)+extra_points.cnt] = SPIRO_END_OPEN_CONTOUR; } SetKnots(knots, posk, type, ((end[0]?3:1)+(end[1]?3:1)+extra_points.cnt)); TaggedSpiroCPsToBezier(knots,Da.bezc); MyFree(posk); MyFree(knots); MyFree(type); if (!bezctx_xtrkcad_close(Da.bezc)) { return FALSE; } return TRUE; } EXPORT BOOL_T CallCornu0(coOrd pos[2], coOrd center[2], ANGLE_T angle[2], DIST_T radius[2], dynArr_t * array_p, BOOL_T spots) { array_p->cnt = 0; //Create LH knots //Find remote end point of track, create start knot int ends[2]; ends[0] = 2; ends[1] = 3; spiro_cp knots[6]; coOrd posk[6]; char type[6]; BOOL_T back; ANGLE_T angle1; if (Da.bezc) free(Da.bezc); Da.bezc = new_bezctx_xtrkcad(array_p,ends,spots,tempD.scale*0.15/4); coOrd pos0 = pos[0]; type[0] = SPIRO_OPEN_CONTOUR; if (radius[0] == 0.0) { Translate(&posk[0],pos0,angle[0],10); Translate(&posk[1],pos0,angle[0],5); } else { angle1 = FindAngle(center[0],pos[0]); if (NormalizeAngle(angle1 - angle[0])<180) back = TRUE; else back = FALSE; posk[0] = pos[0]; Rotate(&posk[0],center[0],(back)?-10:10); posk[1] = pos[0]; Rotate(&posk[1],center[0],(back)?-5:5); } type[1] = SPIRO_G2; posk[2] = pos[0]; type[2] = SPIRO_RIGHT; posk[3] = pos[1]; type[3] = SPIRO_LEFT; coOrd pos1 = pos[1]; if (radius[1] == 0.0 ) { Translate(&posk[4],pos1,angle[1],5); Translate(&posk[5],pos1,angle[1],10); } else { angle1 = FindAngle(center[1],pos[1]); if (NormalizeAngle(angle1 - angle[1])>180) back = TRUE; else back = FALSE; posk[4] = pos[1]; Rotate(&posk[4],center[1],(back)?5:-5); posk[5] = pos[1]; Rotate(&posk[5],center[1],(back)?10:-10); } type[4] = SPIRO_G2; type[5] = SPIRO_END_OPEN_CONTOUR; SetKnots(knots, posk, type, 6); TaggedSpiroCPsToBezier(knots,Da.bezc); if (!bezctx_xtrkcad_close(Da.bezc)) { return FALSE; } return TRUE; } /* * Set up the call to Cornu0. Take the conditions of the two ends from the connected tracks. */ BOOL_T CallCornu(coOrd pos[2], track_p trk[2], EPINX_T ep[2], dynArr_t * array_p, cornuParm_t * cp) { trackParams_t params; ANGLE_T angle; for (int i=0;i<2;i++) { if (trk[i]) { if (!GetTrackParams(PARAMS_CORNU,trk[i],pos[i],¶ms)) return FALSE; cp->pos[i] = pos[i]; if (ep && ep[i]>=0) angle = GetTrkEndAngle(trk[i],ep[i]); else angle = params.angle; //Turntable only if (Da.circleorHelix[i]) { //Helix/Circle only cp->radius[i] = params.arcR; cp->center[i] = params.arcP; if (ep && ep[i]>=0) cp->angle[i] = NormalizeAngle(params.track_angle+(ep[i]?180:0)); } else if (params.type == curveTypeStraight) { cp->angle[i] = NormalizeAngle(angle+180); //Because end always backwards cp->radius[i] = 0.0; } else if ((params.type == curveTypeCornu || params.type == curveTypeBezier) && params.arcR == 0.0 ) { cp->radius[i] = 0.0; if (ep && ep[i]>=0) cp->angle[i] = NormalizeAngle(params.track_angle+(ep[i]?180:0)); //Use point not end } else if (params.type == curveTypeCurve) { if (ep && ep[i]>=0) cp->angle[i] = NormalizeAngle(params.track_angle+(ep[i]?180:0)); cp->radius[i] = params.arcR; cp->center[i] = params.arcP; } else if ((params.type == curveTypeCornu || params.type == curveTypeBezier) && params.arcR != 0.0 ){ if (ep && ep[i]>=0) cp->angle[i] = NormalizeAngle(params.track_angle+(ep[i]?180:0)); cp->radius[i] = params.arcR; cp->center[i] = params.arcP; } else { cp->angle[i] = NormalizeAngle(angle+180); //Unknown - treat like straight cp->radius[i] = params.arcR; cp->center[i] = params.arcP; } } } return CallCornu0(pos,cp->center,cp->angle,cp->radius,array_p,ep?TRUE:FALSE); } /* * Draw Cornu while editing it. It consists of up to five elements - the ends, the curve and one or two End Points. * */ EXPORT void DrawCornuCurve( trkSeg_p first_trk, trkSeg_p point1, int ep1Segs_cnt, trkSeg_p curveSegs, int crvSegs_cnt, trkSeg_p point2, int ep2Segs_cnt, trkSeg_p second_trk, trkSeg_p extend1_trk, trkSeg_p extend2_trk, trkSeg_p mids, int midSegs_cnt, wDrawColor color ) { if (first_trk) DrawSegs( &tempD, zero, 0.0, first_trk, 1, Da.trackGauge, drawColorBlack ); if (crvSegs_cnt>0 && curveSegs) DrawSegs( &tempD, zero, 0.0, curveSegs, crvSegs_cnt, Da.trackGauge, color ); if (second_trk) DrawSegs( &tempD, zero, 0.0, second_trk, 1, Da.trackGauge, drawColorBlack ); if (ep1Segs_cnt>0 && point1) DrawSegs( &tempD, zero, 0.0, point1, ep1Segs_cnt, Da.trackGauge, drawColorBlack ); if (ep2Segs_cnt>0 && point2) DrawSegs( &tempD, zero, 0.0, point2, ep2Segs_cnt, Da.trackGauge, drawColorBlack ); if (midSegs_cnt>0 && mids) DrawSegs( &tempD, zero, 0.0, mids, midSegs_cnt, Da.trackGauge, drawColorBlack ); if (extend1_trk) DrawSegs( &tempD, zero, 0.0, extend1_trk, 1, Da.trackGauge, drawColorBlack); if (extend2_trk) DrawSegs( &tempD, zero, 0.0, extend2_trk, 1, Da.trackGauge, drawColorBlack); } /* * If Track, make it red if the radius is below minimum */ void DrawTempCornu() { DrawCornuCurve(&Da.trk1Seg, &Da.ep1Segs[0],Da.ep1Segs_da_cnt, (trkSeg_t *)Da.crvSegs_da.ptr,Da.crvSegs_da_cnt, &Da.ep2Segs[0],Da.ep2Segs_da_cnt, &Da.trk2Seg, Da.extend[0]?&Da.extendSeg[0]:NULL, Da.extend[1]?&Da.extendSeg[1]:NULL, (trkSeg_t *)Da.midSegs.ptr,Da.midSegs.cnt, fabs(Da.minRadius)<(GetLayoutMinTrackRadius()-EPSILON)?exceptionColor:normalColor); } void CreateBothEnds(int selectEndPoint, int selectMidPoint, int selectEndHandle, int lastSelected ) { BOOL_T selectable[2],modifyable[2]; selectable[0] = !Da.trk[0] || ( Da.trk[0] && !QueryTrack(Da.trk[0],Q_IS_CORNU) && !QueryTrack(Da.trk[0],Q_CAN_MODIFY_CONTROL_POINTS)); modifyable[0] = !Da.trk[0] || ( Da.trk[0] && QueryTrack(Da.trk[0],Q_CORNU_CAN_MODIFY)); selectable[1] = !Da.trk[1] || ( Da.trk[1] && !QueryTrack(Da.trk[1],Q_IS_CORNU) && !QueryTrack(Da.trk[1],Q_CAN_MODIFY_CONTROL_POINTS)); modifyable[1] = !Da.trk[1] || ( Da.trk[1] && QueryTrack(Da.trk[1],Q_CORNU_CAN_MODIFY)); Da.endHandle[0].angle_selected = (selectEndHandle==1)?TRUE:FALSE; Da.endHandle[0].radius_selected = (selectEndHandle==0)?TRUE:FALSE; Da.endHandle[1].angle_selected = (selectEndHandle==3)?TRUE:FALSE; Da.endHandle[1].radius_selected = (selectEndHandle==2)?TRUE:FALSE; Da.endHandle[0].last_selected = lastSelected==0?TRUE:FALSE; Da.endHandle[1].last_selected = lastSelected==1?TRUE:FALSE; if (selectEndPoint == -1) { Da.ep1Segs_da_cnt = createEndPoint(Da.ep1Segs, Da.pos[0],FALSE,selectable[0],modifyable[0],Da.trk[0]!=NULL,Da.angle[0],Da.radius[0],Da.center[0],Da.extend[0]?NULL:&Da.endHandle[0]); Da.ep2Segs_da_cnt = createEndPoint(Da.ep2Segs, Da.pos[1],FALSE,selectable[1],modifyable[1],Da.trk[1]!=NULL,Da.angle[1],Da.radius[1],Da.center[1],Da.extend[1]?NULL:&Da.endHandle[1]); } else { Da.ep1Segs_da_cnt = createEndPoint(Da.ep1Segs, Da.pos[0],selectEndPoint == 0,selectable[0],modifyable[0],Da.trk[0]!=NULL,Da.angle[0],Da.radius[0],Da.center[0],Da.extend[0]?NULL:&Da.endHandle[0]); Da.ep2Segs_da_cnt = createEndPoint(Da.ep2Segs, Da.pos[1],selectEndPoint == 1,selectable[1],modifyable[1],Da.trk[1]!=NULL,Da.angle[1],Da.radius[1],Da.center[1],Da.extend[1]?NULL:&Da.endHandle[1]); } Da.endHandle[0].end_valid = !Da.extend[0]; Da.endHandle[1].end_valid = !Da.extend[1]; DYNARR_RESET(trkSeg_t,Da.midSegs); for (int i=0;i=0.0) Da.ends[0] = TRUE; else Da.ends[0] = FALSE; if (Da.radius[1] >=0.0) Da.ends[1] = TRUE; else Da.ends[1] = FALSE; } BOOL_T GetConnectedTrackParms(track_p t, const coOrd pos, int end, EPINX_T track_end, wBool_t extend) { trackParams_t trackParams; coOrd pos1; if ((track_end>=0) && extend) pos1 = GetTrkEndPos(t,track_end); else pos1 = pos; if (!GetTrackParams(PARAMS_CORNU, t, pos1, &trackParams)) return FALSE; Da.radius[end] = 0.0; Da.center[end] = zero; Da.circleorHelix[end] = FALSE; Da.trackType[end] = trackParams.type; if (trackParams.type == curveTypeCurve) { Da.arcA0[end] = trackParams.arcA0; Da.arcA1[end] = trackParams.arcA1; Da.radius[end] = trackParams.arcR; Da.center[end] = trackParams.arcP; if (trackParams.circleOrHelix) { Da.circleorHelix[end] = TRUE; Da.angle[end] = trackParams.track_angle; //For Now } else { Da.angle[end] = NormalizeAngle(GetTrkEndAngle(t,track_end)+180); //Da.angle[end] = NormalizeAngle(trackParams.track_angle + (track_end?180:0)); } } else if (trackParams.type == curveTypeBezier) { Da.angle[end] = NormalizeAngle(trackParams.track_angle+(track_end?180:0)); if (trackParams.arcR == 0) { Da.radius[end] = 0; Da.center[end] = zero; } else { Da.arcA0[end] = trackParams.arcA0; Da.arcA1[end] = trackParams.arcA1; Da.radius[end] = trackParams.arcR; Da.center[end] = trackParams.arcP; } } else if (trackParams.type == curveTypeCornu) { int ep = trackParams.ep; Da.angle[end] = NormalizeAngle(trackParams.cornuAngle[ep]+180); Da.radius[end] = trackParams.cornuRadius[ep]; Da.pos[end] = trackParams.cornuEnd[ep]; Da.center[end] = trackParams.cornuCenter[ep]; } else if (trackParams.type == curveTypeStraight) { if (trackParams.ep>=0) Da.angle[end] = NormalizeAngle(GetTrkEndAngle(t,track_end)+180); //Ignore params.angle because it gives from nearest end else { Da.angle[end] = NormalizeAngle(trackParams.angle+180); //Turntable Da.pos[end] = trackParams.lineEnd; //End moved to constrain angle } } return TRUE; } void CorrectHelixAngles() { if ( Da.circleorHelix[0] ) { Da.ep[0] = PickArcEndPt( Da.center[0], Da.pos[0], Da.pos[1] ); if (Da.ep[0] == 1) Da.angle[0] = NormalizeAngle(Da.angle[0]+180); } if ( Da.circleorHelix[1] ) { Da.ep[1] = PickArcEndPt( Da.center[1], Da.pos[1], Da.pos[0] ); if (Da.ep[1] == 1) Da.angle[1] = NormalizeAngle(Da.angle[1]+180); } } BOOL_T CheckHelix(track_p trk) { if ( Da.trk[0] && QueryTrack(Da.trk[0],Q_HAS_VARIABLE_ENDPOINTS)) { track_p t = GetTrkEndTrk(Da.trk[0],Da.ep[0]); if ( t != NULL && t != trk) { ErrorMessage( MSG_TRK_ALREADY_CONN, _("First") ); return FALSE; } } if ( Da.trk[1] && QueryTrack(Da.trk[1],Q_HAS_VARIABLE_ENDPOINTS)) { track_p t = GetTrkEndTrk(Da.trk[1],Da.ep[1]); if ( t != NULL && t != trk) { ErrorMessage( MSG_TRK_ALREADY_CONN, _("Second") ); return FALSE; } } return TRUE; } void SetUpCornuParms(cornuParm_t * cp) { cp->center[0] = Da.center[0]; cp->angle[0] = Da.angle[0]; cp->radius[0] = Da.radius[0]; cp->center[1] = Da.center[1]; cp->angle[1] = Da.angle[1]; cp->radius[1] = Da.radius[1]; } track_p CreateCornuFromPoints(coOrd pos[2],BOOL_T track_end[2]) { coOrd center[2]; DIST_T radius[2]; ANGLE_T angle[2]; BOOL_T back, neg; cornuParm_t new; int inx,subinx; coOrd pos_temp[2]; for (int i=0;i<2;i++) { pos_temp[i] = pos[i]; if (!track_end[i] || (Da.radius[i]==-1.0)) { angle[i] = GetAngleSegs(Da.crvSegs_da.cnt,(trkSeg_t *)(Da.crvSegs_da.ptr),&pos_temp[i],&inx,NULL,&back,&subinx,&neg); trkSeg_p segPtr = &DYNARR_N(trkSeg_t, Da.crvSegs_da, inx); if (segPtr->type == SEG_BEZTRK) segPtr = &DYNARR_N(trkSeg_t, segPtr->bezSegs, subinx); if (i==0) { if (neg==back) angle[i] = NormalizeAngle(angle[i]+180); } else { if (!(neg==back)) angle[i] = NormalizeAngle(angle[i]+180); } if (segPtr->type == SEG_STRTRK) { radius[i] = 0.0; center[i] = zero; } else if (segPtr->type == SEG_CRVTRK) { center[i] = segPtr->u.c.center; radius[i] = fabs(segPtr->u.c.radius); } } else { pos[i] = Da.pos[i]; radius[i] = Da.radius[i]; center[i] = Da.center[i]; angle[i] = Da.angle[i]; neg = FALSE; back = FALSE; } } new.pos[0] = pos[0]; new.pos[1] = pos[1]; new.angle[0] = angle[0]; new.angle[1] = angle[1]; new.center[0] = center[0]; new.center[1] = center[1]; new.radius[0] = radius[0]; new.radius[1] = radius[1]; track_p trk1 = NewCornuTrack(new.pos,new.center,new.angle,new.radius,NULL,0); if (trk1==NULL) { wBeep(); InfoMessage(_("Cornu Create Failed for p1[%0.3f,%0.3f] p2[%0.3f,%0.3f], c1[%0.3f,%0.3f] c2[%0.3f,%0.3f], a1=%0.3f a2=%0.3f, r1=%s r2=%s"), new.pos[0].x,new.pos[0].y, new.pos[1].x,new.pos[1].y, new.center[0].x,new.center[0].y, new.center[1].x,new.center[1].y, new.angle[0],new.angle[1], FormatDistance(new.radius[0]),FormatDistance(new.radius[1])); UndoEnd(); return NULL; } return trk1; } struct extraData { cornuData_t cornuData; }; ANGLE_T GetOpenAngle(coOrd pos[2],ANGLE_T angle[2],int moved) { ANGLE_T a = FindAngle(pos[1-moved],pos[moved]); ANGLE_T diff = (180+a)-angle[1-moved]; //Difference between input and line return a+diff; //Change to line plus this at the other end } static struct { ANGLE_T angle; DIST_T radius; } cornuModCmdContext; static BOOL_T infoSubst = FALSE; static paramFloatRange_t r10000_10000 = {-10000, 10000}; static paramFloatRange_t r0_360 = { 0, 360, 80 }; static paramData_t cornuModPLs[] = { #define cornuModEndAnglePD (cornuModPLs[0]) #define cornuModEndAngle 0 { PD_FLOAT, &cornuModCmdContext.angle, "End Angle", PDO_NORECORD|BO_ENTER, &r0_360, N_("End Angle") }, #define cornuModEndRadiusPD (cornuModPLs[1]) #define cornuModEndRadius 1 { PD_FLOAT, &cornuModCmdContext.radius, "End Radius", PDO_DIM|PDO_NORECORD|BO_ENTER, &r10000_10000, N_("End Radius") }, }; static paramGroup_t cornuModPG = { "cornuMod", 0, cornuModPLs, sizeof cornuModPLs/sizeof cornuModPLs[0] }; /* * AdjustCornuCurve * * Called to adjust the curve either when creating it or modifying it * States are "PICK_POINT" and "POINT_PICKED" and "TRACK_SELECTED". * * In PICK_POINT, the user can select an end-point to drag and release in POINT_PICKED. They can also * hit Enter (which saves the changes) or ESC (which cancels them). * * Deal with extended tracks from ends. * */ EXPORT STATUS_T AdjustCornuCurve( wAction_t action, coOrd pos, cornuMessageProc message ) { track_p t; DIST_T d; ANGLE_T a, a2; EPINX_T ep; cornuParm_t cp; wControl_p controls[5]; //Always needs a NULL last entry char * labels[4]; Da.cmdType = (long)commandContext; if (Da.state != PICK_POINT && Da.state != POINT_PICKED && Da.state != TRACK_SELECTED) return C_CONTINUE; switch ( action & 0xFF) { case C_START: DYNARR_RESET(trkSeg_t,anchors_da); infoSubst = FALSE; Da.selectEndPoint = -1; Da.selectMidPoint = -1; Da.selectEndHandle = -1; Da.prevSelected = -1; Da.prevEndPoint = -1; Da.extend[0] = FALSE; Da.extend[1] = FALSE; CreateBothEnds(Da.selectEndPoint, Da.selectMidPoint,Da.selectEndHandle,Da.prevSelected); Da.crvSegs_da.cnt = 0; SetUpCornuParms(&cp); if (CallCornuM(Da.mid_points,Da.ends,Da.pos,&cp,&Da.crvSegs_da,TRUE)) Da.crvSegs_da_cnt = Da.crvSegs_da.cnt; else Da.crvSegs_da_cnt = 0; Da.minRadius = CornuMinRadius(Da.pos,Da.crvSegs_da); InfoMessage( _("Select Point, or Add Point") ); TempRedraw(); // AdjustCornuCurve C_START return C_CONTINUE; case C_UPDATE: if (Da.state != PICK_POINT && Da.prevSelected>-1) return C_CONTINUE; int sel = Da.prevSelected; if (Da.trk[sel]) return C_CONTINUE; //Track Here - should never happen Da.radius[sel] = fabs(cornuModCmdContext.radius); Da.angle[sel] = cornuModCmdContext.angle; if (cornuModCmdContext.radius!=0) Translate(&Da.center[sel],Da.pos[sel],Da.angle[sel]+90,cornuModCmdContext.radius); CreateBothEnds(Da.prevSelected,-1,-1,Da.prevSelected); SetUpCornuParms(&cp); if (CallCornuM(Da.mid_points,Da.ends,Da.pos,&cp,&Da.crvSegs_da,TRUE)) Da.crvSegs_da_cnt = Da.crvSegs_da.cnt; else Da.crvSegs_da_cnt = 0; Da.minRadius = CornuMinRadius(Da.pos,Da.crvSegs_da); return C_CONTINUE; break; case wActionMove: if (Da.state == NONE || Da.state == PICK_POINT) { DYNARR_RESET(trkSeg_t,anchors_da); for(int i=0;i<2;i++) { if (IsClose(FindDistance(pos,Da.pos[i]))) { if (((MyGetKeyState() & WKEY_SHIFT) != 0) && Da.selectTrack) { CreateCornuExtendAnchor(Da.pos[i], Da.angle[i], FALSE); return C_CONTINUE; } else { CreateCornuAnchor(Da.pos[i], FALSE); return C_CONTINUE; } } } CreateBothEnds(Da.selectEndPoint,Da.selectMidPoint,Da.selectEndHandle,Da.prevSelected); Da.selectEndPoint = -1; for (int i=0;i Extend Da.extend[Da.selectEndPoint] = TRUE; //Adding to end Point DYNARR_RESET(trkSeg_t,anchors_da); CreateCornuExtendAnchor(Da.pos[Da.selectEndPoint], Da.angle[Da.selectEndPoint], FALSE); } } if (Da.selectMidPoint ==-1 && Da.selectEndPoint ==-1 && Da.selectEndHandle ==-1) { coOrd temp_pos = pos; wIndex_t index; if (IsClose(DistanceSegs(zero,0.0,Da.crvSegs_da.cnt,(trkSeg_p)Da.crvSegs_da.ptr,&temp_pos,&index))) { //Add Point between two other points //Find closest two points along Track int closest = -1; wIndex_t pIndex, nIndex; temp_pos = Da.pos[0]; DistanceSegs(zero,0.0,Da.crvSegs_da.cnt,(trkSeg_p)Da.crvSegs_da.ptr,&temp_pos,&pIndex); if (Da.mid_points.cnt>0) { for (int i=0;i=index))) { closest = i; break; } pIndex = nIndex; } temp_pos = Da.pos[1]; DistanceSegs(zero,0.0,Da.crvSegs_da.cnt,(trkSeg_p)Da.crvSegs_da.ptr,&temp_pos,&nIndex); if (index == nIndex) closest = Da.mid_points.cnt; if (closest == -1) closest = Da.mid_points.cnt; } else closest = 0; DYNARR_APPEND(coOrd,Da.mid_points,1); for (int i=Da.mid_points.cnt-1;i>closest;i--) { DYNARR_N(coOrd,Da.mid_points,i) = DYNARR_N(coOrd ,Da.mid_points,i-1); } DYNARR_N(coOrd,Da.mid_points,closest) = pos; Da.selectMidPoint = closest; Da.number_of_points++; CreateCornuAnchor(pos,FALSE); InfoMessage("Pin Point Added"); } else { wBeep(); InfoMessage("Add Point Is Not on Track"); return C_CONTINUE; } } if (Da.selectEndPoint == -1 && Da.selectMidPoint == -1 && Da.selectEndHandle ==-1) { wBeep(); InfoMessage( _("Not close enough to track or point, reselect") ); return C_CONTINUE; } else { if (Da.selectEndPoint >=0 ) { pos = Da.pos[Da.selectEndPoint]; if (Da.extend[Da.selectEndPoint]) InfoMessage( _("Drag out end of Cornu")); else if (Da.trk[Da.selectEndPoint]) { InfoMessage( _("Drag along end of track")); } else InfoMessage( _("Drag to move")); } else if (Da.selectMidPoint >=0 ) { pos = DYNARR_N(coOrd,Da.mid_points,Da.selectMidPoint); InfoMessage( _("Drag point to new location, Delete to remove")); } else { if (Da.selectEndHandle%2 == 0) { pos = Da.endHandle[Da.selectEndHandle/2].end_center; InfoMessage( _("Drag to change end radius")); } else { pos = Da.endHandle[Da.selectEndHandle/2].end_curve; InfoMessage( _("Drag to change end angle")); } } Da.state = POINT_PICKED; } CreateBothEnds(Da.selectEndPoint,Da.selectMidPoint,Da.selectEndHandle,Da.prevSelected); SetUpCornuParms(&cp); if (CallCornuM(Da.mid_points,Da.ends,Da.pos,&cp,&Da.crvSegs_da,TRUE)) Da.crvSegs_da_cnt = Da.crvSegs_da.cnt; else Da.crvSegs_da_cnt = 0; Da.minRadius = CornuMinRadius(Da.pos, Da.crvSegs_da); return C_CONTINUE; case C_MOVE: DYNARR_RESET(trkSeg_t,anchors_da); if (Da.state != POINT_PICKED) { InfoMessage(_("Pick any circle to adjust or add - Enter to accept, Esc to cancel")); return C_CONTINUE; } if (Da.selectEndPoint >= 0) { //If locked, reset pos to be on line from other track int sel = Da.selectEndPoint; coOrd pos2 = pos; BOOL_T inside = FALSE; if (Da.trk[sel]) { //Track if (OnTrack(&pos,FALSE,TRUE) == Da.trk[sel]) { //And the pos is on it inside = TRUE; if (!QueryTrack(Da.trk[Da.selectEndPoint],Q_CORNU_CAN_MODIFY)) { //Turnouts InfoMessage(_("Track can't be split")); inside = FALSE; if (Da.ep[sel]>=0) { //If not turntable Da.pos[sel] = pos = GetTrkEndPos(Da.trk[sel],Da.ep[sel]); } else { if (QueryTrack(Da.trk[sel],Q_CAN_ADD_ENDPOINTS)){ //Turntable trackParams_t tp; if (!GetTrackParams(PARAMS_CORNU, Da.trk[sel], pos, &tp)) return C_CONTINUE; ANGLE_T a = tp.angle; Translate(&pos,tp.ttcenter,a,tp.ttradius); Da.angle[sel] = NormalizeAngle(a+180); SetUpCornuParms(&cp); if (CallCornuM(Da.mid_points,Da.ends,Da.pos,&cp,&Da.crvSegs_da,TRUE)) Da.crvSegs_da_cnt = Da.crvSegs_da.cnt; else Da.crvSegs_da_cnt = 0; } else return C_CONTINUE; } } } else { pos = pos2; //Put Back to original position as outside track } if (!inside) { if (Da.ep[sel]>=0) { //Track defined end point ANGLE_T diff = NormalizeAngle(GetTrkEndAngle(Da.trk[sel],Da.ep[sel])-FindAngle(GetTrkEndPos(Da.trk[sel],Da.ep[sel]),pos)); if (diff>90.0 && diff<270.0) { //The point is not on track but outside cone of end angle+/-90 Da.pos[sel] = pos = GetTrkEndPos(Da.trk[sel],Da.ep[sel]); CreateBothEnds(Da.selectEndPoint,Da.selectMidPoint,Da.selectEndHandle,Da.prevSelected); return C_CONTINUE; } } else { //Not an end point if (QueryTrack(Da.trk[sel],Q_CAN_ADD_ENDPOINTS)){ //Turntable trackParams_t tp; if (!GetTrackParams(PARAMS_CORNU, Da.trk[sel], pos, &tp)) return C_CONTINUE; ANGLE_T a = tp.angle; coOrd edge; Translate(&edge,tp.ttcenter,a,tp.ttradius); ANGLE_T da = DifferenceBetweenAngles(FindAngle(edge,pos),a); DIST_T d = fabs(FindDistance(edge,pos)*cos(R2D(da))); Translate(&pos,edge,a,d); Da.angle[sel] = NormalizeAngle(a+180); Da.pos[sel] = pos; CreateBothEnds(Da.selectEndPoint,Da.selectMidPoint,Da.selectEndHandle,Da.prevSelected); Da.extendSeg[sel].type = SEG_STRTRK; Da.extendSeg[sel].width = 0; Da.extendSeg[sel].color = wDrawColorBlack; Da.extendSeg[sel].u.l.pos[1-sel] = pos; Da.extendSeg[sel].u.l.pos[sel] = edge; Da.extend[sel] = TRUE; SetUpCornuParms(&cp); if (CallCornuM(Da.mid_points,Da.ends,Da.pos,&cp,&Da.crvSegs_da,TRUE)) Da.crvSegs_da_cnt = Da.crvSegs_da.cnt; else Da.crvSegs_da_cnt = 0; return C_CONTINUE; //Stop moving end point } else return C_CONTINUE; } } // Stop the user extending right through the other track if (Da.ep[sel]>=0 && QueryTrack(Da.trk[sel],Q_CORNU_CAN_MODIFY)) { //For non-turnouts if ((!QueryTrack(Da.trk[sel],Q_CAN_ADD_ENDPOINTS)) // Not Turntable - may not be needed && (!QueryTrack(Da.trk[sel],Q_HAS_VARIABLE_ENDPOINTS))) { // Not Helix or a Circle DIST_T ab = FindDistance(GetTrkEndPos(Da.trk[sel],Da.ep[sel]),GetTrkEndPos(Da.trk[sel],1-Da.ep[sel])); DIST_T ac = FindDistance(GetTrkEndPos(Da.trk[sel],Da.ep[sel]),pos); DIST_T cb = FindDistance(GetTrkEndPos(Da.trk[sel],1-Da.ep[sel]),pos); if (cb=cb) && (ac>=ab)) { //Closer to far end and as long as the track pos = GetTrkEndPos(Da.trk[sel],1-Da.ep[sel]); //Make other end of track } } } else if (Da.ep[sel]>=0 && inside) { //Has a point and inside track InfoMessage(_("Can't move end inside a turnout")); //Turnouts are stuck to end-point Da.pos[sel] = pos = GetTrkEndPos(Da.trk[sel],Da.ep[sel]); CreateBothEnds(Da.selectEndPoint,Da.selectMidPoint,Da.selectEndHandle,Da.prevSelected); return C_CONTINUE; } } if(!Da.trk[sel]) { //Cornu with no end if (((MyGetKeyState() & WKEY_SHIFT) != 0) && Da.selectTrack) { //Extend end locked SetUpCornuParms(&cp); CallCornuM(Da.mid_points,Da.ends,Da.pos,&cp,&Da.crvSegs_da,FALSE); struct extraData *xx = GetTrkExtraData(Da.selectTrack); if (Da.radius[sel] == 0) { //Straight Da.extendSeg[sel].type = SEG_STRTRK; Da.extendSeg[sel].width = 0; Da.extendSeg[sel].color = wDrawColorBlack; Da.extendSeg[sel].u.l.pos[1-sel] = Da.pos[sel]; d = FindDistance( Da.extendSeg[sel].u.l.pos[1-sel], pos ); a = NormalizeAngle(Da.angle[sel]-FindAngle(pos,Da.pos[sel])); if (cos(D2R(a))<=0) { Translate( &Da.extendSeg[sel].u.l.pos[sel], Da.extendSeg[sel].u.l.pos[1-sel], Da.angle[sel], - d * cos(D2R(a))); pos = Da.extendSeg[sel].u.l.pos[sel]; Da.extend[sel] = TRUE; } else Da.extend[sel] = FALSE; } else { //Curve Da.extendSeg[sel].type = SEG_CRVTRK; Da.extendSeg[sel].width = 0; Da.extendSeg[sel].color = wDrawColorBlack; Da.extendSeg[sel].u.c.center = Da.center[sel]; Da.extendSeg[sel].u.c.radius = Da.radius[sel]; a = FindAngle( Da.center[sel], pos ); PointOnCircle( &pos, Da.extendSeg[sel].u.c.center, Da.radius[sel], a ); a2 = FindAngle(Da.extendSeg[sel].u.c.center,Da.pos[sel]); if (((Da.angle[sel] < 180) && (a2>90 && a2<270)) || ((Da.angle[sel] > 180) && (a2<90 || a2>270))) { Da.extendSeg[sel].u.c.a0 = a; Da.extendSeg[sel].u.c.a1 = NormalizeAngle(a2-a); } else { Da.extendSeg[sel].u.c.a0 = a2; Da.extendSeg[sel].u.c.a1 = NormalizeAngle(a-a2); } if (Da.extendSeg[sel].u.c.a1 == 0 || Da.extendSeg[sel].u.c.a1 >180 ) Da.extend[sel] = FALSE; else Da.extend[sel] = TRUE; } } else { Da.extend[sel] = FALSE; coOrd offset; //Just move end (no shift) offset.x = pos.x-Da.pos[sel].x; offset.y = pos.y-Da.pos[sel].y; Da.pos[sel] = pos; if (Da.radius[sel] >0.0) { Da.center[sel].x += offset.x; Da.center[sel].y += offset.y; } if (Da.selectTrack) { //We have track if (!Da.trk[sel] && ((t = OnTrackIgnore(&pos,FALSE,TRUE,Da.selectTrack))!= NULL) ) { if ((ep = PickUnconnectedEndPointSilent(pos,t))>=0) { pos = GetTrkEndPos(t,ep); if (IsClose(FindDistance(pos,pos)/2)) { CreateCornuEndAnchor(pos,FALSE); } } } } else { //Not yet a track coOrd p = pos; Da.angle[sel] = GetOpenAngle(Da.pos,Da.angle,sel); if ((t = OnTrack(&p,FALSE,TRUE)) !=NULL ) { if ((ep = PickUnconnectedEndPointSilent(pos,t))>=0) { p = GetTrkEndPos(t,ep); if (IsClose(FindDistance(p,pos)/2)) { CreateCornuEndAnchor(p,FALSE); } } } } } } else { //Cornu with ends if (inside) Da.pos[sel] = pos; if (!GetConnectedTrackParms(Da.trk[sel],pos,sel,Da.ep[sel],inside?FALSE:TRUE)) { wBeep(); return C_CONTINUE; //Stop drawing } CorrectHelixAngles(); if (!inside) { //Extend the track if (Da.trackType[sel] == curveTypeStraight) { //Extend with a straight Da.extendSeg[sel].type = SEG_STRTRK; Da.extendSeg[sel].width = 0; Da.extendSeg[sel].color = wDrawColorBlack; if (Da.ep[sel]>=0) { Da.extendSeg[sel].u.l.pos[0] = GetTrkEndPos( Da.trk[sel], Da.ep[sel] ); a = NormalizeAngle(Da.angle[sel]-FindAngle(pos,GetTrkEndPos(Da.trk[sel],Da.ep[sel]))); } else { //Turntable when unconnected Da.extendSeg[sel].u.l.pos[0] = Da.pos[sel]; a = NormalizeAngle(Da.angle[sel]-FindAngle(pos,Da.pos[sel])); } // Remove any extend in opposite direction for Turntable/Turnouts if ((QueryTrack(Da.trk[sel],Q_CAN_ADD_ENDPOINTS) && Da.ep[sel]>=0) && (!QueryTrack(Da.trk[sel],Q_CORNU_CAN_MODIFY)) && (a>90 && a<270)) { Da.extend[sel] = FALSE; //Turntable with point and extension is other side of well Da.pos[sel] = GetTrkEndPos(Da.trk[sel],Da.ep[sel]); } else { Da.extend[sel] = TRUE; d = FindDistance( Da.extendSeg[sel].u.l.pos[0], pos ); Translate( &Da.extendSeg[sel].u.l.pos[1], Da.extendSeg[sel].u.l.pos[0], Da.angle[sel], -d * cos(D2R(a))); Da.pos[sel] = pos = Da.extendSeg[sel].u.l.pos[1]; } } else if (Da.trackType[sel] == curveTypeCurve) { //Extend with temp curve Da.extendSeg[sel].type = SEG_CRVTRK; Da.extendSeg[sel].width = 0; Da.extendSeg[sel].color = wDrawColorBlack; Da.extendSeg[sel].u.c.center = Da.center[sel]; Da.extendSeg[sel].u.c.radius = Da.radius[sel]; a = FindAngle( Da.center[sel], pos ); PointOnCircle( &pos, Da.center[sel], Da.radius[sel], a ); a2 = FindAngle(Da.center[sel],GetTrkEndPos(Da.trk[sel],Da.ep[sel])); if ((Da.angle[sel] < 180 && (a2>90 && a2 <270)) || (Da.angle[sel] > 180 && (a2<90 || a2 >270))) { Da.extendSeg[sel].u.c.a0 = a2; Da.extendSeg[sel].u.c.a1 = NormalizeAngle(a-a2); } else { Da.extendSeg[sel].u.c.a0 = a; Da.extendSeg[sel].u.c.a1 = NormalizeAngle(a2-a); } if (Da.extendSeg[sel].u.c.a1 == 0.0 || Da.extendSeg[sel].u.c.a1 >180 || (Da.extendSeg[sel].u.c.a0 >= Da.arcA0[sel] && Da.extendSeg[sel].u.c.a0 < Da.arcA0[sel]+Da.arcA1[sel] && Da.extendSeg[sel].u.c.a0 + Da.extendSeg[sel].u.c.a1 <= Da.arcA0[sel] + Da.arcA1[sel]) ) { Da.extend[sel] = FALSE; Da.pos[sel] = pos; } else { Da.extend[sel] = TRUE; Da.pos[sel] = pos; } } else { //Bezier and Cornu that we are joining TO can't extend wBeep(); InfoMessage(_("Can't extend connected Bezier or Cornu")); pos = GetTrkEndPos(Da.trk[sel],Da.ep[sel]); return C_CONTINUE; } } else Da.pos[sel] = pos; } } else if (Da.selectMidPoint >=0){ DYNARR_N(coOrd,Da.mid_points,Da.selectMidPoint) = pos; } else if (Da.selectEndHandle >=0) { //Cornu has no end, so has handles int end = Da.selectEndHandle/2; if (Da.selectEndHandle%2 == 0) { //Radius coOrd p0 = Da.pos[end]; //Start coOrd p1 = Da.endHandle[end].end_curve; //End ANGLE_T a0 = FindAngle( p1, p0 ); DIST_T d0 = FindDistance( p0, p1 )/2.0; //Distance to Middle of Chord coOrd pos2 = pos; //New pos Rotate( &pos2, p1, -a0 ); pos2.x -= p1.x; //Deflection at right angles to Chord DIST_T r = 1000.0; if ( fabs(pos2.x) >= 0.01 ) { //Not zero double d2 = sqrt( d0*d0 + pos2.x*pos2.x )/2.0; r = d2*d2*2.0/pos2.x; if ( fabs(r) > 1000.0 ) { //Limit Radius r = 0.0; //r = ((r > 0) ? 1 : -1 ) *1000.0; } } else { r = 0.0; //r = ((r > 0) ? 1 : -1 ) *1000.0; } coOrd posx; //Middle of chord posx.x = (p1.x-p0.x)/2.0 + p0.x; posx.y = (p1.y-p0.y)/2.0 + p0.y; a0 -= 90.0; if (r<0) { //Negative radius means other side coOrd pt = p0; p0 = p1; p1 = pt; a0 += 180.0; } coOrd pc; if (r == 0.0) { Da.center[end] = zero; Da.radius[end] = 0.0; Da.angle[end] = FindAngle(Da.pos[end],Da.endHandle[end].end_curve); } else { Translate( &pc, posx, a0, fabs(r) - fabs(pos2.x) ); //Move Radius less Deflection to get to center Da.center[end] = pc; if (DifferenceBetweenAngles(FindAngle(Da.center[end],Da.pos[end]),FindAngle(Da.center[end],Da.endHandle[end].end_curve))>0.0) Da.angle[end] = NormalizeAngle(FindAngle(Da.center[end],Da.pos[end])+90.0); else Da.angle[end] = NormalizeAngle(FindAngle(Da.center[end],Da.pos[end])-90.0); Da.radius[end] = fabs(r); } } else { Da.angle[end] = FindAngle(Da.pos[end],pos); Da.radius[end] = 0.0; Translate(&Da.center[end],Da.pos[end],NormalizeAngle(Da.angle[end]+90.0),Da.radius[end]); } } CreateBothEnds(Da.selectEndPoint,Da.selectMidPoint,Da.selectEndHandle,Da.prevSelected); SetUpCornuParms(&cp); //In case we want to use these because the ends are not on the track if (CallCornuM(Da.mid_points,Da.ends,Da.pos,&cp,&Da.crvSegs_da,TRUE)) Da.crvSegs_da_cnt = Da.crvSegs_da.cnt; else Da.crvSegs_da_cnt = 0; for (int i=0;i<2;i++) { if (Da.trk[i] || Da.ends[i]) continue; coOrd p = Da.pos[i]; Da.angle[i] = NormalizeAngle((i?0:180)+GetAngleSegs( Da.crvSegs_da_cnt, Da.crvSegs_da.ptr, &p, NULL, NULL, NULL, NULL, NULL)); Da.radius[i] = 0.0; } Da.minRadius = CornuMinRadius(Da.pos,Da.crvSegs_da); DIST_T rin = Da.radius[0]; InfoMessage( _("Cornu : Min Radius=%s MaxRateofCurveChange/Scale=%s Length=%s Winding Arc=%s"), FormatDistance(Da.minRadius), FormatFloat(CornuMaxRateofChangeofCurvature(Da.pos,Da.crvSegs_da,&rin)*GetScaleRatio(GetLayoutCurScale())), FormatDistance(CornuLength(Da.pos,Da.crvSegs_da)), FormatDistance(CornuTotalWindingArc(Da.pos,Da.crvSegs_da))); return C_CONTINUE; case C_UP: DYNARR_RESET(trkSeg_t,anchors_da); if (Da.state != POINT_PICKED) { Da.state = PICK_POINT; return C_CONTINUE; } ep = 0; if (Da.selectMidPoint!=-1) Da.prevSelected = Da.selectMidPoint; else if (Da.selectEndPoint!=-1) { if (!Da.trk[Da.selectEndPoint] && (t=OnTrack(&pos,FALSE,TRUE)) != NULL && t != Da.selectTrack ) { EPINX_T ep = PickUnconnectedEndPoint(pos,t); if (ep>=0) { if (QueryTrack(t,Q_HAS_VARIABLE_ENDPOINTS)) { //Circle/Helix find if there is an open slot and where if ((GetTrkEndTrk(t,0) != NULL) && (GetTrkEndTrk(t,1) != NULL)) { InfoMessage(_("Helix Already Connected")); return C_CONTINUE; } ep = -1; //Not a real ep yet } else ep = PickUnconnectedEndPointSilent(pos, t); //EP if (ep>=0 && QueryTrack(t,Q_CAN_ADD_ENDPOINTS)) { ep=-1; //Don't attach to Turntable trackParams_t tp; if (!GetTrackParams(PARAMS_CORNU, t, pos, &tp)) return C_CONTINUE; ANGLE_T a = tp.angle; Translate(&pos,tp.ttcenter,a,tp.ttradius); } if ( ep==-1 && (!QueryTrack(t,Q_CAN_ADD_ENDPOINTS) && !QueryTrack(t,Q_HAS_VARIABLE_ENDPOINTS))) { //No endpoints and not Turntable or Helix/Circle wBeep(); InfoMessage(_("No Valid end point on that track")); return C_CONTINUE; } if (GetTrkScale(t) != (char)GetLayoutCurScale()) { wBeep(); InfoMessage(_("Track is different scale")); return C_CONTINUE; } } if (ep>=0 && t) { //Real end point, real track Da.trk[Da.selectEndPoint] = t; Da.ep[Da.selectEndPoint] = ep; // Note: -1 for Turntable or Circle pos = GetTrkEndPos(t,ep); Da.pos[Da.selectEndPoint] = pos; if (!GetConnectedTrackParms(t,pos,Da.selectEndPoint,ep,FALSE)) return C_CONTINUE; } } else { cornuModCmdContext.angle = NormalizeAngle(Da.angle[Da.selectEndPoint]); cornuModCmdContext.radius = Da.radius[Da.selectEndPoint]; if (DifferenceBetweenAngles(FindAngle(Da.center[Da.selectEndPoint],Da.pos[Da.selectEndPoint]),Da.angle[Da.selectEndPoint])<0.0) cornuModCmdContext.radius = -cornuModCmdContext.radius; controls[0] = cornuModEndRadiusPD.control; controls[1] = cornuModEndAnglePD.control; controls[2] = NULL; labels[0] = N_("End Radius"); labels[1] = N_("End Angle"); ParamLoadControls( &cornuModPG ); InfoSubstituteControls( controls, labels ); cornuModEndRadiusPD.option &= ~PDO_NORECORD; cornuModEndAnglePD.option &= ~PDO_NORECORD; infoSubst = TRUE; Da.prevSelected = Da.selectEndPoint; Da.selectEndHandle = -1; } } else if (Da.selectEndHandle!=-1) { Da.prevSelected = Da.selectEndHandle>2?1:0; cornuModCmdContext.angle = NormalizeAngle(Da.angle[Da.prevSelected]); cornuModCmdContext.radius = Da.radius[Da.prevSelected]; if (DifferenceBetweenAngles(FindAngle(Da.center[Da.prevSelected],Da.pos[Da.prevSelected]),Da.angle[Da.prevSelected])<0.0) cornuModCmdContext.radius = -cornuModCmdContext.radius; controls[0] = cornuModEndRadiusPD.control; controls[1] = cornuModEndAnglePD.control; controls[2] = NULL; labels[0] = N_("End Radius"); labels[1] = N_("End Angle"); ParamLoadControls( &cornuModPG ); InfoSubstituteControls( controls, labels ); cornuModEndRadiusPD.option &= ~PDO_NORECORD; cornuModEndAnglePD.option &= ~PDO_NORECORD; infoSubst = TRUE; Da.selectEndHandle = -1; } Da.selectEndPoint = -1; Da.selectMidPoint = -1; CreateBothEnds(Da.selectEndPoint,Da.selectMidPoint,Da.selectEndHandle,Da.prevSelected); SetUpCornuParms(&cp); if (CallCornuM(Da.mid_points,Da.ends,Da.pos,&cp,&Da.crvSegs_da,TRUE)) Da.crvSegs_da_cnt = Da.crvSegs_da.cnt; else Da.crvSegs_da_cnt = 0; Da.minRadius = CornuMinRadius(Da.pos,Da.crvSegs_da); InfoMessage(_("Pick on point to adjust it along track - Delete to remove, Enter to confirm, ESC to abort")); Da.state = PICK_POINT; return C_CONTINUE; case C_TEXT: DYNARR_RESET(trkSeg_t,anchors_da); //Delete or backspace deletes last selected index if (action>>8 == 127 || action>>8 == 8) { if ((Da.state == PICK_POINT) && Da.prevSelected !=-1) { for (int i=Da.prevSelected;i4*360) { wBeep(); InfoMessage(_("Cornu has too complex shape - adjust end pts")); return C_CONTINUE; } if (!CheckHelix(NULL)) { wBeep(); return C_CONTINUE; } for (int i=0;i<2;i++) { if (Da.trk[i] && !(QueryTrack(Da.trk[i],Q_CAN_ADD_ENDPOINTS))) { // Not Turntable if (FindDistance(Da.pos[i],GetTrkEndPos(Da.trk[i],1-Da.ep[i])) < minLength) { wBeep(); InfoMessage(_("Cornu point %d too close to other end of connect track - reposition it"),i+1); return C_CONTINUE; } } } UndoStart( _("Create Cornu"),"newCornu curves"); BOOL_T end_point[2]; end_point[0] = TRUE; end_point[1] = FALSE; coOrd sub_pos[2]; sub_pos[0] = Da.pos[0]; if (Da.radius[0] == -1) end_point[0] = FALSE; track_p first_trk= NULL,trk1=NULL,trk2 = NULL; for (int i=0;i=0) ConnectTracks(Da.trk[i],Da.ep[i],i==0?first_trk:trk1,i); } } UndoEnd(); if (infoSubst) { InfoSubstituteControls( NULL, NULL ); infoSubst = FALSE; } Da.state = NONE; return C_TERMINATE; } return C_CONTINUE; case C_REDRAW: if (Da.state == NONE) return C_CONTINUE; DrawTempCornu(); if (anchors_da.cnt) { DrawSegs( &tempD, zero, 0.0, &anchors(0), anchors_da.cnt, trackGauge, wDrawColorBlack ); } return C_CONTINUE; case C_CANCEL: case C_FINISH: break; default: return C_CONTINUE; } return C_CONTINUE; } static void cornuModDlgUpdate( paramGroup_p pg, int inx, void * valueP ) { AdjustCornuCurve(C_UPDATE, zero, InfoMessage); ParamLoadControl(&cornuModPG,cornuModEndRadius); // Make sure Radius updated ParamLoadControl(&cornuModPG,cornuModEndAngle); //Relative Angle as well TempRedraw(); } /** * CmdCornuModify * * Called from Modify Command - this function deals with the real (old) track and calls AdjustCornuCurve to tune up the new one * Sequence is this - * - The C_START is called from CmdModify C_DOWN action if a track has being selected. The old track is hidden, the editable one is shown. * - C_MOVES will be ignored until a C_UP ends the track selection and moves the state to PICK_POINT, * - C_DOWN then hides the track and shows the Cornu circles. Selects a point (if close enough and available) and the state moves to POINT_PICKED * - C_MOVE drags the point around modifying the curve * - C_UP puts the state back to PICK_POINT (pick another) * - C_OK (Enter/Space) creates the new track, deletes the old and shows the changed track. * - C_CANCEL (Esc) sets the state to NONE and reshows the original track unchanged. * */ STATUS_T CmdCornuModify (track_p trk, wAction_t action, coOrd pos, DIST_T trackG ) { struct extraData *xx = GetTrkExtraData(trk); Da.trackGauge = trackG; Da.commandType = CORNU_MODIFY; switch (action&0xFF) { case C_START: Da.state = NONE; DYNARR_RESET(trkSeg_t,Da.crvSegs_da); Da.ep1Segs_da_cnt = 0; Da.ep2Segs_da_cnt = 0; Da.crvSegs_da_cnt = 0; Da.midSegs.cnt = 0; Da.extend[0] = FALSE; Da.extend[1] = FALSE; Da.selectEndPoint = -1; Da.selectTrack = NULL; DYNARR_RESET(coOrd,Da.mid_points); DYNARR_RESET(track_p,Da.tracks); Da.selectTrack = trk; DYNARR_APPEND(track_p,Da.tracks,1); DYNARR_LAST(track_p,Da.tracks) = trk; Da.trk[0] = GetTrkEndTrk( trk, 0 ); track_p prior = trk; if (Da.trk[0]) Da.ep[0] = GetEndPtConnectedToMe(Da.trk[0],trk); EPINX_T ep0 = 0; //Move down the LHS adding tracks until no more Cornu while (Da.trk[0] && QueryTrack(Da.trk[0],Q_IS_CORNU)) { prior = Da.trk[0]; ep0 = 1-Da.ep[0]; DYNARR_APPEND(track_p,Da.tracks,1); DYNARR_LAST(track_p,Da.tracks) = prior; DYNARR_APPEND(coOrd,Da.mid_points,1); for (int i=Da.mid_points.cnt-1;i>0;i--) { DYNARR_N(coOrd,Da.mid_points,i) = DYNARR_N(coOrd,Da.mid_points,i-1); } DYNARR_N(coOrd,Da.mid_points,0) = GetTrkEndPos(prior,1-ep0); Da.trk[0] = GetTrkEndTrk( prior, ep0 ); if (Da.trk[0]) Da.ep[0] = GetEndPtConnectedToMe(Da.trk[0],prior); else Da.ep[0] = -1; } if (prior) { struct extraData *xx0 = GetTrkExtraData(prior); Da.pos[0] = xx0->cornuData.pos[ep0]; //Copy parms from FIRST CORNU trk Da.radius[0] = xx0->cornuData.r[ep0]; Da.angle[0] = xx0->cornuData.a[ep0]; Da.center[0] = xx0->cornuData.c[ep0]; } //Move to RHS Da.trk[1] = GetTrkEndTrk( trk, 1 ); track_p next = trk; EPINX_T ep1 = 1; if (Da.trk[1]) Da.ep[1] = GetEndPtConnectedToMe(Da.trk[1],trk); //Move down RHS adding tracks until no more Cornu while (Da.trk[1] && QueryTrack(Da.trk[1],Q_IS_CORNU)) { next = Da.trk[1]; ep1 = 1-Da.ep[1]; DYNARR_APPEND(track_p,Da.tracks,1); DYNARR_LAST(track_p,Da.tracks) = next; DYNARR_APPEND(coOrd,Da.mid_points,1); DYNARR_LAST(coOrd,Da.mid_points) = GetTrkEndPos(next,1-ep1); Da.trk[1] = GetTrkEndTrk( next, ep1 ); if (Da.trk[1]) Da.ep[1] = GetEndPtConnectedToMe(Da.trk[1],next); } if (next) { struct extraData *xx1 = GetTrkExtraData(next); Da.pos[1] = xx1->cornuData.pos[ep1]; //Copy parms from LAST CORNU trk Da.radius[1] = xx1->cornuData.r[ep1]; Da.angle[1] = xx1->cornuData.a[ep1]; Da.center[1] = xx1->cornuData.c[ep1]; } InfoMessage(_("Now Select or Add (+Shift) a Point")); Da.state = TRACK_SELECTED; for (int i=0;i>8 == 127 || action>>8 == 8) { return AdjustCornuCurve(action, pos, InfoMessage); } //Space bar or enter means done if ( (action>>8 != ' ') && (action>>8 != 13) ) return C_CONTINUE; /* no break */ case C_OK: if (infoSubst) { InfoSubstituteControls( NULL, NULL ); infoSubst = FALSE; } if (Da.state != PICK_POINT) { //Too early - abandon InfoMessage(_("No changes made")); Da.state = NONE; //DYNARR_FREE(trkSeg_t,Da.crvSegs_da); return C_CANCEL; } if (!CheckHelix(trk)) { wBeep(); return C_CONTINUE; } for (int i=0;i<2;i++) { if (Da.trk[i] && !(QueryTrack(Da.trk[i],Q_CAN_ADD_ENDPOINTS))) { // Not Turntable if (FindDistance(Da.pos[i],GetTrkEndPos(Da.trk[i],1-Da.ep[i])) < minLength) { wBeep(); InfoMessage(_("Cornu end %d too close to other end of connect track - reposition it"),i+1); return C_CONTINUE; } } } UndoStart( _("Modify Cornu"), "newCornu - CR" ); for (int i=0;i<2;i++) { if (!Da.trk[i] && Da.extend[i]) { if (Da.extendSeg[i].type == SEG_STRTRK) { Da.trk[i] = NewStraightTrack(Da.extendSeg[i].u.l.pos[0],Da.extendSeg[i].u.l.pos[1]); if (Da.trk[i]) Da.ep[i] = 1-i; } else { Da.trk[i] = NewCurvedTrack(Da.extendSeg[i].u.c.center,fabs(Da.extendSeg[i].u.c.radius), Da.extendSeg[i].u.c.a0,Da.extendSeg[i].u.c.a1,FALSE); if (FindDistance(GetTrkEndPos(Da.trk[i],0),Da.pos[i])<=connectDistance) { Da.ep[i] = 0; } else Da.ep[i] = 1; } if (!Da.trk[i]) { wBeep(); InfoMessage(_("Cornu Extension Create Failed for end %d"),i); Da.state = NONE; return C_TERMINATE; } } } BOOL_T end_point[2]; end_point[0] = TRUE; end_point[1] = FALSE; coOrd sub_pos[2]; sub_pos[0] = Da.pos[0]; track_p first_trk= NULL,trk1=NULL,trk2 = NULL; for (int i=0;i= 0) ConnectTracks(i==0?first_trk:trk1,i,Da.trk[i],Da.ep[i]); } else { UndoUndo(); wBeep(); InfoMessage(_("Connected Track End Adjust for end %d failed"),i); return C_TERMINATE; } } } UndoEnd(); Da.state = NONE; //DYNARR_FREE(trkSeg_t,Da.crvSegs_da) return C_TERMINATE; case C_CANCEL: InfoMessage(_("Modify Cornu Cancelled")); Da.state = NONE; if (infoSubst) { InfoSubstituteControls( NULL, NULL ); infoSubst = FALSE; } //DYNARR_FREE(trkSeg_t,Da.crvSegs_da); return C_TERMINATE; case C_REDRAW: return AdjustCornuCurve(C_REDRAW, pos, InfoMessage); } return C_CONTINUE; } /* * Find length by adding up the underlying segments. The segments can be straights, curves or bezier. */ DIST_T CornuLength(coOrd pos[4],dynArr_t segs) { DIST_T dd = 0.0; if (segs.cnt == 0 ) return dd; for (int i = 0;i0?offset:-offset))*D2R(t.u.c.a1)); } else if (t.type == SEG_BEZLIN || t.type == SEG_BEZTRK) { dd +=CornuOffsetLength(t.bezSegs,offset); } else if (t.type == SEG_STRLIN || t.type == SEG_STRTRK ) { dd += FindDistance(t.u.l.pos[0],t.u.l.pos[1]); } } return dd; } DIST_T CornuMinRadius(coOrd pos[4],dynArr_t segs) { DIST_T r = 100000.0, rr; if (segs.cnt == 0 ) return r; for (int i = 0;i r_max) r_max = rc; } * last_c = lc; return r_max; } /* * Create a Cornu Curve Track * Sequence is * 1. Place 1st End * 2. Place 2nd End * 3 to n. Select and drag around points until done * n+1. Confirm with enter or Cancel with Esc */ STATUS_T CmdCornu( wAction_t action, coOrd pos ) { track_p t = NULL; cornuParm_t cp; Da.commandType = CORNU_CREATE; Da.width = (double)lineWidth/mainD.dpi; Da.trackGauge = trackGauge; Da.selectTrack = NULL; switch (action&0xFF) { case C_START: Da.cmdType = (long)commandContext; Da.state = NONE; Da.selectEndPoint = -1; Da.selectMidPoint = -1; Da.endHandle[0].end_valid = FALSE; Da.endHandle[1].end_valid = FALSE; for (int i=0;i<2;i++) { Da.ends[i] = FALSE; Da.pos[i] = zero; Da.angle[i] = 0.0; Da.radius[i] = -1.0; } Da.trk[0] = Da.trk[1] = NULL; //tempD.orig = mainD.orig; DYNARR_RESET(trkSeg_t,Da.crvSegs_da); Da.ep1Segs_da_cnt = 0; Da.ep2Segs_da_cnt = 0; Da.crvSegs_da_cnt = 0; Da.midSegs.cnt = 0; DYNARR_RESET(coOrd,Da.mid_points); DYNARR_RESET(track_p,Da.tracks); DYNARR_RESET(trkSeg_t,anchors_da); Da.extend[0] = FALSE; Da.extend[1] = FALSE; if (Da.cmdType == cornuCmdCreateTrack) InfoMessage( _("Left click - Start Cornu track") ); else if (Da.cmdType == cornuCmdHotBar) { InfoMessage( _("Left click - Place Flextrack") ); } else { if (selectedTrackCount==0) InfoMessage( _("Left click - join with Cornu track") ); else InfoMessage( _("Left click - join with Cornu track, Shift Left click - move to join") ); } return C_CONTINUE; case C_DOWN: DYNARR_RESET(trkSeg_t,anchors_da); if ( Da.state == NONE || Da.state == LOC_2) { //Set the first or second point coOrd p = pos; t = NULL; int end = 0; if (Da.state != NONE) end=1; EPINX_T ep = -1; //Lock to endpoint if one is available and under pointer if ((t = OnTrack(&p, FALSE, TRUE)) != NULL && t != Da.selectTrack) { if (QueryTrack(t,Q_HAS_VARIABLE_ENDPOINTS)) { //Circle/Helix find if there is an open slot and where if ((GetTrkEndTrk(t,0) != NULL) && (GetTrkEndTrk(t,1) != NULL)) { wBeep(); InfoMessage(_("Helix Already Connected")); t= NULL; } ep = -1; //Not a real ep yet } else if (QueryTrack(t,Q_CAN_ADD_ENDPOINTS)) { ep=-1; //Don't attach to existing Turntable ep trackParams_t tp; if (!GetTrackParams(PARAMS_CORNU, t, pos, &tp)) return C_CONTINUE; ANGLE_T a = tp.angle; Translate(&pos,tp.ttcenter,a,tp.ttradius); p = pos; //Fix to wall of turntable initially } else ep = PickUnconnectedEndPointSilent(p, t); //EP if ( t && ep==-1 && (!QueryTrack(t,Q_CAN_ADD_ENDPOINTS) && !QueryTrack(t,Q_HAS_VARIABLE_ENDPOINTS))) { //No endpoints and not Turntable or Helix/Circle wBeep(); InfoMessage(_("No valid open endpoint on that track")); t = NULL; } if (t && GetTrkGauge(t) != GetScaleTrackGauge(GetLayoutCurScale())) { wBeep(); InfoMessage(_("Track is different gauge")); t = NULL; } } if (ep>=0 && t) { //Real end point, real track Da.trk[end] = t; Da.ep[end] = ep; // Note: -1 for Turntable or Circle pos = GetTrkEndPos(t,ep); Da.pos[end] = pos; Da.angle[end] = GetTrkEndAngle(t,ep); } else if (t == NULL) { //end not on Track, OK for CreateCornu -> empty end point pos = p; //Reset to initial SnapPos( &pos ); if (Da.cmdType == cornuCmdCreateTrack || Da.cmdType == cornuCmdHotBar) { Da.trk[end] = NULL; Da.pos[end] = pos; Da.radius[end] = -1.0; //No End Rad for open if (Da.state == NONE ) { Da.state = POS_1; Da.angle[0] = 270.0; Da.radius[0] = 0.0; Da.ep1Segs_da_cnt = createEndPoint(Da.ep1Segs, Da.pos[0], FALSE,TRUE,TRUE,FALSE,Da.angle[0],Da.radius[0],zero,&Da.endHandle[0]); Da.ep2Segs_da_cnt = 0; InfoMessage( _("Drag arm in the direction of track") ); return C_CONTINUE; } Da.state = POS_2; //Now this is second end and it is open Da.selectEndPoint = 1; Da.mid_points.cnt=0; Da.angle[1] = GetOpenAngle(Da.pos,Da.angle,1); Da.radius[1] = 0.0; CreateBothEnds(1,-1,-1,-1); SetUpCornuParms(&cp); if (CallCornuM(Da.mid_points,Da.ends,Da.pos,&cp,&Da.crvSegs_da,TRUE)) Da.crvSegs_da_cnt = Da.crvSegs_da.cnt; InfoMessage( _("Drag arm in the direction of track") ); return C_CONTINUE; } wBeep(); InfoMessage(_("No Unconnected Track End there")); //Not creating a Cornu - Join can't be open return C_CONTINUE; } else { Da.pos[end] = p; //Either a real end or a track but no end } if (Da.state == NONE) { if (!GetConnectedTrackParms(t, pos, 0, Da.ep[0],FALSE)) { //Must get parms Da.trk[0] = NULL; Da.ep[0] = -1; wBeep(); InfoMessage(_("No Valid Track End there")); return C_CONTINUE; } if (ep<0) { Da.trk[0] = t; Da.pos[0] = p; Da.ep[0] = ep; } Da.state = POS_1; Da.selectEndPoint = 0; //Select first end point Da.ep1Segs_da_cnt = createEndPoint(Da.ep1Segs, Da.pos[0], FALSE, !QueryTrack(Da.trk[0],Q_IS_CORNU),QueryTrack(Da.trk[0],Q_CORNU_CAN_MODIFY), Da.trk[0]!=NULL,Da.angle[0],Da.radius[0],Da.center[0],NULL); InfoMessage( _("Locked - Move 1st end point of Cornu track along track 1") ); return C_CONTINUE; } else { //Second Point if (Da.trk[0] == t) { ErrorMessage( MSG_JOIN_CORNU_SAME ); Da.trk[1] = NULL; Da.ep[1] = -1; return C_CONTINUE; } if (!GetConnectedTrackParms(t, pos, 1, Da.ep[1],FALSE)) { Da.trk[1] = NULL; //Turntable Fail wBeep(); InfoMessage(_("No Valid Track End there")); return C_CONTINUE; } if (ep<0) { Da.trk[1] = t; Da.pos[1] = p; Da.ep[1] = -1; Da.radius[1] = 0.0; } CorrectHelixAngles(); Da.selectEndPoint = 1; //Select second end point Da.state = POINT_PICKED; InfoMessage( _("Locked - Move 2nd end point of Cornu track along track 2") ); } CreateBothEnds(1,-1,-1,-1); SetUpCornuParms(&cp); if (CallCornuM(Da.mid_points,Da.ends,Da.pos,&cp,&Da.crvSegs_da,TRUE)) Da.crvSegs_da_cnt = Da.crvSegs_da.cnt; return C_CONTINUE; } else { //This is after both ends exist return AdjustCornuCurve( action&0xFF, pos, InfoMessage ); } return C_CONTINUE; case wActionMove: DYNARR_RESET(trkSeg_t,anchors_da); if (Da.state != NONE && Da.state != LOC_2) return C_CONTINUE; if (Da.trk[0] && Da.trk[1]) return C_CONTINUE; EPINX_T ep = -1; t = NULL; if (((MyGetKeyState() & WKEY_ALT) == 0) == magneticSnap) { //Lock to endpoint if one is available and under pointer if ((t = OnTrack(&pos, FALSE, TRUE)) != NULL && t != Da.selectTrack) { if (QueryTrack(t,Q_HAS_VARIABLE_ENDPOINTS)) { //Circle/Helix find if there is an open slot and where if ((GetTrkEndTrk(t,0) != NULL) && (GetTrkEndTrk(t,1) != NULL)) { return C_CONTINUE; } ep = -1; //Not a real ep yet } else ep = PickUnconnectedEndPointSilent(pos, t); //EP if (ep>=0 && QueryTrack(t,Q_CAN_ADD_ENDPOINTS)) ep=-1; //Don't attach to Turntable if ( ep==-1 && (!QueryTrack(t,Q_CAN_ADD_ENDPOINTS) && !QueryTrack(t,Q_HAS_VARIABLE_ENDPOINTS))) { //No endpoints and not Turntable or Helix/Circle return C_CONTINUE; } if (GetTrkGauge(t) != GetScaleTrackGauge(GetLayoutCurScale())) { return C_CONTINUE; } if (Da.state != NONE && t==Da.trk[0]) return C_CONTINUE; } } if (ep>=0 && t) { pos = GetTrkEndPos(t,ep); CreateCornuEndAnchor(pos,TRUE); } else if (t) { trackParams_t tp; //Turntable or extendable track if (!GetTrackParams(PARAMS_CORNU, t, pos, &tp)) return C_CONTINUE; if (QueryTrack(t,Q_CAN_ADD_ENDPOINTS)) { if (!GetTrackParams(PARAMS_CORNU, t, pos, &tp)) return C_CONTINUE; ANGLE_T a = tp.angle; Translate(&pos,tp.ttcenter,a,tp.ttradius); CreateCornuEndAnchor(pos,TRUE); } else CreateCornuEndAnchor(pos,TRUE); } return C_CONTINUE; case C_MOVE: if (Da.state == NONE) { //First point not created if (Da.cmdType == cornuCmdCreateTrack || Da.cmdType == cornuCmdHotBar) { InfoMessage("Place 1st end point of Cornu track"); } else InfoMessage("Place 1st end point of Cornu track on unconnected end-point"); return C_CONTINUE; } if (Da.state == POS_1) { //First point has been created if ((Da.cmdType == cornuCmdCreateTrack || Da.cmdType == cornuCmdHotBar) && !Da.trk[0]) { //OK for CreateCornu -> No track selected if (IsClose(FindDistance(pos,Da.pos[0]))) return C_CONTINUE; Da.selectEndPoint = 0; Da.angle[0] = NormalizeAngle(FindAngle(Da.pos[0],pos)+180); Da.ep1Segs_da_cnt = createEndPoint(Da.ep1Segs, Da.pos[0],TRUE,TRUE,TRUE,FALSE,Da.angle[0],0.0,zero,&Da.endHandle[0]); Da.radius[1] = -1.0; /*No end*/ return C_CONTINUE; } EPINX_T ep = 0; BOOL_T found = FALSE; int end = Da.state==POS_1?0:1; if(!QueryTrack(Da.trk[0],Q_CORNU_CAN_MODIFY) && !QueryTrack(Da.trk[0],Q_CAN_ADD_ENDPOINTS)) { InfoMessage(_("Track can't be split - so locked to endpoint")); return C_CONTINUE; } if (Da.trk[0] != OnTrack(&pos, FALSE, TRUE)) { wBeep(); InfoMessage(_("Point not on track 1")); Da.state = POS_1; Da.selectEndPoint = 0; return C_CONTINUE; } t = Da.trk[0]; if (!GetConnectedTrackParms(t, pos, ep, Da.ep[ep],FALSE)) { Da.state = POS_1; Da.selectEndPoint = 0; return C_CONTINUE; } Da.pos[ep] = pos; Da.ep1Segs_da_cnt = createEndPoint(Da.ep1Segs,Da.pos[0],TRUE,!QueryTrack(Da.trk[0],Q_IS_CORNU),QueryTrack(Da.trk[0],Q_CORNU_CAN_MODIFY), Da.trk[0]!=NULL,Da.angle[0],Da.radius[0],Da.center[0],NULL); } else if (Da.state == POS_2 && (Da.cmdType == cornuCmdCreateTrack || Da.cmdType == cornuCmdHotBar) && !Da.trk[1]) { //OK for CreateCornu -> No track selected if (IsClose(FindDistance(pos,Da.pos[1]))) return C_CONTINUE; Da.selectEndPoint = 1; Da.angle[1] = NormalizeAngle(FindAngle(Da.pos[1],pos)+180); Da.radius[1] = 0.0; /*No end*/ Da.ep1Segs_da_cnt = createEndPoint(Da.ep2Segs,Da.pos[1],TRUE,TRUE,TRUE,FALSE,Da.angle[1],0.0,zero,&Da.endHandle[1]); SetUpCornuParms(&cp); if (CallCornuM(Da.mid_points,Da.ends,Da.pos,&cp,&Da.crvSegs_da,TRUE)) Da.crvSegs_da_cnt = Da.crvSegs_da.cnt; else Da.crvSegs_da_cnt = 0; CreateBothEnds(-1,-1,-1,-1); Da.minRadius = CornuMinRadius(Da.pos,Da.crvSegs_da); return C_CONTINUE; } else { //Second Point Has Been Created and aligned return AdjustCornuCurve( action&0xFF, pos, InfoMessage ); } return C_CONTINUE; case C_UP: if (Da.state == POS_1 && (Da.cmdType == cornuCmdCreateTrack || Da.cmdType == cornuCmdHotBar || Da.trk[0])) { Da.state = LOC_2; Da.selectEndPoint = -1; if (Da.cmdType == cornuCmdCreateTrack || Da.cmdType == cornuCmdHotBar) { if (Da.cmdType == cornuCmdCreateTrack) InfoMessage( _("Pick other end of Cornu") ); else InfoMessage( _("Select flextrack ends or anchors and drag, Enter to approve, Esc to Cancel") ); Da.ep1Segs_da_cnt = createEndPoint(Da.ep1Segs, Da.pos[0],FALSE,TRUE,TRUE,FALSE,0.0,0.0,zero,NULL); return C_CONTINUE; } InfoMessage( _("Put other end of Cornu on a track with an unconnected end point") ); if (Da.trk[0]) Da.ep1Segs_da_cnt = createEndPoint(Da.ep1Segs, Da.pos[0], FALSE,!QueryTrack(Da.trk[0],Q_IS_CORNU),QueryTrack(Da.trk[0],Q_CORNU_CAN_MODIFY), Da.trk[0]!=NULL,Da.angle[0],Da.radius[0],Da.center[0],NULL); else Da.ep1Segs_da_cnt = createEndPoint(Da.ep1Segs, Da.pos[0], FALSE,TRUE,TRUE,FALSE, Da.angle[0],Da.radius[0],Da.center[0],&Da.endHandle[0]); return C_CONTINUE; } else if (Da.state == POS_2 && (Da.cmdType == cornuCmdCreateTrack || Da.cmdType == cornuCmdHotBar || Da.trk[1] )){ Da.state = PICK_POINT; Da.selectEndPoint = -1; Da.prevEndPoint = 1; Da.prevSelected = -1; SetUpCornuParms(&cp); if (CallCornuM(Da.mid_points,Da.ends,Da.pos,&cp,&Da.crvSegs_da,TRUE)) Da.crvSegs_da_cnt = Da.crvSegs_da.cnt; else Da.crvSegs_da_cnt = 0; CreateBothEnds(-1,-1,-1,-1); Da.minRadius = CornuMinRadius(Da.pos,Da.crvSegs_da); return C_CONTINUE; } else { return AdjustCornuCurve( action&0xFF, pos, InfoMessage ); } return C_CONTINUE; break; case C_TEXT: if (Da.state != PICK_POINT) return C_CONTINUE; if ((action>>8 == 127) || (action>>8 == 8)) // return AdjustCornuCurve(action, pos, InfoMessage); if (!(action>>8 == 32 )) //Space is same as Enter. return C_CONTINUE; /* no break */ case C_OK: if (Da.state != PICK_POINT) return C_CONTINUE; STATUS_T rc = AdjustCornuCurve( C_OK, pos, InfoMessage); if (rc == C_TERMINATE) { Da.state = NONE; Da.ep1Segs_da_cnt = 0; Da.ep2Segs_da_cnt = 0; Da.crvSegs_da_cnt = 0; for (int i=0;i<2;i++) { Da.radius[i] = 0.0; Da.angle[i] = 0.0; Da.center[i] = zero; Da.trk[i] = NULL; Da.ep[i] = -1; Da.pos[i] = zero; Da.endHandle[i].end_valid = FALSE; } } return rc; case C_REDRAW: if ( Da.state != NONE ) { DrawCornuCurve(NULL,Da.ep1Segs,Da.ep1Segs_da_cnt,Da.ep2Segs,Da.ep2Segs_da_cnt,(trkSeg_t *)Da.crvSegs_da.ptr,Da.crvSegs_da_cnt, NULL, Da.extend[0]?&Da.extendSeg[0]:NULL,Da.extend[1]?&Da.extendSeg[1]:NULL,(trkSeg_t *)Da.midSegs.ptr,Da.midSegs.cnt,wDrawColorBlack); } if (anchors_da.cnt) DrawSegs( &tempD, zero, 0.0, &anchors(0), anchors_da.cnt, trackGauge, wDrawColorBlack ); if (MyGetKeyState()&WKEY_SHIFT) DrawHighlightBoxes(FALSE,FALSE,NULL); return C_CONTINUE; case C_CANCEL: if (Da.state != NONE) { Da.ep1Segs_da_cnt = 0; Da.ep2Segs_da_cnt = 0; Da.crvSegs_da_cnt = 0; for (int i=0;i<2;i++) { Da.radius[i] = 0.0; Da.angle[i] = 0.0; Da.center[i] = zero; Da.trk[i] = NULL; Da.ep[i] = -1; Da.pos[i] = zero; Da.endHandle[i].end_valid = FALSE; } //DYNARR_FREE(trkSeg_t,Da.crvSegs_da); } Da.state = NONE; if (infoSubst) { InfoSubstituteControls( NULL, NULL ); infoSubst = FALSE; } return C_CONTINUE; default: return C_CONTINUE; } return C_CONTINUE; } BOOL_T GetTracksFromCornuTrack(track_p trk, track_p newTracks[2]) { track_p trk_old = NULL; newTracks[0] = NULL, newTracks[1] = NULL; struct extraData * xx = GetTrkExtraData(trk); if (!IsTrack(trk)) return FALSE; for (int i=0; icornuData.arcSegs.cnt;i++) { track_p bezTrack[2]; bezTrack[0] = NULL, bezTrack[1] = NULL; trkSeg_p seg = &DYNARR_N(trkSeg_t,xx->cornuData.arcSegs,i); if (seg->type == SEG_BEZTRK) { DYNARR_RESET(trkSeg_t,seg->bezSegs); FixUpBezierSeg(seg->u.b.pos,seg,TRUE); GetTracksFromBezierSegment(seg, bezTrack, trk); if (newTracks[0] == NULL) newTracks[0] = bezTrack[0]; newTracks[1] = bezTrack[1]; if (trk_old) { for (int i=0;i<2;i++) { if (GetTrkEndTrk(trk_old,i)==NULL) { coOrd pos = GetTrkEndPos(trk_old,i); EPINX_T ep_n = PickUnconnectedEndPoint(pos,bezTrack[0]); if ((connectDistance >= FindDistance(GetTrkEndPos(trk_old,i),GetTrkEndPos(bezTrack[0],ep_n))) && (connectAngle >= fabs(DifferenceBetweenAngles(GetTrkEndAngle(trk_old,i),GetTrkEndAngle(bezTrack[0],ep_n)+180))) ) { ConnectTracks(trk_old,i,bezTrack[0],ep_n); break; } } } } trk_old = newTracks[1]; } else { track_p new_trk; if (seg->type == SEG_CRVTRK) new_trk = NewCurvedTrack(seg->u.c.center,seg->u.c.radius,seg->u.c.a0,seg->u.c.a1,0); else if (seg->type == SEG_STRTRK) new_trk = NewStraightTrack(seg->u.l.pos[0],seg->u.l.pos[1]); if (newTracks[0] == NULL) newTracks[0] = new_trk; CopyAttributes( trk, new_trk ); newTracks[1] = new_trk; if (trk_old) { for (int i=0;i<2;i++) { if (GetTrkEndTrk(trk_old,i)==NULL) { coOrd pos = GetTrkEndPos(trk_old,i); EPINX_T ep_n = PickUnconnectedEndPoint(pos,new_trk); if ((connectDistance >= FindDistance(GetTrkEndPos(trk_old,i),GetTrkEndPos(new_trk,ep_n))) && (connectAngle >= fabs(DifferenceBetweenAngles(GetTrkEndAngle(trk_old,i),GetTrkEndAngle(new_trk,ep_n)+180)))) { ConnectTracks(trk_old,i,new_trk,ep_n); break; } } } } trk_old = new_trk; } } return TRUE; } static STATUS_T cmdCornuCreate( wAction_t action, coOrd pos ) { static int createState = 0; int rc = 0; switch(action&0xFF) { case C_DOWN: return CmdCornu(C_DOWN,pos); case C_UP: rc = CmdCornu(C_UP,pos); return rc; case C_FINISH: if (createState != 0 ) { createState = 0; CmdCornu( C_OK, pos ); } else CmdCornu( C_CANCEL, pos ); Da.prevSelected = -1; Da.selectEndHandle = -1; Da.selectEndPoint = -1; Da.selectMidPoint = -1; Da.selectTrack = NULL; Da.trk[0] = NULL; Da.trk[1] = NULL; Da.radius[0] = Da.radius[1] = -1.0; Da.angle[0] = Da.angle[1] = 0.0; Da.ends[0] = Da.ends[1] = FALSE; Da.endHandle[0].end_valid = Da.endHandle[1].end_valid = FALSE; return C_TERMINATE; case C_TEXT: if ((action>>8) != ' ' && (action>>8) != 32) return CmdCornu(action,pos); /*no break*/ case C_OK: CmdCornu(C_OK,pos); MainRedraw(); return C_CONTINUE; case C_CANCEL: HotBarCancel(); CmdCornu(C_CANCEL, pos); createState = 0; rc = C_TERMINATE; /* no break */ case C_START: createState = 0; commandContext = (void *)cornuCmdHotBar; rc = CmdCornu(C_START, pos); Da.prevSelected = -1; Da.selectEndHandle = -1; Da.selectEndPoint = -1; Da.selectMidPoint = -1; Da.selectTrack = NULL; Da.trk[0] = NULL; Da.trk[1] = NULL; Da.radius[0] = Da.radius[1] = -1.0; Da.angle[0] = Da.angle[1] = 0.0; Da.ends[0] = Da.ends[1] = FALSE; Da.endHandle[0].end_valid = Da.endHandle[1].end_valid = FALSE; return rc; default: return CmdCornu(action,pos); } return C_CONTINUE; } static STATUS_T CmdConvertTo( wAction_t action, coOrd pos ) { static track_p trk; cornuParm_t cp; switch (action) { case wActionMove: if ((trk = OnTrack(&pos,FALSE,TRUE)) == NULL) return C_CONTINUE; if (!QueryTrack(trk, Q_CORNU_CAN_MODIFY) && //Not Fixed Track/Turnout/Turntable !QueryTrack(trk, Q_IGNORE_EASEMENT_ON_EXTEND )) trk = NULL; return C_CONTINUE; case C_LCLICK: if ((trk = OnTrack(&pos,FALSE,TRUE))!=NULL) { SetTrkBits(trk,TB_SELECTED); selectedTrackCount = 1; } else { wBeep(); InfoMessage( _("Not on a Track") ); return C_CONTINUE; } trk = NULL; /* no break */ case C_START: if (selectedTrackCount==0) { InfoMessage( _("Select a Track To Convert") ); return C_CONTINUE; } else if (selectedTrackCount>1) { if (NoticeMessage(_("Convert all Selected Tracks to Cornu Tracks?"), _("Yes"), _("No"))<=0) { SetAllTrackSelect(FALSE); return C_TERMINATE; } } UndoStart( _("Convert Cornu"),"newCornu curves"); trk = NULL; int converted=0, not_convertable = 0, created=0, deleted=0; DYNARR_RESET(track_p,Da.tracks); while ( TrackIterate( &trk ) ) { if (!GetTrkSelected( trk )) continue; //Only selected if (!QueryTrack(trk, Q_CORNU_CAN_MODIFY) && //Not Fixed Track/Turnout/Turntable !QueryTrack( trk, Q_IGNORE_EASEMENT_ON_EXTEND )) { //But Yes to Easement not_convertable++; continue; } converted++; DYNARR_RESET(trkSeg_t,Da.crvSegs_da); Da.ep1Segs_da_cnt = 0; Da.ep2Segs_da_cnt = 0; Da.midSegs.cnt = 0; Da.extend[0] = FALSE; Da.extend[1] = FALSE; Da.selectEndPoint = -1; Da.selectTrack = NULL; DYNARR_RESET(coOrd,Da.mid_points); ClrTrkBits( trk, TB_SELECTED ); //Done with this one Da.selectTrack = trk; DYNARR_APPEND(track_p,Da.tracks,1); DYNARR_LAST(track_p,Da.tracks) = trk; Da.trk[0] = GetTrkEndTrk( trk, 0 ); track_p prior = trk; if (Da.trk[0]) Da.ep[0] = GetEndPtConnectedToMe(Da.trk[0],trk); else Da.ep[0] = -1; EPINX_T ep0 = 0; //Move down the LHS adding tracks until no more Selected or not modifyable while (Da.trk[0] && GetTrkSelected( Da.trk[0]) && IsTrack(Da.trk[0]) && (QueryTrack(Da.trk[0], Q_CORNU_CAN_MODIFY) || QueryTrack(Da.trk[0], Q_IS_CORNU)) ) { prior = Da.trk[0]; ep0 = 1-Da.ep[0]; ClrTrkBits( Da.trk[0], TB_SELECTED ); //Done with this one if (selectedTrackCount>0) selectedTrackCount--; DYNARR_APPEND(track_p,Da.tracks,1); DYNARR_LAST(track_p,Da.tracks) = prior; DYNARR_APPEND(coOrd,Da.mid_points,1); for (int i=Da.mid_points.cnt-1;i>1;i--) { DYNARR_N(coOrd,Da.mid_points,i) = DYNARR_N(coOrd,Da.mid_points,i-1); } DYNARR_N(coOrd,Da.mid_points,0) = GetTrkEndPos(prior,1-ep0); Da.trk[0] = GetTrkEndTrk( prior, ep0 ); if (Da.trk[0]) Da.ep[0] = GetEndPtConnectedToMe(Da.trk[0],prior); else Da.ep[0] = -1; converted++; } Da.radius[0] = -1.0; //Initialize with no end Da.ends[0] = FALSE; Da.center[0] = zero; Da.pos[0] = GetTrkEndPos(prior,ep0); if (Da.trk[0] && Da.ep[0]>=0) { GetConnectedTrackParms(Da.trk[0],GetTrkEndPos(Da.trk[0],Da.ep[0]),0,Da.ep[0],FALSE); } //Move to RHS Da.trk[1] = GetTrkEndTrk( trk, 1 ); track_p next = trk; EPINX_T ep1 = 1; if (Da.trk[1]) Da.ep[1] = GetEndPtConnectedToMe(Da.trk[1],trk); else Da.ep[1] = -1; //Move down RHS adding tracks until no more Selected or not modifyable while (Da.trk[1] && GetTrkSelected( Da.trk[1]) && (QueryTrack(Da.trk[1], Q_CORNU_CAN_MODIFY) || QueryTrack(Da.trk[1], Q_IS_CORNU))) { next = Da.trk[1]; ep1 = 1-Da.ep[1]; if (selectedTrackCount>0) selectedTrackCount--; ClrTrkBits( Da.trk[1], TB_SELECTED ); //Done with this one DYNARR_APPEND(track_p,Da.tracks,1); DYNARR_LAST(track_p,Da.tracks) = next; DYNARR_APPEND(coOrd,Da.mid_points,1); DYNARR_LAST(coOrd,Da.mid_points) = GetTrkEndPos(next,1-ep1); Da.trk[1] = GetTrkEndTrk( next, ep1 ); if (Da.trk[1]) Da.ep[1] = GetEndPtConnectedToMe(Da.trk[1],next); converted++; } Da.radius[1] = -1.0; //Initialize with no end Da.ends[1] = FALSE; Da.center[1] = zero; Da.pos[1] = GetTrkEndPos(next,ep1); if (Da.trk[1] && Da.ep[1]>=0) { GetConnectedTrackParms(Da.trk[1],GetTrkEndPos(Da.trk[1],Da.ep[1]),1,Da.ep[1],FALSE); } SetUpCornuParms(&cp); if (CallCornuM(Da.mid_points,Da.ends,Da.pos,&cp,&Da.crvSegs_da,TRUE)) Da.crvSegs_da_cnt = Da.crvSegs_da.cnt; else continue; //Checks that a solution can be found // Do the deed - Create a replacement Cornu BOOL_T end_point[2]; end_point[0] = TRUE; end_point[1] = FALSE; coOrd sub_pos[2]; sub_pos[0] = Da.pos[0]; if (Da.radius[0] == -1) end_point[0] = FALSE; track_p first_trk= NULL,trk1=NULL,trk2 = NULL; for (int i=0;i=0 && Da.trk[i]) { track_p trk_old = GetTrkEndTrk(Da.trk[i],Da.ep[i]); EPINX_T old_ep = GetEndPtConnectedToMe(trk_old,Da.trk[i]); DisconnectTracks(Da.trk[i],Da.ep[i],trk_old,old_ep); if (Da.ep[i]>=0 && Da.trk[i]) ConnectTracks(Da.trk[i],Da.ep[i],i==0?first_trk:trk1,i); } } } //Find next track SetAllTrackSelect(FALSE); //Get rid of old tracks for (int i = 0; i1) { if (NoticeMessage(_("Convert all Selected Tracks to Fixed Tracks?"), _("Yes"), _("No"))<=0) { SetAllTrackSelect(FALSE); return C_TERMINATE; } } dynArr_t trackSegs_da; DYNARR_RESET(trkSeg_t,trackSegs_da); trk1 = NULL; trk2 = NULL; trk = NULL; UndoStart( _("Convert Bezier and Cornu"),"Try to convert all selected tracks"); track_p tracks[2]; DYNARR_RESET(track_p,Da.tracks); int converted=0, not_convertable = 0, created=0, deleted=0; while ( TrackIterate( &trk1 ) ) { if ( GetTrkSelected( trk1 ) && IsTrack( trk1 ) ) { //Only Cornu or Bezier tracks[0] = NULL, tracks[1] = NULL; if (selectedTrackCount>0) selectedTrackCount--; ClrTrkBits( trk1, TB_SELECTED ); //Done with this one if (GetTrkType(trk1) == T_CORNU) { GetTracksFromCornuTrack(trk1,tracks); DYNARR_APPEND(track_p,Da.tracks,1); DYNARR_LAST(track_p,Da.tracks) = trk1; converted++; } else if (GetTrkType(trk1) == T_BEZIER) { GetTracksFromBezierTrack(trk1,tracks); DYNARR_APPEND(track_p,Da.tracks,1); DYNARR_LAST(track_p,Da.tracks) = trk1; converted++; } else { not_convertable++; continue; } for (int i=0;i<2;i++) { track_p trk2 = GetTrkEndTrk(trk1,i); if (trk2) { EPINX_T ep1 = GetEndPtConnectedToMe( trk2, trk1 ); DisconnectTracks(trk2,ep1,trk1,i); pos = GetTrkEndPos(trk2,ep1); for (int j=0;j<2;j++) { EPINX_T ep2 = PickUnconnectedEndPointSilent( pos, tracks[j] ); coOrd ep_pos; if (ep2<0) continue; ep_pos = GetTrkEndPos(tracks[j],ep2); if (connectDistance>=FindDistance(pos,ep_pos) && connectAngle >= fabs(DifferenceBetweenAngles(GetTrkEndAngle(tracks[j],ep2),GetTrkEndAngle(trk2,ep1)+180))) { ConnectTracks(trk2,ep1,tracks[j],ep2); break; } } } } } } SetAllTrackSelect(FALSE); for (int i = 0; i