summaryrefslogtreecommitdiff
path: root/app/bin/tcurve.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/bin/tcurve.c')
-rw-r--r--app/bin/tcurve.c1587
1 files changed, 1587 insertions, 0 deletions
diff --git a/app/bin/tcurve.c b/app/bin/tcurve.c
new file mode 100644
index 0000000..7e9fc90
--- /dev/null
+++ b/app/bin/tcurve.c
@@ -0,0 +1,1587 @@
+/*
+ * $Header: /home/dmarkle/xtrkcad-fork-cvs/xtrkcad/app/bin/tcurve.c,v 1.3 2009-06-15 19:29:57 m_fischer Exp $
+ *
+ * CURVE
+ *
+ */
+
+/* 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.
+ */
+
+#include "track.h"
+#include "ccurve.h"
+#include "cstraigh.h"
+#include "cjoin.h"
+#include "i18n.h"
+
+static TRKTYP_T T_CURVE = -1;
+
+struct extraData {
+ coOrd pos;
+ DIST_T radius;
+ BOOL_T circle;
+ long helixTurns;
+ coOrd descriptionOff;
+ };
+#define xpos extraData->pos
+#define xradius extraData->radius
+#define xcircle extraData->circle
+
+static int log_curve = 0;
+
+static DIST_T GetLengthCurve( track_p );
+
+/****************************************
+ *
+ * UTILITIES
+ *
+ */
+
+static void GetCurveAngles( ANGLE_T *a0, ANGLE_T *a1, track_p trk )
+{
+ struct extraData *xx = GetTrkExtraData(trk);
+ assert( trk != NULL );
+ if (xx->circle != TRUE) {
+ *a0 = NormalizeAngle( GetTrkEndAngle(trk,0) + 90 );
+ *a1 = NormalizeAngle(
+ GetTrkEndAngle(trk,1) - GetTrkEndAngle(trk,0) + 180 );
+ } else {
+ *a0 = 0.0;
+ *a1 = 360.0;
+ }
+LOG( log_curve, 4, ( "getCurveAngles: = %0.3f %0.3f\n", *a0, *a1 ) )
+}
+
+static void SetCurveAngles( track_p p, ANGLE_T a0, ANGLE_T a1, struct extraData * xx )
+{
+ coOrd pos0, pos1;
+ xx->circle = (a0 == 0.0 && a1 == 0.0);
+ PointOnCircle( &pos0, xx->pos, xx->radius, a0 );
+ PointOnCircle( &pos1, xx->pos, xx->radius, a0+a1 );
+ SetTrkEndPoint( p, 0, pos0, NormalizeAngle(a0-90.0) );
+ SetTrkEndPoint( p, 1, pos1, NormalizeAngle(a0+a1+90.0) );
+}
+
+static void ComputeCurveBoundingBox( track_p trk, struct extraData * xx )
+{
+ coOrd p = xx->pos;
+ DIST_T r = xx->radius;
+ ANGLE_T a0, a1, aa;
+ POS_T x0, x1, y0, y1;
+ coOrd hi, lo;
+
+ GetCurveAngles( &a0, &a1, trk );
+ if ( xx->helixTurns > 0 ) {
+ a0 = 0.0;
+ a1 = 360.0;
+ }
+ aa = a0+a1;
+ x0 = r * sin(D2R(a0));
+ x1 = r * sin(D2R(aa));
+ y0 = r * cos(D2R(a0));
+ y1 = r * cos(D2R(aa));
+ hi.y = p.y + ((aa>=360.0) ? (r) : max(y0,y1));
+ lo.y = p.y + (((a0>180.0?aa-180.0:aa+180.0)>=360.0) ? (-r) : min(y0,y1));
+ hi.x = p.x + (((a0> 90.0?aa- 90.0:aa+270.0)>=360.0) ? (r) : max(x0,x1));
+ lo.x = p.x + (((a0>270.0?aa-270.0:aa+ 90.0)>=360.0) ? (-r) : min(x0,x1));
+ SetBoundingBox( trk, hi, lo );
+}
+
+static void AdjustCurveEndPt( track_p t, EPINX_T inx, ANGLE_T a )
+{
+ struct extraData *xx = GetTrkExtraData(t);
+ coOrd pos;
+ ANGLE_T aa;
+ if (GetTrkType(t) != T_CURVE) {
+ AbortProg( "AdjustCurveEndPt( %d, %d ) not on CURVE %d",
+ GetTrkIndex(t), inx, GetTrkType(t) );
+ return;
+ }
+ UndoModify( t );
+LOG( log_curve, 1, ( "adjustCurveEndPt T%d[%d] a=%0.3f\n", GetTrkIndex(t), inx, a ) )
+ aa = a = NormalizeAngle(a);
+ a += inx==0?90.0:-90.0;
+ (void)PointOnCircle( &pos, xx->pos, xx->radius, a );
+ SetTrkEndPoint( t, inx, pos, aa );
+ if (xx->circle) {
+ (void)PointOnCircle( &pos, xx->pos, xx->radius, aa );
+ SetTrkEndPoint( t, 1-inx, pos, a );
+ xx->circle = 0;
+ }
+LOG( log_curve, 1, ( " E0:[%0.3f %0.3f] A%0.3f, E1:[%0.3f %0.3f] A%0.3f\n",
+ GetTrkEndPosXY(t,0), GetTrkEndAngle(t,0),
+ GetTrkEndPosXY(t,1), GetTrkEndAngle(t,1) ) )
+ ComputeCurveBoundingBox( t, xx );
+ CheckTrackLength( t );
+}
+
+static void GetTrkCurveCenter( track_p t, coOrd *p, DIST_T *r )
+{
+ struct extraData *xx = GetTrkExtraData(t);
+ *p = xx->pos;
+ *r = xx->radius;
+}
+
+BOOL_T IsCurveCircle( track_p t )
+{
+ struct extraData *xx;
+ if ( GetTrkType(t) != T_CURVE )
+ return FALSE;
+ xx = GetTrkExtraData(t);
+ return xx->circle || xx->helixTurns>0;
+}
+
+
+BOOL_T GetCurveMiddle( track_p trk, coOrd * pos )
+{
+ struct extraData *xx;
+ ANGLE_T a0, a1;
+ if ( GetTrkType(trk) != T_CURVE )
+ return FALSE;
+ xx = GetTrkExtraData(trk);
+ if (xx->circle || xx->helixTurns>0) {
+ PointOnCircle( pos, xx->pos, xx->radius, 0 );
+ } else {
+ GetCurveAngles( &a0, &a1, trk );
+ PointOnCircle( pos, xx->pos, xx->radius, a0+a1/2 );
+ }
+ return TRUE;
+}
+
+DIST_T CurveDescriptionDistance(
+ coOrd pos,
+ track_p trk )
+{
+ struct extraData *xx = GetTrkExtraData(trk);
+ coOrd p1;
+ FLOAT_T ratio;
+ ANGLE_T a, a0, a1;
+
+ if ( GetTrkType( trk ) != T_CURVE || ( GetTrkBits( trk ) & TB_HIDEDESC ) != 0 )
+ return 100000;
+ if ( xx->helixTurns > 0 ) {
+ p1.x = xx->pos.x + xx->descriptionOff.x;
+ p1.y = xx->pos.y + xx->descriptionOff.y;
+ } else {
+ GetCurveAngles( &a0, &a1, trk );
+ ratio = ( xx->descriptionOff.x + 1.0 ) / 2.0;
+ a = a0 + ratio * a1;
+ ratio = ( xx->descriptionOff.y + 1.0 ) / 2.0;
+ Translate( &p1, xx->pos, a, xx->radius * ratio );
+ }
+ return FindDistance( p1, pos );
+}
+
+
+static void DrawCurveDescription(
+ track_p trk,
+ drawCmd_p d,
+ wDrawColor color )
+{
+ struct extraData *xx = GetTrkExtraData(trk);
+ wFont_p fp;
+ coOrd pos, p0, p1;
+ DIST_T elev0, elev1, dist, grade=0, sep=0;
+ BOOL_T elevValid;
+ ANGLE_T a, a0, a1;
+ FLOAT_T ratio;
+
+ if (layoutLabels == 0)
+ return;
+ if ((labelEnable&LABELENABLE_TRKDESC)==0)
+ return;
+
+ if ( xx->helixTurns > 0 ) {
+ pos = xx->pos;
+ pos.x += xx->descriptionOff.x;
+ pos.y += xx->descriptionOff.y;
+ dist = GetLengthCurve( trk );
+ elevValid = FALSE;
+ if ( (!xx->circle) &&
+ ComputeElev( trk, 0, FALSE, &elev0, NULL ) &&
+ ComputeElev( trk, 1, FALSE, &elev1, NULL ) ) {
+ if( elev0 == elev1 )
+ elevValid = FALSE;
+ else {
+ elevValid = TRUE;
+ grade = fabs((elev1-elev0)/dist);
+ sep = grade*(xx->radius*M_PI*2.0);
+ }
+ }
+ fp = wStandardFont( F_TIMES, FALSE, FALSE );
+ if (elevValid)
+ sprintf( message, _("Helix: turns=%ld length=%s grade=%0.1f%% sep=%s"),
+ xx->helixTurns,
+ FormatDistance(dist),
+ grade*100.0,
+ FormatDistance(sep) );
+ else
+ sprintf( message, _("Helix: turns=%ld length=%s"),
+ xx->helixTurns,
+ FormatDistance(dist) );
+ DrawBoxedString( BOX_BOX, d, pos, message, fp, (wFontSize_t)descriptionFontSize, color, 0.0 );
+ } else {
+ dist = trackGauge/2.0;
+ DrawArc( d, xx->pos, dist, 0.0, 360.0, FALSE, 0, color );
+ Translate( &p0, xx->pos, 90.0, dist );
+ Translate( &p1, xx->pos, 270.0, dist );
+ DrawLine( d, p0, p1, 0, color );
+ Translate( &p0, xx->pos, 0.0, dist );
+ Translate( &p1, xx->pos, 180.0, dist );
+ DrawLine( d, p0, p1, 0, color );
+ GetCurveAngles( &a0, &a1, trk );
+ ratio = ( xx->descriptionOff.x + 1.0 ) / 2.0;
+ a = a0 + ratio * a1;
+ PointOnCircle( &p0, xx->pos, xx->radius, a );
+ sprintf( message, "R %s", FormatDistance( xx->radius ) );
+ ratio = ( xx->descriptionOff.y + 1.0 ) / 2.0;
+ DrawDimLine( d, xx->pos, p0, message, (wFontSize_t)descriptionFontSize, ratio, 0, color, 0x11 );
+ }
+}
+
+
+STATUS_T CurveDescriptionMove(
+ track_p trk,
+ wAction_t action,
+ coOrd pos )
+{
+ struct extraData *xx = GetTrkExtraData(trk);
+ static coOrd p0;
+ wDrawColor color;
+ ANGLE_T a, a0, a1;
+ DIST_T d;
+
+ switch (action) {
+ case C_DOWN:
+ case C_MOVE:
+ case C_UP:
+ color = GetTrkColor( trk, &mainD );
+ DrawCurveDescription( trk, &tempD, color );
+ if ( xx->helixTurns > 0 ) {
+ if (action != C_DOWN)
+ DrawLine( &tempD, xx->pos, p0, 0, wDrawColorBlack );
+ xx->descriptionOff.x = (pos.x-xx->pos.x);
+ xx->descriptionOff.y = (pos.y-xx->pos.y);
+ p0 = pos;
+ if (action != C_UP)
+ DrawLine( &tempD, xx->pos, p0, 0, wDrawColorBlack );
+ } else {
+ GetCurveAngles( &a0, &a1, trk );
+ if ( a1 < 1 ) a1 = 1.0;
+ a = FindAngle( xx->pos, pos );
+ if ( ! IsCurveCircle( trk ) ) {
+ a = NormalizeAngle( a - a0 );
+ if ( a > a1 ) {
+ if ( a < a1 + ( 360.0 - a1 ) / 2 ) {
+ a = a1;
+ } else {
+ a = 0.0;
+ }
+ }
+ }
+ xx->descriptionOff.x = ( a / a1 ) * 2.0 - 1.0;
+ d = FindDistance( xx->pos, pos ) / xx->radius;
+ if ( d > 0.9 )
+ d = 0.9;
+ if ( d < 0.1 )
+ d = 0.1;
+ xx->descriptionOff.y = d * 2.0 - 1.0;
+ }
+ DrawCurveDescription( trk, &tempD, color );
+ MainRedraw();
+ return action==C_UP?C_TERMINATE:C_CONTINUE;
+
+ case C_REDRAW:
+ if ( xx->helixTurns > 0 ) {
+ DrawLine( &tempD, xx->pos, p0, 0, wDrawColorBlack );
+ }
+ break;
+
+ }
+ return C_CONTINUE;
+}
+
+/****************************************
+ *
+ * GENERIC FUNCTIONS
+ *
+ */
+
+static struct {
+ coOrd endPt[2];
+ FLOAT_T elev[2];
+ FLOAT_T length;
+ coOrd center;
+ DIST_T radius;
+ long turns;
+ DIST_T separation;
+ ANGLE_T angle0;
+ ANGLE_T angle1;
+ ANGLE_T angle;
+ FLOAT_T grade;
+ descPivot_t pivot;
+ LAYER_T layerNumber;
+ } crvData;
+typedef enum { E0, Z0, E1, Z1, CE, RA, TU, SE, LN, AL, A1, A2, GR, PV, LY } crvDesc_e;
+static descData_t crvDesc[] = {
+/*E0*/ { DESC_POS, N_("End Pt 1: X"), &crvData.endPt[0] },
+/*Z0*/ { DESC_DIM, N_("Z"), &crvData.elev[0] },
+/*E1*/ { DESC_POS, N_("End Pt 2: X"), &crvData.endPt[1] },
+/*Z1*/ { DESC_DIM, N_("Z"), &crvData.elev[1] },
+/*CE*/ { DESC_POS, N_("Center: X"), &crvData.center },
+/*RA*/ { DESC_DIM, N_("Radius"), &crvData.radius },
+/*TU*/ { DESC_LONG, N_("Turns"), &crvData.turns },
+/*SE*/ { DESC_DIM, N_("Separation"), &crvData.separation },
+/*LN*/ { DESC_DIM, N_("Length"), &crvData.length },
+/*AL*/ { DESC_FLOAT, N_("Angular Length"), &crvData.angle },
+/*A1*/ { DESC_ANGLE, N_("CCW Angle"), &crvData.angle0 },
+/*A2*/ { DESC_ANGLE, N_("CW Angle"), &crvData.angle1 },
+/*GR*/ { DESC_FLOAT, N_("Grade"), &crvData.grade },
+/*PV*/ { DESC_PIVOT, N_("Pivot"), &crvData.pivot },
+/*LY*/ { DESC_LAYER, N_("Layer"), &crvData.layerNumber },
+ { DESC_NULL } };
+
+static void UpdateCurve( track_p trk, int inx, descData_p descUpd, BOOL_T final )
+{
+ struct extraData *xx = GetTrkExtraData(trk);
+ BOOL_T updateEndPts;
+ ANGLE_T a0, a1;
+ EPINX_T ep;
+ struct extraData xx0;
+ FLOAT_T turns;
+
+ if ( inx == -1 )
+ return;
+ xx0 = *xx;
+ updateEndPts = FALSE;
+ GetCurveAngles( &a0, &a1, trk );
+ switch ( inx ) {
+ case CE:
+ xx0.pos = crvData.center;
+ updateEndPts = TRUE;
+ break;
+ case RA:
+ if ( crvData.radius <= 0 ) {
+ ErrorMessage( MSG_RADIUS_GTR_0 );
+ crvData.radius = xx0.radius;
+ crvDesc[RA].mode |= DESC_CHANGE;
+ } else {
+ if ( crvData.pivot == DESC_PIVOT_FIRST || GetTrkEndTrk(trk,0) ) {
+ Translate( &xx0.pos, xx0.pos, a0, xx0.radius-crvData.radius );
+ } else if ( crvData.pivot == DESC_PIVOT_SECOND || GetTrkEndTrk(trk,1) ) {
+ Translate( &xx0.pos, xx0.pos, a0+a1, xx0.radius-crvData.radius );
+ } else {
+ Translate( &xx0.pos, xx0.pos, a0+a1/2.0, xx0.radius-crvData.radius );
+ }
+ crvDesc[CE].mode |= DESC_CHANGE;
+ xx0.radius = crvData.radius;
+ crvDesc[LN].mode |= DESC_CHANGE;
+ updateEndPts = TRUE;
+ }
+ break;
+ case TU:
+ if ( crvData.turns <= 0 ) {
+ ErrorMessage( MSG_HELIX_TURNS_GTR_0 );
+ crvData.turns = xx0.helixTurns;
+ crvDesc[TU].mode |= DESC_CHANGE;
+ } else {
+ xx0.helixTurns = crvData.turns;
+ crvDesc[LN].mode |= DESC_CHANGE;
+ updateEndPts = TRUE;
+ crvDesc[SE].mode |= DESC_CHANGE;
+ crvDesc[GR].mode |= DESC_CHANGE;
+ }
+ break;
+ case AL:
+ if ( crvData.angle <= 0.0 || crvData.angle >= 360.0 ) {
+ ErrorMessage( MSG_CURVE_OUT_OF_RANGE );
+ crvData.angle = a1;
+ crvDesc[AL].mode |= DESC_CHANGE;
+ } else {
+ if ( crvData.pivot == DESC_PIVOT_FIRST || GetTrkEndTrk(trk,0) ) {
+ a1 = crvData.angle;
+ crvData.angle1 = NormalizeAngle( a0+a1 );
+ crvDesc[A2].mode |= DESC_CHANGE;
+ } else if ( crvData.pivot == DESC_PIVOT_SECOND || GetTrkEndTrk(trk,1) ) {
+ a0 = NormalizeAngle( a0+a1-crvData.angle );
+ a1 = crvData.angle;
+ crvData.angle0 = NormalizeAngle( a0 );
+ crvDesc[A1].mode |= DESC_CHANGE;
+ } else {
+ a0 = NormalizeAngle( a0+a1/2.0-crvData.angle/2.0);
+ a1 = crvData.angle;
+ crvData.angle0 = NormalizeAngle( a0 );
+ crvData.angle1 = NormalizeAngle( a0+a1 );
+ crvDesc[A1].mode |= DESC_CHANGE;
+ crvDesc[A2].mode |= DESC_CHANGE;
+ }
+ crvDesc[LN].mode |= DESC_CHANGE;
+ updateEndPts = TRUE;
+ }
+ break;
+ case A1:
+ a0 = crvData.angle0 = NormalizeAngle( crvData.angle0 );
+ a1 = NormalizeAngle( crvData.angle1-crvData.angle0 );
+ if ( a1 <= 0.0 ) {
+ ErrorMessage( MSG_CURVE_OUT_OF_RANGE );
+ } else {
+ updateEndPts = TRUE;
+ crvData.angle = a1;
+ crvDesc[AL].mode |= DESC_CHANGE;
+ crvDesc[LN].mode |= DESC_CHANGE;
+ }
+ break;
+ case A2:
+ a1 = NormalizeAngle( crvData.angle1-crvData.angle0 );
+ if ( a1 <= 0.0 ) {
+ ErrorMessage( MSG_CURVE_OUT_OF_RANGE );
+ } else {
+ updateEndPts = TRUE;
+ crvData.angle = a1;
+ crvDesc[AL].mode |= DESC_CHANGE;
+ crvDesc[LN].mode |= DESC_CHANGE;
+ }
+ break;
+ case Z0:
+ case Z1:
+ ep = (inx==Z0?0:1);
+ UpdateTrkEndElev( trk, ep, GetTrkEndElevUnmaskedMode(trk,ep), crvData.elev[ep], NULL );
+ ComputeElev( trk, 1-ep, FALSE, &crvData.elev[1-ep], NULL );
+ if ( crvData.length > minLength )
+ crvData.grade = fabs( (crvData.elev[0]-crvData.elev[1])/crvData.length )*100.0;
+ else
+ crvData.grade = 0.0;
+ crvDesc[GR].mode |= DESC_CHANGE;
+ crvDesc[inx==Z0?Z1:Z0].mode |= DESC_CHANGE;
+ if ( xx->helixTurns > 0 ) {
+ turns = crvData.length/(2*M_PI*crvData.radius);
+ crvData.separation = fabs(crvData.elev[0]-crvData.elev[1])/turns;
+ crvDesc[SE].mode |= DESC_CHANGE;
+ }
+ return;
+ case LY:
+ SetTrkLayer( trk, crvData.layerNumber);
+ break;
+ default:
+ AbortProg( "updateCurve: Bad inx %d", inx );
+ }
+ UndrawNewTrack( trk );
+ *xx = xx0;
+ if (updateEndPts) {
+ if ( GetTrkEndTrk(trk,0) == NULL ) {
+ (void)PointOnCircle( &crvData.endPt[0], xx0.pos, xx0.radius, a0 );
+ SetTrkEndPoint( trk, 0, crvData.endPt[0], NormalizeAngle( a0-90.0 ) );
+ crvDesc[E0].mode |= DESC_CHANGE;
+ }
+ if ( GetTrkEndTrk(trk,1) == NULL ) {
+ (void)PointOnCircle( &crvData.endPt[1], xx0.pos, xx0.radius, a0+a1 );
+ SetTrkEndPoint( trk, 1, crvData.endPt[1], NormalizeAngle( a0+a1+90.0 ) );
+ crvDesc[E1].mode |= DESC_CHANGE;
+ }
+ }
+ crvData.length = GetLengthCurve( trk );
+
+ if ( crvDesc[SE].mode&DESC_CHANGE ) {
+ DrawCurveDescription( trk, &mainD, wDrawColorWhite );
+ DrawCurveDescription( trk, &mainD, wDrawColorBlack );
+ turns = crvData.length/(2*M_PI*crvData.radius);
+ crvData.separation = fabs(crvData.elev[0]-crvData.elev[1])/turns;
+ if ( crvData.length > minLength )
+ crvData.grade = fabs( (crvData.elev[0]-crvData.elev[1])/crvData.length )*100.0;
+ else
+ crvData.grade = 0.0;
+ crvDesc[GR].mode |= DESC_CHANGE;
+ }
+
+ ComputeCurveBoundingBox( trk, xx );
+ DrawNewTrack( trk );
+}
+
+static void DescribeCurve( track_p trk, char * str, CSIZE_T len )
+{
+ struct extraData *xx = GetTrkExtraData(trk);
+ ANGLE_T a0, a1;
+ DIST_T d;
+ int fix0, fix1;
+ FLOAT_T turns;
+
+ GetCurveAngles( &a0, &a1, trk );
+ d = xx->radius * 2.0 * M_PI * a1 / 360.0;
+ if (xx->helixTurns > 0) {
+ d += (xx->helixTurns-(xx->circle?1:0)) * xx->radius * 2.0 * M_PI;
+ sprintf( str, _("Helix Track(%d): Layer=%d Radius=%s Turns=%ld Length=%s Center=[%s,%s] EP=[%0.3f,%0.3f A%0.3f] [%0.3f,%0.3f A%0.3f]"),
+ GetTrkIndex(trk),
+ GetTrkLayer(trk)+1,
+ FormatDistance(xx->radius),
+ xx->helixTurns,
+ FormatDistance(d),
+ FormatDistance(xx->pos.x), FormatDistance(xx->pos.y),
+ GetTrkEndPosXY(trk,0), GetTrkEndAngle(trk,0),
+ GetTrkEndPosXY(trk,1), GetTrkEndAngle(trk,1) );
+ } else {
+ sprintf( str, _("Curved Track(%d): Layer=%d Radius=%s Length=%s Center=[%s,%s] EP=[%0.3f,%0.3f A%0.3f] [%0.3f,%0.3f A%0.3f]"),
+ GetTrkIndex(trk),
+ GetTrkLayer(trk)+1,
+ FormatDistance(xx->radius),
+ FormatDistance(d),
+ FormatDistance(xx->pos.x), FormatDistance(xx->pos.y),
+ GetTrkEndPosXY(trk,0), GetTrkEndAngle(trk,0),
+ GetTrkEndPosXY(trk,1), GetTrkEndAngle(trk,1) );
+ }
+
+ fix0 = GetTrkEndTrk(trk,0)!=NULL;
+ fix1 = GetTrkEndTrk(trk,1)!=NULL;
+
+ crvData.endPt[0] = GetTrkEndPos(trk,0);
+ crvData.endPt[1] = GetTrkEndPos(trk,1);
+ crvData.length = GetLengthCurve(trk);
+ crvData.center = xx->pos;
+ crvData.radius = xx->radius;
+ crvData.turns = xx->helixTurns;
+ crvData.angle0 = NormalizeAngle( a0 );
+ crvData.angle1 = NormalizeAngle( a0+a1);
+ crvData.angle = a1;
+ crvData.layerNumber = GetTrkLayer(trk);
+ if ( !xx->circle ) {
+ ComputeElev( trk, 0, FALSE, &crvData.elev[0], NULL );
+ ComputeElev( trk, 1, FALSE, &crvData.elev[1], NULL );
+ } else {
+ crvData.elev[0] = crvData.elev[1] = 0;
+ }
+ ComputeElev( trk, 0, FALSE, &crvData.elev[0], NULL );
+ ComputeElev( trk, 1, FALSE, &crvData.elev[1], NULL );
+ if ( crvData.length > minLength )
+ crvData.grade = fabs( (crvData.elev[0]-crvData.elev[1])/crvData.length )*100.0;
+ else
+ crvData.grade = 0.0;
+ if ( xx->helixTurns > 0 ) {
+ turns = crvData.length/(2*M_PI*crvData.radius);
+ crvData.separation = fabs(crvData.elev[0]-crvData.elev[1])/turns;
+ crvDesc[SE].mode |= DESC_CHANGE;
+ }
+
+ crvDesc[E0].mode =
+ crvDesc[E1].mode =
+ crvDesc[LN].mode =
+ DESC_RO;
+ crvDesc[Z0].mode = (EndPtIsDefinedElev(trk,0)?0:DESC_RO)|DESC_NOREDRAW;
+ crvDesc[Z1].mode = (EndPtIsDefinedElev(trk,1)?0:DESC_RO)|DESC_NOREDRAW;
+ crvDesc[GR].mode = DESC_RO;
+ crvDesc[CE].mode = (fix0|fix1)?DESC_RO:0;
+ crvDesc[RA].mode =
+ crvDesc[AL].mode =
+ (fix0&fix1)?DESC_RO:0;
+ crvDesc[TU].mode = DESC_NOREDRAW;
+ crvDesc[A1].mode = fix0?DESC_RO:0;
+ crvDesc[A2].mode = fix1?DESC_RO:0;
+ crvDesc[PV].mode = (fix0|fix1)?DESC_IGNORE:0;
+ crvDesc[LY].mode = DESC_NOREDRAW;
+ crvData.pivot = (fix0&fix1)?DESC_PIVOT_NONE:
+ fix0?DESC_PIVOT_FIRST:
+ fix1?DESC_PIVOT_SECOND:
+ DESC_PIVOT_MID;
+
+ crvDesc[SE].mode |= DESC_IGNORE;
+ if ( xx->circle ) {
+ crvDesc[E0].mode |= DESC_IGNORE;
+ crvDesc[Z0].mode |= DESC_IGNORE;
+ crvDesc[E1].mode |= DESC_IGNORE;
+ crvDesc[Z1].mode |= DESC_IGNORE;
+ crvDesc[AL].mode |= DESC_IGNORE;
+ crvDesc[A1].mode |= DESC_IGNORE;
+ crvDesc[A2].mode |= DESC_IGNORE;
+ crvDesc[PV].mode |= DESC_IGNORE;
+ }
+
+ if ( xx->helixTurns ) {
+ if ( !xx->circle )
+ crvDesc[SE].mode = DESC_RO;
+ DoDescribe( _("Helix Track"), trk, crvDesc, UpdateCurve );
+ } else if ( xx->circle ) {
+ crvDesc[TU].mode |= DESC_IGNORE;
+ DoDescribe( _("Circle Track"), trk, crvDesc, UpdateCurve );
+ } else {
+ crvDesc[TU].mode |= DESC_IGNORE;
+ DoDescribe( _("Curved Track"), trk, crvDesc, UpdateCurve );
+ }
+}
+
+static DIST_T DistanceCurve( track_p t, coOrd * p )
+{
+ struct extraData *xx = GetTrkExtraData(t);
+ ANGLE_T a0, a1;
+ DIST_T d;
+ GetCurveAngles( &a0, &a1, t );
+ if ( xx->helixTurns > 0 ) {
+ a0 = 0.0;
+ a1 = 360.0;
+ }
+ d = CircleDistance( p, xx->pos, xx->radius, a0, a1 );
+ return d;
+}
+
+static void DrawCurve( track_p t, drawCmd_p d, wDrawColor color )
+{
+ struct extraData *xx = GetTrkExtraData(t);
+ ANGLE_T a0, a1;
+ track_p tt = t;
+ long widthOptions = DTS_LEFT|DTS_RIGHT|DTS_TIES;
+
+ if (GetTrkWidth(t) == 2)
+ widthOptions |= DTS_THICK2;
+ if (GetTrkWidth(t) == 3)
+ widthOptions |= DTS_THICK3;
+ GetCurveAngles( &a0, &a1, t );
+ if (xx->circle) {
+ tt = NULL;
+ }
+ if (xx->helixTurns > 0) {
+ a0 = 0.0;
+ a1 = 360.0;
+ }
+ if ( ((d->funcs->options&wDrawOptTemp)==0) &&
+ (labelWhen == 2 || (labelWhen == 1 && (d->options&DC_PRINT))) &&
+ labelScale >= d->scale &&
+ ( GetTrkBits( t ) & TB_HIDEDESC ) == 0 ) {
+ DrawCurveDescription( t, d, color );
+ }
+ DrawCurvedTrack( d, xx->pos, xx->radius, a0, a1,
+ GetTrkEndPos(t,0), GetTrkEndPos(t,1),
+ t, GetTrkGauge(t), color, widthOptions );
+ if ( (d->funcs->options & wDrawOptTemp) == 0 &&
+ (d->options&DC_QUICK) == 0 &&
+ (!IsCurveCircle(t)) ) {
+ DrawEndPt( d, t, 0, color );
+ DrawEndPt( d, t, 1, color );
+ }
+}
+
+static void DeleteCurve( track_p t )
+{
+}
+
+static BOOL_T WriteCurve( track_p t, FILE * f )
+{
+ struct extraData *xx = GetTrkExtraData(t);
+ long options;
+ BOOL_T rc = TRUE;
+ options = GetTrkWidth(t) & 0x0F;
+ if ( ( ( GetTrkBits(t) & TB_HIDEDESC ) != 0 ) == ( xx->helixTurns > 0 ) )
+ options |= 0x80;
+ rc &= fprintf(f, "CURVE %d %d %ld 0 0 %s %d %0.6f %0.6f 0 %0.6f %ld %0.6f %0.6f\n",
+ GetTrkIndex(t), GetTrkLayer(t), (long)options,
+ GetTrkScaleName(t), GetTrkVisible(t), xx->pos.x, xx->pos.y, xx->radius,
+ xx->helixTurns, xx->descriptionOff.x, xx->descriptionOff.y )>0;
+ rc &= WriteEndPt( f, t, 0 );
+ rc &= WriteEndPt( f, t, 1 );
+ rc &= fprintf(f, "\tEND\n" )>0;
+ return rc;
+}
+
+static void ReadCurve( char * line )
+{
+ struct extraData *xx;
+ track_p t;
+ wIndex_t index;
+ BOOL_T visible;
+ DIST_T r;
+ coOrd p;
+ DIST_T elev;
+ char scale[10];
+ wIndex_t layer;
+ long options;
+ char * cp = NULL;
+
+ if (!GetArgs( line+6, paramVersion<3?"dXZsdpYfc":paramVersion<9?"dLl00sdpYfc":"dLl00sdpffc",
+ &index, &layer, &options, scale, &visible, &p, &elev, &r, &cp ) ) {
+ return;
+ }
+ t = NewTrack( index, T_CURVE, 0, sizeof *xx );
+ xx = GetTrkExtraData(t);
+ SetTrkVisible(t, visible);
+ SetTrkScale(t, LookupScale(scale));
+ SetTrkLayer(t, layer );
+ SetTrkWidth(t, (int)(options&3));
+ xx->pos = p;
+ xx->radius = r;
+ xx->helixTurns = 0;
+ xx->descriptionOff.x = xx->descriptionOff.y = 0.0;
+ if (cp) {
+ GetArgs( cp, "lp", &xx->helixTurns, &xx->descriptionOff );
+ }
+ if ( ( ( options & 0x80 ) != 0 ) == ( xx->helixTurns > 0 ) )
+ SetTrkBits(t,TB_HIDEDESC);
+ ReadSegs();
+ SetEndPts(t,2);
+ if (GetTrkEndAngle( t, 0 ) == 270.0 &&
+ GetTrkEndAngle( t, 1 ) == 90.0 )
+ xx->circle = TRUE;
+ ComputeCurveBoundingBox( t, xx );
+}
+
+static void MoveCurve( track_p trk, coOrd orig )
+{
+ struct extraData *xx = GetTrkExtraData(trk);
+ xx->pos.x += orig.x;
+ xx->pos.y += orig.y;
+ ComputeCurveBoundingBox( trk, xx );
+}
+
+static void RotateCurve( track_p trk, coOrd orig, ANGLE_T angle )
+{
+ struct extraData *xx = GetTrkExtraData(trk);
+ Rotate( &xx->pos, orig, angle );
+ ComputeCurveBoundingBox( trk, xx );
+}
+
+static void RescaleCurve( track_p trk, FLOAT_T ratio )
+{
+ struct extraData *xx = GetTrkExtraData(trk);
+ xx->pos.x *= ratio;
+ xx->pos.y *= ratio;
+ xx->radius *= ratio;
+}
+
+static ANGLE_T GetAngleCurve( track_p trk, coOrd pos, EPINX_T *ep0, EPINX_T *ep1 )
+{
+ coOrd center;
+ DIST_T radius;
+ if ( ep0 ) *ep0 = 0;
+ if ( ep1 ) *ep1 = 1;
+ GetTrkCurveCenter( trk, &center, &radius );
+ return FindAngle( center, pos ) - 90.0;
+}
+
+static BOOL_T SplitCurve( track_p trk, coOrd pos, EPINX_T ep, track_p *leftover, EPINX_T * ep0, EPINX_T * ep1 )
+{
+ struct extraData *xx = GetTrkExtraData(trk);
+ ANGLE_T a, a0, a1;
+ track_p trk1;
+
+ if ( xx->helixTurns > 0 ) {
+ ErrorMessage( MSG_CANT_SPLIT_TRK, _("Helix") );
+ return FALSE;
+ }
+ a = FindAngle( xx->pos, pos );
+ GetCurveAngles( &a0, &a1, trk );
+ if (xx->circle) {
+ a0 = a;
+ a1 = 359;
+ SetCurveAngles( trk, a0, a1, xx );
+ *leftover = NULL;
+ return TRUE;
+ }
+ if (ep == 0)
+ a1 = NormalizeAngle(a-a0);
+ else {
+ a1 = NormalizeAngle(a0+a1-a);
+ a0 = a;
+ }
+ trk1 = NewCurvedTrack( xx->pos, xx->radius, a0, a1, 0 );
+ AdjustCurveEndPt( trk, ep, a+(ep==0?-90.0:90.0) );
+ *leftover = trk1;
+ *ep0 = 1-ep;
+ *ep1 = ep;
+
+ return TRUE;
+}
+
+static BOOL_T TraverseCurve( traverseTrack_p trvTrk, DIST_T * distR )
+{
+ track_p trk = trvTrk->trk;
+ struct extraData *xx = GetTrkExtraData(trk);
+ ANGLE_T a, a0, a1, a2, a3;
+ DIST_T arcDist;
+ DIST_T circum;
+ DIST_T dist;
+ long turns;
+ if ( xx->circle )
+ return FALSE;
+ circum = 2*M_PI*xx->radius;
+ GetCurveAngles( &a0, &a1, trk );
+ a2 = FindAngle( xx->pos, trvTrk->pos );
+ a = NormalizeAngle( (a2-90.0) - trvTrk->angle );
+ if ( xx->helixTurns <= 0 ) {
+ if ( NormalizeAngle(a2-a0) > a1 ) {
+ if ( NormalizeAngle( a2-(a0+a1/2.0+180.0 ) ) < 180.0 )
+ a2 = a0;
+ else
+ a2 = NormalizeAngle(a0+a1);
+ }
+ }
+ if ( a>270 || a<90 )
+ arcDist = NormalizeAngle(a2-a0)/360.0*circum;
+ else
+ arcDist = NormalizeAngle(a0+a1-a2)/360.0*circum;
+ if ( xx->helixTurns > 0 ) {
+ turns = xx->helixTurns;
+ if ( NormalizeAngle(a2-a0) > a1 )
+ turns -= 1;
+ dist = (a1/360.0+xx->helixTurns)*circum;
+ if ( trvTrk->length < 0 ) {
+ trvTrk->length = dist;
+ trvTrk->dist = a1/360.0*circum - arcDist;
+ while ( trvTrk->dist < 0 ) {
+ if ( trvTrk->dist > -0.1 )
+ trvTrk->dist = 0.0;
+ else
+ trvTrk->dist += circum;
+ }
+ } else {
+ if ( trvTrk->length != dist ) {
+ printf( "traverseCurve: trvTrk->length(%0.3f) != Dist(%0.3f)\n", trvTrk->length, dist );
+ trvTrk->length = dist;
+ }
+ if ( trvTrk->length < trvTrk->dist ) {
+ printf( "traverseCurve: trvTrk->length(%0.3f) < trvTrk->dist(%0.3f)\n", trvTrk->length, trvTrk->dist );
+ trvTrk->dist = trvTrk->length;
+ }
+ a3 = trvTrk->dist/circum*360.0;
+ if ( a>270 || a<90 )
+ a3 = (a0+a1-a3);
+ else
+ a3 = (a0+a3);
+ a3 = NormalizeAngle(a3);
+ if ( NormalizeAngle(a2-a3+1.0) > 2.0 )
+ printf( "traverseCurve: A2(%0.3f) != A3(%0.3f)\n", a2, a3 );
+ turns = (int)((trvTrk->length-trvTrk->dist)/circum);
+ }
+ arcDist += turns * circum;
+ }
+ if ( a>270 || a<90 ) {
+ /* CCW */
+ if ( arcDist < *distR ) {
+ PointOnCircle( &trvTrk->pos, xx->pos, xx->radius, a0 );
+ *distR -= arcDist;
+ trvTrk->angle = NormalizeAngle( a0-90.0 );
+ trk = GetTrkEndTrk( trk, 0 );
+ } else {
+ trvTrk->dist += *distR;
+ a2 -= *distR/circum*360.0;
+ PointOnCircle( &trvTrk->pos, xx->pos, xx->radius, a2 );
+ *distR = 0;
+ trvTrk->angle = NormalizeAngle( a2-90.0 );
+ }
+ } else {
+ /* CW */
+ if ( arcDist < *distR ) {
+ PointOnCircle( &trvTrk->pos, xx->pos, xx->radius, a0+a1 );
+ *distR -= arcDist;
+ trvTrk->angle = NormalizeAngle( a0+a1+90.0 );
+ trk = GetTrkEndTrk( trk, 1 );
+ } else {
+ trvTrk->dist += *distR;
+ a2 += *distR/circum*360.0;
+ PointOnCircle( &trvTrk->pos, xx->pos, xx->radius, a2 );
+ *distR = 0;
+ trvTrk->angle = NormalizeAngle( a2+90.0 );
+ }
+ }
+ trvTrk->trk = trk;
+ return TRUE;
+}
+
+
+static BOOL_T EnumerateCurve( track_p trk )
+{
+ struct extraData *xx;
+ ANGLE_T a0, a1;
+ DIST_T d;
+ if (trk != NULL) {
+ xx = GetTrkExtraData(trk);
+ GetCurveAngles( &a0, &a1, trk );
+ d = xx->radius * 2.0 * M_PI * a1 / 360.0;
+ if (xx->helixTurns > 0)
+ d += (xx->helixTurns-(xx->circle?1:0)) * xx->radius * 2.0 * M_PI;
+ ScaleLengthIncrement( GetTrkScale(trk), d );
+ }
+ return TRUE;
+}
+
+static BOOL_T TrimCurve( track_p trk, EPINX_T ep, DIST_T dist )
+{
+ DIST_T d;
+ DIST_T radius;
+ ANGLE_T a, aa;
+ ANGLE_T a0, a1;
+ coOrd pos, center;
+ struct extraData *xx = GetTrkExtraData(trk);
+ if (xx->helixTurns>0) {
+ ErrorMessage( MSG_CANT_TRIM_HELIX );
+ return FALSE;
+ }
+ a = NormalizeAngle( GetTrkEndAngle(trk,ep) + 180.0 );
+ Translate( &pos, GetTrkEndPos(trk,ep), a, dist );
+ GetTrkCurveCenter( trk, &center, &radius );
+ GetCurveAngles( &a0, &a1, trk );
+ a = FindAngle( center, pos );
+ aa = NormalizeAngle(a - a0);
+ d = radius * aa * 2.0*M_PI/360.0;
+ if ( aa <= a1 && d > minLength ) {
+ UndrawNewTrack( trk );
+ AdjustCurveEndPt( trk, ep, a+(ep==0?-90.0:90.0) );
+ DrawNewTrack( trk );
+ } else
+ DeleteTrack( trk, TRUE );
+ return TRUE;
+}
+
+static BOOL_T MergeCurve(
+ track_p trk0,
+ EPINX_T ep0,
+ track_p trk1,
+ EPINX_T ep1 )
+{
+ struct extraData *xx0 = GetTrkExtraData(trk0);
+ struct extraData *xx1 = GetTrkExtraData(trk1);
+ ANGLE_T a00, a01, a10, a11;
+ DIST_T d;
+ track_p trk2;
+ EPINX_T ep2=-1;
+ coOrd pos;
+
+ if (ep0 == ep1)
+ return FALSE;
+ if ( IsCurveCircle(trk0) ||
+ IsCurveCircle(trk1) )
+ return FALSE;
+ if ( xx0->helixTurns > 0 ||
+ xx1->helixTurns > 0 )
+ return FALSE;
+ d = FindDistance( xx0->pos, xx1->pos );
+ d += fabs( xx0->radius - xx1->radius );
+ if ( d > connectDistance )
+ return FALSE;
+
+ GetCurveAngles( &a00, &a01, trk0 );
+ GetCurveAngles( &a10, &a11, trk1 );
+
+ UndoStart( _("Merge Curves"), "MergeCurve( T%d[%d] T%d[%d] )", GetTrkIndex(trk0), ep0, GetTrkIndex(trk1), ep1 );
+ UndoModify( trk0 );
+ UndrawNewTrack( trk0 );
+ trk2 = GetTrkEndTrk( trk1, 1-ep1 );
+ if (trk2) {
+ ep2 = GetEndPtConnectedToMe( trk2, trk1 );
+ DisconnectTracks( trk1, 1-ep1, trk2, ep2 );
+ }
+ if (ep0 == 0) {
+ (void)PointOnCircle( &pos, xx0->pos, xx0->radius, a10 );
+ a10 = NormalizeAngle( a10-90.0 );
+ SetTrkEndPoint( trk0, ep0, pos, a10 );
+ } else {
+ (void)PointOnCircle( &pos, xx0->pos, xx0->radius, a10+a11 );
+ a10 = NormalizeAngle( a10+a11+90.0 );
+ SetTrkEndPoint( trk0, ep0, pos, a10 );
+ }
+ DeleteTrack( trk1, FALSE );
+ if (trk2) {
+ ConnectTracks( trk0, ep0, trk2, ep2 );
+ }
+ DrawNewTrack( trk0 );
+ ComputeCurveBoundingBox( trk0, GetTrkExtraData(trk0) );
+ return TRUE;
+}
+
+
+static STATUS_T ModifyCurve( track_p trk, wAction_t action, coOrd pos )
+{
+ static BOOL_T arcTangent;
+ static ANGLE_T arcA0, arcA1;
+ static EPINX_T ep;
+ static coOrd arcPos;
+ static DIST_T arcRadius;
+ static coOrd tangentOrig;
+ static coOrd tangentEnd;
+ static ANGLE_T angle;
+ static easementData_t jointD;
+ static BOOL_T valid;
+
+ ANGLE_T a, aa1, aa2;
+ DIST_T r, d;
+ track_p trk1;
+ struct extraData *xx = GetTrkExtraData(trk);
+
+ switch ( action ) {
+
+ case C_DOWN:
+ arcTangent = FALSE;
+ GetCurveAngles( &arcA0, &arcA1, trk );
+ if ( arcA0 == 0.0 && arcA1 == 360.0 )
+ return C_ERROR;
+ if ( xx->helixTurns > 0 ) {
+ return C_ERROR;
+ }
+ ep = PickUnconnectedEndPoint( pos, trk );
+ if ( ep == -1 )
+ return C_ERROR;
+ GetTrkCurveCenter( trk, &arcPos, &arcRadius );
+ UndrawNewTrack( trk );
+ tempSegs(0).type = SEG_CRVTRK;
+ tempSegs(0).width = 0;
+ tempSegs(0).u.c.center = arcPos;
+ tempSegs(0).u.c.radius = arcRadius;
+ tempSegs(0).u.c.a0 = arcA0;
+ tempSegs(0).u.c.a1 = arcA1;
+ tempSegs_da.cnt = 1;
+ InfoMessage( _("Drag to change angle or create tangent") );
+ case C_MOVE:
+ if (xx->helixTurns>0)
+ return C_CONTINUE;
+ valid = FALSE;
+ a = FindAngle( arcPos, pos );
+ r = FindDistance( arcPos, pos );
+ if ( r > arcRadius*(arcTangent?1.0:1.10) ) {
+ arcTangent = TRUE;
+ if ( easeR > 0.0 && arcRadius < easeR ) {
+ ErrorMessage( MSG_RADIUS_LSS_EASE_MIN,
+ FormatDistance( arcRadius ), FormatDistance( easeR ) );
+ return C_CONTINUE;
+ }
+ aa1 = 90.0-R2D( asin( arcRadius/r ) );
+ aa2 = NormalizeAngle( a + (ep==0?aa1:-aa1) );
+ PointOnCircle( &tangentOrig, arcPos, arcRadius, aa2 );
+ if (ComputeJoint( ep==0?-arcRadius:+arcRadius, 0, &jointD ) == E_ERROR)
+ return C_CONTINUE;
+ tangentEnd = pos;
+ if (jointD.x != 0.0) {
+ Translate( &tangentOrig, tangentOrig, aa2, jointD.x );
+ Translate( &tangentEnd, tangentEnd, aa2, jointD.x );
+ }
+ if (ep == 0) {
+ tempSegs(0).u.c.a0 = aa2;
+ tempSegs(0).u.c.a1 = NormalizeAngle( arcA0+arcA1-aa2 );
+ } else {
+ tempSegs(0).u.c.a1 = NormalizeAngle(aa2-arcA0);
+ }
+ d = arcRadius * tempSegs(0).u.c.a1 * 2.0*M_PI/360.0;
+ d -= jointD.d0;
+ if ( d <= minLength) {
+ ErrorMessage( MSG_TRK_TOO_SHORT, _("Curved "), PutDim(fabs(minLength-d)) );
+ return C_CONTINUE;
+ }
+ d = FindDistance( tangentOrig, tangentEnd );
+ d -= jointD.d1;
+ if ( d <= minLength) {
+ ErrorMessage( MSG_TRK_TOO_SHORT, _("Tangent "), PutDim(fabs(minLength-d)) );
+ return C_CONTINUE;
+ }
+ tempSegs(1).type = SEG_STRTRK;
+ tempSegs(1).width = 0;
+ tempSegs(1).u.l.pos[0] = tangentOrig;
+ tempSegs(1).u.l.pos[1] = tangentEnd;
+ tempSegs_da.cnt = 2;
+ if (action == C_MOVE)
+ InfoMessage( _("Tangent track: Length %s Angle %0.3f"),
+ FormatDistance( d ),
+ PutAngle( FindAngle( tangentOrig, tangentEnd ) ) );
+ } else {
+ arcTangent = FALSE;
+ angle = NormalizeAngle( a +
+ ((ep==0)?-90:90));
+ PointOnCircle( &pos, arcPos, arcRadius, a );
+ if (ep != 0) {
+ tempSegs(0).u.c.a0 = NormalizeAngle( GetTrkEndAngle(trk,0)+90.0 );
+ tempSegs(0).u.c.a1 = NormalizeAngle( a-tempSegs(0).u.c.a0 );
+ } else {
+ tempSegs(0).u.c.a0 = a;
+ tempSegs(0).u.c.a1 = NormalizeAngle( (GetTrkEndAngle(trk,1)-90.0) - a );
+ }
+ d = arcRadius*tempSegs(0).u.c.a1*2.0*M_PI/360.0;
+ if ( d <= minLength ) {
+ ErrorMessage( MSG_TRK_TOO_SHORT, _("Curved "), PutDim( fabs(minLength-d) ) );
+ return C_CONTINUE;
+ }
+ tempSegs_da.cnt = 1;
+ if (action == C_MOVE)
+ InfoMessage( _("Curved: Radius=%s Length=%s Angle=%0.3f"),
+ FormatDistance( arcRadius ), FormatDistance( d ),
+ tempSegs(0).u.c.a1 );
+ }
+ valid = TRUE;
+ return C_CONTINUE;
+ case C_UP:
+ if (xx->helixTurns>0)
+ return C_CONTINUE;
+ if (valid) {
+ if (arcTangent) {
+ trk1 = NewStraightTrack( tangentOrig, tangentEnd );
+ CopyAttributes( trk, trk1 );
+ /*UndrawNewTrack( trk );*/
+ AdjustCurveEndPt( trk, ep, angle );
+ JoinTracks( trk, ep, tangentOrig,
+ trk1, 0, tangentOrig, &jointD );
+ DrawNewTrack( trk1 );
+ } else {
+ AdjustCurveEndPt( trk, ep, angle );
+ }
+ }
+ DrawNewTrack( trk );
+ return C_TERMINATE;
+ default:
+ ;
+ }
+ return C_ERROR;
+}
+
+
+static DIST_T GetLengthCurve( track_p trk )
+{
+ DIST_T dist, rad;
+ ANGLE_T a0, a1;
+ coOrd cen;
+ struct extraData *xx = GetTrkExtraData(trk);
+
+ GetTrkCurveCenter( trk, &cen, &rad );
+ if (xx->circle)
+ a1 = 360.0;
+ else
+ GetCurveAngles( &a0, &a1, trk );
+ dist = (rad+GetTrkGauge(trk)/2.0)*a1*2.0*M_PI/360.0;
+ if (xx->helixTurns>0)
+ dist += (xx->helixTurns-(xx->circle?1:0)) * xx->radius * 2.0 * M_PI;
+ return dist;
+}
+
+
+static BOOL_T GetParamsCurve( int inx, track_p trk, coOrd pos, trackParams_t * params )
+{
+ struct extraData *xx = GetTrkExtraData(trk);
+ params->type = curveTypeCurve;
+ GetTrkCurveCenter( trk, &params->arcP, &params->arcR);
+ GetCurveAngles( &params->arcA0, &params->arcA1, trk );
+ if ( easeR > 0.0 && params->arcR < easeR ) {
+ ErrorMessage( MSG_RADIUS_LSS_EASE_MIN,
+ FormatDistance( params->arcR ), FormatDistance( easeR ) );
+ return FALSE;
+ }
+ if ( inx == PARAMS_EXTEND && ( IsCurveCircle(trk) || xx->helixTurns > 0 ) ) {
+ ErrorMessage( MSG_CANT_EXTEND_HELIX );
+ return FALSE;
+ }
+ params->len = params->arcR * params->arcA1 *2.0*M_PI/360.0;
+ if (xx->helixTurns > 0)
+ params->len += (xx->helixTurns-(xx->circle?1:0)) * xx->radius * 2.0 * M_PI;
+ params->helixTurns = xx->helixTurns;
+ if ( inx == PARAMS_PARALLEL ) {
+ params->ep = 0;
+ if (xx->helixTurns > 0) {
+ params->arcA0 = 0.0;
+ params->arcA1 = 360.0;
+ }
+ } else {
+ if ( IsCurveCircle( trk ) )
+ params->ep = PickArcEndPt( params->arcP, /*Dj.inp[0].*/pos, pos );
+ else
+ params->ep = PickUnconnectedEndPoint( pos, trk );
+ if (params->ep == -1)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+static BOOL_T MoveEndPtCurve( track_p *trk, EPINX_T *ep, coOrd pos, DIST_T d0 )
+{
+ coOrd posCen;
+ DIST_T r;
+ ANGLE_T angle0;
+ ANGLE_T aa;
+
+ GetTrkCurveCenter( *trk, &posCen, &r );
+ angle0 = FindAngle( posCen, pos );
+ aa = R2D( d0/r );
+ if ( *ep==0 )
+ angle0 += aa - 90.0;
+ else
+ angle0 -= aa - 90.0;
+ AdjustCurveEndPt( *trk, *ep, angle0 );
+ return TRUE;
+}
+
+
+static BOOL_T QueryCurve( track_p trk, int query )
+{
+ struct extraData * xx = GetTrkExtraData(trk);
+ switch ( query ) {
+ case Q_CAN_PARALLEL:
+ case Q_CAN_MODIFYRADIUS:
+ case Q_CAN_GROUP:
+ case Q_FLIP_ENDPTS:
+ case Q_ISTRACK:
+ case Q_HAS_DESC:
+ return TRUE;
+ case Q_EXCEPTION:
+ return xx->radius < minTrackRadius;
+ case Q_NOT_PLACE_FROGPOINTS:
+ return IsCurveCircle( trk );
+ default:
+ return FALSE;
+ }
+}
+
+
+static void FlipCurve(
+ track_p trk,
+ coOrd orig,
+ ANGLE_T angle )
+{
+ struct extraData * xx = GetTrkExtraData(trk);
+ FlipPoint( &xx->pos, orig, angle );
+ ComputeCurveBoundingBox( trk, xx );
+}
+
+
+static BOOL_T MakeParallelCurve(
+ track_p trk,
+ coOrd pos,
+ DIST_T sep,
+ track_p * newTrkR,
+ coOrd * p0R,
+ coOrd * p1R )
+{
+ struct extraData * xx = GetTrkExtraData(trk);
+ struct extraData * xx1;
+ DIST_T rad;
+ ANGLE_T a0, a1;
+
+ rad = FindDistance( pos, xx->pos );
+ if ( rad > xx->radius )
+ rad = xx->radius + sep;
+ else
+ rad = xx->radius - sep;
+ GetCurveAngles( &a0, &a1, trk );
+ if ( newTrkR ) {
+ *newTrkR = NewCurvedTrack( xx->pos, rad, a0, a1, 0 );
+ xx1 = GetTrkExtraData(*newTrkR);
+ xx1->helixTurns = xx->helixTurns;
+ xx1->circle = xx->circle;
+ ComputeCurveBoundingBox( *newTrkR, xx1 );
+ } else {
+ if ( xx->helixTurns > 0) {
+ a0 = 0;
+ a1 = 360.0;
+ }
+ tempSegs(0).color = wDrawColorBlack;
+ tempSegs(0).width = 0;
+ tempSegs_da.cnt = 1;
+ tempSegs(0).type = SEG_CRVTRK;
+ tempSegs(0).u.c.center = xx->pos;
+ tempSegs(0).u.c.radius = rad;
+ tempSegs(0).u.c.a0 = a0;
+ tempSegs(0).u.c.a1 = a1;
+ }
+ if ( p0R ) PointOnCircle( p0R, xx->pos, rad, a0 );
+ if ( p1R ) PointOnCircle( p1R, xx->pos, rad, a0+a1 );
+ return TRUE;
+}
+
+
+static trackCmd_t curveCmds = {
+ "CURVE",
+ DrawCurve,
+ DistanceCurve,
+ DescribeCurve,
+ DeleteCurve,
+ WriteCurve,
+ ReadCurve,
+ MoveCurve,
+ RotateCurve,
+ RescaleCurve,
+ NULL,
+ GetAngleCurve,
+ SplitCurve,
+ TraverseCurve,
+ EnumerateCurve,
+ NULL, /* redraw */
+ TrimCurve,
+ MergeCurve,
+ ModifyCurve,
+ GetLengthCurve,
+ GetParamsCurve,
+ MoveEndPtCurve,
+ QueryCurve,
+ NULL, /* ungroup */
+ FlipCurve,
+ NULL,
+ NULL,
+ NULL,
+ MakeParallelCurve };
+
+
+EXPORT void CurveSegProc(
+ segProc_e cmd,
+ trkSeg_p segPtr,
+ segProcData_p data )
+{
+ ANGLE_T a0, a1, a2;
+ DIST_T d, circum, d0;
+ coOrd p0;
+ wIndex_t s0, s1;
+
+ switch (cmd) {
+
+ case SEGPROC_TRAVERSE1:
+ a1 = FindAngle( segPtr->u.c.center, data->traverse1.pos );
+ a1 += (segPtr->u.c.radius>0?90.0:-90.0);
+ a2 = NormalizeAngle( data->traverse1.angle+a1 );
+ data->traverse1.backwards = (a2 < 270 && a2 > 90 );
+ a2 = FindAngle( segPtr->u.c.center, data->traverse1.pos );
+ if ( data->traverse1.backwards == (segPtr->u.c.radius<0) ) {
+ a2 = NormalizeAngle( a2-segPtr->u.c.a0 );
+ } else {
+ a2 = NormalizeAngle( segPtr->u.c.a0+segPtr->u.c.a1-a2 );
+ }
+ data->traverse1.dist = a2/360.0*2*M_PI*fabs(segPtr->u.c.radius);
+ break;
+
+ case SEGPROC_TRAVERSE2:
+ circum = 2*M_PI*segPtr->u.c.radius;
+ if ( circum < 0 )
+ circum = - circum;
+ d = segPtr->u.c.a1/360.0*circum;
+ if ( d > data->traverse2.dist ) {
+ a2 = (data->traverse2.dist)/circum*360.0;
+ if ( data->traverse2.segDir == (segPtr->u.c.radius<0) ) {
+ a2 = NormalizeAngle( segPtr->u.c.a0+a2 );
+ a1 = a2+90;
+ } else {
+ a2 = NormalizeAngle( segPtr->u.c.a0+segPtr->u.c.a1-a2 );
+ a1 = a2-90;
+ }
+ PointOnCircle( &data->traverse2.pos, segPtr->u.c.center, fabs(segPtr->u.c.radius), a2 );
+ data->traverse2.dist = 0;
+ data->traverse2.angle = a1;
+ } else {
+ data->traverse2.dist -= d;
+ }
+ break;
+
+ case SEGPROC_DRAWROADBEDSIDE:
+ REORIGIN( p0, segPtr->u.c.center, data->drawRoadbedSide.angle, data->drawRoadbedSide.orig );
+ d0 = segPtr->u.c.radius;
+ if ( d0 > 0 ) {
+ a0 = NormalizeAngle( segPtr->u.c.a0 + segPtr->u.c.a1*data->drawRoadbedSide.first/32.0 + data->drawRoadbedSide.angle );
+ } else {
+ d0 = -d0;
+ a0 = NormalizeAngle( segPtr->u.c.a0 + segPtr->u.c.a1*(32-data->drawRoadbedSide.last)/32.0 + data->drawRoadbedSide.angle );
+ }
+ a1 = segPtr->u.c.a1*(data->drawRoadbedSide.last-data->drawRoadbedSide.first)/32.0;
+ if (data->drawRoadbedSide.side>0)
+ d0 += data->drawRoadbedSide.roadbedWidth/2.0;
+ else
+ d0 -= data->drawRoadbedSide.roadbedWidth/2.0;
+ DrawArc( data->drawRoadbedSide.d, p0, d0, a0, a1, FALSE, data->drawRoadbedSide.rbw, data->drawRoadbedSide.color );
+ break;
+
+ case SEGPROC_DISTANCE:
+ data->distance.dd = CircleDistance( &data->distance.pos1, segPtr->u.c.center, fabs(segPtr->u.c.radius), segPtr->u.c.a0, segPtr->u.c.a1 );
+ break;
+
+ case SEGPROC_FLIP:
+ segPtr->u.c.radius = - segPtr->u.c.radius;
+ break;
+
+ case SEGPROC_NEWTRACK:
+ data->newTrack.trk = NewCurvedTrack( segPtr->u.c.center, fabs(segPtr->u.c.radius), segPtr->u.c.a0, segPtr->u.c.a1, 0 );
+ data->newTrack.ep[0] = (segPtr->u.c.radius>0?0:1);
+ data->newTrack.ep[1] = 1-data->newTrack.ep[0];
+ break;
+
+ case SEGPROC_LENGTH:
+ data->length.length = fabs(segPtr->u.c.radius) * segPtr->u.c.a1 * (2.0*M_PI/360.0);
+ break;
+
+ case SEGPROC_SPLIT:
+ d = segPtr->u.c.a1/360.0 * 2*M_PI * fabs(segPtr->u.c.radius);
+ a2 = FindAngle( segPtr->u.c.center, data->split.pos );
+ a2 = NormalizeAngle( a2 - segPtr->u.c.a0 );
+ if ( a2 > segPtr->u.c.a1 ) {
+ if ( a2-segPtr->u.c.a1 < (360-segPtr->u.c.a1)/2.0 )
+ a2 = segPtr->u.c.a1;
+ else
+ a2 = 0.0;
+ }
+ s0 = 0;
+ if ( segPtr->u.c.radius<0 )
+ s0 = 1-s0;
+ s1 = 1-s0;
+ data->split.length[s0] = a2/360.0 * 2*M_PI * fabs(segPtr->u.c.radius);
+ data->split.length[s1] = d-data->split.length[s0];
+ data->split.newSeg[0] = *segPtr;
+ data->split.newSeg[1] = *segPtr;
+ data->split.newSeg[s0].u.c.a1 = a2;
+ data->split.newSeg[s1].u.c.a0 = NormalizeAngle( data->split.newSeg[s1].u.c.a0 + a2 );
+ data->split.newSeg[s1].u.c.a1 -= a2;
+ break;
+
+ case SEGPROC_GETANGLE:
+ data->getAngle.angle = NormalizeAngle( FindAngle( data->getAngle.pos, segPtr->u.c.center ) + 90 );
+ break;
+ }
+}
+
+
+/****************************************
+ *
+ * GRAPHICS COMMANDS
+ *
+ */
+
+
+
+EXPORT void PlotCurve(
+ long mode,
+ coOrd pos0,
+ coOrd pos1,
+ coOrd pos2,
+ curveData_t * curveData,
+ BOOL_T constrain )
+{
+ DIST_T d0, d2, r;
+ ANGLE_T angle, a0, a1, a2;
+ coOrd posx;
+
+ switch ( mode ) {
+ case crvCmdFromEP1:
+ angle = FindAngle( pos0, pos1 );
+ d0 = FindDistance( pos0, pos2 )/2.0;
+ a0 = FindAngle( pos0, pos2 );
+ a1 = NormalizeAngle( a0 - angle );
+LOG( log_curve, 3, ( "P1 = [%0.3f %0.3f] D=%0.3f A0=%0.3f A1=%0.3f\n", pos2.x, pos2.y, d0, a0, a1 ) )
+ if (fabs(d0*sin(D2R(a1))) < (4.0/75.0)*mainD.scale) {
+LOG( log_curve, 3, ( "Straight: %0.3f < %0.3f\n", d0*sin(D2R(a1)), (4.0/75.0)*mainD.scale ) )
+ curveData->pos1.x = pos0.x + d0*2.0*sin(D2R(angle));
+ curveData->pos1.y = pos0.y + d0*2.0*cos(D2R(angle));
+ curveData->type = curveTypeStraight;
+ } else if (a1 >= 179.0 && a1 <= 181.0) {
+ curveData->type = curveTypeNone;
+ } else {
+ if (a1<180.0) {
+ a2 = NormalizeAngle( angle + 90.0 );
+ if (constrain)
+ curveData->curveRadius = ConstrainR( d0/sin(D2R(a1)) );
+ else
+ curveData->curveRadius = d0/sin(D2R(a1));
+ } else {
+ a1 -= 360.0;
+ a2 = NormalizeAngle( angle - 90.0 );
+ if (constrain)
+ curveData->curveRadius = ConstrainR( d0/sin(D2R(-a1)) );
+ else
+ curveData->curveRadius = d0/sin(D2R(-a1));
+ }
+ if (curveData->curveRadius > 1000) {
+ LOG( log_curve, 3, ( "Straight %0.3f > 1000\n", curveData->curveRadius ) )
+ curveData->pos1.x = pos0.x + d0*2.0*sin(D2R(angle));
+ curveData->pos1.y = pos0.y + d0*2.0*cos(D2R(angle));
+ curveData->type = curveTypeStraight;
+ } else {
+ curveData->curvePos.x = pos0.x + curveData->curveRadius*sin(D2R(a2));
+ curveData->curvePos.y = pos0.y + curveData->curveRadius*cos(D2R(a2));
+ LOG( log_curve, 3, ( "Center = [%0.3f %0.3f] A1=%0.3f A2=%0.3f R=%0.3f\n",
+ curveData->curvePos.x, curveData->curvePos.y, a1, a2, curveData->curveRadius ) )
+ if (a1 > 0.0) {
+ curveData->a0 = NormalizeAngle( a2-180 );
+ curveData->a1 = a1 * 2.0;
+ } else {
+ curveData->a1 = (-a1) * 2.0;
+ curveData->a0 = NormalizeAngle( a2-180-curveData->a1 );
+ }
+ curveData->type = curveTypeCurve;
+ }
+ }
+ break;
+ case crvCmdFromTangent:
+ case crvCmdFromCenter:
+ if ( mode == crvCmdFromCenter ) {
+ curveData->curvePos = pos0;
+ curveData->pos1 = pos1;
+ } else {
+ curveData->curvePos = pos1;
+ curveData->pos1 = pos0;
+ }
+ curveData->curveRadius = FindDistance( pos0, pos1 );
+ a0 = FindAngle( curveData->curvePos, curveData->pos1 );
+ a1 = FindAngle( curveData->curvePos, pos2 );
+ if ( NormalizeAngle(a1-a0) < 180 ) {
+ curveData->a0 = a0;
+ curveData->a1 = NormalizeAngle(a1-a0);
+ } else {
+ curveData->a0 = a1;
+ curveData->a1 = NormalizeAngle(a0-a1);
+ }
+ curveData->type = curveTypeCurve;
+ break;
+ case crvCmdFromChord:
+ curveData->pos1 = pos1;
+ curveData->type = curveTypeStraight;
+ a0 = FindAngle( pos1, pos0 );
+ d0 = FindDistance( pos0, pos1 )/2.0;
+ Rotate( &pos2, pos1, -a0 );
+ pos2.x -= pos1.x;
+ if ( fabs(pos2.x) < 0.01 )
+ break;
+ d2 = sqrt( d0*d0 + pos2.x*pos2.x )/2.0;
+ r = d2*d2*2.0/pos2.x;
+ if ( r > 1000.0 )
+ break;
+ posx.x = (pos1.x+pos0.x)/2.0;
+ posx.y = (pos1.y+pos0.y)/2.0;
+ a0 -= 90.0;
+ LOG( log_curve, 3, ( "CHORD: [%0.3f %0.3f] [%0.3f %0.3f] [%0.3f %0.3f] A0=%0.3f D0=%0.3f D2=%0.3f R=%0.3f\n", pos0.x, pos0.y, pos1.x, pos1.y, pos2.x, pos2.y, a0, d0, d2, r ) )
+ Translate( &curveData->curvePos, posx, a0, r-pos2.x );
+ curveData->curveRadius = fabs(r);
+ a0 = FindAngle( curveData->curvePos, pos0 );
+ a1 = FindAngle( curveData->curvePos, pos1 );
+ if ( r > 0 ) {
+ curveData->a0 = a0;
+ curveData->a1 = NormalizeAngle(a1-a0);
+ } else {
+ curveData->a0 = a1;
+ curveData->a1 = NormalizeAngle(a0-a1);
+ }
+ curveData->type = curveTypeCurve;
+ break;
+ }
+}
+
+EXPORT track_p NewCurvedTrack( coOrd pos, DIST_T r, ANGLE_T a0, ANGLE_T a1, long helixTurns )
+{
+ struct extraData *xx;
+ track_p p;
+ p = NewTrack( 0, T_CURVE, 2, sizeof *xx );
+ xx = GetTrkExtraData(p);
+ xx->pos = pos;
+ xx->radius = r;
+ xx->helixTurns = helixTurns;
+ if ( helixTurns <= 0 )
+ SetTrkBits( p, TB_HIDEDESC );
+ SetCurveAngles( p, a0, a1, xx );
+LOG( log_curve, 1, ( "NewCurvedTrack( %0.3f, %0.3f, %0.3f ) = %d\n", pos.x, pos.y, r, GetTrkIndex(p) ) )
+ ComputeCurveBoundingBox( p, xx );
+ CheckTrackLength( p );
+ return p;
+}
+
+
+
+EXPORT void InitTrkCurve( void )
+{
+ T_CURVE = InitObject( &curveCmds );
+ log_curve = LogFindIndex( "curve" );
+}