summaryrefslogtreecommitdiff
path: root/app/bin/tbezier.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/bin/tbezier.c')
-rw-r--r--app/bin/tbezier.c1629
1 files changed, 1629 insertions, 0 deletions
diff --git a/app/bin/tbezier.c b/app/bin/tbezier.c
new file mode 100644
index 0000000..8d7ca6c
--- /dev/null
+++ b/app/bin/tbezier.c
@@ -0,0 +1,1629 @@
+/** \file tbezier.c
+ * XTrkCad - Model Railroad CAD
+ * Copyright (C) 2005 Dave Bullis
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ *****************************************************************************
+ * BEZIER TRACK (and LINE)
+ *
+ * tbezier.c has all the Track functions (for both T_BEZIER and T_BEZLIN) but all the heavy-math-lifting is delegated to cbezier.c
+ *
+ * Both Bezier Tracks and Lines are defined with two end points and two control points. Each end and control point pair is joined by a control arm.
+ * The angle between the control and end point (arm angle) determines the angle at the end point.
+ * The way the curve moves in between the ends is driven by the relative lengths of the two control arms.
+ * In general, lengthening one arm while keeping the other arm length fixed results in a curve that changes more slowly from the lengthened end and more swiftly from the other.
+ * Very un-prototypical track curves are easy to draw with Bezier, so beware!
+ *
+ * Another large user of tbezier.c is the Cornu function by way of its support for Bezier segments, which are used to approximate Cornu curves.
+ *
+ * In XTrkcad, Bezier curves are rendered into a set of Curved and Straight segments for display. This set is also saved, although the code recalculates a fresh set upon reload.
+ *
+ */
+
+
+#include "track.h"
+#include "draw.h"
+#include "tbezier.h"
+#include "cbezier.h"
+#include "ccurve.h"
+#include "cstraigh.h"
+#include "cjoin.h"
+#include "utility.h"
+#include "i18n.h"
+#include "param.h"
+#include "math.h"
+#include "string.h"
+#include "cundo.h"
+#include "layout.h"
+#include "fileio.h"
+#include "assert.h"
+
+EXPORT TRKTYP_T T_BEZIER = -1;
+EXPORT TRKTYP_T T_BZRLIN = -1;
+
+
+struct extraData {
+ BezierData_t bezierData;
+ };
+
+static int log_bezier = 0;
+
+static DIST_T GetLengthBezier( track_p );
+
+/****************************************
+ *
+ * UTILITIES
+ *
+ */
+
+/*
+ * Run after any changes to the Bezier points
+ */
+EXPORT void FixUpBezier(coOrd pos[4], struct extraData* xx, BOOL_T track) {
+ xx->bezierData.a0 = NormalizeAngle(FindAngle(pos[1], pos[0]));
+ xx->bezierData.a1 = NormalizeAngle(FindAngle(pos[2], pos[3]));
+
+ ConvertToArcs(pos, &xx->bezierData.arcSegs, track, xx->bezierData.segsColor,
+ xx->bezierData.segsWidth);
+ xx->bezierData.minCurveRadius = BezierMinRadius(pos,
+ xx->bezierData.arcSegs);
+ xx->bezierData.length = BezierLength(pos, xx->bezierData.arcSegs);
+}
+
+/*
+ * Run after any changes to the Bezier points for a Segment
+ */
+EXPORT void FixUpBezierSeg(coOrd pos[4], trkSeg_p p, BOOL_T track) {
+ p->u.b.angle0 = NormalizeAngle(FindAngle(pos[1], pos[0]));
+ p->u.b.angle3 = NormalizeAngle(FindAngle(pos[2], pos[3]));
+ ConvertToArcs(pos, &p->bezSegs, track, p->color,
+ p->width);
+ p->u.b.minRadius = BezierMinRadius(pos,
+ p->bezSegs);
+ p->u.b.length = BezierLength(pos, p->bezSegs);
+}
+
+EXPORT void FixUpBezierSegs(trkSeg_p p,int segCnt) {
+ for (int i=0;i<segCnt;i++,p++) {
+ if (p->type == SEG_BEZTRK || p->type == SEG_BEZLIN) {
+ FixUpBezierSeg(p->u.b.pos,p,p->type == SEG_BEZTRK);
+ }
+ }
+}
+
+
+static void GetBezierAngles( ANGLE_T *a0, ANGLE_T *a1, track_p trk )
+{
+ assert( trk != NULL );
+
+ *a0 = NormalizeAngle( GetTrkEndAngle(trk,0) );
+ *a1 = NormalizeAngle( GetTrkEndAngle(trk,1) );
+
+ LOG( log_bezier, 4, ( "getBezierAngles: = %0.3f %0.3f\n", *a0, *a1 ) )
+}
+
+
+static void ComputeBezierBoundingBox( track_p trk, struct extraData * xx )
+{
+ coOrd hi, lo;
+ hi.x = lo.x = xx->bezierData.pos[0].x;
+ hi.y = lo.y = xx->bezierData.pos[0].y;
+
+ for (int i=1; i<=3;i++) {
+ hi.x = hi.x < xx->bezierData.pos[i].x ? xx->bezierData.pos[i].x : hi.x;
+ hi.y = hi.y < xx->bezierData.pos[i].y ? xx->bezierData.pos[i].y : hi.y;
+ lo.x = lo.x > xx->bezierData.pos[i].x ? xx->bezierData.pos[i].x : lo.x;
+ lo.y = lo.y > xx->bezierData.pos[i].y ? xx->bezierData.pos[i].y : lo.y;
+ }
+ SetBoundingBox( trk, hi, lo );
+}
+
+
+DIST_T BezierDescriptionDistance(
+ coOrd pos,
+ track_p trk )
+{
+ struct extraData *xx = GetTrkExtraData(trk);
+ coOrd p1;
+
+ if ( GetTrkType( trk ) != T_BEZIER || ( GetTrkBits( trk ) & TB_HIDEDESC ) != 0 )
+ return 100000;
+
+ p1.x = xx->bezierData.pos[0].x + ((xx->bezierData.pos[3].x-xx->bezierData.pos[0].x)/2) + xx->bezierData.descriptionOff.x;
+ p1.y = xx->bezierData.pos[0].y + ((xx->bezierData.pos[3].y-xx->bezierData.pos[0].y)/2) + xx->bezierData.descriptionOff.y;
+
+ return FindDistance( p1, pos );
+}
+
+
+static void DrawBezierDescription(
+ track_p trk,
+ drawCmd_p d,
+ wDrawColor color )
+{
+ struct extraData *xx = GetTrkExtraData(trk);
+ wFont_p fp;
+ coOrd pos;
+
+ if (layoutLabels == 0)
+ return;
+ if ((labelEnable&LABELENABLE_TRKDESC)==0)
+ return;
+ pos.x = xx->bezierData.pos[0].x + ((xx->bezierData.pos[3].x - xx->bezierData.pos[0].x)/2);
+ pos.y = xx->bezierData.pos[0].y + ((xx->bezierData.pos[3].y - xx->bezierData.pos[0].y)/2);
+ pos.x += xx->bezierData.descriptionOff.x;
+ pos.y += xx->bezierData.descriptionOff.y;
+ fp = wStandardFont( F_TIMES, FALSE, FALSE );
+ sprintf( message, _("Bezier Curve: length=%s min radius=%s"),
+ FormatDistance(xx->bezierData.length), FormatDistance(xx->bezierData.minCurveRadius));
+ DrawBoxedString( BOX_BOX, d, pos, message, fp, (wFontSize_t)descriptionFontSize, color, 0.0 );
+}
+
+
+STATUS_T BezierDescriptionMove(
+ track_p trk,
+ wAction_t action,
+ coOrd pos )
+{
+ struct extraData *xx = GetTrkExtraData(trk);
+ static coOrd p0,p1;
+ static BOOL_T editState;
+ wDrawColor color;
+ if (GetTrkType(trk) != T_BEZIER) return C_TERMINATE;
+ p0.x = xx->bezierData.pos[0].x + ((xx->bezierData.pos[3].x - xx->bezierData.pos[0].x)/2);
+ p0.y = xx->bezierData.pos[0].y + ((xx->bezierData.pos[3].y - xx->bezierData.pos[0].y)/2);
+ switch (action) {
+ case C_DOWN:
+ case C_MOVE:
+ case C_UP:
+ editState = TRUE;
+ p1 = pos;
+ color = GetTrkColor( trk, &mainD );
+ DrawLine( &mainD, p0, pos, 0, wDrawColorBlack );
+ xx->bezierData.descriptionOff.x = pos.x - p0.x;
+ xx->bezierData.descriptionOff.y = pos.y - p0.y;
+ if (action == C_UP) {
+ editState = FALSE;
+ }
+ MainRedraw();
+ MapRedraw();
+ return action==C_UP?C_TERMINATE:C_CONTINUE;
+ case C_REDRAW:
+ if (editState)
+ DrawLine( &mainD, p1, p0, 0, wDrawColorBlack );
+ break;
+
+
+ }
+ return C_CONTINUE;
+}
+
+/****************************************
+ *
+ * GENERIC FUNCTIONS
+ *
+ */
+
+static struct {
+ coOrd pos[4];
+ FLOAT_T elev[2];
+ FLOAT_T length;
+ DIST_T minRadius;
+ FLOAT_T grade;
+ unsigned int layerNumber;
+ ANGLE_T angle[2];
+ DIST_T radius[2];
+ coOrd center[2];
+ dynArr_t segs;
+ long width;
+ wDrawColor color;
+ } bezData;
+typedef enum { P0, A0, R0, C0, Z0, CP1, CP2, P1, A1, R1, C1, Z1, RA, LN, GR, LY, WI, CO } crvDesc_e;
+static descData_t bezDesc[] = {
+/*P0*/ { DESC_POS, N_("End Pt 1: X,Y"), &bezData.pos[0] },
+/*A0*/ { DESC_ANGLE, N_("End Angle"), &bezData.angle[0] },
+/*R0*/ { DESC_DIM, N_("Radius"), &bezData.radius[0] },
+/*C0*/ { DESC_POS, N_("Center X,Y"), &bezData.center[0]},
+/*Z0*/ { DESC_DIM, N_("Z1"), &bezData.elev[0] },
+/*CP1*/ { DESC_POS, N_("Ctl Pt 1: X,Y"), &bezData.pos[1] },
+/*CP2*/ { DESC_POS, N_("Ctl Pt 2: X,Y"), &bezData.pos[2] },
+/*P1*/ { DESC_POS, N_("End Pt 2: X,Y"), &bezData.pos[3] },
+/*A1*/ { DESC_ANGLE, N_("End Angle"), &bezData.angle[1] },
+/*R1*/ { DESC_DIM, N_("Radius"), &bezData.radius[1] },
+/*C1*/ { DESC_POS, N_("Center X,Y"), &bezData.center[1]},
+/*Z1*/ { DESC_DIM, N_("Z2"), &bezData.elev[1] },
+/*RA*/ { DESC_DIM, N_("MinRadius"), &bezData.radius },
+/*LN*/ { DESC_DIM, N_("Length"), &bezData.length },
+/*GR*/ { DESC_FLOAT, N_("Grade"), &bezData.grade },
+/*LY*/ { DESC_LAYER, N_("Layer"), &bezData.layerNumber },
+/*WI*/ { DESC_LONG, N_("Line Width"), &bezData.width},
+/*CO*/ { DESC_COLOR, N_("Line Color"), &bezData.color},
+ { DESC_NULL } };
+
+static void UpdateBezier( track_p trk, int inx, descData_p descUpd, BOOL_T final )
+{
+ struct extraData *xx = GetTrkExtraData(trk);
+ BOOL_T updateEndPts;
+ EPINX_T ep;
+ ANGLE_T angle1, angle2;
+
+ if ( inx == -1 )
+ return;
+ updateEndPts = FALSE;
+ switch ( inx ) {
+ case P0:
+ if (GetTrkEndTrk(trk,0)) break;
+ updateEndPts = TRUE;
+ xx->bezierData.pos[0] = bezData.pos[0];
+ bezDesc[P0].mode |= DESC_CHANGE;
+ /* no break */
+ case P1:
+ if (GetTrkEndTrk(trk,0) && GetTrkEndTrk(trk,1)) break;
+ updateEndPts = TRUE;
+ xx->bezierData.pos[3]= bezData.pos[3];
+ bezDesc[P1].mode |= DESC_CHANGE;
+ break;
+ case A0:
+ case A1:
+ break;
+ case CP1:
+ if (GetTrkEndTrk(trk,0)) {
+ angle1 = NormalizeAngle(GetTrkEndAngle(trk,0));
+ angle2 = NormalizeAngle(FindAngle(bezData.pos[1], xx->bezierData.pos[0])-angle1);
+ if (angle2 > 90.0 && angle2 < 270.0)
+ Translate( &bezData.pos[1], xx->bezierData.pos[0], angle1, -FindDistance( xx->bezierData.pos[0], bezData.pos[1] )*cos(D2R(angle2)));
+ }
+ xx->bezierData.pos[1] = bezData.pos[1];
+ bezDesc[CP1].mode |= DESC_CHANGE;
+ updateEndPts = TRUE;
+ break;
+ case CP2:
+ if (GetTrkEndTrk(trk,1)) {
+ angle1 = NormalizeAngle(GetTrkEndAngle(trk,1));
+ angle2 = NormalizeAngle(FindAngle(bezData.pos[2], xx->bezierData.pos[3])-angle1);
+ if (angle2 > 90.0 && angle2 < 270.0)
+ Translate( &bezData.pos[2], xx->bezierData.pos[3], angle1, -FindDistance( xx->bezierData.pos[3], bezData.pos[0] )*cos(D2R(angle2)));
+ }
+ xx->bezierData.pos[2] = bezData.pos[2];
+ bezDesc[CP2].mode |= DESC_CHANGE;
+ updateEndPts = TRUE;
+ break;
+ case Z0:
+ case Z1:
+ ep = (inx==Z0?0:1);
+ UpdateTrkEndElev( trk, ep, GetTrkEndElevUnmaskedMode(trk,ep), bezData.elev[ep], NULL );
+ ComputeElev( trk, 1-ep, FALSE, &bezData.elev[1-ep], NULL );
+ if ( bezData.length > minLength )
+ bezData.grade = fabs( (bezData.elev[0]-bezData.elev[1])/bezData.length )*100.0;
+ else
+ bezData.grade = 0.0;
+ bezDesc[GR].mode |= DESC_CHANGE;
+ bezDesc[inx==Z0?Z1:Z0].mode |= DESC_CHANGE;
+ return;
+ case LY:
+ SetTrkLayer( trk, bezData.layerNumber);
+ break;
+ case WI:
+ xx->bezierData.segsWidth = bezData.width/mainD.dpi;
+ break;
+ case CO:
+ xx->bezierData.segsColor = bezData.color;
+ break;
+ default:
+ AbortProg( "updateBezier: Bad inx %d", inx );
+ }
+ ConvertToArcs(xx->bezierData.pos, &xx->bezierData.arcSegs, IsTrack(trk)?TRUE:FALSE, xx->bezierData.segsColor, xx->bezierData.segsWidth);
+ trackParams_t params;
+ for (int i=0;i<2;i++) {
+ GetTrackParams(0,trk,xx->bezierData.pos[i],&params);
+ bezData.radius[i] = params.arcR;
+ bezData.center[i] = params.arcP;
+ }
+ if (updateEndPts) {
+ if ( GetTrkEndTrk(trk,0) == NULL ) {
+ SetTrkEndPoint( trk, 0, bezData.pos[0], NormalizeAngle( FindAngle(bezData.pos[0], bezData.pos[1]) ) );
+ bezData.angle[0] = GetTrkEndAngle(trk,0);
+ bezDesc[A0].mode |= DESC_CHANGE;
+ GetTrackParams(PARAMS_CORNU,trk,xx->bezierData.pos[0],&params);
+ bezData.radius[0] = params.arcR;
+ bezData.center[0] = params.arcP;
+ }
+ if ( GetTrkEndTrk(trk,1) == NULL ) {
+ SetTrkEndPoint( trk, 1, bezData.pos[3], NormalizeAngle( FindAngle(bezData.pos[2], bezData.pos[3]) ) );
+ bezData.angle[1] = GetTrkEndAngle(trk,1);
+ bezDesc[A1].mode |= DESC_CHANGE;
+ GetTrackParams(PARAMS_CORNU,trk,xx->bezierData.pos[1],&params);
+ bezData.radius[1] = params.arcR;
+ bezData.center[1] = params.arcP;
+ }
+ }
+
+ FixUpBezier(xx->bezierData.pos, xx, IsTrack(trk));
+ ComputeBezierBoundingBox(trk, xx);
+ DrawNewTrack( trk );
+}
+
+static void DescribeBezier( track_p trk, char * str, CSIZE_T len )
+{
+ struct extraData *xx = GetTrkExtraData(trk);
+ DIST_T d;
+ int fix0, fix1 = 0;
+
+ d = xx->bezierData.length;
+ sprintf( str, _("Bezier %s(%d): Layer=%u MinRadius=%s Length=%s EP=[%0.3f,%0.3f] [%0.3f,%0.3f] CP1=[%0.3f,%0.3f] CP2=[%0.3f, %0.3f]"),
+ GetTrkType(trk)==T_BEZIER?"Track":"Line",
+ GetTrkIndex(trk),
+ GetTrkLayer(trk)+1,
+ FormatDistance(xx->bezierData.minCurveRadius),
+ FormatDistance(d),
+ PutDim(xx->bezierData.pos[0].x),PutDim(xx->bezierData.pos[0].y),
+ PutDim(xx->bezierData.pos[3].x),PutDim(xx->bezierData.pos[3].y),
+ PutDim(xx->bezierData.pos[1].x),PutDim(xx->bezierData.pos[1].y),
+ PutDim(xx->bezierData.pos[2].x),PutDim(xx->bezierData.pos[2].y));
+
+ if (GetTrkType(trk) == T_BEZIER) {
+ fix0 = GetTrkEndTrk(trk,0)!=NULL;
+ fix1 = GetTrkEndTrk(trk,1)!=NULL;
+ }
+
+ bezData.length = GetLengthBezier(trk);
+ bezData.minRadius = xx->bezierData.minCurveRadius;
+ if (bezData.minRadius >= 100000.00) bezData.minRadius = 0;
+ bezData.layerNumber = GetTrkLayer(trk);
+ bezData.pos[0] = xx->bezierData.pos[0];
+ bezData.pos[1] = xx->bezierData.pos[1];
+ bezData.pos[2] = xx->bezierData.pos[2];
+ bezData.pos[3] = xx->bezierData.pos[3];
+ bezData.angle[0] = xx->bezierData.a0;
+ bezData.angle[1] = xx->bezierData.a1;
+ trackParams_t params;
+ GetTrackParams(PARAMS_CORNU,trk,xx->bezierData.pos[0],&params);
+ bezData.radius[0] = params.arcR;
+ bezData.center[0] = params.arcP;
+ GetTrackParams(PARAMS_CORNU,trk,xx->bezierData.pos[3],&params);
+ bezData.radius[1] = params.arcR;
+ bezData.center[1] = params.arcP;
+
+ if (GetTrkType(trk) == T_BEZIER) {
+ ComputeElev( trk, 0, FALSE, &bezData.elev[0], NULL );
+ ComputeElev( trk, 1, FALSE, &bezData.elev[1], NULL );
+
+ if ( bezData.length > minLength )
+ bezData.grade = fabs( (bezData.elev[0]-bezData.elev[1])/bezData.length )*100.0;
+ else
+ bezData.grade = 0.0;
+ }
+
+ bezDesc[P0].mode = fix0?DESC_RO:0;
+ bezDesc[P1].mode = fix1?DESC_RO:0;
+ bezDesc[LN].mode = DESC_RO;
+ bezDesc[CP1].mode = 0;
+ bezDesc[CP2].mode = 0;
+ if (GetTrkType(trk) == T_BEZIER) {
+ bezDesc[Z0].mode = EndPtIsDefinedElev(trk,0)?0:DESC_RO;
+ bezDesc[Z1].mode = EndPtIsDefinedElev(trk,1)?0:DESC_RO;
+ }
+ else
+ bezDesc[Z0].mode = bezDesc[Z1].mode = DESC_IGNORE;
+ bezDesc[A0].mode = DESC_RO;
+ bezDesc[A1].mode = DESC_RO;
+ bezDesc[C0].mode = DESC_RO;
+ bezDesc[C1].mode = DESC_RO;
+ bezDesc[R0].mode = DESC_RO;
+ bezDesc[R1].mode = DESC_RO;
+ bezDesc[GR].mode = DESC_RO;
+ bezDesc[RA].mode = DESC_RO;
+ bezDesc[LY].mode = DESC_NOREDRAW;
+ bezData.width = (long)floor(xx->bezierData.segsWidth*mainD.dpi+0.5);
+ bezDesc[WI].mode = GetTrkType(trk) == T_BEZIER?DESC_IGNORE:0;
+ bezData.color = xx->bezierData.segsColor;
+ bezDesc[CO].mode = GetTrkType(trk) == T_BEZIER?DESC_IGNORE:0;
+
+ if (GetTrkType(trk) == T_BEZIER)
+ DoDescribe( _("Bezier Track"), trk, bezDesc, UpdateBezier );
+ else
+ DoDescribe( _("Bezier Line"), trk, bezDesc, UpdateBezier );
+
+}
+
+static DIST_T DistanceBezier( track_p t, coOrd * p )
+{
+ struct extraData *xx = GetTrkExtraData(t);
+
+ DIST_T d = 100000.0;
+ coOrd p2 = xx->bezierData.pos[0]; //Set initial point
+ segProcData_t segProcData;
+ for (int i = 0;i<xx->bezierData.arcSegs.cnt;i++) {
+ segProcData.distance.pos1 = * p;
+ SegProc(SEGPROC_DISTANCE,&DYNARR_N(trkSeg_t,xx->bezierData.arcSegs,i),&segProcData);
+ if (segProcData.distance.dd<d) {
+ d = segProcData.distance.dd;
+ p2 = segProcData.distance.pos1;
+ }
+ }
+ * p = p2;
+ return d;
+}
+
+static void DrawBezier( track_p t, drawCmd_p d, wDrawColor color )
+{
+ struct extraData *xx = GetTrkExtraData(t);
+ long widthOptions = DTS_LEFT|DTS_RIGHT;
+
+
+ if (GetTrkType(t) == T_BZRLIN) {
+ DrawSegsO(d,t,zero,0.0,xx->bezierData.arcSegs.ptr,xx->bezierData.arcSegs.cnt, 0.0, color, 0);
+ return;
+ }
+
+ if (GetTrkWidth(t) == 2)
+ widthOptions |= DTS_THICK2;
+ if (GetTrkWidth(t) == 3)
+ widthOptions |= DTS_THICK3;
+
+
+ if ( ((d->funcs->options&wDrawOptTemp)==0) &&
+ (labelWhen == 2 || (labelWhen == 1 && (d->options&DC_PRINT))) &&
+ labelScale >= d->scale &&
+ ( GetTrkBits( t ) & TB_HIDEDESC ) == 0 ) {
+ DrawBezierDescription( t, d, color );
+ }
+ DIST_T scale2rail = (d->options&DC_PRINT)?(twoRailScale*2+1):twoRailScale;
+ if ( tieDrawMode!=TIEDRAWMODE_NONE &&
+ d!=&mapD &&
+ (d->options&DC_TIES)!=0 &&
+ d->scale<scale2rail/2 )
+ DrawSegsO(d,t,zero,0.0,xx->bezierData.arcSegs.ptr,xx->bezierData.arcSegs.cnt, GetTrkGauge(t), color, widthOptions|DTS_TIES);
+ DrawSegsO(d,t,zero,0.0,xx->bezierData.arcSegs.ptr,xx->bezierData.arcSegs.cnt, GetTrkGauge(t), color, widthOptions);
+ if ( (d->funcs->options & wDrawOptTemp) == 0 &&
+ (d->options&DC_QUICK) == 0 ) {
+ DrawEndPt( d, t, 0, color );
+ DrawEndPt( d, t, 1, color );
+ }
+}
+
+static void DeleteBezier( track_p t )
+{
+ struct extraData *xx = GetTrkExtraData(t);
+
+ for (int i=0;i<xx->bezierData.arcSegs.cnt;i++) {
+ trkSeg_t s = DYNARR_N(trkSeg_t,xx->bezierData.arcSegs,i);
+ if (s.type == SEG_BEZTRK || s.type == SEG_BEZLIN) {
+ if (s.bezSegs.ptr) MyFree(s.bezSegs.ptr);
+ s.bezSegs.max = 0;
+ s.bezSegs.cnt = 0;
+ }
+ }
+ if (xx->bezierData.arcSegs.ptr && !xx->bezierData.arcSegs.max)
+ MyFree(xx->bezierData.arcSegs.ptr);
+ xx->bezierData.arcSegs.max = 0;
+ xx->bezierData.arcSegs.cnt = 0;
+ xx->bezierData.arcSegs.ptr = NULL;
+}
+
+static BOOL_T WriteBezier( track_p t, FILE * f )
+{
+ struct extraData *xx = GetTrkExtraData(t);
+ long options;
+ BOOL_T rc = TRUE;
+ BOOL_T track =(GetTrkType(t)==T_BEZIER);
+ options = GetTrkWidth(t) & 0x0F;
+ if ( ( GetTrkBits(t) & TB_HIDEDESC ) == 0 ) options |= 0x80;
+ rc &= fprintf(f, "%s %d %u %ld %ld %0.6f %s %d %0.6f %0.6f %0.6f %0.6f %0.6f %0.6f %0.6f %0.6f 0 %0.6f %0.6f \n",
+ track?"BEZIER":"BZRLIN",GetTrkIndex(t), GetTrkLayer(t), (long)options, wDrawGetRGB(xx->bezierData.segsColor), xx->bezierData.segsWidth,
+ GetTrkScaleName(t), GetTrkVisible(t),
+ xx->bezierData.pos[0].x, xx->bezierData.pos[0].y,
+ xx->bezierData.pos[1].x, xx->bezierData.pos[1].y,
+ xx->bezierData.pos[2].x, xx->bezierData.pos[2].y,
+ xx->bezierData.pos[3].x, xx->bezierData.pos[3].y,
+ xx->bezierData.descriptionOff.x, xx->bezierData.descriptionOff.y )>0;
+ if (track) {
+ rc &= WriteEndPt( f, t, 0 );
+ rc &= WriteEndPt( f, t, 1 );
+ }
+ rc &= WriteSegs( f, xx->bezierData.arcSegs.cnt, xx->bezierData.arcSegs.ptr );
+ return rc;
+}
+
+static void ReadBezier( char * line )
+{
+ struct extraData *xx;
+ track_p t;
+ wIndex_t index;
+ BOOL_T visible;
+ coOrd p0, c1, c2, p1, dp;
+ char scale[10];
+ wIndex_t layer;
+ long options;
+ char * cp = NULL;
+ unsigned long rgb;
+ DIST_T width;
+
+ if (!GetArgs( line+6, "dLluwsdpppp0p",
+ &index, &layer, &options, &rgb, &width, scale, &visible, &p0, &c1, &c2, &p1, &dp ) ) {
+ return;
+ }
+ if (strncmp(line,"BEZIER",6)==0)
+ t = NewTrack( index, T_BEZIER, 0, sizeof *xx );
+ else
+ t = NewTrack( index, T_BZRLIN, 0, sizeof *xx );
+ xx = GetTrkExtraData(t);
+ SetTrkVisible(t, visible);
+ SetTrkScale(t, LookupScale(scale));
+ SetTrkLayer(t, layer );
+ SetTrkWidth(t, (int)(options&0x0F));
+ if ( ( options & 0x80 ) == 0 ) SetTrkBits(t,TB_HIDEDESC);
+ xx->bezierData.pos[0] = p0;
+ xx->bezierData.pos[1] = c1;
+ xx->bezierData.pos[2] = c2;
+ xx->bezierData.pos[3] = p1;
+ xx->bezierData.descriptionOff = dp;
+ xx->bezierData.segsWidth = width;
+ xx->bezierData.segsColor = wDrawFindColor( rgb );
+ ReadSegs();
+ FixUpBezier(xx->bezierData.pos,xx,GetTrkType(t) == T_BEZIER);
+ ComputeBezierBoundingBox(t,xx);
+ if (GetTrkType(t) == T_BEZIER) {
+ SetEndPts(t,2);
+ }
+}
+
+static void MoveBezier( track_p trk, coOrd orig )
+{
+ struct extraData *xx = GetTrkExtraData(trk);
+ UndoModify(trk);
+ for (int i=0;i<4;i++) {
+ xx->bezierData.pos[i].x += orig.x;
+ xx->bezierData.pos[i].y += orig.y;
+ }
+ FixUpBezier(xx->bezierData.pos,xx,IsTrack(trk));
+ ComputeBezierBoundingBox(trk,xx);
+
+}
+
+static void RotateBezier( track_p trk, coOrd orig, ANGLE_T angle )
+{
+ struct extraData *xx = GetTrkExtraData(trk);
+ UndoModify(trk);
+ for (int i=0;i<5;i++) {
+ Rotate( &xx->bezierData.pos[i], orig, angle );
+ }
+ FixUpBezier(xx->bezierData.pos,xx,IsTrack(trk));
+ ComputeBezierBoundingBox(trk,xx);
+
+}
+
+static void RescaleBezier( track_p trk, FLOAT_T ratio )
+{
+ struct extraData *xx = GetTrkExtraData(trk);
+ UndoModify(trk);
+ xx->bezierData.pos[0].x *= ratio;
+ xx->bezierData.pos[0].y *= ratio;
+ xx->bezierData.pos[1].x *= ratio;
+ xx->bezierData.pos[1].y *= ratio;
+ xx->bezierData.pos[2].x *= ratio;
+ xx->bezierData.pos[2].y *= ratio;
+ xx->bezierData.pos[3].x *= ratio;
+ xx->bezierData.pos[3].y *= ratio;
+ FixUpBezier(xx->bezierData.pos,xx,IsTrack(trk));
+ ComputeBezierBoundingBox(trk,xx);
+
+}
+
+EXPORT void AdjustBezierEndPt( track_p trk, EPINX_T inx, coOrd pos ) {
+ struct extraData *xx = GetTrkExtraData(trk);
+ UndoModify(trk);
+ if (inx ==0 ) {
+ xx->bezierData.pos[1].x += -xx->bezierData.pos[0].x+pos.x;
+ xx->bezierData.pos[1].y += -xx->bezierData.pos[0].y+pos.y;
+ xx->bezierData.pos[0] = pos;
+ }
+ else {
+ xx->bezierData.pos[2].x += -xx->bezierData.pos[3].x+pos.x;
+ xx->bezierData.pos[2].y += -xx->bezierData.pos[3].y+pos.y;
+ xx->bezierData.pos[3] = pos;
+ }
+ FixUpBezier(xx->bezierData.pos, xx, IsTrack(trk));
+ ComputeBezierBoundingBox(trk,xx);
+ SetTrkEndPoint( trk, inx, pos, inx==0?xx->bezierData.a0:xx->bezierData.a1);
+}
+
+
+/**
+ * Split the Track at approximately the point pos.
+ */
+static BOOL_T SplitBezier( track_p trk, coOrd pos, EPINX_T ep, track_p *leftover, EPINX_T * ep0, EPINX_T * ep1 )
+{
+ struct extraData *xx = GetTrkExtraData(trk);
+ track_p trk1;
+ double t;
+ BOOL_T track;
+ track = IsTrack(trk);
+
+ coOrd current[4], newl[4], newr[4];
+
+ double dd = DistanceBezier(trk, &pos);
+ if (dd>minLength) return FALSE;
+
+ BezierMathDistance(&pos, xx->bezierData.pos, 500, &t); //Find t value
+
+ for (int i=0;i<4;i++) {
+ current[i] = xx->bezierData.pos[i];
+ }
+
+ BezierSplit(current, newl, newr, t);
+
+ if (track) {
+ trk1 = NewBezierTrack(ep?newr:newl,NULL,0);
+ } else
+ trk1 = NewBezierLine(ep?newr:newl,NULL,0, xx->bezierData.segsColor,xx->bezierData.segsWidth);
+ UndoModify(trk);
+ for (int i=0;i<4;i++) {
+ xx->bezierData.pos[i] = ep?newl[i]:newr[i];
+ }
+ FixUpBezier(xx->bezierData.pos,xx,track);
+ ComputeBezierBoundingBox(trk,xx);
+ SetTrkEndPoint( trk, ep, xx->bezierData.pos[ep?3:0], ep?xx->bezierData.a1:xx->bezierData.a0);
+
+ *leftover = trk1;
+ *ep0 = 1-ep;
+ *ep1 = ep;
+
+ return TRUE;
+}
+
+static int log_traverseBezier = 0;
+static int log_bezierSegments = 0;
+/*
+ * TraverseBezier is used to position a train/car.
+ * We find a new position and angle given a current pos, angle and a distance to travel.
+ *
+ * The output can be TRUE -> we have moved the point to a new point or to the start/end of the next track
+ * FALSE -> we have not found that point because pos was not on/near the track
+ *
+ * If true we supply the remaining distance to go (always positive).
+ * We detect the movement direction by comparing the current angle to the angle of the track at the point.
+ *
+ * Each segment may be processed forwards or in reverse (this really only applies to curved segments).
+ * So for each segment we call traverse1 to get the direction and extra distance to go to get to the current point
+ * and then use that for traverse2 to actually move to the new point
+ *
+ * If we exceed the current point's segment we move on to the next until the end of this track or we have found the spot.
+ *
+ */
+static BOOL_T TraverseBezier( traverseTrack_p trvTrk, DIST_T * distR )
+{
+ track_p trk = trvTrk->trk;
+ struct extraData *xx = GetTrkExtraData(trk);
+ DIST_T dist = *distR;
+ segProcData_t segProcData;
+ BOOL_T segs_backwards= FALSE;
+ DIST_T d = 10000;
+ coOrd pos2 = trvTrk->pos;
+ ANGLE_T a1,a2;
+ int inx,segInx = 0;
+ EPINX_T ep;
+ BOOL_T back,neg;
+ trkSeg_p segPtr = (trkSeg_p)xx->bezierData.arcSegs.ptr;
+
+ a2 = GetAngleSegs( //Find correct Segment and nearest point in it
+ xx->bezierData.arcSegs.cnt,segPtr,
+ &pos2, &segInx, &d , &back, NULL, &neg ); //d = how far pos2 from old pos2 = trvTrk->pos
+
+ if ( d > 10 ) {
+ ErrorMessage( "traverseBezier: Position is not near track: %0.3f", d );
+ return FALSE; //This means the input pos is not on or close to the track.
+ }
+ if (back) a2 = NormalizeAngle(a2+180); //GetAngleSegs has reversed angle for backwards
+ a1 = NormalizeAngle(a2-trvTrk->angle); //Establish if we are going fwds or backwards globally
+ if (a1 <270 && a1>90) { //Must add 180 if the seg is reversed or inverted (but not both)
+ segs_backwards = TRUE;
+ ep = 0;
+ } else {
+ segs_backwards = FALSE;
+ ep = 1;
+ }
+ if ( neg ) {
+ segs_backwards = !segs_backwards; //neg implies all the segs are reversed
+ ep = 1-ep; //other end
+ }
+
+ segProcData.traverse1.pos = pos2; //actual point on curve
+ segProcData.traverse1.angle = trvTrk->angle; //direction car is going for Traverse 1 has to be reversed...
+LOG( log_traverseBezier, 1, ( " TraverseBezier [%0.3f %0.3f] D%0.3f A%0.3f SB%d \n", trvTrk->pos.x, trvTrk->pos.y, dist, trvTrk->angle, segs_backwards ) )
+ inx = segInx;
+ while (inx >=0 && inx<xx->bezierData.arcSegs.cnt) {
+ segPtr = (trkSeg_p)xx->bezierData.arcSegs.ptr+inx; //move in to the identified segment
+ SegProc( SEGPROC_TRAVERSE1, segPtr, &segProcData ); //Backwards or forwards for THIS segment - note that this can differ from segs_backward!!
+ BOOL_T backwards = segProcData.traverse1.backwards; //Are we going to EP0?
+ BOOL_T reverse_seg = segProcData.traverse1.reverse_seg; //is it a backwards segment (we don't actually care as Traverse1 takes care of it)
+
+ dist += segProcData.traverse1.dist;
+ segProcData.traverse2.dist = dist;
+ segProcData.traverse2.segDir = backwards;
+LOG( log_traverseBezier, 2, ( " TraverseBezierT1 D%0.3f B%d RS%d \n", dist, backwards, reverse_seg ) )
+ SegProc( SEGPROC_TRAVERSE2, segPtr, &segProcData ); //Angle at pos2
+ if ( segProcData.traverse2.dist <= 0 ) { //-ve or zero distance left over so stop there
+ *distR = 0;
+ trvTrk->pos = segProcData.traverse2.pos;
+ trvTrk->angle = segProcData.traverse2.angle;
+LOG( log_traverseBezier, 1, ( " -> [%0.3f %0.3f] A%0.3f D%0.3f\n", trvTrk->pos.x, trvTrk->pos.y, trvTrk->angle, *distR ) )
+ return TRUE;
+ } //NOTE Traverse1 and Traverse2 are overlays so get all out before storing
+ dist = segProcData.traverse2.dist; //How far left?
+ coOrd pos = segProcData.traverse2.pos; //Will be at seg end
+ ANGLE_T angle = segProcData.traverse2.angle; //Angle of end
+ segProcData.traverse1.angle = angle; //Reverse to suit Traverse1
+ segProcData.traverse1.pos = pos;
+ inx = segs_backwards?inx-1:inx+1; //Here's where the global segment direction comes in
+LOG( log_traverseBezier, 2, ( " TraverseBezierL D%0.3f A%0.3f\n", dist, angle ) )
+ }
+ *distR = dist; //Tell caller what is left
+ //Must be at one end or another
+ trvTrk->pos = GetTrkEndPos(trk,ep);
+ trvTrk->angle = NormalizeAngle(GetTrkEndAngle(trk, ep)+(segs_backwards?180:0));
+ trvTrk->trk = GetTrkEndTrk(trk,ep); //go to next track
+ if (trvTrk->trk==NULL) {
+ trvTrk->pos = pos2;
+ return TRUE;
+ }
+
+LOG( log_traverseBezier, 1, ( " -> [%0.3f %0.3f] A%0.3f D%0.3f\n", trvTrk->pos.x, trvTrk->pos.y, trvTrk->angle, *distR ) )
+ return TRUE;
+
+}
+
+
+static BOOL_T MergeBezier(
+ track_p trk0,
+ EPINX_T ep0,
+ track_p trk1,
+ EPINX_T ep1 )
+{
+ struct extraData *xx0 = GetTrkExtraData(trk0);
+ struct extraData *xx1 = GetTrkExtraData(trk1);
+ track_p trk2 = NULL;
+ EPINX_T ep2=-1;
+ BOOL_T tracks = FALSE;
+
+ if (IsTrack(trk0) && IsTrack(trk1) ) tracks = TRUE;
+ if (GetTrkType(trk0) != GetTrkType(trk1)) return FALSE;
+
+ if (ep0 == ep1)
+ return FALSE;
+
+ UndoStart( _("Merge Bezier"), "MergeBezier( T%d[%d] T%d[%d] )", GetTrkIndex(trk0), ep0, GetTrkIndex(trk1), ep1 );
+ UndoModify( trk0 );
+ UndrawNewTrack( trk0 );
+ if (tracks) {
+ trk2 = GetTrkEndTrk( trk1, 1-ep1 );
+ if (trk2) {
+ ep2 = GetEndPtConnectedToMe( trk2, trk1 );
+ DisconnectTracks( trk1, 1-ep1, trk2, ep2 );
+ }
+ }
+ if (ep0 == 0) {
+ xx0->bezierData.pos[3] = xx1->bezierData.pos[3];
+ xx0->bezierData.pos[2] = xx1->bezierData.pos[2];
+ } else {
+ xx0->bezierData.pos[0] = xx1->bezierData.pos[0];
+ xx0->bezierData.pos[1] = xx1->bezierData.pos[1];
+ }
+ FixUpBezier(xx0->bezierData.pos,xx0,tracks);
+ ComputeBezierBoundingBox(trk0,xx0);
+ DeleteTrack( trk1, FALSE );
+ if (tracks && trk2) {
+ if (ep0 == 1)
+ SetTrkEndPoint( trk2, 1, xx0->bezierData.pos[0], xx0->bezierData.a0);
+ else
+ SetTrkEndPoint( trk2, 2, xx0->bezierData.pos[3], xx0->bezierData.a1);
+ ConnectTracks( trk0, ep0, trk2, ep2 );
+ }
+ DrawNewTrack( trk0 );
+
+
+ return TRUE;
+}
+
+
+static BOOL_T EnumerateBezier( track_p trk )
+{
+
+ if (trk != NULL) {
+ DIST_T d;
+ struct extraData *xx = GetTrkExtraData(trk);
+ d = xx->bezierData.length;
+ ScaleLengthIncrement( GetTrkScale(trk), d );
+ }
+ return TRUE;
+}
+
+static DIST_T GetLengthBezier( track_p trk )
+{
+ struct extraData *xx = GetTrkExtraData(trk);
+ DIST_T length = 0.0;
+ segProcData_t segProcData;
+ for(int i=0;i<xx->bezierData.arcSegs.cnt;i++) {
+ SegProc(SEGPROC_LENGTH,&(DYNARR_N(trkSeg_t,xx->bezierData.arcSegs,i)), &segProcData);
+ length += segProcData.length.length;
+ }
+ return length;
+}
+
+
+static BOOL_T GetParamsBezier( int inx, track_p trk, coOrd pos, trackParams_t * params )
+{
+ int segInx;
+ BOOL_T back,negative;
+ DIST_T d;
+
+ params->type = curveTypeBezier;
+ struct extraData *xx = GetTrkExtraData(trk);
+ for (int i=0;i<4;i++) params->bezierPoints[i] = xx->bezierData.pos[i];
+ params->len = xx->bezierData.length;
+ params->track_angle = GetAngleSegs( //Find correct Segment and nearest point in it
+ xx->bezierData.arcSegs.cnt,xx->bezierData.arcSegs.ptr,
+ &pos, &segInx, &d , &back, NULL, &negative );
+ if ( negative != back ) params->track_angle = NormalizeAngle(params->track_angle+180); //Bezier is in reverse
+ trkSeg_p segPtr = &DYNARR_N(trkSeg_t,xx->bezierData.arcSegs,segInx);
+ if (segPtr->type == SEG_STRLIN) {
+ params->arcR = 0.0;
+ } else {
+ params->arcR = fabs(segPtr->u.c.radius);
+ params->arcP = segPtr->u.c.center;
+ params->arcA0 = segPtr->u.c.a0;
+ params->arcA1 = segPtr->u.c.a1;
+ }
+ if ( inx == PARAMS_PARALLEL ) {
+ params->ep = 0;
+ } else if (inx == PARAMS_CORNU ){
+ params->ep = PickEndPoint( pos, trk);
+ } else {
+ params->ep = PickUnconnectedEndPointSilent( pos, trk);
+ }
+ if (params->ep>=0)
+ params->angle = GetTrkEndAngle(trk, params->ep);
+ return TRUE;
+
+}
+
+static BOOL_T TrimBezier( track_p trk, EPINX_T ep, DIST_T dist ) {
+ DeleteTrack(trk, TRUE);
+ return TRUE;
+}
+
+
+
+static BOOL_T QueryBezier( track_p trk, int query )
+{
+ struct extraData * xx = GetTrkExtraData(trk);
+ switch ( query ) {
+ case Q_CAN_GROUP:
+ return FALSE;
+ break;
+ case Q_FLIP_ENDPTS:
+ case Q_HAS_DESC:
+ return TRUE;
+ break;
+ case Q_EXCEPTION:
+ return GetTrkType(trk) == T_BEZIER?xx->bezierData.minCurveRadius < (GetLayoutMinTrackRadius()-EPSILON):FALSE;
+ break;
+ case Q_CAN_MODIFY_CONTROL_POINTS:
+ return TRUE;
+ break;
+ case Q_CANNOT_PLACE_TURNOUT:
+ return FALSE;
+ break;
+ case Q_ISTRACK:
+ return GetTrkType(trk) == T_BEZIER?TRUE:FALSE;
+ break;
+ case Q_CAN_PARALLEL:
+ return (GetTrkType(trk) == T_BEZIER);
+ break;
+ case Q_MODIFY_CAN_SPLIT:
+ case Q_CORNU_CAN_MODIFY:
+ return (GetTrkType(trk) == T_BEZIER);
+ default:
+ return FALSE;
+ }
+}
+
+
+static void FlipBezier(
+ track_p trk,
+ coOrd orig,
+ ANGLE_T angle )
+{
+ struct extraData * xx = GetTrkExtraData(trk);
+ FlipPoint( &xx->bezierData.pos[0], orig, angle );
+ FlipPoint( &xx->bezierData.pos[1], orig, angle );
+ FlipPoint( &xx->bezierData.pos[2], orig, angle );
+ FlipPoint( &xx->bezierData.pos[3], orig, angle );
+ FixUpBezier(xx->bezierData.pos,xx,IsTrack(trk));
+ ComputeBezierBoundingBox(trk,xx);
+
+}
+
+static ANGLE_T GetAngleBezier(
+ track_p trk,
+ coOrd pos,
+ EPINX_T * ep0,
+ EPINX_T * ep1 )
+{
+ struct extraData * xx = GetTrkExtraData(trk);
+ ANGLE_T angle;
+ BOOL_T back, neg;
+ int indx;
+ angle = GetAngleSegs( xx->bezierData.arcSegs.cnt, (trkSeg_p)xx->bezierData.arcSegs.ptr, &pos, &indx, NULL, &back, NULL, &neg );
+ if (!back) angle = NormalizeAngle(angle+180); //Make CCW
+ if ( ep0 ) *ep0 = neg?1:0;
+ if ( ep1 ) *ep1 = neg?0:1;
+ return angle;
+}
+
+BOOL_T GetBezierSegmentFromTrack(track_p trk, trkSeg_p seg_p) {
+ struct extraData * xx = GetTrkExtraData(trk);
+
+ seg_p->type = IsTrack(trk)?SEG_BEZTRK:SEG_BEZLIN;
+ for (int i=0;i<4;i++) seg_p->u.b.pos[i] = xx->bezierData.pos[i];
+ seg_p->color = xx->bezierData.segsColor;
+ seg_p->bezSegs.cnt = 0;
+ if (seg_p->bezSegs.ptr) MyFree(seg_p->bezSegs.ptr);
+ seg_p->bezSegs.max = 0;
+ FixUpBezierSeg(seg_p->u.b.pos,seg_p,seg_p->type == SEG_BEZTRK);
+ return TRUE;
+
+}
+
+
+static BOOL_T MakeParallelBezier(
+ track_p trk,
+ coOrd pos,
+ DIST_T sep,
+ track_p * newTrkR,
+ coOrd * p0R,
+ coOrd * p1R )
+{
+ struct extraData * xx = GetTrkExtraData(trk);
+ coOrd np[4], p;
+ ANGLE_T a,a2;
+
+ //Produce bezier that is translated parallel to the existing Bezier
+ // - not a precise result if the bezier end angles are not in the same general direction.
+ // The expectation is that the user will have to adjust it - unless and until we produce
+ // a new algo to adjust the control points to be parallel to the endpoints.
+
+ a = FindAngle(xx->bezierData.pos[0],xx->bezierData.pos[3]);
+ p = pos;
+ DistanceBezier(trk, &p);
+ a2 = NormalizeAngle(FindAngle(pos,p)-a);
+ //find parallel move x and y for points
+ for (int i =0; i<4;i++) {
+ np[i] = xx->bezierData.pos[i];
+ }
+
+ if ( a2 > 180 ) {
+ Translate(&np[0],np[0],a+90,sep);
+ Translate(&np[1],np[1],a+90,sep);
+ Translate(&np[2],np[2],a+90,sep);
+ Translate(&np[3],np[3],a+90,sep);
+ } else {
+ Translate(&np[0],np[0],a-90,sep);
+ Translate(&np[1],np[1],a-90,sep);
+ Translate(&np[2],np[2],a-90,sep);
+ Translate(&np[3],np[3],a-90,sep);
+ }
+
+ if ( newTrkR ) {
+ *newTrkR = NewBezierTrack( np, NULL, 0);
+ } else {
+ DYNARR_SET( trkSeg_t, tempSegs_da, 1 );
+ tempSegs(0).color = wDrawColorBlack;
+ tempSegs(0).width = 0;
+ tempSegs_da.cnt = 1;
+ tempSegs(0).type = SEG_BEZTRK;
+ if (tempSegs(0).bezSegs.ptr) MyFree(tempSegs(0).bezSegs.ptr);
+ tempSegs(0).bezSegs.max = 0;
+ tempSegs(0).bezSegs.cnt = 0;
+ for (int i=0;i<4;i++) tempSegs(0).u.b.pos[i] = np[i];
+ FixUpBezierSeg(tempSegs(0).u.b.pos,&tempSegs(0),TRUE);
+ }
+ if ( p0R ) *p0R = np[0];
+ if ( p1R ) *p1R = np[1];
+ return TRUE;
+}
+
+/*
+ * When an undo is run, the array of segs is missing - they are not saved to the Undo log. So Undo calls this routine to
+ * ensure
+ * - that the Segs are restored and
+ * - other fields reset.
+ */
+BOOL_T RebuildBezier (track_p trk)
+{
+ struct extraData *xx;
+ xx = GetTrkExtraData(trk);
+ xx->bezierData.arcSegs.cnt = 0;
+ FixUpBezier(xx->bezierData.pos,xx,IsTrack(trk));
+ ComputeBezierBoundingBox(trk, xx);
+ return TRUE;
+}
+
+BOOL_T MoveBezierEndPt ( track_p *trk, EPINX_T *ep, coOrd pos, DIST_T d0 ) {
+ track_p trk2;
+ struct extraData *xx;
+ if (SplitTrack(*trk,pos,*ep,&trk2,TRUE)) {
+ if (trk2) DeleteTrack(trk2,TRUE);
+ xx = GetTrkExtraData(*trk);
+ SetTrkEndPoint( *trk, *ep, *ep?xx->bezierData.pos[3]:xx->bezierData.pos[0], *ep?xx->bezierData.a1:xx->bezierData.a0 );
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static trackCmd_t bezlinCmds = {
+ "BZRLIN",
+ DrawBezier,
+ DistanceBezier,
+ DescribeBezier,
+ DeleteBezier,
+ WriteBezier,
+ ReadBezier,
+ MoveBezier,
+ RotateBezier,
+ RescaleBezier,
+ NULL,
+ GetAngleBezier,
+ SplitBezier,
+ NULL,
+ NULL,
+ NULL, /* redraw */
+ NULL, /* trim */
+ MergeBezier,
+ NULL, /* modify */
+ GetLengthBezier,
+ GetParamsBezier,
+ NULL, /* Move EndPt */
+ QueryBezier,
+ NULL, /* ungroup */
+ FlipBezier,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ RebuildBezier
+ };
+
+static trackCmd_t bezierCmds = {
+ "BEZIER",
+ DrawBezier,
+ DistanceBezier,
+ DescribeBezier,
+ DeleteBezier,
+ WriteBezier,
+ ReadBezier,
+ MoveBezier,
+ RotateBezier,
+ RescaleBezier,
+ NULL,
+ GetAngleBezier,
+ SplitBezier,
+ TraverseBezier,
+ EnumerateBezier,
+ NULL, /* redraw */
+ TrimBezier, /* trim */
+ MergeBezier,
+ NULL, /* modify */
+ GetLengthBezier,
+ GetParamsBezier,
+ MoveBezierEndPt, /* Move EndPt */
+ QueryBezier,
+ NULL, /* ungroup */
+ FlipBezier,
+ NULL,
+ NULL,
+ NULL,
+ MakeParallelBezier,
+ NULL,
+ RebuildBezier
+ };
+
+
+EXPORT void BezierSegProc(
+ segProc_e cmd,
+ trkSeg_p segPtr,
+ segProcData_p data )
+{
+ ANGLE_T a1, a2;
+ DIST_T d, dd;
+ coOrd p0,p2 ;
+ segProcData_t segProcData;
+ trkSeg_p subSegsPtr;
+ coOrd temp0,temp1,temp2,temp3;
+ int inx,segInx;
+ BOOL_T back, segs_backwards, neg;
+#define bezSegs(N) DYNARR_N( trkSeg_t, segPtr->bezSegs, N )
+
+ switch (cmd) {
+
+ case SEGPROC_TRAVERSE1: //Work out how much extra dist and what direction
+ if (segPtr->type != SEG_BEZTRK) {
+ data->traverse1.dist = 0;
+ return;
+ }
+ d = data->traverse1.dist;
+ p0 = data->traverse1.pos;
+LOG( log_bezierSegments, 1, ( " BezTr1-Enter P[%0.3f %0.3f] A%0.3f\n", p0.x, p0.y, data->traverse1.angle ))
+ a2 = GetAngleSegs(segPtr->bezSegs.cnt,segPtr->bezSegs.ptr,&p0,&segInx,&d,&back, NULL, &neg); //Find right seg and pos
+ inx = segInx;
+ data->traverse1.BezSegInx = segInx;
+ data->traverse1.reverse_seg = FALSE;
+ data->traverse1.backwards = FALSE;
+ if (d>10) {
+ data->traverse1.dist = 0;
+ return;
+ }
+
+ if (back) a2 = NormalizeAngle(a2+180);
+ a1 = NormalizeAngle(a2-data->traverse1.angle); //Establish if we are going fwds or backwards globally
+ if (a1<270 && a1>90) { //Must add 180 if the seg is reversed or inverted (but not both)
+ segs_backwards = TRUE;
+ } else {
+ segs_backwards = FALSE;
+ }
+ if ( neg ) {
+ segs_backwards = !segs_backwards; //neg implies all the segs are reversed
+ }
+ segProcData.traverse1.pos = data->traverse1.pos = p0; //actual point on curve
+ segProcData.traverse1.angle = data->traverse1.angle; //Angle of car
+ LOG( log_bezierSegments, 1, ( " BezTr1-GSA I%d P[%0.3f %0.3f] N%d SB%d\n", segInx, p0.x, p0.y, neg, segs_backwards ))
+ subSegsPtr = (trkSeg_p)segPtr->bezSegs.ptr+inx;
+ SegProc( SEGPROC_TRAVERSE1, subSegsPtr, &segProcData );
+ data->traverse1.reverse_seg = segProcData.traverse1.reverse_seg; //which way is curve (info)
+ data->traverse1.backwards = segProcData.traverse1.backwards; //Pass through Train direction
+ data->traverse1.dist = segProcData.traverse1.dist; //Get last seg partial dist
+ data->traverse1.segs_backwards = segs_backwards; //Get last
+ data->traverse1.negative = segProcData.traverse1.negative; //Is curve flipped (info)
+ data->traverse1.BezSegInx = inx; //Copy up Index
+LOG( log_bezierSegments, 1, ( " BezTr1-Exit -> A%0.3f B%d R%d N%d D%0.3f\n", a2, segProcData.traverse1.backwards, segProcData.traverse1.reverse_seg, segProcData.traverse1.negative, segProcData.traverse1.dist ))
+ break;
+
+ case SEGPROC_TRAVERSE2:
+ if (segPtr->type != SEG_BEZTRK) return; //Not SEG_BEZLIN
+LOG( log_bezierSegments, 1, ( " BezTr2-Enter D%0.3f SD%d SI%d SB%d\n", data->traverse2.dist, data->traverse2.segDir, data->traverse2.BezSegInx, data->traverse2.segs_backwards))
+ if (data->traverse2.dist <= segPtr->u.b.length) {
+
+ segProcData.traverse2.pos = data->traverse2.pos;
+ DIST_T dist = data->traverse2.dist;
+ segProcData.traverse2.dist = data->traverse2.dist;
+ segProcData.traverse2.angle = data->traverse2.angle;
+ segProcData.traverse2.segDir = data->traverse2.segDir;
+ segs_backwards = data->traverse2.segs_backwards;
+ BOOL_T backwards = data->traverse2.segDir;
+ inx = data->traverse2.BezSegInx; //Special from Traverse1
+ while (inx>=0 && inx<segPtr->bezSegs.cnt) {
+ subSegsPtr = (trkSeg_p)segPtr->bezSegs.ptr+inx;
+ SegProc(SEGPROC_TRAVERSE2, subSegsPtr, &segProcData);
+ if (segProcData.traverse2.dist<=0) { //Done
+ data->traverse2.angle = segProcData.traverse2.angle;
+ data->traverse2.dist = 0;
+ data->traverse2.pos = segProcData.traverse2.pos;
+LOG( log_bezierSegments, 1, ( " BezTr2-Exit1 -> A%0.3f P[%0.3f %0.3f] \n", data->traverse2.angle, data->traverse2.pos.x, data->traverse2.pos.y ))
+ return;
+ } else dist = segProcData.traverse2.dist;
+ p2 = segProcData.traverse2.pos;
+ a2 = segProcData.traverse2.angle;
+LOG( log_bezierSegments, 2, ( " BezTr2-Tr2 D%0.3f P[%0.3f %0.3f] A%0.3f\n", dist, p2.x, p2.y, a2 ))
+
+ segProcData.traverse1.pos = p2;
+ segProcData.traverse1.angle = a2;
+ inx = segs_backwards?inx-1:inx+1;
+ if (inx<0 || inx>=segPtr->bezSegs.cnt) break;
+ subSegsPtr = (trkSeg_p)segPtr->bezSegs.ptr+inx;
+ SegProc(SEGPROC_TRAVERSE1, subSegsPtr, &segProcData);
+ BOOL_T reverse_seg = segProcData.traverse1.reverse_seg; //For Info only
+ backwards = segProcData.traverse1.backwards;
+ dist += segProcData.traverse1.dist; //Add extra if needed - this is if we have to go from the other end of this seg
+ segProcData.traverse2.dist = dist; //distance left
+ segProcData.traverse2.segDir = backwards; //which way
+ segProcData.traverse2.pos = p2;
+ segProcData.traverse2.angle = a2;
+LOG( log_bezierSegments, 2, ( " BezTr2-Loop A%0.3f P[%0.3f %0.3f] D%0.3f SI%d B%d RS%d\n", a2, p2.x, p2.y, dist, inx, backwards, reverse_seg ))
+ }
+ data->traverse2.dist = dist;
+ } else data->traverse2.dist -= segPtr->u.b.length; //we got here because the desired point is not inside the segment
+ if (segs_backwards) {
+ data->traverse2.pos = segPtr->u.b.pos[0]; // Backwards so point 0
+ data->traverse2.angle = segPtr->u.b.angle0;
+ } else {
+ data->traverse2.pos = segPtr->u.b.pos[3]; // Forwards so point 3
+ data->traverse2.angle = segPtr->u.b.angle3;
+ }
+LOG( log_bezierSegments, 1, ( " BezTr-Exit2 --> SI%d A%0.3f P[%0.3f %0.3f] D%0.3f\n", inx, data->traverse2.angle, data->traverse2.pos.x, data->traverse2.pos.y, data->traverse2.dist))
+ break;
+
+ case SEGPROC_DRAWROADBEDSIDE:
+ //TODO - needs parallel bezier problem solved...
+ break;
+
+ case SEGPROC_DISTANCE:
+
+ dd = 100000.00; //Just find one distance
+ p0 = data->distance.pos1;
+
+ //initialize p2
+ p2 = segPtr->u.b.pos[0];
+ for(int i=0;i<segPtr->bezSegs.cnt;i++) {
+ segProcData.distance.pos1 = p0;
+ SegProc(SEGPROC_DISTANCE,&(DYNARR_N(trkSeg_t,segPtr->bezSegs,i)),&segProcData);
+ d = segProcData.distance.dd;
+ if (d<dd) {
+ dd = d;
+ p2 = segProcData.distance.pos1;
+ }
+ }
+ data->distance.dd = dd;
+ data->distance.pos1 = p2;
+ break;
+
+ case SEGPROC_FLIP:
+
+ temp0 = segPtr->u.b.pos[0];
+ temp1 = segPtr->u.b.pos[1];
+ temp2 = segPtr->u.b.pos[2];
+ temp3 = segPtr->u.b.pos[3];
+ segPtr->u.b.pos[0] = temp3;
+ segPtr->u.b.pos[1] = temp2;
+ segPtr->u.b.pos[2] = temp1;
+ segPtr->u.b.pos[3] = temp0;
+ FixUpBezierSeg(segPtr->u.b.pos,segPtr,segPtr->type == SEG_BEZTRK);
+ break;
+
+ case SEGPROC_NEWTRACK:
+ data->newTrack.trk = NewBezierTrack( segPtr->u.b.pos, (trkSeg_t *)segPtr->bezSegs.ptr, segPtr->bezSegs.cnt);
+ data->newTrack.ep[0] = 0;
+ data->newTrack.ep[1] = 1;
+ break;
+
+ case SEGPROC_LENGTH:
+ data->length.length = 0;
+ for(int i=0;i<segPtr->bezSegs.cnt;i++) {
+ SegProc(cmd,&(DYNARR_N(trkSeg_t,segPtr->bezSegs,i)),&segProcData);
+ data->length.length += segProcData.length.length;
+ }
+ break;
+
+ case SEGPROC_SPLIT:
+ //TODO Split
+ break;
+
+ case SEGPROC_GETANGLE:
+ inx = 0;
+ back = FALSE;
+ subSegsPtr = (trkSeg_p) segPtr->bezSegs.ptr;
+ coOrd pos = data->getAngle.pos;
+LOG( log_bezierSegments, 1, ( " BezGA-In P[%0.3f %0.3f] \n", pos.x, pos.y))
+ data->getAngle.angle = GetAngleSegs(segPtr->bezSegs.cnt,subSegsPtr, &pos, &inx, NULL, &back, NULL, NULL);
+ //Recurse for Bezier sub-segs (only straights and curves)
+
+ data->getAngle.negative_radius = FALSE;
+ data->getAngle.backwards = back;
+ data->getAngle.pos = pos;
+ data->getAngle.bezSegInx = inx;
+ subSegsPtr +=inx;
+ if (subSegsPtr->type == SEG_CRVTRK || subSegsPtr->type == SEG_CRVLIN ) {
+ data->getAngle.radius = fabs(subSegsPtr->u.c.radius);
+ if (subSegsPtr->u.c.radius<0 ) data->getAngle.negative_radius = TRUE;
+ data->getAngle.center = subSegsPtr->u.c.center;
+ }
+ else data->getAngle.radius = 0.0;
+LOG( log_bezierSegments, 1, ( " BezGA-Out SI%d A%0.3f P[%0.3f %0.3f] B%d\n", inx, data->getAngle.angle, pos.x, pos.y, back))
+ break;
+
+ }
+
+}
+
+
+/****************************************
+ *
+ * GRAPHICS COMMANDS
+ *
+ */
+
+
+track_p NewBezierTrack(coOrd pos[4], trkSeg_t * tempsegs, int count)
+{
+ struct extraData *xx;
+ track_p p;
+ p = NewTrack( 0, T_BEZIER, 2, sizeof *xx );
+ xx = GetTrkExtraData(p);
+ xx->bezierData.pos[0] = pos[0];
+ xx->bezierData.pos[1] = pos[1];
+ xx->bezierData.pos[2] = pos[2];
+ xx->bezierData.pos[3] = pos[3];
+ xx->bezierData.segsColor = wDrawColorBlack;
+ xx->bezierData.segsWidth = 0;
+ FixUpBezier(pos, xx, TRUE);
+LOG( log_bezier, 1, ( "NewBezierTrack( EP1 %0.3f, %0.3f, CP1 %0.3f, %0.3f, CP2 %0.3f, %0.3f, EP2 %0.3f, %0.3f ) = %d\n", pos[0].x, pos[0].y, pos[1].x, pos[1].y, pos[2].x, pos[2].y, pos[3].x, pos[3].y, GetTrkIndex(p) ) )
+ ComputeBezierBoundingBox( p, xx );
+ SetTrkEndPoint( p, 0, pos[0], xx->bezierData.a0);
+ SetTrkEndPoint( p, 1, pos[3], xx->bezierData.a1);
+ CheckTrackLength( p );
+ SetTrkBits( p, TB_HIDEDESC );
+ return p;
+}
+
+EXPORT track_p NewBezierLine( coOrd pos[4], trkSeg_t * tempsegs, int count, wDrawColor color, DIST_T width )
+{
+ struct extraData *xx;
+ track_p p;
+ p = NewTrack( 0, T_BZRLIN, 2, sizeof *xx );
+ xx = GetTrkExtraData(p);
+ xx->bezierData.pos[0] = pos[0];
+ xx->bezierData.pos[1] = pos[1];
+ xx->bezierData.pos[2] = pos[2];
+ xx->bezierData.pos[3] = pos[3];
+ xx->bezierData.segsColor = color;
+ xx->bezierData.segsWidth = width;
+ FixUpBezier(pos, xx, FALSE);
+LOG( log_bezier, 1, ( "NewBezierLine( EP1 %0.3f, %0.3f, CP1 %0.3f, %0.3f, CP2 %0.3f, %0.3f, EP2 %0.3f, %0.3f) = %d\n", pos[0].x, pos[0].y, pos[1].x, pos[1].y, pos[2].x, pos[2].y, pos[3].x, pos[3].y, GetTrkIndex(p) ) )
+ ComputeBezierBoundingBox( p, xx );
+ return p;
+}
+
+
+
+EXPORT void InitTrkBezier( void )
+{
+ T_BEZIER = InitObject( &bezierCmds );
+ T_BZRLIN = InitObject( &bezlinCmds );
+ log_bezier = LogFindIndex( "Bezier" );
+ log_traverseBezier = LogFindIndex( "traverseBezier" );
+ log_bezierSegments = LogFindIndex( "traverseBezierSegs");
+}
+
+/********************************************************************************
+ *
+ * Bezier Functions
+ *
+ ********************************************************************************/
+
+
+/**
+ * Return point on Bezier using "t" (from 0 to 1)
+ */
+extern coOrd BezierPointByParameter(coOrd p[4], double t)
+{
+
+ double a,b,c,d;
+ double mt = 1-t;
+ double mt2 = mt*mt;
+ double t2 = t*t;
+
+ a = mt2*mt;
+ b = mt2*t*3;
+ c = mt*t2*3;
+ d = t*t2;
+
+ coOrd o;
+ o.x = a*p[0].x+b*p[1].x+c*p[2].x+d*p[3].x;
+ o.y = a*p[0].y+b*p[1].y+c*p[2].y+d*p[3].y;
+
+ return o;
+
+}
+/**
+ * Find distance from point to Bezier. Return also the "t" value of that closest point.
+ */
+extern DIST_T BezierMathDistance( coOrd * pos, coOrd p[4], int segments, double * t_value)
+{
+ DIST_T dd = 10000.0;
+ double t = 0.0;
+ coOrd pt;
+ coOrd save_pt = p[0];
+ for (int i=0; i<=segments; i++) {
+ pt = BezierPointByParameter(p, (double)i/segments);
+ if (FindDistance(*pos,pt) < dd) {
+ dd = FindDistance(*pos,pt);
+ t = (double)i/segments;
+ save_pt = pt;
+ }
+ }
+ if (t_value) *t_value = t;
+ * pos = save_pt;
+ return dd;
+}
+
+extern coOrd BezierMathFindNearestPoint(coOrd *pos, coOrd p[4], int segments) {
+ double t = 0.0;
+ BezierMathDistance(pos, p, segments, &t);
+ return BezierPointByParameter(p, t);
+}
+
+void BezierSlice(coOrd input[], coOrd output[], double t) {
+ coOrd p1,p12,p2,p23,p3,p34,p4;
+ coOrd p123, p234, p1234;
+
+ p1 = input[0];
+ p2 = input[1];
+ p3 = input[2];
+ p4 = input[3];
+
+ p12.x = (p2.x-p1.x)*t+p1.x;
+ p12.y = (p2.y-p1.y)*t+p1.y;
+
+ p23.x = (p3.x-p2.x)*t+p2.x;
+ p23.y = (p3.y-p2.y)*t+p2.y;
+
+ p34.x = (p4.x-p3.x)*t+p3.x;
+ p34.y = (p4.y-p3.y)*t+p3.y;
+
+ p123.x = (p23.x-p12.x)*t+p12.x;
+ p123.y = (p23.y-p12.y)*t+p12.y;
+
+ p234.x = (p34.x-p23.x)*t+p23.x;
+ p234.y = (p34.y-p23.y)*t+p23.y;
+
+ p1234.x = (p234.x-p123.x)*t+p123.x;
+ p1234.y = (p234.y-p123.y)*t+p123.y;
+
+ output[0]= p1;
+ output[1] = p12;
+ output[2] = p123;
+ output[3] = p1234;
+
+};
+
+/**
+ * Split bezier into two parts
+ */
+extern void BezierSplit(coOrd input[], coOrd left[], coOrd right[] , double t) {
+
+ BezierSlice(input,left,t);
+
+ coOrd back[4],backright[4];
+
+ for (int i = 0;i<4;i++) {
+ back[i] = input[3-i];
+ }
+ BezierSlice(back,backright,1-t);
+ for (int i = 0;i<4;i++) {
+ right[i] = backright[3-i];
+ }
+
+}
+
+
+/**
+ * If close enough (length of control polygon exceeds chord by < error) add length of polygon.
+ * Else split and recurse
+ */
+double BezierAddLengthIfClose(coOrd start[4], double error) {
+ coOrd left[4], right[4]; /* bez poly splits */
+ double len = 0.0; /* arc length */
+ double chord; /* chord length */
+ int index; /* misc counter */
+
+ for (index = 0; index <= 2; index++)
+ len = len + FindDistance(start[index],start[index+1]); //add up control polygon
+
+ chord = FindDistance(start[0],start[3]); //find chord length
+
+ if((len-chord) > error) { // If error too large -
+ BezierSplit(start,left,right,0.5); /* split in two */
+ len = BezierAddLengthIfClose(left, error); /* recurse left side */
+ len += BezierAddLengthIfClose(right, error); /* recurse right side */
+ }
+ return len; // Add length of this curve
+
+}
+
+/**
+ * Use recursive splitting to get close approximation ot length of bezier
+ *
+ */
+extern double BezierMathLength(coOrd p[4], double error)
+{
+ if (error == 0.0) error = 0.01;
+ return BezierAddLengthIfClose(p, error); /* kick off recursion */
+
+}
+
+coOrd BezierFirstDerivative(coOrd p[4], double t)
+{
+ //checkParameterT(t);
+
+ double tSquared = t * t;
+ double s0 = -3 + 6 * t - 3 * tSquared;
+ double s1 = 3 - 12 * t + 9 * tSquared;
+ double s2 = 6 * t - 9 * tSquared;
+ double s3 = 3 * tSquared;
+ double resultX = p[0].x * s0 + p[1].x * s1 + p[2].x * s2 + p[3].x * s3;
+ double resultY = p[0].y * s0 + p[1].y * s1 + p[2].y * s2 + p[3].y * s3;
+
+ coOrd v;
+
+ v.x = resultX;
+ v.y = resultY;
+ return v;
+}
+
+/**
+ * Gets 2nd derivate wrt t of a Bezier curve at a point
+
+ */
+coOrd BezierSecondDerivative(coOrd p[4], double t)
+{
+ //checkParameterT(t);
+
+ double s0 = 6 - 6 * t;
+ double s1 = -12 + 18 * t;
+ double s2 = 6 - 18 * t;
+ double s3 = 6 * t;
+ double resultX = p[0].x * s0 + p[1].x * s1 + p[2].x * s2 + p[3].x * s3;
+ double resultY = p[0].y * s0 + p[1].y * s1 + p[2].y * s2 + p[3].y * s3;
+
+ coOrd v;
+ v.x = resultX;
+ v.y = resultY;
+ return v;
+}
+
+/**
+ * Get curvature of a Bezier at a point
+*/
+extern double BezierCurvature(coOrd p[4], double t, coOrd * center)
+{
+ //checkParameterT(t);
+
+ coOrd d1 = BezierFirstDerivative(p, t);
+ coOrd d2 = BezierSecondDerivative(p, t);
+
+ if (center) {
+ double curvnorm = (d1.x * d1.x + d1.y* d1.y)/(d1.x * d2.y - d2.x * d1.y);
+ coOrd p = BezierPointByParameter(&p, t);
+ center->x = p.x-d1.y*curvnorm;
+ center->y = p.y+d1.x*curvnorm;
+ }
+
+ double r1 = sqrt(pow(d1.x * d1.x + d1.y* d1.y, 3.0));
+ double r2 = fabs(d1.x * d2.y - d2.x * d1.y);
+ return r2 / r1;
+}
+
+/**
+ * Get Maximum Curvature
+ */
+extern double BezierMaxCurve(coOrd p[4]) {
+ double max = 0;
+ for (int t = 0;t<100;t++) {
+ double curv = BezierCurvature(p, t/100, NULL);
+ if (max<curv) max = curv;
+ }
+ return max;
+}
+
+/**
+ * Get Minimum Radius
+ */
+extern double BezierMathMinRadius(coOrd p[4]) {
+ double curv = BezierMaxCurve(p);
+ if (curv >= 1000.0 || curv <= 0.001 ) return 0.0;
+ return 1/curv;
+}
+