/** \file tcornu.c * * CORNU SPIRAL TRACK * * A Cornu is a spiral arc defined by a polynomial that has the property * that curvature varies linearly with distance along the curve. It is a family * of curves that include Euler spirals. * * In order to be useful in XtrkCAD it is defined as a set of Bezier curves each of * which is defined as a set of circular arcs and lines. * * The derivation of the Beziers is done by the Cornu library which must be recalled * whenever a change is made in the end conditions. * * A cornu has a minimum radius and a maximum rate of change of radius. * * Acknowledgment is given to Dr. Raph Levien whose seminal PhD work on Cornu curves and * generous open-sourcing of his libraries both inspired and powers this function. * * * XTrkCad - Model Railroad CAD * * 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 "draw.h" #include "cbezier.h" #include "tbezier.h" #include "tcornu.h" #include "ccornu.h" #include "ccurve.h" #include "cstraigh.h" #include "cjoin.h" #include "common.h" #include "param.h" #include "cundo.h" #include "layout.h" #include "fileio.h" EXPORT TRKTYP_T T_CORNU = -1; static int log_cornu = 0; static DIST_T GetLengthCornu( track_p ); /**************************************** * * UTILITIES * */ /* * Run after any changes to the Cornu points */ void SetUpCornuParmFromTracks(track_p trk[2],cornuParm_t * cp, struct extraDataCornu_t* xx) { if (!trk[0]) { cp->center[0] = xx->c[0]; cp->angle[0] = xx->a[0]; cp->radius[0] = xx->r[0]; } if (!trk[1]) { cp->center[1] = xx->c[1]; cp->angle[1] = xx->a[1]; cp->radius[1] = xx->r[1]; } } EXPORT BOOL_T FixUpCornu(coOrd pos[2], track_p trk[2], EPINX_T ep[2], struct extraDataCornu_t* xx) { cornuParm_t cp; SetUpCornuParmFromTracks(trk,&cp,xx); if (!CallCornu(pos, trk, ep, &xx->arcSegs, &cp)) return FALSE; xx->r[0] = cp.radius[0]; if (cp.radius[0]==0) { xx->a[0] = cp.angle[0]; } else { xx->c[0] = cp.center[0]; } xx->r[1] = cp.radius[1]; if (cp.radius[1]==0) { xx->a[1] = cp.angle[1]; } else { xx->c[1] = cp.center[1]; } xx->minCurveRadius = CornuMinRadius(pos,xx->arcSegs); xx->windingAngle = CornuTotalWindingArc(pos,xx->arcSegs); DIST_T last_c; if (xx->r[0] == 0) last_c = 0; else last_c = 1/xx->r[0]; xx->maxRateofChange = CornuMaxRateofChangeofCurvature(pos,xx->arcSegs,&last_c); xx->length = CornuLength(pos, xx->arcSegs); return TRUE; } EXPORT BOOL_T FixUpCornu0(coOrd pos[2],coOrd center[2],ANGLE_T angle[2],DIST_T radius[2],struct extraDataCornu_t* xx) { DIST_T last_c; if (!CallCornu0(pos, center, angle, radius,&xx->arcSegs,FALSE)) return FALSE; xx->minCurveRadius = CornuMinRadius(pos, xx->arcSegs); if (xx->r[0] == 0) last_c = 0; else last_c = 1/xx->r[0]; xx->maxRateofChange = CornuMaxRateofChangeofCurvature(pos,xx->arcSegs,&last_c); xx->length = CornuLength(pos, xx->arcSegs); xx->windingAngle = CornuTotalWindingArc(pos,xx->arcSegs); return TRUE; } EXPORT char * CreateSegPathList(track_p trk) { char * cp = "\0\0"; if (GetTrkType(trk) != T_CORNU) return cp; struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t); if (xx->cornuPath) MyFree(xx->cornuPath); xx->cornuPath = MyMalloc(xx->arcSegs.cnt+2); int j= 0; for (int i = 0;iarcSegs.cnt;i++,j++) { xx->cornuPath[j] = i+1; } xx->cornuPath[j] = cp[0]; xx->cornuPath[j+1] = cp[0]; return xx->cornuPath; } static void GetCornuAngles( ANGLE_T *a0, ANGLE_T *a1, track_p trk ) { assert( trk != NULL ); *a0 = NormalizeAngle( GetTrkEndAngle(trk,0) ); *a1 = NormalizeAngle( GetTrkEndAngle(trk,1) ); LOG( log_cornu, 4, ( "getCornuAngles: = %0.3f %0.3f\n", *a0, *a1 ) ) } static void ComputeCornuBoundingBox( track_p trk, struct extraDataCornu_t * xx ) { coOrd orig, size; GetSegBounds(zero,0,xx->arcSegs.cnt,xx->arcSegs.ptr, &orig, &size); coOrd hi, lo; lo.x = orig.x; lo.y = orig.y; hi.x = orig.x+size.x; hi.y = orig.y+size.y; SetBoundingBox( trk, hi, lo ); } DIST_T CornuDescriptionDistance( coOrd pos, track_p trk, coOrd * dpos, BOOL_T show_hidden, BOOL_T * hidden) { coOrd p1; if (hidden) *hidden = FALSE; if ( GetTrkType( trk ) != T_CORNU || ((( GetTrkBits( trk ) & TB_HIDEDESC ) != 0) && !show_hidden) ) return DIST_INF; struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t); if (( GetTrkBits( trk ) & TB_HIDEDESC ) != 0) xx->descriptionOff = zero; coOrd end0, end0off, end1, end1off; end0 = xx->pos[0]; end1 = xx->pos[1]; ANGLE_T a; a = FindAngle(end0,end1); Translate(&end0off,end0,a+90,xx->descriptionOff.y); Translate(&end1off,end1,a+90,xx->descriptionOff.y); p1.x = (end1off.x - end0off.x)*(xx->descriptionOff.x+0.5) + end0off.x; p1.y = (end1off.y - end0off.y)*(xx->descriptionOff.x+0.5) + end0off.y; if (hidden) *hidden = (GetTrkBits( trk ) & TB_HIDEDESC); *dpos = p1; coOrd tpos = pos; if (DistanceCornu(trk,&tpos)pos[0]; epos1 = xx->pos[1]; ANGLE_T a = FindAngle(epos0,epos1); Translate(&offpos0,epos0,a+90,xx->descriptionOff.y); Translate(&offpos1,epos1,a+90,xx->descriptionOff.y); fp = wStandardFont( F_TIMES, FALSE, FALSE ); sprintf( message, _("Cornu: L %s A %0.3f L %s MinR %s"), FormatDistance(FindDistance(xx->pos[0], xx->pos[1])), FindAngle(xx->pos[0], xx->pos[1]), FormatDistance(xx->length), FormatDistance((xx->minCurveRadius>=DIST_INF)?0.0:xx->minCurveRadius)); DrawLine(d,xx->pos[0],offpos0,0,color); DrawLine(d,xx->pos[1],offpos1,0,color); DrawDimLine( d, offpos0, offpos1, message, (wFontSize_t)descriptionFontSize, xx->descriptionOff.x+0.5, 0, color, 0x00 ); if (GetTrkBits( trk ) & TB_DETAILDESC) { coOrd details_pos; details_pos.x = (offpos1.x - offpos0.x)*(xx->descriptionOff.x+0.5) + offpos0.x; details_pos.y = (offpos1.y - offpos0.y)*(xx->descriptionOff.x+0.5) + offpos0.y-(2*descriptionFontSize/mainD.dpi); AddTrkDetails(d, trk, details_pos, xx->length, color); } } STATUS_T CornuDescriptionMove( track_p trk, wAction_t action, coOrd pos ) { static coOrd p0,p1; static BOOL_T editState; if (GetTrkType(trk) != T_CORNU) return C_CONTINUE; struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t); ANGLE_T ap; coOrd end0, end1; end0 = xx->pos[0]; end1 = xx->pos[1]; ap = NormalizeAngle(FindAngle(end0,pos)-FindAngle(end0,end1)); xx->descriptionOff.y = FindDistance(end0,pos)*sin(D2R(ap)); xx->descriptionOff.x = -0.5 + FindDistance(end0,pos)*cos(D2R(ap))/FindDistance(end0,end1); if (xx->descriptionOff.x > 0.5) xx->descriptionOff.x = 0.5; if (xx->descriptionOff.x < -0.5) xx->descriptionOff.x = -0.5; return C_CONTINUE; } /**************************************** * * GENERIC FUNCTIONS * */ static struct { coOrd pos[2]; ANGLE_T angle[2]; DIST_T radius[2]; coOrd center[2]; FLOAT_T elev[2]; FLOAT_T length; FLOAT_T grade; DIST_T minRadius; DIST_T maxRateOfChange; ANGLE_T windingAngle; unsigned int layerNumber; dynArr_t segs; long width; wDrawColor color; } cornData; typedef enum { P0, A0, R0, C0, Z0, P1, A1, R1, C1, Z1, RA, RR, WA, LN, GR, LY } cornuDesc_e; static descData_t cornuDesc[] = { /*P0*/ { DESC_POS, N_("End Pt 1: X,Y"), &cornData.pos[0] }, /*A0*/ { DESC_ANGLE, N_("End Angle"), &cornData.angle[0] }, /*R0*/ { DESC_DIM, N_("Radius "), &cornData.radius[0] }, /*C0*/ { DESC_POS, N_("Center X,Y"), &cornData.center[0] }, /*Z0*/ { DESC_DIM, N_("Z1"), &cornData.elev[0] }, /*P1*/ { DESC_POS, N_("End Pt 2: X,Y"), &cornData.pos[1] }, /*A1*/ { DESC_ANGLE, N_("End Angle"), &cornData.angle[1] }, /*R1*/ { DESC_DIM, N_("Radius"), &cornData.radius[1] }, /*C1*/ { DESC_POS, N_("Center X,Y"), &cornData.center[1] }, /*Z1*/ { DESC_DIM, N_("Z2"), &cornData.elev[1] }, /*RA*/ { DESC_DIM, N_("Minimum Radius"), &cornData.minRadius }, /*RR*/ { DESC_FLOAT, N_("Max Rate Of Curve Change/Scale"), &cornData.maxRateOfChange }, /*WA*/ { DESC_ANGLE, N_("Total Winding Angle"), &cornData.windingAngle }, /*LN*/ { DESC_DIM, N_("Length"), &cornData.length }, /*GR*/ { DESC_FLOAT, N_("Grade"), &cornData.grade }, /*LY*/ { DESC_LAYER, N_("Layer"), &cornData.layerNumber }, { DESC_NULL } }; static void UpdateCornu( track_p trk, int inx, descData_p descUpd, BOOL_T final ) { BOOL_T updateEndPts; EPINX_T ep; cornuParm_t cp; if ( inx == -1 ) return; updateEndPts = FALSE; UndrawNewTrack(trk); struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t); switch ( inx ) { case P0: if (GetTrkEndTrk(trk,0)) break; updateEndPts = TRUE; xx->pos[0] = cornData.pos[0]; Translate(&xx->c[0],xx->pos[0],xx->a[0]+90,xx->r[0]); cornData.center[0] = xx->c[0]; cornuDesc[P0].mode |= DESC_CHANGE; cornuDesc[C0].mode |= DESC_CHANGE; /* no break */ case P1: if (GetTrkEndTrk(trk,1)) break; updateEndPts = TRUE; xx->pos[1]= cornData.pos[1]; Translate(&xx->c[1],xx->pos[1],xx->a[1]-90,xx->r[1]); cornData.center[1] = xx->c[1]; cornuDesc[P1].mode |= DESC_CHANGE; cornuDesc[C1].mode |= DESC_CHANGE; break; case A0: if (GetTrkEndTrk(trk,0)) break; updateEndPts = TRUE; xx->a[0] = cornData.angle[0]; Translate(&xx->c[0],xx->pos[0],xx->a[0]+90,xx->r[0]); cornData.center[0] = xx->c[0]; cornuDesc[A0].mode |= DESC_CHANGE; cornuDesc[C0].mode |= DESC_CHANGE; break; case A1: if (GetTrkEndTrk(trk,1)) break; updateEndPts = TRUE; xx->a[1]= cornData.angle[1]; Translate(&xx->c[1],xx->pos[1],xx->a[1]-90,xx->r[1]); cornData.center[1] = xx->c[1]; cornuDesc[A1].mode |= DESC_CHANGE; cornuDesc[C1].mode |= DESC_CHANGE; break; case C0: if (GetTrkEndTrk(trk,0)) break; //updateEndPts = TRUE; //xx->c[0] = cornData.center[0]; //cornuDesc[C0].mode |= DESC_CHANGE; break; case C1: if (GetTrkEndTrk(trk,1)) break; //updateEndPts = TRUE; //xx->c[1] = cornData.center[1]; //cornuDesc[C1].mode |= DESC_CHANGE; break; case R0: if (GetTrkEndTrk(trk,0)) break; updateEndPts = TRUE; xx->r[0] = fabs(cornData.radius[0]); Translate(&xx->c[0],xx->pos[0],NormalizeAngle(xx->a[0]+90),cornData.radius[0]); cornData.center[0] = xx->c[0]; cornData.radius[0] = fabs(cornData.radius[0]); cornuDesc[R0].mode |= DESC_CHANGE; cornuDesc[C0].mode |= DESC_CHANGE; break; case R1: if (GetTrkEndTrk(trk,1)) break; updateEndPts = TRUE; xx->r[1]= fabs(cornData.radius[1]); Translate(&xx->c[1],xx->pos[1],NormalizeAngle(xx->a[1]-90),cornData.radius[1]); cornData.center[1] = xx->c[1]; cornData.radius[1] = fabs(cornData.radius[1]); cornuDesc[R1].mode |= DESC_CHANGE; cornuDesc[C1].mode |= DESC_CHANGE; break; case Z0: case Z1: ep = (inx==Z0?0:1); UpdateTrkEndElev( trk, ep, GetTrkEndElevUnmaskedMode(trk,ep), cornData.elev[ep], NULL ); ComputeElev( trk, 1-ep, FALSE, &cornData.elev[1-ep], NULL, TRUE ); if ( cornData.length > minLength ) cornData.grade = fabs( (cornData.elev[0]-cornData.elev[1])/cornData.length )*100.0; else cornData.grade = 0.0; cornuDesc[GR].mode |= DESC_CHANGE; cornuDesc[inx==Z0?Z1:Z0].mode |= DESC_CHANGE; return; case LY: SetTrkLayer( trk, cornData.layerNumber); break; default: AbortProg( "updateCornu: Bad inx %d", inx ); } track_p tracks[2]; tracks[0] = GetTrkEndTrk(trk,0); tracks[1] = GetTrkEndTrk(trk,1); if (updateEndPts) { if ( GetTrkEndTrk(trk,0) == NULL ) { SetTrkEndPoint( trk, 0, cornData.pos[0], xx->a[0]); cornuDesc[A0].mode |= DESC_CHANGE; } if ( GetTrkEndTrk(trk,1) == NULL ) { SetTrkEndPoint( trk, 1, cornData.pos[1], xx->a[1]); cornuDesc[A1].mode |= DESC_CHANGE; } } track_p ts[2]; ts[0] = GetTrkEndTrk(trk,0); ts[1] = GetTrkEndTrk(trk,1); SetUpCornuParmFromTracks(ts,&cp,xx); CallCornu0(xx->pos, xx->c, xx->a, xx->r, &xx->arcSegs, FALSE); //FixUpCornu(xx->bezierData.pos, xx, IsTrack(trk)); ComputeCornuBoundingBox(trk, xx); DrawNewTrack( trk ); } static void DescribeCornu( track_p trk, char * str, CSIZE_T len ) { struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t); DIST_T d; d = xx->length; sprintf( str, _("Cornu Track(%d): Layer=%u MinRadius=%s Length=%s EP=[%0.3f,%0.3f] [%0.3f,%0.3f]"), GetTrkIndex(trk), GetTrkLayer(trk)+1, FormatDistance(xx->minCurveRadius), FormatDistance(d), PutDim(xx->pos[0].x),PutDim(xx->pos[0].y), PutDim(xx->pos[1].x),PutDim(xx->pos[1].y) ); cornData.length = xx->length; cornData.minRadius = xx->minCurveRadius; cornData.maxRateOfChange = xx->maxRateofChange*GetScaleRatio(GetLayoutCurScale()); cornData.windingAngle = xx->windingAngle; cornData.layerNumber = GetTrkLayer(trk); cornData.pos[0] = xx->pos[0]; cornData.pos[1] = xx->pos[1]; cornData.angle[0] = xx->a[0]; cornData.angle[1] = xx->a[1]; cornData.center[0] = xx->c[0]; cornData.center[1] = xx->c[1]; cornData.radius[0] = xx->r[0]; cornData.radius[1] = xx->r[1]; if (GetTrkType(trk) == T_CORNU) { ComputeElev( trk, 0, FALSE, &cornData.elev[0], NULL, FALSE ); ComputeElev( trk, 1, FALSE, &cornData.elev[1], NULL, FALSE ); if ( cornData.length > minLength ) cornData.grade = fabs( (cornData.elev[0]-cornData.elev[1])/cornData.length )*100.0; else cornData.grade = 0.0; } BOOL_T trk0 = (GetTrkEndTrk(trk,0)!=NULL); BOOL_T trk1 = (GetTrkEndTrk(trk,1)!=NULL); cornuDesc[P0].mode = !trk0?0:DESC_RO; cornuDesc[P1].mode = !trk1?0:DESC_RO; cornuDesc[LN].mode = DESC_RO; cornuDesc[Z0].mode = EndPtIsDefinedElev(trk,0)?0:DESC_RO; cornuDesc[Z1].mode = EndPtIsDefinedElev(trk,1)?0:DESC_RO; cornuDesc[A0].mode = !trk0?0:DESC_RO; cornuDesc[A1].mode = !trk1?0:DESC_RO; cornuDesc[C0].mode = DESC_RO; cornuDesc[C1].mode = DESC_RO; cornuDesc[R0].mode = !trk0?0:DESC_RO; cornuDesc[R1].mode = !trk1?0:DESC_RO; cornuDesc[GR].mode = DESC_RO; cornuDesc[RA].mode = DESC_RO; cornuDesc[RR].mode = DESC_RO; cornuDesc[WA].mode = DESC_RO; cornuDesc[LY].mode = DESC_NOREDRAW; DoDescribe( _("Cornu Track"), trk, cornuDesc, UpdateCornu ); } DIST_T DistanceCornu( track_p t, coOrd * p ) { struct extraDataCornu_t *xx = GET_EXTRA_DATA(t, T_CORNU, extraDataCornu_t); //return BezierMathDistance(p,xx->bezierData.pos,100, &s); DIST_T d = DIST_INF; coOrd p2 = xx->pos[0]; //Set initial point segProcData_t segProcData; for (int i = 0;iarcSegs.cnt;i++) { trkSeg_t seg = DYNARR_N(trkSeg_t,xx->arcSegs,i); if (seg.type == SEG_FILCRCL) continue; segProcData.distance.pos1 = * p; SegProc(SEGPROC_DISTANCE,&seg,&segProcData); if (segProcData.distance.ddbezierData.pos[0], xx->bezierData.pos[1], xx->bezierData.pos[2], xx->bezierData.pos[1], 100, NULL ); * p = p2; return d; } static void DrawCornu( track_p t, drawCmd_p d, wDrawColor color ) { long widthOptions = DTS_LEFT|DTS_RIGHT; if ( ((d->options&DC_SIMPLE)==0) && (labelWhen == 2 || (labelWhen == 1 && (d->options&DC_PRINT))) && labelScale >= d->scale && ( GetTrkBits( t ) & TB_HIDEDESC ) == 0 ) { DrawCornuDescription( t, d, color ); } DIST_T scale2rail = (d->options&DC_PRINT)?(twoRailScale*2+1):twoRailScale; struct extraDataCornu_t *xx = GET_EXTRA_DATA(t, T_CORNU, extraDataCornu_t); DrawSegsO(d,t,zero,0.0,xx->arcSegs.ptr,xx->arcSegs.cnt, GetTrkGauge(t), color, widthOptions); DrawEndPt( d, t, 0, color ); DrawEndPt( d, t, 1, color ); } void FreeSubSegs(trkSeg_t* s) { 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; s->bezSegs.ptr = NULL; } } static void DeleteCornu( track_p t ) { struct extraDataCornu_t *xx = GET_EXTRA_DATA(t, T_CORNU, extraDataCornu_t); for (int i=0;iarcSegs.cnt;i++) { trkSeg_t s = DYNARR_N(trkSeg_t,xx->arcSegs,i); FreeSubSegs(&s); } if (xx->arcSegs.ptr) MyFree(xx->arcSegs.ptr); xx->arcSegs.max = 0; xx->arcSegs.cnt = 0; xx->arcSegs.ptr = NULL; } static BOOL_T WriteCornu( track_p t, FILE * f ) { long options; BOOL_T rc = TRUE; BOOL_T track =(GetTrkType(t)==T_CORNU); options = GetTrkWidth(t) & 0x0F; struct extraDataCornu_t *xx = GET_EXTRA_DATA(t, T_CORNU, extraDataCornu_t); if ( ( GetTrkBits(t) & TB_HIDEDESC ) == 0 ) options |= 0x80; rc &= fprintf(f, "%s %d %d %ld 0 0 %s %d %0.6f %0.6f %0.6f %0.6f %0.6f %0.6f %0.6f %0.6f %0.6f %0.6f %0.6f %0.6f \n", "CORNU",GetTrkIndex(t), GetTrkLayer(t), (long)options, GetTrkScaleName(t), GetTrkVisible(t)|(GetTrkNoTies(t)?1<<2:0)|(GetTrkBridge(t)?1<<3:0), xx->pos[0].x, xx->pos[0].y, xx->a[0], xx->r[0], xx->c[0].x,xx->c[0].y, xx->pos[1].x, xx->pos[1].y, xx->a[1], xx->r[1], xx->c[1].x,xx->c[1].y )>0; if (track) { rc &= WriteEndPt( f, t, 0 ); rc &= WriteEndPt( f, t, 1 ); } rc &= WriteSegs( f, xx->arcSegs.cnt, xx->arcSegs.ptr ); return rc; } static BOOL_T ReadCornu( char * line ) { struct extraDataCornu_t *xx; track_p t; wIndex_t index; BOOL_T visible; DIST_T r0,r1; ANGLE_T a0,a1; coOrd p0, p1, c0, c1; char scale[10]; wIndex_t layer; long options; char * cp = NULL; if (!GetArgs( line+6, "dLl00sdpffppffp", &index, &layer, &options, scale, &visible, &p0, &a0, &r0, &c0, &p1, &a1, &r1, &c1 ) ) { return FALSE; } if ( !ReadSegs() ) return FALSE; t = NewTrack( index, T_CORNU, 0, sizeof *xx ); SetTrkVisible(t, visible&2); SetTrkNoTies(t, visible&4); SetTrkBridge(t, visible&8); SetTrkScale(t, LookupScale(scale)); SetTrkLayer(t, layer ); SetTrkWidth(t, (int)(options&0x0F)); if ( paramVersion < VERSION_DESCRIPTION2 || ( options & 0x80 ) == 0 ) SetTrkBits(t,TB_HIDEDESC); xx = GET_EXTRA_DATA(t, T_CORNU, extraDataCornu_t); xx->pos[0] = p0; xx->pos[1] = p1; xx->a[0] = a0; xx->r[0] = r0; xx->a[1] = a1; xx->c[0] = c0; xx->c[1] = c1; xx->r[1] = r1; xx->descriptionOff.x = xx->descriptionOff.y = 0.0; FixUpCornu0(xx->pos,xx->c,xx->a, xx->r, xx); ComputeCornuBoundingBox(t,xx); SetEndPts(t,2); return TRUE; } static void MoveCornu( track_p trk, coOrd orig ) { struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t); for (int i=0;i<2;i++) { xx->pos[i].x += orig.x; xx->pos[i].y += orig.y; xx->c[i].x += orig.x; xx->c[i].y += orig.y; } RebuildCornu(trk); } static void RotateCornu( track_p trk, coOrd orig, ANGLE_T angle ) { struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t); for (int i=0;i<2;i++) { Rotate( &xx->pos[i], orig, angle ); Rotate( &xx->c[i], orig, angle); xx->a[i] = NormalizeAngle(xx->a[i]+angle); } RebuildCornu(trk); } static void RescaleCornu( track_p trk, FLOAT_T ratio ) { struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t); for (int i=0;i<2;i++) { xx->pos[i].x *= ratio; xx->pos[i].y *= ratio; xx->c[i].x *= ratio; xx->c[i].y *= ratio; xx->r[i] *= ratio; } RebuildCornu(trk); } EXPORT BOOL_T SetCornuEndPt(track_p trk, EPINX_T inx, coOrd pos, coOrd center, ANGLE_T angle, DIST_T radius) { struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t); xx->pos[inx] = pos; xx->c[inx] = center; xx->a[inx] = angle; xx->r[inx] = radius; if (!RebuildCornu(trk)) return FALSE; SetTrkEndPoint( trk, inx, xx->pos[inx], xx->a[inx]); return TRUE; } void GetCornuParmsNear(track_p t, int sel, coOrd * pos2, coOrd * center, ANGLE_T * angle2, DIST_T * radius ) { coOrd pos = *pos2; double dd = DistanceCornu(t, &pos); //Pos adjusted to be on curve int inx; *radius = 0.0; *angle2 = 0.0; *center = zero; wBool_t back,neg; struct extraDataCornu_t *xx = GET_EXTRA_DATA(t, T_CORNU, extraDataCornu_t); ANGLE_T angle = GetAngleSegs(xx->arcSegs.cnt,(trkSeg_t *)(xx->arcSegs.ptr),&pos,&inx,NULL,&back,NULL,&neg); if (inx == -1) { return; //Error in GetAngle } trkSeg_p segPtr = &DYNARR_N(trkSeg_t, xx->arcSegs, inx); if (segPtr->type == SEG_BEZTRK) { GetAngleSegs(segPtr->bezSegs.cnt,(trkSeg_t *)(segPtr->bezSegs.ptr),&pos,&inx,NULL,&back,NULL,&neg); if (inx ==-1) return; segPtr = &DYNARR_N(trkSeg_t, segPtr->bezSegs, inx); } if (segPtr->type == SEG_STRTRK) { *radius = 0.0; *center = zero; } else if (segPtr->type == SEG_CRVTRK) { *center = segPtr->u.c.center; *radius = fabs(segPtr->u.c.radius); } if (sel) angle = NormalizeAngle(angle+(neg==back?0:180)); else angle = NormalizeAngle(angle+(neg==back?180:0)); *angle2 = angle; *pos2 = pos; } void GetCornuParmsTemp(dynArr_t * array_p, int sel, coOrd * pos2, coOrd * center, ANGLE_T * angle2, DIST_T * radius ) { coOrd pos = *pos2; int inx; wBool_t back,neg; *radius = 0.0; *center = zero; *angle2 = 0.0; ANGLE_T angle = GetAngleSegs(array_p->cnt,(trkSeg_p)array_p->ptr,&pos,&inx,NULL,&back,NULL,&neg); if (inx==-1) return; trkSeg_p segPtr = &DYNARR_N(trkSeg_t, *array_p, inx); if (segPtr->type == SEG_BEZTRK) { GetAngleSegs(segPtr->bezSegs.cnt,(trkSeg_t *)(segPtr->bezSegs.ptr),&pos,&inx,NULL,&back,NULL,&neg); if (inx ==-1) return; segPtr = &DYNARR_N(trkSeg_t, segPtr->bezSegs, inx); } if (segPtr->type == SEG_STRTRK) { *radius = 0.0; *center = zero; } else if (segPtr->type == SEG_CRVTRK) { *center = segPtr->u.c.center; *radius = fabs(segPtr->u.c.radius); } if (sel) angle = NormalizeAngle(angle+(neg==back?0:180)); else angle = NormalizeAngle(angle+(neg==back?180:0)); *angle2 = angle; *pos2 = pos; } /** * Split the Track at approximately the point pos. */ static BOOL_T SplitCornu( track_p trk, coOrd pos, EPINX_T ep, track_p *leftover, EPINX_T * ep0, EPINX_T * ep1 ) { track_p trk1; DIST_T radius = 0.0; coOrd center; int inx,subinx; BOOL_T track; track = IsTrack(trk); cornuParm_t new; double dd = DistanceCornu(trk, &pos); if (dd>minLength) return FALSE; BOOL_T back, neg; struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t); ANGLE_T angle = GetAngleSegs(xx->arcSegs.cnt,(trkSeg_t *)(xx->arcSegs.ptr),&pos,&inx,NULL,&back,&subinx,&neg); if (inx == -1) return FALSE; trkSeg_p segPtr = &DYNARR_N(trkSeg_t, xx->arcSegs, inx); if (subinx != -1) { segPtr = &DYNARR_N(trkSeg_t, segPtr->bezSegs, subinx); } if (segPtr->type == SEG_STRTRK) { radius = 0.0; center = zero; } else if (segPtr->type == SEG_CRVTRK) { center = segPtr->u.c.center; radius = fabs(segPtr->u.c.radius); } if (ep) { new.pos[0] = pos; new.pos[1] = xx->pos[1]; new.angle[0] = NormalizeAngle(angle+(neg==back?180:0)); new.angle[1] = xx->a[1]; new.center[0] = center; new.center[1] = xx->c[1]; new.radius[0] = radius; new.radius[1] = xx->r[1]; } else { new.pos[1] = pos; new.pos[0] = xx->pos[0]; new.angle[1] = NormalizeAngle(angle+(neg==back?0:180)); new.angle[0] = xx->a[0]; new.center[1] = center; new.center[0] = xx->c[0]; new.radius[1] = radius; new.radius[0] = xx->r[0]; } trk1 = NewCornuTrack(new.pos,new.center,new.angle,new.radius,NULL,0); //Copy elevation details from old ep to new ep 0/1 if (trk1==NULL) { wBeep(); InfoMessage(_("Cornu Create Failed for p1[%0.3f,%0.3f] p2[%0.3f,%0.3f], c1[%0.3f,%0.3f] c2[%0.3f,%0.3f], a1=%0.3f a2=%0.3f, r1=%s r2=%s"), new.pos[0].x,new.pos[0].y, new.pos[1].x,new.pos[1].y, new.center[0].x,new.center[0].y, new.center[1].x,new.center[1].y, new.angle[0],new.angle[1], FormatDistance(new.radius[0]),FormatDistance(new.radius[1])); UndoEnd(); return FALSE; } DIST_T height; int opt; GetTrkEndElev(trk,ep,&opt,&height); UpdateTrkEndElev( trk1, ep, opt, height, (opt==ELEV_STATION)?GetTrkEndElevStation(trk,ep):NULL ); UndoModify(trk); xx->pos[ep] = pos; xx->a[ep] = NormalizeAngle(new.angle[1-ep]+180); xx->r[ep] = new.radius[1-ep]; xx->c[ep] = new.center[1-ep]; //Wipe out old elevation for ep1 RebuildCornu(trk); SetTrkEndPoint(trk, ep, xx->pos[ep], xx->a[ep]); UpdateTrkEndElev( trk, ep, ELEV_NONE, 0, NULL); *leftover = trk1; *ep0 = 1-ep; //Which end is for new on pos? *ep1 = ep; //Which end is for old trk? return TRUE; } BOOL_T MoveCornuEndPt ( track_p *trk, EPINX_T *ep, coOrd pos, DIST_T d0 ) { track_p trk2; if (SplitTrack(*trk,pos,*ep,&trk2,TRUE)) { if (trk2) { UndrawNewTrack( trk2 ); DeleteTrack(trk2,TRUE); } struct extraDataCornu_t *xx = GET_EXTRA_DATA(*trk, T_CORNU, extraDataCornu_t); SetTrkEndPoint( *trk, *ep, *ep?xx->pos[1]:xx->pos[0], *ep?xx->a[1]:xx->a[0] ); DrawNewTrack( *trk ); return TRUE; } return FALSE; } static int log_traverseCornu = 0; /* * TraverseCornu 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. * * The entire Cornu may be reversed or forwards depending on the way it was drawn. * * Each Bezier segment within that Cornu structure therefore may be processed forwards or in reverse. * 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 TraverseCornu( traverseTrack_p trvTrk, DIST_T * distR ) { track_p trk = trvTrk->trk; DIST_T dist = *distR; segProcData_t segProcData; BOOL_T cornu_backwards= FALSE; BOOL_T neg = FALSE; DIST_T d = DIST_INF; coOrd pos1 = trvTrk->pos, pos2 = trvTrk->pos; ANGLE_T a1,a2; int inx, segInx = 0; EPINX_T ep; BOOL_T back; LOG( log_traverseCornu, 1, ( "TravCornu-In [%0.3f %0.3f] A%0.3f D%0.3f \n", trvTrk->pos.x, trvTrk->pos.y, trvTrk->angle, *distR )) struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t); trkSeg_p segPtr = (trkSeg_p)xx->arcSegs.ptr; a2 = GetAngleSegs( //Find correct Segment and nearest point in it xx->arcSegs.cnt,segPtr, &pos2, &segInx, &d , &back , NULL, &neg); //d = how far pos2 from old pos2 = trvTrk->pos if ( d > 10 ) { ErrorMessage( "traverseCornu: 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); //If reverse segs - reverse angle 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) cornu_backwards = TRUE; ep = 0; } else { cornu_backwards = FALSE; ep = 1; } if (neg) { cornu_backwards = !cornu_backwards; //Reversed direction ep = 1-ep; } segProcData.traverse1.pos = pos2; //actual point on curve segProcData.traverse1.angle = trvTrk->angle; //direction car is going for Traverse 1 LOG( log_traverseCornu, 1, ( " TravCornu-GetSubA A%0.3f I%d N%d B%d CB%d\n", a2, segInx, neg, back, cornu_backwards )) inx = segInx; while (inx >=0 && inxarcSegs.cnt) { segPtr = (trkSeg_p)xx->arcSegs.ptr+inx; //move in to the identified Bezier segment SegProc( SEGPROC_TRAVERSE1, segPtr, &segProcData ); BOOL_T backwards = segProcData.traverse1.backwards; //do we process this segment backwards? BOOL_T reverse_seg = segProcData.traverse1.reverse_seg; //Info only int BezSegInx = segProcData.traverse1.BezSegInx; //Which subSeg was it? BOOL_T segs_backwards = segProcData.traverse1.segs_backwards; dist += segProcData.traverse1.dist; //Add in the part of the Bezier to get to pos segProcData.traverse2.dist = dist; //Set up Traverse2 segProcData.traverse2.segDir = backwards; segProcData.traverse2.BezSegInx = BezSegInx; segProcData.traverse2.segs_backwards = segs_backwards; LOG( log_traverseCornu, 2, ( " TravCornu-Tr1 SI%d D%0.3f B%d RS%d \n", BezSegInx, 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; //Use finishing pos trvTrk->angle = segProcData.traverse2.angle; //Use finishing angle LOG( log_traverseCornu, 1, ( "TravCornu-Ex1 -> [%0.3f %0.3f] A%0.3f D%0.3f\n", trvTrk->pos.x, trvTrk->pos.y, trvTrk->angle, *distR ) ) return TRUE; } dist = segProcData.traverse2.dist; //How far left? coOrd pos = segProcData.traverse2.pos; //Will always be at a Bezseg end ANGLE_T angle = segProcData.traverse2.angle; //Angle of end therefore segProcData.traverse1.angle = angle; //Set up Traverse1 segProcData.traverse1.pos = pos; inx = cornu_backwards?inx-1:inx+1; //Here's where the global segment direction comes in LOG( log_traverseCornu, 2, ( " TravCornu-Loop D%0.3f A%0.3f I%d \n", dist, angle, inx ) ) } //Ran out of Bez-Segs so punt to next Track *distR = dist; //Tell caller what dist is left trvTrk->pos = GetTrkEndPos(trk,ep); //Which end were we heading for? trvTrk->angle = NormalizeAngle(GetTrkEndAngle(trk, ep)); //+(cornu_backwards?180:0)); trvTrk->trk = GetTrkEndTrk(trk,ep); //go onto next track (or NULL) if (trvTrk->trk==NULL) { trvTrk->pos = pos1; return TRUE; } LOG( log_traverseCornu, 1, ( "TravCornu-Ex2 --> [%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 EnumerateCornu( track_p trk ) { if (trk != NULL) { struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t); DIST_T d; d = max(CornuOffsetLength(xx->arcSegs,-GetTrkGauge(trk)/2.0), CornuOffsetLength(xx->arcSegs,GetTrkGauge(trk)/2.0)); ScaleLengthIncrement( GetTrkScale(trk), d ); return TRUE; } return FALSE; } static BOOL_T MergeCornu( track_p trk0, EPINX_T ep0, track_p trk1, EPINX_T ep1 ) { return FALSE; } BOOL_T GetBezierSegmentsFromCornu(track_p trk, dynArr_t * segs, BOOL_T track) { struct extraDataCornu_t * xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t); for (int i=0;iarcSegs.cnt;i++) { trkSeg_p p = (trkSeg_t *) xx->arcSegs.ptr+i; if (p->type == SEG_BEZTRK) { if (track) { DYNARR_APPEND(trkSeg_t, * segs, 10); trkSeg_p segPtr = &DYNARR_N(trkSeg_t,* segs,segs->cnt-1); segPtr->type = SEG_BEZTRK; segPtr->color = wDrawColorBlack; segPtr->width = 0; if (segPtr->bezSegs.ptr) MyFree(segPtr->bezSegs.ptr); segPtr->bezSegs.cnt = 0; segPtr->bezSegs.max = 0; segPtr->bezSegs.ptr = NULL; for (int j=0;j<4;j++) segPtr->u.b.pos[j] = p->u.b.pos[j]; FixUpBezierSeg(segPtr->u.b.pos,segPtr,TRUE); } else { for (int j=0;jbezSegs.cnt;j++) { trkSeg_p bez_p = &DYNARR_N(trkSeg_t,p->bezSegs,j); DYNARR_APPEND(trkSeg_t, * segs, 10); trkSeg_p segPtr = &DYNARR_LAST(trkSeg_t,* segs); if (bez_p->type == SEG_CRVTRK) segPtr->type = SEG_CRVLIN; if (bez_p->type == SEG_STRTRK) segPtr->type = SEG_STRLIN; segPtr->u = bez_p->u; segPtr->width = bez_p->width; segPtr->color = bez_p->color; } } } else if (p->type == SEG_STRTRK) { DYNARR_APPEND(trkSeg_t, * segs, 1); trkSeg_p segPtr = &DYNARR_N(trkSeg_t,* segs,segs->cnt-1); segPtr->type = track?SEG_STRTRK:SEG_STRLIN; segPtr->color = wDrawColorBlack; segPtr->width = 0; for (int j=0;j<2;j++) segPtr->u.l.pos[i] = p->u.l.pos[i]; segPtr->u.l.angle = p->u.l.angle; segPtr->u.l.option = 0; } else if (p->type == SEG_CRVTRK) { DYNARR_APPEND(trkSeg_t, * segs, 1); trkSeg_p segPtr = &DYNARR_N(trkSeg_t,* segs,segs->cnt-1); segPtr->type = track?SEG_CRVTRK:SEG_CRVLIN; segPtr->color = wDrawColorBlack; segPtr->width = 0; segPtr->u.c.a0 = p->u.c.a0; segPtr->u.c.a1 = p->u.c.a1; segPtr->u.c.center = p->u.c.center; segPtr->u.c.radius = p->u.c.radius; } } return TRUE; } static DIST_T GetLengthCornu( track_p trk ) { struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t); DIST_T length = 0.0; segProcData_t segProcData; for(int i=0;iarcSegs.cnt;i++) { trkSeg_t seg = DYNARR_N(trkSeg_t,xx->arcSegs,i); if (seg.type == SEG_FILCRCL) continue; SegProc(SEGPROC_LENGTH, &seg, &segProcData); length += segProcData.length.length; } return length; } EXPORT BOOL_T GetCornuMiddle( track_p trk, coOrd * pos) { if (GetTrkType(trk) != T_CORNU) return FALSE; DIST_T length = GetLengthCornu(trk)/2; traverseTrack_t tp; tp.pos = GetTrkEndPos(trk,0); tp.angle = NormalizeAngle(GetTrkEndAngle(trk,0)+180.0); tp.trk = trk; tp.length = length; TraverseCornu(&tp,&length); *pos = tp.pos; return TRUE; } static BOOL_T GetParamsCornu( int inx, track_p trk, coOrd pos, trackParams_t * params ) { int segInx, segInx2; BOOL_T back, negative; DIST_T d; struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t); params->type = curveTypeCornu; params->track_angle = GetAngleSegs( //Find correct Segment and nearest point in it xx->arcSegs.cnt,xx->arcSegs.ptr, &pos, &segInx, &d , &back, &segInx2, &negative ); if (segInx ==-1) return FALSE; trkSeg_p segPtr = &DYNARR_N(trkSeg_t,xx->arcSegs,segInx); if (negative != back) params->track_angle = NormalizeAngle(params->track_angle+180); //Cornu is in reverse if (segPtr->type == SEG_STRTRK) { params->arcR = 0.0; } else if (segPtr->type == SEG_CRVTRK) { 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; } else if (segPtr->type == SEG_BEZTRK) { trkSeg_p segPtr2 = &DYNARR_N(trkSeg_t,segPtr->bezSegs,segInx2); if (segPtr2->type == SEG_STRTRK) { params->arcR = 0.0; } else if (segPtr2->type == SEG_CRVTRK) { params->arcR = fabs(segPtr2->u.c.radius); params->arcP = segPtr2->u.c.center; params->arcA0 = segPtr2->u.c.a0; params->arcA1 = segPtr2->u.c.a1; } } for (int i=0;i<2;i++) { params->cornuEnd[i] = xx->pos[i]; params->cornuAngle[i] = xx->a[i]; params->cornuRadius[i] = xx->r[i]; params->cornuCenter[i] = xx->c[i]; } params->len = xx->length; if ( inx == PARAMS_NODES ) { return FALSE; } else if ((inx == PARAMS_CORNU) || (inx == PARAMS_1ST_JOIN) || (inx == PARAMS_2ND_JOIN) ) { params->ep = PickEndPoint( pos, trk); } else { params->ep = PickUnconnectedEndPointSilent( pos, trk ); } if (params->ep == -1) return FALSE; if (params->ep>=0) { params->angle = GetTrkEndAngle(trk,params->ep); } return TRUE; } static BOOL_T QueryCornu( track_p trk, int query ) { switch ( query ) { case Q_CAN_GROUP: return FALSE; break; case Q_FLIP_ENDPTS: case Q_HAS_DESC: return TRUE; break; case Q_EXCEPTION: { struct extraDataCornu_t * xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t); return fabs(xx->minCurveRadius) < (GetLayoutMinTrackRadius()-EPSILON); } case Q_IS_CORNU: return TRUE; break; case Q_ISTRACK: return TRUE; break; case Q_CAN_PARALLEL: return TRUE; break; // case Q_MODIFY_CANT_SPLIT: Remove Split Restriction // case Q_CANNOT_BE_ON_END: Remove Restriction - Can have Cornu with no ends case Q_CANNOT_PLACE_TURNOUT: return FALSE; break; case Q_IGNORE_EASEMENT_ON_EXTEND: return TRUE; break; case Q_MODIFY_CAN_SPLIT: return TRUE; default: return FALSE; } } static void FlipCornu( track_p trk, coOrd orig, ANGLE_T angle ) { struct extraDataCornu_t * xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t); FlipPoint( &xx->pos[0], orig, angle ); FlipPoint( &xx->pos[1], orig, angle ); FlipPoint( &xx->c[0], orig, angle); FlipPoint( &xx->c[1], orig, angle); xx->a[0] = NormalizeAngle( 2*angle - xx->a[0] ); xx->a[1] = NormalizeAngle( 2*angle - xx->a[1] ); /* Reverse internals so that they match the new ends */ coOrd pos_save = xx->pos[0]; xx->pos[0] = xx->pos[1]; xx->pos[1] = pos_save; ANGLE_T angle_save = xx->a[0]; xx->a[0] = xx->a[1]; xx->a[1] = angle_save; coOrd c_save = xx->c[0]; xx->c[0] = xx->c[1]; xx->c[1] = c_save; DIST_T rad_save = xx->r[0]; xx->r[0] = xx->r[1]; xx->r[1] = rad_save; RebuildCornu(trk); } static ANGLE_T GetAngleCornu( track_p trk, coOrd pos, EPINX_T * ep0, EPINX_T * ep1 ) { struct extraDataCornu_t * xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t); ANGLE_T angle; BOOL_T back, neg; int indx; angle = GetAngleSegs( xx->arcSegs.cnt, (trkSeg_p)xx->arcSegs.ptr, &pos, &indx, NULL, &back, NULL, &neg ); if (!back) angle = NormalizeAngle(angle+180); if ( ep0 ) *ep0 = neg?1:0; if ( ep1 ) *ep1 = neg?0:1; return angle; } BOOL_T GetCornuSegmentFromTrack(track_p trk, trkSeg_p seg_p) { //TODO If we support Group return TRUE; } static dynArr_t cornuSegs_da; static BOOL_T MakeParallelCornu( track_p trk, coOrd pos, DIST_T sep, DIST_T factor, track_p * newTrkR, coOrd * p0R, coOrd * p1R, BOOL_T track ) { coOrd np[4], p, nc[2]; ANGLE_T atrk, diff_a, na[2]; DIST_T nr[2]; //Produce cornu that is translated parallel to the existing Cornu // - not a precise result if the cornu 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. p = pos; DistanceCornu(trk, &p); //Find nearest point on curve struct extraDataCornu_t * xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t); atrk = GetAngleSegs(xx->arcSegs.cnt,(trkSeg_t *)(xx->arcSegs.ptr),&p,NULL,NULL,NULL,NULL, NULL); diff_a = NormalizeAngle(FindAngle(pos,p)-atrk); //we know it will be +/-90... //find parallel move x and y for points BOOL_T above = FALSE; if ( diff_a < 180 ) above = TRUE; //Above track if (xx->a[0] <180) above = !above; DIST_T sep0 = sep+((xx->r[0]!=0.0)?fabs(factor/xx->r[0]):0); DIST_T sep1 = sep+((xx->r[1]!=0.0)?fabs(factor/xx->r[1]):0); Translate(&np[0],xx->pos[0],xx->a[0]+(above?90:-90),sep0); Translate(&np[1],xx->pos[1],xx->a[1]+(above?-90:90),sep1); na[0]=xx->a[0]; na[1]=xx->a[1]; if (xx->r[0] != 0.0) { //Find angle between center and end angle of track ANGLE_T ea0 = NormalizeAngle(FindAngle(xx->c[0],xx->pos[0])-xx->a[0]); if (ea0>180) sep0 = -sep0; nr[0]=xx->r[0]+(above?sep0:-sep0); //Needs adjustment nc[0]=xx->c[0]; } else { nr[0] = 0.0; nc[0] = zero; } if (xx->r[1] != 0.0) { ANGLE_T ea1 = NormalizeAngle(FindAngle(xx->c[1],xx->pos[1])-xx->a[1]); if (ea1<180) sep1 = -sep1; nr[1]=xx->r[1]+(above?sep1:-sep1); //Needs adjustment nc[1]=xx->c[1]; } else { nr[1] = 0.0; nc[1] = zero; } if ( newTrkR ) { if (track) { *newTrkR = NewCornuTrack( np, nc, na, nr, NULL, 0); if (*newTrkR==NULL) { wBeep(); InfoMessage(_("Cornu Create Failed for p1[%0.3f,%0.3f] p2[%0.3f,%0.3f], c1[%0.3f,%0.3f] c2[%0.3f,%0.3f], a1=%0.3f a2=%0.3f, r1=%s r2=%s"), np[0].x,np[0].y, np[1].x,np[1].y, nc[0].x,nc[0].y, nc[1].x,nc[1].y, na[0],na[1], FormatDistance(nr[0]),FormatDistance(nr[1])); return FALSE; } } else { tempSegs_da.cnt = 0; CallCornu0(np,nc,na,nr,&tempSegs_da,FALSE); *newTrkR = MakePolyLineFromSegs( zero, 0.0, &tempSegs_da ); } } else { tempSegs_da.cnt = 0; CallCornu0(np,nc,na,nr,&tempSegs_da,FALSE); if (!track) { for (int i=0;itype == SEG_STRTRK) { seg->type = SEG_STRLIN; seg->color = wDrawColorBlack; seg->width = 0; } if (seg->type == SEG_CRVTRK) { seg->type = SEG_CRVLIN; seg->color = wDrawColorBlack; seg->width = 0; } if (seg->type == SEG_BEZTRK) { for (int j=0;jbezSegs.cnt;j++) { trkSeg_p bseg = &(((trkSeg_t *)seg->bezSegs.ptr)[j]); if (bseg->type == SEG_STRTRK) { bseg->type = SEG_STRLIN; bseg->color = wDrawColorBlack; bseg->width = 0; } if (bseg->type == SEG_CRVTRK) { bseg->type = SEG_CRVLIN; bseg->color = wDrawColorBlack; bseg->width = 0; } } seg->type = SEG_BEZLIN; seg->color = wDrawColorBlack; seg->width = 0; } } } } if ( p0R ) *p0R = np[0]; if ( p1R ) *p1R = np[1]; return TRUE; } static BOOL_T TrimCornu( track_p trk, EPINX_T ep, DIST_T dist, coOrd endpos, ANGLE_T angle, DIST_T radius, coOrd center ) { UndoModify(trk); if (dist>0.0 && dista[ep] = angle; xx->c[ep] = center; xx->r[ep] = radius; xx->pos[ep] = endpos; RebuildCornu(trk); SetTrkEndPoint(trk, ep, xx->pos[ep], xx->a[ep]); DrawNewTrack( trk ); } 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. */ EXPORT BOOL_T RebuildCornu (track_p trk) { struct extraDataCornu_t *xx; xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t); xx->arcSegs.max = 0; xx->arcSegs.cnt = 0; //if (xx->arcSegs.ptr) MyFree(xx->arcSegs.ptr); xx->arcSegs.ptr = NULL; if (!FixUpCornu0(xx->pos,xx->c,xx->a,xx->r, xx)) return FALSE; ComputeCornuBoundingBox(trk, xx); return TRUE; } static wBool_t CompareCornu( track_cp trk1, track_cp trk2 ) { struct extraDataCornu_t *xx1 = GET_EXTRA_DATA( trk1, T_CORNU, extraDataCornu_t ); struct extraDataCornu_t *xx2 = GET_EXTRA_DATA( trk2, T_CORNU, extraDataCornu_t ); char * cp = message + strlen(message); REGRESS_CHECK_POS( "Pos[0]", xx1, xx2, pos[0] ) REGRESS_CHECK_POS( "Pos[1]", xx1, xx2, pos[1] ) REGRESS_CHECK_POS( "C[0]", xx1, xx2, c[0] ) REGRESS_CHECK_POS( "C[1]", xx1, xx2, c[1] ) REGRESS_CHECK_ANGLE( "A[0]", xx1, xx2, a[0] ) REGRESS_CHECK_ANGLE( "A[1]", xx1, xx2, a[1] ) REGRESS_CHECK_DIST( "R[0]", xx1, xx2, r[0] ) REGRESS_CHECK_DIST( "R[1]", xx1, xx2, r[1] ) REGRESS_CHECK_DIST( "MinCurveRadius", xx1, xx2, minCurveRadius ) REGRESS_CHECK_DIST( "MaxRateofChange", xx1, xx2, maxRateofChange ) REGRESS_CHECK_DIST( "Length", xx1, xx2, length ) REGRESS_CHECK_ANGLE( "WindingAngle", xx1, xx2, windingAngle ) // CHECK arcSegs REGRESS_CHECK_POS( "DescOff", xx1, xx2, descriptionOff ) // CHECK cornuPath return TRUE; } static trackCmd_t cornuCmds = { "CORNU", DrawCornu, DistanceCornu, DescribeCornu, DeleteCornu, WriteCornu, ReadCornu, MoveCornu, RotateCornu, RescaleCornu, NULL, GetAngleCornu, SplitCornu, TraverseCornu, EnumerateCornu, NULL, /* redraw */ TrimCornu, /* trim */ MergeCornu, NULL, /* modify */ GetLengthCornu, GetParamsCornu, MoveCornuEndPt, /* Move EndPt */ QueryCornu, NULL, /* ungroup */ FlipCornu, NULL, NULL, NULL, MakeParallelCornu, NULL, RebuildCornu, NULL, NULL, NULL, CompareCornu }; /**************************************** * * GRAPHICS COMMANDS * */ track_p NewCornuTrack(coOrd pos[2], coOrd center[2],ANGLE_T angle[2], DIST_T radius[2], trkSeg_t * tempsegs, int count) { struct extraDataCornu_t *xx; track_p p; p = NewTrack( 0, T_CORNU, 2, sizeof *xx ); xx = GET_EXTRA_DATA(p, T_CORNU, extraDataCornu_t); xx->pos[0] = pos[0]; xx->pos[1] = pos[1]; xx->a[0] = angle[0]; xx->a[1] = angle[1]; xx->r[0] = radius[0]; xx->r[1] = radius[1]; xx->c[0] = center[0]; xx->c[1] = center[1]; if (!FixUpCornu0(xx->pos,xx->c,xx->a,xx->r, xx)) { ErrorMessage("Create Cornu Failed"); return NULL; } LOG( log_cornu, 1, ( "NewCornuTrack( EP1 %0.3f, %0.3f, EP2 %0.3f, %0.3f ) = %d\n", pos[0].x, pos[0].y, pos[1].x, pos[1].y, GetTrkIndex(p) ) ) ComputeCornuBoundingBox( p, xx ); SetTrkEndPoint( p, 0, pos[0], xx->a[0]); SetTrkEndPoint( p, 1, pos[1], xx->a[1]); CheckTrackLength( p ); SetTrkBits( p, TB_HIDEDESC ); return p; } EXPORT void InitTrkCornu( void ) { T_CORNU = InitObject( &cornuCmds ); log_cornu = LogFindIndex( "Cornu" ); log_traverseCornu = LogFindIndex( "traverseCornu" ); }