/* * $Header: $ */ /* XTrkCad - Model Railroad CAD * Copyright (C) 2022 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include "common.h" #include "common-ui.h" #include "fileio.h" #include "param.h" #include "track.h" #include "trkendpt.h" #include "trkendptx.h" #include "draw.h" /************************************************************************ * Basic Access */ EXPORT CSIZE_T EndPtSize( EPINX_T epCnt ) { return epCnt * sizeof *(trkEndPt_p)NULL; } EXPORT coOrd GetEndPtPos( trkEndPt_p epp ) { return epp->pos; } EXPORT ANGLE_T GetEndPtAngle( trkEndPt_p epp ) { return epp->angle; } EXPORT track_p GetEndPtTrack( trkEndPt_p epp ) { return epp->track; } EXPORT void SetEndPt( trkEndPt_p epp, coOrd pos, ANGLE_T angle ) { epp->pos = pos; epp->angle = angle; } EXPORT EPINX_T GetEndPtEndPt( trkEndPt_p epp ) { return (EPINX_T)(epp->index); } EXPORT TRKINX_T GetEndPtIndex( trkEndPt_p epp ) { return (TRKINX_T)(epp->index); } EXPORT void SetEndPtTrack( trkEndPt_p epp, track_p trk ) { epp->track = trk; epp->index = -1; } EXPORT void SetEndPtEndPt( trkEndPt_p epp, EPINX_T ep ) { epp->index = ep; } EXPORT trkEndPt_p EndPtIndex( trkEndPt_p epp, EPINX_T ep ) { return epp+ep; } /************************************************************************ * Temp End Points */ static dynArr_t tempEndPts_da; EXPORT void TempEndPtsReset( void ) { DYNARR_RESET( trkEndPt_t, tempEndPts_da ); } EXPORT void TempEndPtsSet( EPINX_T ep ) { DYNARR_SET( trkEndPt_t, tempEndPts_da, ep ); memset( &DYNARR_N(trkEndPt_t,tempEndPts_da,0), 0, ep * sizeof *(trkEndPt_p)NULL ); } EXPORT EPINX_T TempEndPtsCount( void ) { return tempEndPts_da.cnt; } EXPORT trkEndPt_p TempEndPt( EPINX_T ep ) { CHECK( ep < tempEndPts_da.cnt ); return &DYNARR_N( trkEndPt_t, tempEndPts_da, ep ); } #ifndef MKTURNOUT EXPORT trkEndPt_p TempEndPtsAppend( void ) { DYNARR_APPEND( trkEndPt_t, tempEndPts_da, 10 ); trkEndPt_p epp = &DYNARR_LAST( trkEndPt_t, tempEndPts_da ); memset( epp, 0, sizeof *epp ); return epp; } EXPORT void SwapEndPts( trkEndPt_p epp, EPINX_T ep0, EPINX_T ep1 ) { trkEndPt_t tempEP; tempEP = epp[ep0]; epp[ep0] = epp[ep1]; epp[ep1] = tempEP; } /************************************************************************ * Track level access */ EXPORT void SetTrkEndPoint( track_p trk, EPINX_T ep, coOrd pos, ANGLE_T angle ) { CHECK( ep < GetTrkEndPtCnt(trk) ); // check setTrkEndPoint: endPt is not connected CHECK( GetEndPtTrack( GetTrkEndPt( trk, ep ) ) == NULL ); SetEndPt( GetTrkEndPt( trk, ep ), pos, angle ); } EXPORT void SetTrkEndPointSilent( track_p trk, EPINX_T ep, coOrd pos, ANGLE_T angle ) { CHECK( ep < GetTrkEndPtCnt(trk) ); SetEndPt( GetTrkEndPt( trk, ep ), pos, angle ); } EXPORT coOrd GetTrkEndPos( track_p trk, EPINX_T ep ) { CHECK( ep < GetTrkEndPtCnt(trk) ); return GetTrkEndPt(trk,ep)->pos; } EXPORT ANGLE_T GetTrkEndAngle( track_p trk, EPINX_T ep ) { CHECK( ep < GetTrkEndPtCnt(trk) ); return GetTrkEndPt(trk,ep)->angle; } EXPORT track_p GetTrkEndTrk( track_p trk, EPINX_T ep ) { CHECK( ep < GetTrkEndPtCnt(trk) ); return GetTrkEndPt(trk,ep)->track; } EXPORT long GetTrkEndOption( track_p trk, EPINX_T ep ) { CHECK( ep < GetTrkEndPtCnt(trk) ); return GetTrkEndPt(trk,ep)->option; } EXPORT long SetTrkEndOption( track_p trk, EPINX_T ep, long option ) { CHECK( ep < GetTrkEndPtCnt(trk) ); return GetTrkEndPt(trk,ep)->option = option; } /************************************************************************ * Elevations */ EXPORT void SetTrkEndElev( track_p trk, EPINX_T ep, int option, DIST_T height, char * station ) { track_p trk1; EPINX_T ep1; trkEndPt_p epp = GetTrkEndPt( trk, ep ); epp->elev.option = option; epp->elev.cacheSet = FALSE; if (EndPtIsDefinedElev(trk,ep)) { epp->elev.u.height = height; } else if (EndPtIsStationElev(trk,ep)) { if (station == NULL) { station = ""; } epp->elev.u.name = MyStrdup(station); } if ( (trk1=GetTrkEndTrk(trk, ep)) != NULL ) { ep1 = GetEndPtConnectedToMe( trk1, trk ); if (ep1 >= 0) { trkEndPt_p epp1 = GetTrkEndPt( trk1, ep1 ); epp1->elev.option = option; epp1->elev.u.height = height; if (EndPtIsDefinedElev(trk1,ep1)) { epp1->elev.u.height = height; } else if (EndPtIsStationElev(trk,ep)) { epp1->elev.u.name = MyStrdup(station); } } } } EXPORT void GetTrkEndElev( track_p trk, EPINX_T e, int *option, DIST_T *height ) { trkEndPt_p epp = GetTrkEndPt( trk, e ); *option = epp->elev.option; *height = epp->elev.u.height; } EXPORT int GetTrkEndElevUnmaskedMode( track_p trk, EPINX_T e ) { trkEndPt_p epp = GetTrkEndPt( trk, e ); return epp->elev.option; } EXPORT int GetTrkEndElevMode( track_p trk, EPINX_T e ) { trkEndPt_p epp = GetTrkEndPt( trk, e ); return epp->elev.option&ELEV_MASK; } EXPORT DIST_T GetTrkEndElevHeight( track_p trk, EPINX_T e ) { CHECK( EndPtIsDefinedElev(trk,e) ); trkEndPt_p epp = GetTrkEndPt( trk, e ); return epp->elev.u.height; } EXPORT void ClrEndPtElevCache( EPINX_T epCnt, trkEndPt_p epPts ) { for ( EPINX_T ep = 0; ep < epCnt; ep++ ) { epPts[ep].elev.cacheSet = FALSE; } } static BOOL_T bCacheElev = TRUE; EXPORT BOOL_T GetTrkEndElevCachedHeight (track_p trk, EPINX_T e, DIST_T * height, DIST_T * grade) { if ( ! bCacheElev ) { return FALSE; } trkEndPt_p epp = GetTrkEndPt( trk, e ); if (epp->elev.cacheSet) { *height = epp->elev.cachedElev; *grade = epp->elev.cachedGrade; return TRUE; } return FALSE; } EXPORT void SetTrkEndElevCachedHeight ( track_p trk, EPINX_T e, DIST_T height, DIST_T grade) { trkEndPt_p epp = GetTrkEndPt( trk, e ); epp->elev.cachedElev = height; epp->elev.cachedGrade = grade; epp->elev.cacheSet = TRUE; } EXPORT char * GetTrkEndElevStation( track_p trk, EPINX_T e ) { CHECK( EndPtIsStationElev(trk,e) ); trkEndPt_p epp = GetTrkEndPt( trk, e ); if ( epp->elev.u.name == NULL ) { return ""; } else { return epp->elev.u.name; } } EXPORT void DrawEndElev( drawCmd_p d, track_p trk, EPINX_T ep, wDrawColor color ) { coOrd pp; wFont_p fp; elev_t * elev; track_p trk1; DIST_T elev0, grade; ANGLE_T a=0; int style = BOX_BOX_BACKGROUND; BOOL_T gradeOk = TRUE; char *elevStr; if ((labelEnable&LABELENABLE_ENDPT_ELEV)==0) { return; } elev = &GetTrkEndPt(trk,ep)->elev; if ( (elev->option&ELEV_MASK)==ELEV_NONE || (elev->option&ELEV_VISIBLE)==0 ) { return; } if ( (trk1=GetTrkEndTrk(trk,ep)) && GetTrkIndex(trk1)option&ELEV_MASK)) { case ELEV_COMP: case ELEV_GRADE: if ( color == wDrawColorWhite ) { elev0 = grade = elev->u.height; } else if ( !ComputeElev( trk, ep, FALSE, &elev0, &grade, FALSE ) ) { elev0 = grade = 0; gradeOk = FALSE; } if ((elev->option&ELEV_MASK)==ELEV_COMP) { elevStr = FormatDistance(elev0); elev->u.height = elev0; } else if (gradeOk) { sprintf( message, "%0.1f%%", round(fabs(grade*100.0)*10)/10 ); elevStr = message; a = GetTrkEndAngle( trk, ep ); style = BOX_ARROW_BACKGROUND; if (grade <= -0.001) { a = NormalizeAngle( a+180.0 ); } else if ( grade < 0.001 ) { style = BOX_BOX_BACKGROUND; } elev->u.height = grade; } else { elevStr = "????%%"; } break; case ELEV_DEF: elevStr = FormatDistance( elev->u.height); break; case ELEV_STATION: elevStr = elev->u.name; break; default: return; } coOrd startLine = pp, endLine = pp; pp.x += elev->doff.x; pp.y += elev->doff.y; if (color==drawColorPreviewSelected) { Translate(&endLine,pp,FindAngle(pp,startLine),descriptionFontSize/d->dpi); DrawLine( d, startLine, endLine, 0, color ); } DrawBoxedString( style, d, pp, elevStr, fp, (wFontSize_t)descriptionFontSize, color, a ); } /************************************************************************ * Descriptions */ EXPORT DIST_T EndPtDescriptionDistance( coOrd pos, track_p trk, EPINX_T ep, coOrd *dpos, BOOL_T show_hidden, BOOL_T * hidden) { elev_t *e; coOrd pos1; track_p trk1; *dpos = pos; if (hidden) { *hidden = FALSE; } e = &GetTrkEndPt(trk,ep)->elev; if ((e->option&ELEV_MASK)==ELEV_NONE) { return DIST_INF; } if (((e->option&ELEV_VISIBLE)==0) && !show_hidden) { return DIST_INF; } if ((trk1=GetTrkEndTrk(trk,ep)) && GetTrkIndex(trk1)option&ELEV_VISIBLE)==0) { //Hidden - disregard offset if (hidden) { *hidden = TRUE; } return FindDistance( GetTrkEndPos(trk,ep), pos ); } /*REORIGIN( pos1, e->doff, GetTrkEndPos(trk,ep), GetTrkEndAngle(trk,ep) );*/ pos1 = GetTrkEndPos(trk,ep); coOrd tpos = pos1; pos1.x += e->doff.x; pos1.y += e->doff.y; *dpos = pos1; if (hidden) { *hidden = !(e->option&ELEV_VISIBLE); } if (FindDistance(tpos,pos)elev; switch (action) { case C_DOWN: p0 = GetTrkEndPos(trk,ep); // p1 = pos; e->option |= ELEV_VISIBLE; //Make sure we make visible DrawEndElev( &mainD, trk, ep, wDrawColorWhite ); /*no break*/ case C_MOVE: case C_UP: // p1 = pos; e->doff.x = (pos.x-p0.x); e->doff.y = (pos.y-p0.y); if ((trk1=GetTrkEndTrk(trk,ep))) { EPINX_T ep1 = GetEndPtConnectedToMe(trk1,trk); trkEndPt_p epp = GetTrkEndPt( trk1, ep1 ); // e1 = &trk1->endPt[GetEndPtConnectedToMe(trk1,trk)].elev; epp->elev.doff = e->doff; } if ( action == C_UP ) { wDrawColor color = GetTrkColor( trk, &mainD ); DrawEndElev( &mainD, trk, ep, color ); } return action==C_UP?C_TERMINATE:C_CONTINUE; case C_REDRAW: DrawEndElev( &tempD, trk, ep, drawColorPreviewSelected ); break; } return C_CONTINUE; } /************************************************************************ * Read/Write End Points */ EXPORT BOOL_T GetEndPtArg( char * cp, char type, BOOL_T bImprovedEnds ) { long option; long option2; trkEndPt_p e = TempEndPtsAppend(); FLOAT_T ignoreFloat; if (type == SEG_CONEP) { if ( !GetArgs( cp, "dc", &e->index, &cp ) ) { /*??*/return FALSE; } } else { e->index = -1; } if ( !GetArgs( cp, "pfc", &e->pos, &e->angle, &cp) ) { /*??*/return FALSE; } e->elev.option = 0; e->elev.u.height = 0.0; e->elev.doff = zero; e->option = 0; if (bImprovedEnds) { //E4 and T4 if (!GetArgs( cp, "lpc", &option, &e->elev.doff, &cp )) { /*??*/return FALSE; } switch (option&ELEV_MASK) { case ELEV_STATION: GetArgs( cp, "qc", &e->elev.u.name, &cp); break; default: GetArgs( cp, "fc", &e->elev.u.height, &cp); //First height } DIST_T height2; if (!GetArgs( cp, "flLlc", &height2, &option2, &e->elev.option, &e->option, &cp ) ) { return FALSE; } if (option2) { e->elev.option |= ELEV_VISIBLE; } GetArgs(cp, "fc", &ignoreFloat, &cp); return TRUE; } if ( cp != NULL ) { if (paramVersion < 7) { GetArgs( cp, "dfp", &e->elev.option, &e->elev.u.height, &e->elev.doff, &cp ); /*??*/return TRUE; } GetArgs( cp, "lpc", &option, &e->elev.doff, &cp ); e->option = option >> 8; e->elev.option = (int)(option&0xFF); if ( (e->elev.option&ELEV_MASK) != ELEV_NONE ) { switch (e->elev.option&ELEV_MASK) { case ELEV_DEF: GetArgs( cp, "fc", &e->elev.u.height, &cp ); break; case ELEV_STATION: GetArgs( cp, "qc", &e->elev.u.name, &cp ); /*??*/break; default: ; } } } return TRUE; } EXPORT BOOL_T bWriteEndPtDirectIndex = FALSE; EXPORT BOOL_T bWriteEndPtExporting = FALSE; EXPORT BOOL_T WriteEndPt( FILE * f, track_cp trk, EPINX_T ep ) { trkEndPt_p endPt = GetTrkEndPt(trk,ep); BOOL_T rc = TRUE; long option; CHECK ( endPt ); if (bWriteEndPtDirectIndex && endPt->index > 0) { rc &= fprintf( f, "\tT4 %ld ", endPt->index )>0; } else if (endPt->track == NULL || ( bWriteEndPtExporting && !GetTrkSelected(endPt->track) ) ) { rc &= fprintf( f, "\tE4 " )>0; } else { rc &= fprintf( f, "\tT4 %d ", GetTrkIndex(endPt->track) )>0; } rc &= fprintf( f, "%0.6f %0.6f %0.6f", endPt->pos.x, endPt->pos.y, endPt->angle )>0; option = (endPt->option<<8) | (endPt->elev.option&0xFF); if ( option != 0 ) { rc &= fprintf( f, " %ld %0.6f %0.6f", option, endPt->elev.doff.x, endPt->elev.doff.y )>0; switch ( endPt->elev.option&ELEV_MASK ) { case ELEV_DEF: rc &= fprintf( f, " %0.6f ", endPt->elev.u.height )>0; break; case ELEV_STATION: rc &= fprintf( f, " \"%s\" ", PutTitle( endPt->elev.u.name ) )>0; break; default: rc &= fprintf( f, " 0.0 ")>0; } } else { rc &= fprintf( f, " 0 0.0 0.0 0.0 ")>0; } if ((endPt->elev.option&ELEV_MASK) == ELEV_DEF) { rc &= fprintf( f, "%0.6f ",endPt->elev.u.height)>0; } else { rc &= fprintf( f, "0.0 ")>0; } long elevVisible = (endPt->elev.option&ELEV_VISIBLE)?1:0; long elevType = endPt->elev.option&ELEV_MASK; long gapType = endPt->option; rc &= fprintf( f, "%ld %ld %ld ", elevVisible, elevType, gapType)>0; rc &= fprintf( f, "%0.6f ", GetTrkElev( trk ) )>0; rc &= fprintf( f, "\n" )>0; return rc; } /************************************************************************ * Read/Write End Points */ wBool_t CompareEndPt( char * cp, track_p trk1, track_p trk2, EPINX_T ep ) { trkEndPt_p epp1 = GetTrkEndPt( trk1, ep ); trkEndPt_p epp2 = GetTrkEndPt( trk2, ep ); REGRESS_CHECK_POS( "Pos", epp1, epp2, pos ) REGRESS_CHECK_ANGLE( "Angle", epp1, epp2, angle ) // Actual EP connection int inx1 = -1; if ( epp1->track ) { inx1 = GetTrkIndex( epp1->track ); } // Expected EP connection. endPt[inx].track is not resolved int inx2 = epp2->index; if ( inx1 != inx2 ) { sprintf( cp, "Index: Actual` %d, Expected %d\n", inx1, inx2 ); return FALSE; } REGRESS_CHECK_INT( "Option", epp1, epp2, option ) return TRUE; } #endif