summaryrefslogtreecommitdiff
path: root/app/bin/ccornu.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/bin/ccornu.c')
-rw-r--r--app/bin/ccornu.c1240
1 files changed, 1240 insertions, 0 deletions
diff --git a/app/bin/ccornu.c b/app/bin/ccornu.c
new file mode 100644
index 0000000..b67d245
--- /dev/null
+++ b/app/bin/ccornu.c
@@ -0,0 +1,1240 @@
+/** \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 "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"
+
+extern drawCmd_t tempD;
+extern TRKTYP_T T_BEZIER;
+extern TRKTYP_T T_CORNU;
+
+
+/*
+ * STATE INFO
+ */
+enum Cornu_States { NONE,
+ POS_1,
+ LOC_2,
+ POS_2,
+ PICK_POINT,
+ POINT_PICKED,
+ TRACK_SELECTED };
+
+static struct {
+ enum Cornu_States state;
+ coOrd pos[2];
+ int selectPoint;
+ wDrawColor color;
+ 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[2];
+ int ep1Segs_da_cnt;
+ trkSeg_t ep2Segs[2];
+ 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];
+
+ bezctx * bezc;
+ } Da;
+
+
+
+/**
+ * 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
+ )
+{
+ DIST_T d, w;
+ d = tempD.scale*0.25;
+ w = tempD.scale/tempD.dpi; /*double width*/
+ 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>=0)?drawColorRed:drawColorBlack;
+ if (track_modifyable)
+ sp[0].type = SEG_CRVLIN;
+ else
+ sp[0].type = SEG_FILCRCL;
+ 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 = drawColorRed;
+ return 2;
+ }
+ return 1;
+}
+
+
+/*
+ * 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; i<seg->bezSegs.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[6], coOrd posk[6]) {
+ for (int i = 0; i < 6; i++) {
+ knots[i].x = posk[i].x;
+ knots[i].y = posk[i].y;
+ }
+ knots[0].ty = SPIRO_OPEN_CONTOUR;
+ knots[1].ty = SPIRO_G2;
+ knots[2].ty = SPIRO_RIGHT;
+ knots[3].ty = SPIRO_LEFT;
+ knots[4].ty = SPIRO_G2;
+ knots[5].ty = SPIRO_END_OPEN_CONTOUR;
+}
+
+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];
+ BOOL_T back;
+ ANGLE_T angle1;
+
+ if (Da.bezc) free(Da.bezc);
+
+ Da.bezc = new_bezctx_xtrkcad(array_p,ends,spots);
+
+ coOrd pos0 = pos[0];
+
+ 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);
+ }
+ posk[2] = pos[0];
+
+ posk[3] = pos[1];
+
+ 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);
+ }
+ SetKnots(knots,posk);
+ 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],&params)) return FALSE;
+ cp->pos[i] = pos[i];
+ if (Da.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;
+ 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;
+ cp->angle[i] = NormalizeAngle(params.track_angle+(ep[i]?180:0)); //Use point not end
+ } else if (params.type == curveTypeCurve) {
+ 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 ){
+ 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,TRUE);
+}
+
+
+/*
+ * 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,
+ wDrawColor color
+ ) {
+ long oldDrawOptions = tempD.funcs->options;
+ tempD.funcs->options = wDrawOptTemp;
+ long oldOptions = tempD.options;
+ tempD.options = DC_TICKS;
+ tempD.orig = mainD.orig;
+ tempD.angle = mainD.angle;
+ if (first_trk)
+ DrawSegs( &tempD, zero, 0.0, first_trk, 1, trackGauge, drawColorBlack );
+ if (crvSegs_cnt && curveSegs)
+ DrawSegs( &tempD, zero, 0.0, curveSegs, crvSegs_cnt, trackGauge, color );
+ if (second_trk)
+ DrawSegs( &tempD, zero, 0.0, second_trk, 1, trackGauge, drawColorBlack );
+ if (ep1Segs_cnt && point1)
+ DrawSegs( &tempD, zero, 0.0, point1, ep1Segs_cnt, trackGauge, drawColorBlack );
+ if (ep2Segs_cnt && point2)
+ DrawSegs( &tempD, zero, 0.0, point2, ep2Segs_cnt, trackGauge, drawColorBlack );
+ if (extend1_trk)
+ DrawSegs( &tempD, zero, 0.0, extend1_trk, 1, trackGauge, drawColorBlack);
+ if (extend2_trk)
+ DrawSegs( &tempD, zero, 0.0, extend2_trk, 1, trackGauge, drawColorBlack);
+ tempD.funcs->options = oldDrawOptions;
+ tempD.options = oldOptions;
+
+}
+
+/*
+ * 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,
+ Da.minRadius<(GetLayoutMinTrackRadius()-EPSILON)?drawColorRed:drawColorBlack);
+
+}
+
+void CreateBothEnds(int selectPoint) {
+ BOOL_T selectable[2],modifyable[2];
+ selectable[0] = Da.trk[0] && !QueryTrack(Da.trk[0],Q_IS_CORNU);
+ modifyable[0] = Da.trk[0] && QueryTrack(Da.trk[0],Q_CORNU_CAN_MODIFY);
+ selectable[1] = Da.trk[1] && !QueryTrack(Da.trk[1],Q_IS_CORNU);
+ modifyable[1] = Da.trk[1] && QueryTrack(Da.trk[1],Q_CORNU_CAN_MODIFY);
+ if (selectPoint == -1) {
+ Da.ep1Segs_da_cnt = createEndPoint(Da.ep1Segs, Da.pos[0],FALSE,selectable[0],modifyable[0]);
+ Da.ep2Segs_da_cnt = createEndPoint(Da.ep2Segs, Da.pos[1],FALSE,selectable[1],modifyable[1]);
+ } else if (selectPoint == 0 || selectPoint == 1) {
+ Da.ep1Segs_da_cnt = createEndPoint(Da.ep1Segs, Da.pos[0],selectPoint == 0,selectable[0],modifyable[0]);
+ Da.ep2Segs_da_cnt = createEndPoint(Da.ep2Segs, Da.pos[1],selectPoint == 1,selectable[1],modifyable[1]);
+ } else {
+ Da.ep1Segs_da_cnt = createEndPoint(Da.ep1Segs, Da.pos[0],FALSE,selectable[0],modifyable[0]);
+ Da.ep2Segs_da_cnt = createEndPoint(Da.ep2Segs, Da.pos[1],FALSE,selectable[1],modifyable[1]);
+ }
+}
+
+BOOL_T GetConnectedTrackParms(track_p t, const coOrd pos, int end, EPINX_T track_end) {
+ trackParams_t trackParams;
+ if (!GetTrackParams(PARAMS_CORNU, t, pos, &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(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]+(track_end?180:0));
+ Da.radius[end] = trackParams.cornuRadius[ep];
+ Da.pos[end] = trackParams.cornuEnd[ep];
+ Da.center[end] = trackParams.cornuCenter[ep];
+ } else if (trackParams.type == curveTypeStraight) {
+ if (Da.ep[end]>=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];
+}
+
+/*
+ * 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;
+ DIST_T dd;
+ EPINX_T ep;
+ cornuParm_t cp;
+
+
+ if (Da.state != PICK_POINT && Da.state != POINT_PICKED && Da.state != TRACK_SELECTED) return C_CONTINUE;
+
+ switch ( action & 0xFF) {
+
+ case C_START:
+ Da.selectPoint = -1;
+ Da.extend[0] = FALSE;
+ Da.extend[1] = FALSE;
+ CreateBothEnds(Da.selectPoint);
+ Da.crvSegs_da.cnt = 0;
+ SetUpCornuParms(&cp);
+ if (CallCornu(Da.pos,Da.trk,Da.ep,&Da.crvSegs_da,&cp)) Da.crvSegs_da_cnt = Da.crvSegs_da.cnt;
+ else Da.crvSegs_da_cnt = 0;
+ Da.minRadius = CornuMinRadius(Da.pos,Da.crvSegs_da);
+ InfoMessage( _("Select End-Point") );
+ DrawTempCornu();
+ return C_CONTINUE;
+
+ case C_DOWN:
+ if (Da.state != PICK_POINT) return C_CONTINUE;
+ dd = 10000.0;
+ Da.selectPoint = -1;
+ for (int i=0;i<2;i++) {
+ d = FindDistance(Da.pos[i],pos);
+ if (d < dd) {
+ dd = d;
+ Da.selectPoint = i;
+ }
+ }
+ if (!IsClose(dd) ) Da.selectPoint = -1;
+ if (Da.selectPoint == -1) {
+ wBeep();
+ InfoMessage( _("Not close enough to end point, reselect") );
+ return C_CONTINUE;
+ } else if (Da.trk[Da.selectPoint] && QueryTrack(Da.trk[Da.selectPoint],Q_IS_CORNU)){
+ wBeep();
+ InfoMessage( _("Is Cornu End -> Not Selectable") );
+ return C_CONTINUE;
+ } else {
+ pos = Da.pos[Da.selectPoint];
+ Da.state = POINT_PICKED;
+ InfoMessage( _("Drag point %d to new location and release it"),Da.selectPoint+1 );
+ }
+ DrawTempCornu(); //wipe out
+ CreateBothEnds(Da.selectPoint);
+ SetUpCornuParms(&cp);
+ if (CallCornu(Da.pos, Da.trk,Da.ep, &Da.crvSegs_da, &cp)) Da.crvSegs_da_cnt = Da.crvSegs_da.cnt;
+ else Da.crvSegs_da_cnt = 0;
+ Da.minRadius = CornuMinRadius(Da.pos, Da.crvSegs_da);
+ DrawTempCornu();
+ return C_CONTINUE;
+
+ case C_MOVE:
+ if (Da.state != POINT_PICKED) {
+ InfoMessage(_("Pick any circle to adjust it by dragging - Enter to accept, Esc to cancel"));
+ return C_CONTINUE;
+ }
+ //If locked, reset pos to be on line from other track
+ int sel = Da.selectPoint;
+ coOrd pos2 = pos;
+ BOOL_T inside = FALSE;
+ if (Da.trk[sel]) { //There is a track
+ if (OnTrack(&pos,FALSE,TRUE) == Da.trk[sel]) { //And the pos is on it
+ inside = TRUE;
+ if (!QueryTrack(Da.trk[Da.selectPoint],Q_CORNU_CAN_MODIFY)) { //Turnouts
+ InfoMessage(_("Track can't be split"));
+ if (Da.ep[sel]>=0) //Ignore if turntable
+ pos = GetTrkEndPos(Da.trk[sel],Da.ep[sel]);
+ }
+ } else {
+ pos = pos2; //Put Back to original position as outside track
+ }
+ // 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)) // But Not Helix or Circle
+ && (!QueryTrack(Da.trk[sel],Q_HAS_VARIABLE_ENDPOINTS))) { // Not a Turntable
+ 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<minLength) {
+ InfoMessage(_("Too close to other end of selected Track"));
+ return C_CONTINUE;
+ }
+ if ((ac>=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
+ }
+ }
+ }
+ }
+ DrawTempCornu(); //wipe out old
+ Da.extend[sel] = FALSE;
+ if(!Da.trk[sel]) { //Cornu with no ends
+ 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[1-sel];
+ Da.extend[sel] = TRUE;
+ }
+ } 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.center[sel], Da.radius[sel], a );
+ a2 = FindAngle(Da.center[sel],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 { //Cornu with ends
+ if (inside) Da.pos[sel] = pos;
+ if (!GetConnectedTrackParms(Da.trk[sel],pos,sel,Da.ep[sel])) {
+ DrawTempCornu();
+ 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
+ DrawTempCornu(); //put back
+ wBeep();
+ InfoMessage(_("Must be on the %s Track"),Da.trackType[sel]==curveTypeBezier?"Bezier":Da.trackType[sel]==curveTypeCornu?"Cornu":"Unknown Type");
+ pos = GetTrkEndPos(Da.trk[sel],Da.ep[sel]);
+ return C_CONTINUE;
+ }
+ }
+ }
+
+ CreateBothEnds(Da.selectPoint);
+ SetUpCornuParms(&cp); //In case we want to use these because the ends are not on the track
+
+ if (CallCornu(Da.pos, Da.trk, Da.ep, &Da.crvSegs_da,&cp)) Da.crvSegs_da_cnt = Da.crvSegs_da.cnt;
+ else Da.crvSegs_da_cnt = 0;
+ Da.minRadius = CornuMinRadius(Da.pos,Da.crvSegs_da);
+ DIST_T rin = Da.radius[0];
+ InfoMessage( _("Cornu : Min Radius=%s Max Rate of Radius Change=%s Length=%s Winding Arc=%s"),
+ FormatDistance(Da.minRadius),
+ FormatDistance(CornuMaxRateofChangeofCurvature(Da.pos,Da.crvSegs_da,&rin)),
+ FormatDistance(CornuLength(Da.pos,Da.crvSegs_da)),
+ FormatDistance(CornuTotalWindingArc(Da.pos,Da.crvSegs_da)));
+ DrawTempCornu();
+ return C_CONTINUE;
+
+ case C_UP:
+ if (Da.state != POINT_PICKED) return C_CONTINUE;
+ ep = 0;
+ DrawTempCornu(); //wipe out
+ Da.selectPoint = -1;
+ CreateBothEnds(Da.selectPoint);
+ SetUpCornuParms(&cp);
+ if (CallCornu(Da.pos,Da.trk,Da.ep,&Da.crvSegs_da,&cp)) 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 - Enter to confirm, ESC to abort"));
+ DrawTempCornu();
+ Da.state = PICK_POINT;
+ return C_CONTINUE;
+
+ case C_OK: //C_OK is not called by Modify.
+ if ( Da.state == PICK_POINT ) {
+ Da.minRadius = CornuMinRadius(Da.pos,Da.crvSegs_da);
+ if (CornuTotalWindingArc(Da.pos,Da.crvSegs_da)>4*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 (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;
+ }
+ }
+
+ DrawTempCornu();
+ UndoStart( _("Create Cornu"),"newCornu curve");
+ t = NewCornuTrack( Da.pos, Da.center, Da.angle, Da.radius,(trkSeg_p)Da.crvSegs_da.ptr, Da.crvSegs_da.cnt);
+ if (t==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"),
+ Da.pos[0].x,Da.pos[0].y,
+ Da.pos[1].x,Da.pos[1].y,
+ Da.center[0].x,Da.center[0].y,
+ Da.center[1].x,Da.center[1].y,
+ Da.angle[0],Da.angle[1],
+ FormatDistance(Da.radius[0]),FormatDistance(Da.radius[1]));
+ return C_TERMINATE;
+ }
+
+ CopyAttributes( Da.trk[0], t );
+
+ for (int i=0;i<2;i++) {
+ UndoModify(Da.trk[i]);
+ MoveEndPt(&Da.trk[i],&Da.ep[i],Da.pos[i],0);
+ if ((GetTrkType(Da.trk[i])==T_BEZIER) || (GetTrkType(Da.trk[i])==T_CORNU)) { //Bezier split position not precise, so readjust Cornu
+ GetConnectedTrackParms(Da.trk[i],GetTrkEndPos(Da.trk[i],Da.ep[i]),i,Da.ep[i]);
+ ANGLE_T endAngle = NormalizeAngle(GetTrkEndAngle(Da.trk[i],Da.ep[i])+180);
+ SetCornuEndPt(t,i,GetTrkEndPos(Da.trk[i],Da.ep[i]),Da.center[i],endAngle,Da.radius[i]);
+ }
+ if (Da.ep[i]>=0)
+ ConnectTracks(Da.trk[i],Da.ep[i],t,i);
+ }
+ UndoEnd();
+ DrawNewTrack(t);
+ Da.state = NONE;
+ MainRedraw();
+ MapRedraw();
+ return C_TERMINATE;
+ }
+ return C_CONTINUE;
+
+ case C_REDRAW:
+ DrawTempCornu();
+ return C_CONTINUE;
+
+ default:
+ return C_CONTINUE;
+ }
+
+
+}
+
+struct extraData {
+ cornuData_t cornuData;
+ };
+
+/**
+ * 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) {
+ track_p t;
+ struct extraData *xx = GetTrkExtraData(trk);
+
+ 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.extend[0] = FALSE;
+ Da.extend[1] = FALSE;
+ Da.selectPoint = -1;
+ Da.selectTrack = NULL;
+
+
+ Da.selectTrack = trk;
+ Da.trk[0] = GetTrkEndTrk( trk, 0 );
+ if (Da.trk[0]) Da.ep[0] = GetEndPtConnectedToMe(Da.trk[0],trk);
+ Da.trk[1] = GetTrkEndTrk( trk, 1 );
+ if (Da.trk[1]) Da.ep[1] = GetEndPtConnectedToMe(Da.trk[1],trk);
+
+ for (int i=0;i<2;i++) {
+ Da.pos[i] = xx->cornuData.pos[i]; //Copy parms from old trk
+ Da.radius[i] = xx->cornuData.r[i];
+ Da.angle[i] = xx->cornuData.a[i];
+ Da.center[i] = xx->cornuData.c[i];
+ }
+
+
+ if ((Da.trk[0] && (!QueryTrack(Da.trk[0],Q_CORNU_CAN_MODIFY) && !QueryTrack(Da.trk[0],Q_CAN_EXTEND))) &&
+ (Da.trk[1] && (!QueryTrack(Da.trk[1],Q_CORNU_CAN_MODIFY) && !QueryTrack(Da.trk[1],Q_CAN_EXTEND)))) {
+ wBeep();
+ ErrorMessage("Both Ends of this Cornu are UnAdjustable");
+ return C_TERMINATE;
+ }
+
+ InfoMessage(_("Track picked - now select a Point"));
+ Da.state = TRACK_SELECTED;
+ DrawTrack(Da.selectTrack,&mainD,wDrawColorWhite); //Wipe out real track, draw replacement
+ return AdjustCornuCurve(C_START, pos, InfoMessage);
+
+ case C_DOWN:
+ if (Da.state == TRACK_SELECTED) return C_CONTINUE; //Ignore until first up
+ return AdjustCornuCurve(C_DOWN, pos, InfoMessage);
+
+
+ case C_MOVE:
+ if (Da.state == TRACK_SELECTED) return C_CONTINUE; //Ignore until first up and down
+ return AdjustCornuCurve(C_MOVE, pos, InfoMessage);
+
+ case C_UP:
+ if (Da.state == TRACK_SELECTED) {
+ Da.state = PICK_POINT; //First time up, next time pick a point
+ }
+ return AdjustCornuCurve(C_UP, pos, InfoMessage); //Run Adjust
+
+ case C_TEXT:
+ if ((action>>8) != 32)
+ return C_CONTINUE;
+ /* no break */
+ case C_OK:
+ 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;
+ }
+ 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 (Da.angle[i]>180)
+ Da.ep[i] = (Da.extendSeg[i].u.c.a0>90 && Da.extendSeg[i].u.c.a0<270)?0:1;
+ else
+ Da.ep[i] = (Da.extendSeg[i].u.c.a0>90 && Da.extendSeg[i].u.c.a0<270)?1:0;
+ }
+ if (!Da.trk[i]) {
+ wBeep();
+ InfoMessage(_("Cornu Extension Create Failed for end %d"),i);
+ return C_TERMINATE;
+ }
+
+ }
+ }
+
+ t = NewCornuTrack( Da.pos, Da.center, Da.angle, Da.radius, (trkSeg_p)Da.crvSegs_da.ptr, Da.crvSegs_da.cnt);
+ if (t==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"),
+ Da.pos[0].x,Da.pos[0].y,
+ Da.pos[1].x,Da.pos[1].y,
+ Da.center[0].x,Da.center[0].y,
+ Da.center[1].x,Da.center[1].y,
+ Da.angle[0],Da.angle[1],
+ FormatDistance(Da.radius[0]),FormatDistance(Da.radius[1]));
+ UndoUndo();
+ MainRedraw();
+ MapRedraw();
+ //DYNARR_FREE(trkSeg_t,Da.crvSegs_da);
+ return C_TERMINATE;
+ }
+
+ DeleteTrack(trk, TRUE);
+
+ if (Da.trk[0]) UndoModify(Da.trk[0]);
+ if (Da.trk[1]) UndoModify(Da.trk[1]);
+
+ for (int i=0;i<2;i++) { //Attach new track
+ if (Da.trk[i] && Da.ep[i] != -1) { //Like the old track
+ MoveEndPt(&Da.trk[i],&Da.ep[i],Da.pos[i],0);
+ if (GetTrkType(Da.trk[i])==T_BEZIER) { //Bezier split position not precise, so readjust Cornu
+ GetConnectedTrackParms(Da.trk[i],GetTrkEndPos(Da.trk[i],Da.ep[i]),i,Da.ep[i]);
+ ANGLE_T endAngle = NormalizeAngle(GetTrkEndAngle(Da.trk[i],Da.ep[i])+180);
+ SetCornuEndPt(t,i,GetTrkEndPos(Da.trk[i],Da.ep[i]),Da.center[i],endAngle,Da.radius[i]);
+ }
+ if (Da.ep[i]>= 0)
+ ConnectTracks(t,i,Da.trk[i],Da.ep[i]);
+ }
+ }
+ UndoEnd();
+ MainRedraw();
+ MapRedraw();
+ Da.state = NONE;
+ //DYNARR_FREE(trkSeg_t,Da.crvSegs_da);
+ return C_TERMINATE;
+
+ case C_CANCEL:
+ InfoMessage(_("Modify Cornu Cancelled"));
+ Da.state = NONE;
+ //DYNARR_FREE(trkSeg_t,Da.crvSegs_da);
+ MainRedraw();
+ MapRedraw();
+ 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;i<segs.cnt;i++) {
+ trkSeg_t t = DYNARR_N(trkSeg_t, segs, i);
+ if (t.type == SEG_CRVTRK || t.type == SEG_CRVLIN) {
+ dd += fabs(t.u.c.radius*D2R(t.u.c.a1));
+ } else if (t.type == SEG_BEZLIN || t.type == SEG_BEZTRK) {
+ dd +=CornuLength(t.u.b.pos,t.bezSegs);
+ } 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<segs.cnt;i++) {
+ trkSeg_t t = DYNARR_N(trkSeg_t, segs, i);
+ if (t.type == SEG_CRVTRK || t.type == SEG_CRVLIN) {
+ rr = fabs(t.u.c.radius);
+ } else if (t.type == SEG_BEZLIN || t.type == SEG_BEZTRK) {
+ rr = CornuMinRadius(t.u.b.pos, t.bezSegs);
+ } else rr = 100000.00;
+ if (rr<r) r = rr;
+ }
+ return r;
+}
+
+DIST_T CornuTotalWindingArc(coOrd pos[4],dynArr_t segs) {
+ DIST_T rr = 0;
+ if (segs.cnt == 0 ) return 0;
+ for (int i = 0;i<segs.cnt;i++) {
+ trkSeg_t t = DYNARR_N(trkSeg_t, segs, i);
+ if (t.type == SEG_CRVTRK || t.type == SEG_CRVLIN) {
+ rr += t.u.c.a1;
+ } else if (t.type == SEG_BEZLIN || t.type == SEG_BEZTRK) {
+ rr += CornuTotalWindingArc(t.u.b.pos, t.bezSegs);
+ }
+ }
+ return rr;
+}
+
+DIST_T CornuMaxRateofChangeofCurvature(coOrd pos[4], dynArr_t segs, DIST_T * last_c) {
+ DIST_T r_max = 0.0, rc, lc = 0;
+ lc = * last_c;
+ segProcData_t segProcData;
+ if (segs.cnt == 0 ) return r_max;
+ for (int i = 0;i<segs.cnt;i++) {
+ trkSeg_t t = DYNARR_N(trkSeg_t, segs, i);
+ if (t.type == SEG_FILCRCL) continue;
+ SegProc(SEGPROC_LENGTH,&t,&segProcData);
+ if (t.type == SEG_CRVTRK || t.type == SEG_CRVLIN) {
+ rc = fabs(1/t.u.c.radius - lc)/segProcData.length.length/2;
+ lc = 1/t.u.c.radius;
+ } else if (t.type == SEG_BEZLIN || t.type == SEG_BEZTRK) {
+ rc = CornuMaxRateofChangeofCurvature(t.u.b.pos, t.bezSegs,&lc); //recurse
+ } else {
+ rc = fabs(0-lc)/segProcData.length.length/2;
+ lc = 0;
+ }
+ if (rc>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;
+ cornuParm_t cp;
+
+ Da.color = lineColor;
+ Da.width = (double)lineWidth/mainD.dpi;
+
+ switch (action&0xFF) {
+
+ case C_START:
+ Da.state = NONE;
+ Da. selectPoint = -1;
+ for (int i=0;i<2;i++) {
+ Da.pos[i] = zero;
+ }
+ 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.extend[0] = FALSE;
+ Da.extend[1] = FALSE;
+ 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:
+ if ( Da.state == NONE || Da.state == LOC_2) { //Set the first or second point
+ coOrd p = pos;
+ int end = Da.state==NONE?0:1;
+ EPINX_T ep;
+ if ((t = OnTrack(&p, FALSE, TRUE)) != NULL) {
+ 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(p, t);
+ if (ep>=0 && QueryTrack(t,Q_CAN_ADD_ENDPOINTS)) ep=-1; //Ignore Turntable Unconnected
+ else 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 Unconnected end point on that track"));
+ return C_CONTINUE;
+ }
+ Da.trk[end] = t;
+ Da.ep[end] = ep; // Note: -1 for Turntable or Circle
+ if (ep ==-1) pos = p;
+ else pos = GetTrkEndPos(t,ep);
+ Da.pos[end] = pos;
+ InfoMessage( _("Place 2nd end point of Cornu track on track with an unconnected end-point") );
+ } else {
+ wBeep();
+ InfoMessage(_("No Unconnected Track End there"));
+ return C_CONTINUE;
+ }
+ if (Da.state == NONE) {
+ if (!GetConnectedTrackParms(t, pos, 0, Da.ep[0])) {
+ Da.trk[0] = NULL;
+ return C_CONTINUE;
+ }
+ Da.state = POS_1;
+ Da.selectPoint = 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));
+ DrawCornuCurve(NULL,Da.ep1Segs,Da.ep1Segs_da_cnt,NULL,0,NULL,0,NULL,NULL,NULL,drawColorBlack);
+ InfoMessage( _("Move 1st end point of Cornu track along track 1") );
+ } else {
+ if ( Da.trk[0] == t) {
+ ErrorMessage( MSG_JOIN_CORNU_SAME );
+ Da.trk[1] = NULL;
+ return C_CONTINUE;
+ }
+ if (!GetConnectedTrackParms(t, pos, 1, Da.ep[1])) {
+ Da.trk[1] = NULL; //Turntable Fail
+ return C_CONTINUE;
+ }
+ CorrectHelixAngles();
+ Da.selectPoint = 1;
+ Da.state = POINT_PICKED;
+ DrawCornuCurve(NULL,Da.ep1Segs,Da.ep1Segs_da_cnt,NULL,0,NULL,0,NULL,NULL,NULL,drawColorBlack); //Wipe out initial Arm
+ CreateBothEnds(1);
+ if (CallCornu(Da.pos,Da.trk,Da.ep,&Da.crvSegs_da, &cp))
+ Da.crvSegs_da_cnt = Da.crvSegs_da.cnt;
+ DrawTempCornu();
+ InfoMessage( _("Move 2nd end point of Cornu track along track 2") );
+ }
+ return C_CONTINUE;
+ } else {
+ return AdjustCornuCurve( action&0xFF, pos, InfoMessage );
+ }
+ return C_CONTINUE;
+
+ case C_MOVE:
+ if (Da.state == NONE) {
+ InfoMessage("Place 1st end point of Cornu track on unconnected end-point");
+ return C_CONTINUE;
+ }
+ if (Da.state == POS_1) {
+ 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(_("Can't Split - Locked to End Point"));
+ return C_CONTINUE;
+ }
+ if (Da.trk[0] != OnTrack(&pos, FALSE, TRUE)) {
+ wBeep();
+ InfoMessage(_("Point not on track 1"));
+ Da.state = POS_1;
+ Da.selectPoint = 1;
+ return C_CONTINUE;
+ }
+ t = Da.trk[0];
+ DrawCornuCurve(NULL,Da.ep1Segs,Da.ep1Segs_da_cnt,NULL,0,NULL,0,NULL,NULL,NULL,drawColorBlack);
+ if (!GetConnectedTrackParms(t, pos, ep, Da.ep[ep])) {
+ Da.state = POS_1;
+ Da.selectPoint = 1;
+ 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));
+ DrawCornuCurve(NULL,Da.ep1Segs,Da.ep1Segs_da_cnt,NULL,0,NULL,0,NULL,NULL,NULL,drawColorBlack);
+ } else {
+ return AdjustCornuCurve( action&0xFF, pos, InfoMessage );
+ }
+ return C_CONTINUE;
+
+ case C_UP:
+ if (Da.state == POS_1 && Da.trk[0]) {
+ DrawCornuCurve(NULL,Da.ep1Segs,Da.ep1Segs_da_cnt,NULL,0,NULL,0,NULL,NULL,NULL,drawColorBlack);
+ Da.state = LOC_2;
+ InfoMessage( _("Put other end of Cornu on a track with an unconnected 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));
+ DrawCornuCurve(NULL,Da.ep1Segs,Da.ep1Segs_da_cnt,NULL,0,NULL,0,NULL,NULL,NULL,drawColorBlack);
+ return C_CONTINUE;
+ } else {
+ return AdjustCornuCurve( action&0xFF, pos, InfoMessage );
+ }
+ case C_TEXT:
+ if (Da.state != PICK_POINT || (action>>8) != 32) //Space is same as Enter.
+ return C_CONTINUE;
+ /* no break */
+ case C_OK:
+ if (Da.state != PICK_POINT) return C_CONTINUE;
+ return AdjustCornuCurve( C_OK, pos, InfoMessage);
+
+ 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.extendSeg[0],&Da.extendSeg[1],Da.color);
+ }
+ return C_CONTINUE;
+
+ case C_CANCEL:
+ 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.extendSeg[0],&Da.extendSeg[1],Da.color);
+ 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;
+ }
+ //DYNARR_FREE(trkSeg_t,Da.crvSegs_da);
+ }
+ Da.state = NONE;
+ return C_CONTINUE;
+
+ default:
+
+ return C_CONTINUE;
+ }
+
+}
+
+
+EXPORT void InitCmdCornu( wMenu_p menu )
+{
+
+}