diff --git a/.gitignore b/.gitignore
index 34fe017..830909f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,4 +22,7 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
-public/sceneries/*
\ No newline at end of file
+public/sceneries/*
+
+# IDEs
+.idea
diff --git a/src/components/map/Map.css b/src/components/map/Map.css
index 7c95820..3e72537 100644
--- a/src/components/map/Map.css
+++ b/src/components/map/Map.css
@@ -63,11 +63,11 @@
fill: #111;
}
-.map svg g.route g {
+.map svg g.route-arrow {
fill: #eee;
}
-.map svg g.route text {
+.map svg g.route-name text {
fill: #eee;
font-size: 72mm;
}
diff --git a/src/components/map/ObjectsRenderQueue.js b/src/components/map/ObjectsRenderQueue.js
index a4f84bf..738951c 100644
--- a/src/components/map/ObjectsRenderQueue.js
+++ b/src/components/map/ObjectsRenderQueue.js
@@ -10,6 +10,7 @@ import NEVPRenderer from './object-renderers/NEVPRenderer';
import DerailerRenderer from './object-renderers/DerailerRenderer';
import ElectrificationStatusPopup from './additional-layer-components/ElectrificationStatusPopup';
import SpawnPointRenderer from './object-renderers/SpawnPointRenderer';
+import IsolationEndRenderer from './object-renderers/IsolationEndRenderer';
const ObjectRendererQueue = [
{
@@ -23,8 +24,15 @@ const ObjectRendererQueue = [
'pointerEvents': true
},
{
- 'name': 'isolations-ids',
+ 'name': 'isolations-ends',
'category': 'tracks',
+ 'renderer': IsolationEndRenderer,
+ 'cond': (layers) => layers['isolations-ids']
+ },
+ {
+ 'name': 'isolations-ids',
+ 'category': ['tracks', 'switches'],
+ 'types': ['StandardTrack', 'BezierTrack', 'Switch'],
'renderer': IsolationIdRenderer,
'cond': (layers) => layers['isolations-ids']
},
@@ -92,4 +100,4 @@ const ObjectRendererQueue = [
}
]
-export default ObjectRendererQueue;
\ No newline at end of file
+export default ObjectRendererQueue;
diff --git a/src/components/map/SceneryLayer.js b/src/components/map/SceneryLayer.js
index 5ac1b08..ee26395 100644
--- a/src/components/map/SceneryLayer.js
+++ b/src/components/map/SceneryLayer.js
@@ -8,19 +8,26 @@ export default function SceneryLayer(props) {
const { name, category, type, types, cond, renderer: Renderer, additionalComponents: AdditionalComponents } = queueItem;
const { scenery } = useContext(MainContext);
const { layers } = useContext(SettingsContext);
-
+
const isVisible = (cond ? cond(layers) : true);
-
+
const objects = useMemo(() => {
if (!scenery || !isVisible) return [];
- const categoryObjs = scenery.objects[category];
- return categoryObjs ? Object.values(categoryObjs) : [];
+
+ const getCategoryObjects = (category) => {
+ const categoryObjs = scenery.objects[category];
+ if (!categoryObjs) return [];
+ return Object.values(categoryObjs);
+ }
+ // flatMap is not used in the case of a single category to avoid an unnecessary array copy
+ if (typeof category === 'string') return getCategoryObjects(category);
+ return category.flatMap(getCategoryObjects);
}, [scenery, category, isVisible]);
-
+
if (!scenery || !isVisible) return null;
const pointerEvents = Constants.map.forcePointerEvents || queueItem.pointerEvents || false;
-
+
return (
);
}
-
+
const MemoizedSceneryLayer = memo(StatelessSceneryLayer);
function StatelessSceneryLayer({ name, Renderer, objects, type, types, pointerEvents = false, AdditionalComponents = [] }) {
return (
-
@@ -53,4 +60,4 @@ function StatelessSceneryLayer({ name, Renderer, objects, type, types, pointerEv
))}
);
-}
\ No newline at end of file
+}
diff --git a/src/components/map/object-renderers/IsolationEndRenderer.js b/src/components/map/object-renderers/IsolationEndRenderer.js
new file mode 100644
index 0000000..12e3e5e
--- /dev/null
+++ b/src/components/map/object-renderers/IsolationEndRenderer.js
@@ -0,0 +1,40 @@
+import { useMemo } from "react";
+import { TrackConnectionEnd } from "../../../model/track-connection";
+
+export default function IsolationEndRenderer(props) {
+ const { object } = props;
+ return (<>
+
+
+ >);
+}
+
+function IsolationEndMarker(props) {
+ const { object, end = false } = props;
+
+ const isShown = useMemo(() => {
+ return object.connections.some((conn) => {
+ const isCorrectEnd = conn.end === end;
+ const isolationIdChanged = conn.otherTrack.id_isolation !== object.id_isolation;
+
+ // Prevent the marker from showing twice for both tracks
+ const isPreferred = object.id < conn.otherTrackId;
+
+ return isCorrectEnd && isolationIdChanged && isPreferred;
+ });
+ }, [object, end]);
+
+ if (!isShown) return null;
+
+ const pos = object.getEndPos(end);
+ const [x, y] = pos.toSVGCoords();
+ const angle = object.getAngleXZForEnd(end);
+
+ const cos = Math.cos(angle);
+ const sin = Math.sin(angle);
+
+ return
+}
diff --git a/src/components/map/object-renderers/IsolationIdRenderer.js b/src/components/map/object-renderers/IsolationIdRenderer.js
index 3a6088f..f1be890 100644
--- a/src/components/map/object-renderers/IsolationIdRenderer.js
+++ b/src/components/map/object-renderers/IsolationIdRenderer.js
@@ -1,50 +1,20 @@
-import { useMemo } from "react";
import SimpleLabelText from "../text/SimpleLabelText";
-import { TrackConnectionType } from "../../../model/track-connection";
export default function IsolationIdRenderer(props) {
const { object } = props;
- const pos = object.points.start.lerp(object.points.end, 0.5);
- const [x, y] = pos.toSVGCoords();
+
const text = object.id_isolation ?? '';
+ if (!text) return null;
- const showText = (!object.hide_isolation && !!text);
-
- return (<>
- {showText && }
-
-
- >);
-}
-
-function IsolationEndMarker(props) {
- const { object, isStart = false } = props;
-
- const isShown = useMemo(() => {
- return object.connections.some(conn =>
- (isStart ? conn.type === TrackConnectionType.START : conn.type === TrackConnectionType.END) &&
- conn.otherTrack.id_isolation !== object.id_isolation
- );
- }, [object, isStart]);
-
- if (!isShown) return null;
-
- const pos = isStart ? object.points.start : object.points.end;
+ let pos = (object.type === 'Switch' ? object.isolation_id_pos : object.points.middle) ?? object.pos;
const [x, y] = pos.toSVGCoords();
- const angle = isStart ? object.getStartAngleXZ() : object.getEndAngleXZ();
- const cos = Math.cos(angle);
- const sin = Math.sin(angle);
-
- return
+ return ();
}
diff --git a/src/components/map/object-renderers/RouteRenderer.js b/src/components/map/object-renderers/RouteRenderer.js
index 474ead5..e42de3c 100644
--- a/src/components/map/object-renderers/RouteRenderer.js
+++ b/src/components/map/object-renderers/RouteRenderer.js
@@ -1,27 +1,33 @@
import { ReactSVG } from "react-svg";
import AlwaysUpText from "../text/AlwaysUpText";
+import AngleHelper from "../../../helpers/angleHelper";
export default function RouteRenderer(props) {
const { object } = props;
- const [x, y] = object.pos.toSVGCoords();
+
+ const [startX, startY] = object.pos.toSVGCoords();
+ const startAngle = object.rot.y;
+
+ const [endX, endY] = object.end_center.toSVGCoords();
+ const endAngle = AngleHelper.radToDeg(object.end_angle_rad);
const offY = object.track_count === 2 ? -22 : -20;
const arrowOffset = - object.track_offset - 37.8;
- return (
-
-
- {
- svg.setAttribute('width', '20mm');
- svg.setAttribute('height', '20mm');
- }}
- />
-
+ return (<>
+
+ {
+ svg.setAttribute('width', '20mm');
+ svg.setAttribute('height', '20mm');
+ }}
+ />
+
+
- );
+ >);
}
diff --git a/src/components/map/object-renderers/TrackRenderer.js b/src/components/map/object-renderers/TrackRenderer.js
index 8f85636..b9fa747 100644
--- a/src/components/map/object-renderers/TrackRenderer.js
+++ b/src/components/map/object-renderers/TrackRenderer.js
@@ -22,6 +22,10 @@ const MemoizedTrackRenderer = React.memo(StatelessTrackRenderer);
function StatelessTrackRenderer(props) {
const { object, trackColorMode } = props;
+ if (object.points.start.distanceSq(object.points.end) < 0.001) {
+ return null;
+ }
+
const onMouseEnter = () => {
setHoveredTrack(object);
};
@@ -56,7 +60,8 @@ function StatelessTrackRenderer(props) {
function getTrackPath(object) {
switch (object.type) {
case "StandardTrack":
- case "PointTrack":
+ case "PointTrack":
+ case "RouteTrack":
const [x1, y1] = object.points.start.toSVGCoords();
const [x2, y2] = object.points.end.toSVGCoords();
const ra = Math.abs(object.r);
@@ -84,7 +89,7 @@ function getTrackColor(object, trackColorMode) {
switch (trackColorMode) {
case "standard":
default:
- if(object.prefab_name.includes('trans-mat'))
+ if(object.prefab_name && object.prefab_name.includes('trans-mat'))
return modeDef.options['invisible'][0];
return modeDef.options[modeDef.optionDefault][0];
@@ -102,13 +107,15 @@ function getTrackColor(object, trackColorMode) {
default:
return modeDef.options[modeDef.optionDefault][0];
}
-
+
case "type":
switch (object.type) {
case "StandardTrack":
return modeDef.options['standard-track'][0];
case "PointTrack":
return modeDef.options['point-track'][0];
+ case "RouteTrack":
+ return modeDef.options['route-track'][0];
case "BezierTrack":
return modeDef.options['bezier-track'][0];
default:
@@ -123,7 +130,8 @@ function getTrackColor(object, trackColorMode) {
return `url(#track-slope-${object.id})`;
case "max-speed":
- if(!object.maxspeed) {
+ if (!object.maxspeed) {
+ if (object.type === 'RouteTrack') return modeDef.options['unknown'][0];
return modeDef.options['derail'][0];
}
return MiscHelper.getTrackGradient(modeDef.gradient, object.maxspeed);
@@ -133,13 +141,13 @@ function getTrackColor(object, trackColorMode) {
function getTrackDefs(object, trackColorMode) {
if (trackColorMode !== "slope") return null;
if (object.start_slope === object.end_slope) return null;
-
+
const [x1, y1] = object.points.start.toSVGCoords();
const [x2, y2] = object.points.end.toSVGCoords();
const gradId = `track-slope-${object.id}`;
const startColor = MiscHelper.getTrackGradient(Constants.trackColorModes['slope'].gradient, Math.abs(object.start_slope));
const endColor = MiscHelper.getTrackGradient(Constants.trackColorModes['slope'].gradient, Math.abs(object.end_slope));
-
+
return (
{
- connectTrack(scenery, track, false);
- connectTrack(scenery, track, true);
+ _connectRoutes(scenery);
+ _connectRemainingTracks(scenery);
+}
+
+function _connectRoutes(scenery) {
+ const routeTracks = new Map();
+
+ Object.values(scenery.objects['routes'] || {}).forEach((route) => {
+ if (!route.segments[0]) return;
+ route.segments[0].tracks.forEach((track) => {
+ routeTracks.set(track.id, track);
+ });
+ });
+
+ Object.values(scenery.objects['tracks'] || {}).forEach((track) => {
+ track.connections.forEach((connection) => {
+ const routeTrack = routeTracks.get(connection.otherTrackId);
+ if (!routeTrack) return;
+ const alreadyConnected = routeTrack.connections.some(
+ (reverseConnection) => reverseConnection.otherTrackId === track.id,
+ );
+ if (alreadyConnected) return;
+ routeTrack.connections.push(new TrackConnection(track, TrackConnectionEnd.START));
+ });
});
}
-function connectTrack(scenery, track, isPrev) {
- const vNext = isPrev ? track.previd : track.nextid;
- if (!vNext) return;
-
- const vNextTrackId = scenery.getTrackIdByAlias(vNext);
- const vNextTrack = scenery.getObject('tracks', vNextTrackId);
-
- if (!vNextTrack) {
- SceneryParserLog.warn('connectTracks', `Track ${track.id} has non-existing ${isPrev ? 'previous' : 'next'} track: ${isPrev ? track.previd : track.nextid} (${vNextTrackId})`);
+function _connectRemainingTracks(scenery) {
+ Object.values(scenery.objects['tracks'] || {}).forEach((track) => {
+ track.connections.forEach((connection) => {
+ _checkConnection(scenery, track, connection);
+ });
+ track.connections = track.connections.filter((connection) => connection.otherTrack !== null);
+ });
+}
+
+function _checkConnection(scenery, track, connection) {
+ if (connection.otherTrack === null) {
+ const otherTrack = scenery.getObject('tracks', connection.otherTrackId);
+ if (!otherTrack) {
+ SceneryParserLog.warn('connectTracksFailed', `Track ${track.id} has a non-existing ${connection.end === TrackConnectionEnd.START ? 'previous' : 'next'} track: ${connection.otherTrackId}`);
+ return;
+ }
+ connection.otherTrack = otherTrack;
+ }
+
+ if (!Constants.parser.runTracksConnectionTest) return;
+
+ const reverseConnections = connection.otherTrack.connections.filter(
+ (otherConnection) => otherConnection.otherTrackId === track.id,
+ );
+ if (reverseConnections.length > 1) {
+ SceneryParserLog.warn(
+ 'tracksConnectionTest',
+ `Track ${connection.otherTrack.id} has multiple connections to ${track.id}`,
+ );
+ }
+ if (reverseConnections.length === 0) {
+ SceneryParserLog.warn(
+ 'tracksConnectionTest',
+ `Track ${track.id} is connected to ${connection.otherTrackId} but there is no reverse connection`,
+ );
return;
}
-
- const vNextTrackNextId = scenery.getTrackIdByAlias(vNextTrack.nextid);
- const vNextTrackPrevId = scenery.getTrackIdByAlias(vNextTrack.previd);
- const isConnectedAsNext = vNextTrackPrevId === track.id || track.aliases.includes(vNextTrackPrevId);
- const isConnectedAsPrev = vNextTrackNextId === track.id || track.aliases.includes(vNextTrackNextId);
-
- let otherConnectionType = isConnectedAsNext ? TrackConnectionType.START : TrackConnectionType.END;
- let addReverseConnection = false;
-
- if (!isConnectedAsNext && !isConnectedAsPrev) {
- // switch connection
- if(!track.switch) {
- SceneryParserLog.warn('connectTracksFailed', `${isPrev ? 'Previous' : 'Next'} track ${vNextTrackId} is not connected back to track ${track.id} (${track.previd} / ${track.nextid}). Cannot connect tracks`);
- return;
- }
-
- const switchId = track.switch.id;
- const isConnectedAsSwitchNext = vNextTrackNextId.slice(0, -1) === switchId;
- const isConnectedAsSwitchPrev = vNextTrackPrevId.slice(0, -1) === switchId;
-
- if(!isConnectedAsSwitchNext && !isConnectedAsSwitchPrev) {
- SceneryParserLog.warn('connectTracksFailed', `${isPrev ? 'Previous' : 'Next'} track ${vNextTrackId} is not connected back to switch track ${track.id} (${track.previd} / ${track.nextid}). Cannot connect tracks`);
- return;
- }
-
- otherConnectionType = isConnectedAsSwitchNext ? TrackConnectionType.START : TrackConnectionType.END;
- addReverseConnection = true;
+ const reverseConnection = reverseConnections[0];
+
+ const ownPosition = track.getEndPos(connection.end);
+ const otherPosition = connection.otherTrack.getEndPos(reverseConnection.end);
+ const distSq = ownPosition.distanceSq(otherPosition);
+ if (distSq > connectionThresholdDistanceSq) {
+ SceneryParserLog.warn(
+ 'tracksConnectionTest',
+ `Track ${track.id} with ${connection.end === TrackConnectionEnd.START ? 'start' : 'end'} position ${ownPosition.toString()} is too far from the track ${connection.otherTrackId} with ${connection.end === TrackConnectionEnd.START ? 'start' : 'end'} position ${otherPosition.toString()}. Distance: ${Math.sqrt(distSq).toFixed(3)}`,
+ );
}
-
- const mainConnectionType = isPrev ? TrackConnectionType.START : TrackConnectionType.END;
-
- track.connections.push(new TrackConnection(
- track,
- vNextTrack,
- mainConnectionType,
- otherConnectionType
- ));
-
- if(!addReverseConnection) return;
-
- vNextTrack.connections.push(new TrackConnection(
- vNextTrack,
- track,
- otherConnectionType,
- mainConnectionType
- ));
-}
\ No newline at end of file
+}
diff --git a/src/model/defs/defined-switches.js b/src/model/defs/defined-switches.js
index ca0f3a1..48a2260 100644
--- a/src/model/defs/defined-switches.js
+++ b/src/model/defs/defined-switches.js
@@ -1,573 +1,96 @@
-import Vector3 from "../vector3";
+import SwitchPrefab from "../switch-descriptions/switch-prefab";
const DefinedSwitches = {
- "Crossing": [
- 0,
- 0,
- [0,1],
- new Vector3(-0.919, 0, -16.59),
- new Vector3(0.919, 0, -16.59),
- new Vector3(0.919, 0, 16.59),
- new Vector3(-0.919, 0, 16.59)
- ],
- "Crossing4.444": [
- 0,
- 0,
- [0,1],
- new Vector3(-1.104, 0, -9.939),
- new Vector3(1.104, 0, -9.939),
- new Vector3(1.104, 0, 9.939),
- new Vector3(-1.104, 0, 9.939)
- ],
- "Rz 60E1-300-1_9 R": [
- 0,
- -300,
- [0, 0, 3, 4, 2, 1],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 33.231),
- new Vector3(1.835, 0, 33.129)
- ],
- "Rz 60E1-300-1_9 L": [
- 0,
- 300,
- [0, 0, 3, 4, 2, 1],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 33.231),
- new Vector3(-1.835, 0, 33.129)
- ],
- "Rz 60E1-190-1_9 R": [
- 0,
- -190,
- [0, 0, 5, 6, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 27.139),
- new Vector3(1.835, 0, 27.037)
- ],
- "Rz 60E1-190-1_9 L": [
- 0,
- 190,
- [0, 0, 5, 6, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 27.139),
- new Vector3(-1.835, 0, 27.037)
- ],
- "Rz 60E1-500-1_12 R": [
- 0,
- -500,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 41.595),
- new Vector3(1.727, 0, 41.523)
- ],
- "Rz 60E1-500-1_12 L": [
- 0,
- 500,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 41.595),
- new Vector3(-1.727, 0, 41.523)
- ],
- "Rkpd 60E1-190-1_9": [
- 0,
- 0,
- [0, 1, 2, 3],
- new Vector3(-0.918, 0, -16.557),
- new Vector3(0.917, 0, -16.557),
- new Vector3(0.917, 0, 16.557),
- new Vector3(-0.917, 0, 16.557)
- ],
- "Rkp 60E1-190-1_9 ab": [
- 0,
- 0,
- [0, 1, 2, 3],
- new Vector3(-0.918, 0, -16.557),
- new Vector3(0.917, 0, -16.556),
- new Vector3(0.917, 0, 16.558),
- new Vector3(-0.921, 0, 16.558)
- ],
- "Rkp 60E1-190-1_9 ba": [
- 0,
- 0,
- [0, 1, 2, 3],
- new Vector3(-0.918, 0, -16.557),
- new Vector3(0.917, 0, -16.556),
- new Vector3(0.917, 0, 16.558),
- new Vector3(-0.921, 0, 16.558)
- ],
- "Rz 60E1-265-1_10 R": [
- 0,
- -265,
- [0, 0, 5, 6],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 31.181),
- new Vector3(1.789, 0, 31.094)
- ],
- "Rz 60E1-265-1_10 L": [
- 0,
- 265,
- [0, 0, 5, 6],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 31.181),
- new Vector3(-1.789, 0, 31.094)
- ],
- "Rz 60E1-760-1_14 R": [
- 0,
- -760,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 54.217),
- new Vector3(1.931, 0, 54.148)
- ],
- "Rz 60E1-760-1_14 L": [
- 0,
- 760,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 54.217),
- new Vector3(-1.931, 0, 54.148)
- ],
- "Rz 60E1-1200-1_18.5 R": [
- 0,
- -1200,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 64.817),
- new Vector3(1.749, 0, 64.77)
- ],
- "Rz 60E1-1200-1_18.5 L": [
- 0,
- 1200,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 64.817),
- new Vector3(-1.75, 0, 64.77)
- ],
- "Rld 60E1-2500_250-1_8.5 R": [
- 2500,
- -250,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0.172, 0, 29.31),
- new Vector3(-1.716, 0, 29.244)
- ],
- "Rld 60E1-2500_250-1_8.5 L": [
- -2500,
- 250,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(-0.172, 0, 29.31),
- new Vector3(1.716, 0, 29.244)
- ],
- "Rld 60E1-700_300-1_10 R": [
- 700,
- -300,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(-0.616, 0, 29.377),
- new Vector3(1.438, 0, 29.339)
- ],
- "Rld 60E1-700_300-1_10 L": [
- -700,
- 300,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0.616, 0, 29.377),
- new Vector3(-1.438, 0, 29.339)
- ],
- "Rld 60E1-1800_300-1_9 R": [
- 1800,
- -300,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(-0.307, 0, 33.228),
- new Vector3(1.838, 0, 33.163)
- ],
- "Rld 60E1-1800_300-1_9 L": [
- -1800,
- 300,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0.307, 0, 33.228),
- new Vector3(-1.838, 0, 33.163)
- ],
- "Rld 60E1-2500_1200-1_22 R": [
- 2500,
- -1200,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(-0.586, 0, 54.51),
- new Vector3(1.238, 0, 54.499)
- ],
- "Rld 60E1-2500_1200-1_22 L": [
- -2500,
- 1200,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0.586, 0, 54.51),
- new Vector3(-1.238, 0, 54.499)
- ],
- "Rld 60E1-650_450-1_15 R": [
- 650,
- -450,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(-0.691, 0, 29.956),
- new Vector3(0.997, 0, 29.945)
- ],
- "Rld 60E1-650_450-1_15 L": [
- -650,
- 450,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0.691, 0, 29.956),
- new Vector3(-0.997, 0, 29.945)
- ],
- "Rld 60E1-600_300-1_9 R": [
- 600,
- -300,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(-0.749, 0, 29.918),
- new Vector3(1.49, 0, 29.881)
- ],
- "Rld 60E1-600_300-1_9 L": [
- -600,
- 300,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0.749, 0, 29.918),
- new Vector3(-1.49, 0, 29.881)
- ],
- "Rz 60E1-205-1_9 R": [
- 0,
- -205,
- [0, 0, 5, 6],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 28.127),
- new Vector3(1.853, 0, 28.025)
- ],
- "Rz 60E1-205-1_9 L": [
- 0,
- 205,
- [0, 0, 5, 6],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 28.127),
- new Vector3(-1.853, 0, 28.025),
- ],
- "Rz 60E1-190-1_7.5 R": [
- 0,
- -190,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 25.221),
- new Vector3(1.667, 0, 25.111)
- ],
- "Rz 60E1-190-1_7.5 L": [
- 0,
- 190,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 25.221),
- new Vector3(-1.667, 0, 25.111)
- ],
- "Rz 60E1-2500-1_26.5 R": [
- 0,
- -2500,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 94.306),
- new Vector3(1.778, 0, 94.273)
- ],
- "Rz 60E1-2500-1_26.5 L": [
- 0,
- 2500,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 94.306),
- new Vector3(-1.778, 0, 94.273)
- ],
- "Rlds 60E1-600-600-1_18.5": [
- -600,
- 600,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0.92, 0, 33.206),
- new Vector3(-0.92, 0, 33.206)
- ],
- "Rlds 60E1-190-190-1_9": [
- -190,
- 190,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(1.184, 0, 21.186),
- new Vector3(-1.185, 0, 21.186)
- ],
- "Rlj 60E1-650_190-1_20 R": [
- -650,
- -190,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0.811, 0, 32.466),
- new Vector3(2.77, 0, 32.332)
- ],
- "Rlj 60E1-650_190-1_20 L": [
- 650,
- 190,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(-0.811, 0, 32.466),
- new Vector3(-2.77, 0, 32.332)
- ],
- "Rlj 60E1-1200_300-1_7 R": [
- -1200,
- -300,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0.758, 0, 42.632),
- new Vector3(3.025, 0, 42.498)
- ],
- "Rlj 60E1-1200_300-1_7 L": [
- 1200,
- 300,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(-0.758, 0, 42.632),
- new Vector3(-3.025, 0, 42.498)
- ],
- "Rlj 60E1-750_190-1_6 R": [
- -750,
- -190,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0.66, 0, 31.44),
- new Vector3(2.597, 0, 31.306)
- ],
- "Rlj 60E1-750_190-1_6 L": [
- 750,
- 190,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(-0.66, 0, 31.44),
- new Vector3(-2.597, 0, 31.306)
- ],
- "Rld 60E1-1200_600-1_15 R": [
- 1200,
- -600,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(-0.665, 0, 39.948),
- new Vector3(1.33, 0, 39.926)
- ],
- "Rld 60E1-1200_600-1_15 L": [
- -1200,
- 600,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0.665, 0, 39.948),
- new Vector3(-1.33, 0, 39.926)
- ],
- "Rld 60E1-1200_900-1_18.5 R": [
- 1200,
- -900,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(-0.984, 0, 48.6),
- new Vector3(1.313, 0, 48.589)
- ],
- "Rld 60E1-1200_900-1_18.5 L": [
- -1200,
- 900,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0.984, 0, 48.6),
- new Vector3(-1.313, 0, 48.589)
- ],
- "Rlj 60E1-900_300-1_7.5 R": [
- -900,
- -300,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0.881, 0, 39.81),
- new Vector3(2.639, 0, 39.706)
- ],
- "Rlj 60E1-900_300-1_7.5 L": [
- 900,
- 300,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(-0.881, 0, 39.81),
- new Vector3(-2.639, 0, 39.706)
- ],
- "Rlj 60E1-600_300-1_6 R": [
- -600,
- -300,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(1.919, 0, 47.949),
- new Vector3(3.832, 0, 47.795)
- ],
- "Rlj 60E1-600_300-1_6 L": [
- 600,
- 300,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(-1.919, 0, 47.949),
- new Vector3(-3.832, 0, 47.795)
- ],
- "Rld 60E1-900_450-1_12 R": [
- 900,
- -450,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(-0.778, 0, 37.425),
- new Vector3(1.556, 0, 37.392)
- ],
- "Rld 60E1-900_450-1_12 L": [
- -900,
- 450,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0.778, 0, 37.425),
- new Vector3(-1.556, 0, 37.392)
- ],
- "Rld 60E1-700_500-1_14 R": [
- 700,
- -500,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(-0.909, 0, 35.653),
- new Vector3(1.272, 0, 35.639)
- ],
- "Rld 60E1-700_500-1_14 L": [
- -700,
- 500,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0.909, 0, 35.653),
- new Vector3(-1.272, 0, 35.639)
- ],
- "Rld 60E1-2500_400-1_10.5 R": [
- 2500,
- -400,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(-0.289, 0, 38.008),
- new Vector3(1.805, 0, 37.952)
- ],
- "Rld 60E1-2500_400-1_10.5 L": [
- -2500,
- 400,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0.289, 0, 38.008),
- new Vector3(-1.805, 0, 37.952)
- ],
- "Rlj 60E1-1800_300-1_7.5 R": [
- -1800,
- -300,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0.423, 0, 38.998),
- new Vector3(2.531, 0, 38.89)
- ],
- "Rlj 60E1-1800_300-1_7.5 L": [
- 1800,
- 300,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(-0.423, 0, 38.998),
- new Vector3(-2.531, 0, 38.89)
- ],
- "Rld 60E1-1800_600-1_14 R": [
- 1800,
- -600,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(-0.509, 0, 42.798),
- new Vector3(1.526, 0, 42.767)
- ],
- "Rld 60E1-1800_600-1_14 L": [
- -1800,
- 600,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0.509, 0, 42.798),
- new Vector3(-1.526, 0, 42.767)
- ],
- "Rlj 60E1-1800_450-1_9 R": [
- -1800,
- -450,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(0.69, 0, 49.84),
- new Vector3(2.758, 0, 49.744)
- ],
- "Rlj 60E1-1800_450-1_9 L": [
- 1800,
- 450,
- [0, 0, 3, 4],
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 0),
- new Vector3(-0.69, 0, 49.84),
- new Vector3(-2.758, 0, 49.745)
- ]
-}
+ "Crossing": SwitchPrefab.crossing(33.2294, 9.0),
+ "Crossing4.444": SwitchPrefab.crossing(20.0, 4.444),
-export default DefinedSwitches;
\ No newline at end of file
+ "Rkp 60E1-190-1_9 ab": SwitchPrefab.slip(33.165, 6.06, 7.461676, 190.0, 9.0, false, true),
+ "Rkp 60E1-190-1_9 ba": SwitchPrefab.slip(33.165, 6.06, 7.461676, 190.0, 9.0, false, true),
+ "Rkpd 60E1-190-1_9": SwitchPrefab.slip(33.165, 6.06, 7.461676, 190.0, 9.0, true, true),
+
+ "Rld 60E1-1200_600-1_15 L": SwitchPrefab.fork(600.0, -1200.0, 39.95566),
+ "Rld 60E1-1200_600-1_15 R": SwitchPrefab.fork(-600.0, 1200.0, 39.95566),
+ "Rld 60E1-1200_900-1_18.5 L": SwitchPrefab.fork(900.0, -1200.0, 48.61317),
+ "Rld 60E1-1200_900-1_18.5 R": SwitchPrefab.fork(-900.0, 1200.0, 48.61317),
+ "Rld 60E1-1800_300-1_9 L": SwitchPrefab.fork(300.0, -1800.0, 33.23108),
+ "Rld 60E1-1800_300-1_9 R": SwitchPrefab.fork(-300.0, 1800.0, 33.23108),
+ "Rld 60E1-1800_600-1_14 L": SwitchPrefab.fork(600.0, -1800.0, 42.80262),
+ "Rld 60E1-1800_600-1_14 R": SwitchPrefab.fork(-600.0, 1800.0, 42.80262),
+ "Rld 60E1-1800_900-1_18.5 L": SwitchPrefab.fork(900.0, -1800.0, 48.61317),
+ "Rld 60E1-1800_900-1_18.5 R": SwitchPrefab.fork(-900.0, 1800.0, 48.61317),
+ "Rld 60E1-2500_1200-1_22 L": SwitchPrefab.fork(1200.0, -2500.0, 54.51731),
+ "Rld 60E1-2500_1200-1_22 R": SwitchPrefab.fork(-1200.0, 2500.0, 54.51731),
+ "Rld 60E1-2500_250-1_8.5 L": SwitchPrefab.fork(250.0, -2500.0, 29.31069),
+ "Rld 60E1-2500_250-1_8.5 R": SwitchPrefab.fork(-250.0, 2500.0, 29.31069),
+ "Rld 60E1-2500_400-1_10.5 L": SwitchPrefab.fork(400.0, -2500.0, 38.00925),
+ "Rld 60E1-2500_400-1_10.5 R": SwitchPrefab.fork(-400.0, 2500.0, 38.00925),
+ "Rld 60E1-2500_600-1_14 L": SwitchPrefab.fork(600.0, -2500.0, 42.80262),
+ "Rld 60E1-2500_600-1_14 R": SwitchPrefab.fork(-600.0, 2500.0, 42.80262),
+ "Rld 60E1-2500_900-1_17 L": SwitchPrefab.fork(900.0, -2500.0, 52.14928),
+ "Rld 60E1-2500_900-1_17 R": SwitchPrefab.fork(-900.0, 2500.0, 52.14928),
+ "Rld 60E1-600_300-1_9 L": SwitchPrefab.fork(300.0, -600.0, 29.92537),
+ "Rld 60E1-600_300-1_9 R": SwitchPrefab.fork(-300.0, 600.0, 29.92537),
+ "Rld 60E1-650_450-1_15 L": SwitchPrefab.fork(450.0, -650.0, 29.96674),
+ "Rld 60E1-650_450-1_15 R": SwitchPrefab.fork(-450.0, 650.0, 29.96674),
+ "Rld 60E1-700_190-1_7.5 L": SwitchPrefab.fork(190.0, -700.0, 25.22173),
+ "Rld 60E1-700_190-1_7.5 R": SwitchPrefab.fork(-190.0, 700.0, 25.22173),
+ "Rld 60E1-700_300-1_10 L": SwitchPrefab.fork(300.0, -700.0, 29.38637),
+ "Rld 60E1-700_300-1_10 R": SwitchPrefab.fork(-300.0, 700.0, 29.38637),
+ "Rld 60E1-700_500-1_14 L": SwitchPrefab.fork(500.0, -700.0, 35.66885),
+ "Rld 60E1-700_500-1_14 R": SwitchPrefab.fork(-500.0, 700.0, 35.66885),
+ "Rld 60E1-900_300-1_9 L": SwitchPrefab.fork(300.0, -900.0, 33.23108),
+ "Rld 60E1-900_300-1_9 R": SwitchPrefab.fork(-300.0, 900.0, 33.23108),
+ "Rld 60E1-900_450-1_12 L": SwitchPrefab.fork(450.0, -900.0, 37.43512),
+ "Rld 60E1-900_450-1_12 R": SwitchPrefab.fork(-450.0, 900.0, 37.43512),
+ "Rld 60E1-900_600-1_15 L": SwitchPrefab.fork(600.0, -900.0, 39.95566),
+ "Rld 60E1-900_600-1_15 R": SwitchPrefab.fork(-600.0, 900.0, 39.95566),
+ "Rlds 60E1-1000-1000-1_23": SwitchPrefab.fork(1000.0, -1000.0, 43.45773),
+ "Rlds 60E1-190-190-1_9": SwitchPrefab.fork(190.0, -190.0, 21.23),
+ "Rlds 60E1-600-600-1_18.5": SwitchPrefab.fork(599.9205, -600.0, 33.22262),
+ "Rlj 60E1-1200_300-1_7 L": SwitchPrefab.fork(300.0, 1200.0, 42.64069),
+ "Rlj 60E1-1200_300-1_7 R": SwitchPrefab.fork(-300.0, -1200.0, 42.64069),
+ "Rlj 60E1-1200_600-1_9 L": SwitchPrefab.fork(600.0, 1200.0, 68.0),
+ "Rlj 60E1-1200_600-1_9 R": SwitchPrefab.fork(-600.0, -1200.0, 68.0),
+ "Rlj 60E1-1500_450-1_9 L": SwitchPrefab.fork(450.0, 1500.0, 49.84663),
+ "Rlj 60E1-1500_450-1_9 R": SwitchPrefab.fork(-450.0, -1500.0, 49.84663),
+ "Rlj 60E1-1800_300-1_7.5 L": SwitchPrefab.fork(300.0, 1800.0, 39.0),
+ "Rlj 60E1-1800_300-1_7.5 R": SwitchPrefab.fork(-300.0, -1800.0, 39.0),
+ "Rlj 60E1-1800_450-1_9 L": SwitchPrefab.fork(450.0, 1800.0, 49.84663),
+ "Rlj 60E1-1800_450-1_9 R": SwitchPrefab.fork(-450.0, -1800.0, 49.84663),
+ "Rlj 60E1-1800_600-1_10 L": SwitchPrefab.fork(599.48096, 1800.0, 59.85075),
+ "Rlj 60E1-1800_600-1_10 R": SwitchPrefab.fork(-599.48096, -1800.0, 59.85075),
+ "Rlj 60E1-600_300-1_6 L": SwitchPrefab.fork(300.0, 600.0, 48.0),
+ "Rlj 60E1-600_300-1_6 R": SwitchPrefab.fork(-300.0, -600.0, 48.0),
+ "Rlj 60E1-650_190-1_20 L": SwitchPrefab.fork(190.0, 650.0, 32.47971),
+ "Rlj 60E1-650_190-1_20 R": SwitchPrefab.fork(-190.0, -650.0, 32.47971),
+ "Rlj 60E1-750_190-1_6 L": SwitchPrefab.fork(190.0, 750.0, 31.44976),
+ "Rlj 60E1-750_190-1_6 R": SwitchPrefab.fork(-190.0, -750.0, 31.44976),
+ "Rlj 60E1-800_250-1_6.5 L": SwitchPrefab.fork(250.0, 800.0, 38.23661),
+ "Rlj 60E1-800_250-1_6.5 R": SwitchPrefab.fork(-250.0, -800.0, 38.23661),
+ "Rlj 60E1-900_300-1_7.5 L": SwitchPrefab.fork(300.0, 900.0, 39.82378),
+ "Rlj 60E1-900_300-1_7.5 R": SwitchPrefab.fork(-300.0, -900.0, 39.82378),
+ "Rlj 60E1-900_300-1_9 L": SwitchPrefab.fork(300.0, 900.0, 49.84663),
+ "Rlj 60E1-900_300-1_9 R": SwitchPrefab.fork(-300.0, -900.0, 49.84663),
+ "Rlj 60E1-900_450-1_7.5 L": SwitchPrefab.fork(450.0, 900.0, 59.0),
+ "Rlj 60E1-900_450-1_7.5 R": SwitchPrefab.fork(-450.0, -900.0, 59.0),
+ "Rz 60E1-1200-1_18.5 L": SwitchPrefab.fork(1200.0, 0.0, 64.81756),
+ "Rz 60E1-1200-1_18.5 R": SwitchPrefab.fork(-1200.0, 0.0, 64.81756),
+ "Rz 60E1-190-1_7.5 L": SwitchPrefab.fork(190.0, 0.0, 25.221731),
+ "Rz 60E1-190-1_7.5 R": SwitchPrefab.fork(-190.0, 0.0, 25.221731),
+ "Rz 60E1-190-1_9 L": SwitchPrefab.fork(0.0, 190.0, 21.046352, 6.0923653),
+ "Rz 60E1-190-1_9 R": SwitchPrefab.fork(0.0, -190.0, 21.046352, 6.0923653),
+ "Rz 60E1-205-1_9 L": SwitchPrefab.fork(205.0, 0.0, 22.707907, 5.42 /* added manually */),
+ "Rz 60E1-205-1_9 R": SwitchPrefab.fork(-205.0, 0.0, 22.707907, 5.42 /* added manually */),
+ "Rz 60E1-2500-1_26.5 L": SwitchPrefab.fork(2500.0, 0.0, 94.30607),
+ "Rz 60E1-2500-1_26.5 R": SwitchPrefab.fork(-2500.0, 0.0, 94.30607),
+ "Rz 60E1-265-1_10 L": SwitchPrefab.fork(0.0, 265.0, 26.434078, 4.75 /* added manually */),
+ "Rz 60E1-265-1_10 R": SwitchPrefab.fork(0.0, -265.0, 26.434078, 4.75 /* added manually */),
+ "Rz 60E1-300-1_9 L": SwitchPrefab.fork(300.0, 0.0, 33.231083),
+ "Rz 60E1-300-1_9 R": SwitchPrefab.fork(-300.0, 0.0, 33.231083),
+ "Rz 60E1-500-1_12 L": SwitchPrefab.fork(500.0, 0.0, 41.59458),
+ "Rz 60E1-500-1_12 R": SwitchPrefab.fork(-500.0, 0.0, 41.59458),
+ "Rz 60E1-760-1_14 L": SwitchPrefab.fork(760.0, 0.0, 54.21665),
+ "Rz 60E1-760-1_14 R": SwitchPrefab.fork(-760.0, 0.0, 54.21665),
+};
+
+export default DefinedSwitches;
diff --git a/src/model/electrification-resolver.js b/src/model/electrification-resolver.js
index dea0702..9cebb88 100644
--- a/src/model/electrification-resolver.js
+++ b/src/model/electrification-resolver.js
@@ -2,16 +2,6 @@ import Constants from "../helpers/constants";
import SceneryParserLog from "./scenery-parser-log";
import {ElectrificationStatus, ElectrificationResolutionStatus} from "./electrification-status";
-class RouteTrack {
- route;
- track;
- electrified;
-
- constructor(route, track, electrified) {
- Object.assign(this, { route, track, electrified });
- }
-}
-
export default class ElectrificationResolver {
static hasWarnings = false;
static propagationQueue = [];
@@ -21,21 +11,22 @@ export default class ElectrificationResolver {
ElectrificationResolver.propagationQueue = [];
try {
- const routeTracks = this._findRouteTracks(scenery);
+ const routeTracks = this._findTracksAdjacentToRoutes(scenery);
this._markNEVPTracks(scenery);
- routeTracks.forEach(routeTrack => {
+ routeTracks.forEach(({ track, routeTrack }) => {
this._propagate(
- routeTrack.track,
- [],
- routeTrack.electrified ? ElectrificationStatus.ELECTRIFIED : ElectrificationStatus.NON_ELECTRIFIED
+ track,
+ routeTrack.id,
+ routeTrack.electrificationStatus,
);
});
this._runResolution();
} catch (error) {
- scenery.electrificationResolved = ElectrificationResolutionStatus.ERROR;
+ scenery.electrificationResolved = ElectrificationResolutionStatus.RESOLVING_ERROR;
SceneryParserLog.warn('electrificationResolverError', `Error resolving electrification: ${error.message}`);
+ console.warn(error);
return;
}
@@ -53,48 +44,18 @@ export default class ElectrificationResolver {
SceneryParserLog.warn(type, message);
}
- static _checkTrackConnectedToRoute(track, routePoint) {
- const mStartDist = track.points.start.mannhattanDistance(routePoint.point);
- if(mStartDist <= Constants.parser.maxRouteConnectionDistance) {
- const startDistSq = track.points.start.distanceSq(routePoint.point);
- if(startDistSq <= Constants.parser.maxRouteConnectionDistance ** 2) {
- return true;
- }
- }
-
- const mEndDist = track.points.end.mannhattanDistance(routePoint.point);
- if(mEndDist <= Constants.parser.maxRouteConnectionDistance) {
- const endDistSq = track.points.end.distanceSq(routePoint.point);
- if(endDistSq <= Constants.parser.maxRouteConnectionDistance ** 2) {
- return true;
- }
- }
-
- return false;
- }
-
- static _findRouteTracks(scenery) {
- const routePoints = Object.values(scenery.objects.routes || {})
- .map(route => route.points.map(point => ({ route, point }))).flat();
-
- const results = [];
-
- Object.values(scenery.objects.tracks || {}).forEach(track => {
- routePoints.forEach(routePoint => {
- if (this._checkTrackConnectedToRoute(track, routePoint)) {
- results.push(new RouteTrack(routePoint.route, track, routePoint.route.electrified));
- }
+ static _findTracksAdjacentToRoutes(scenery) {
+ return Object.values(scenery.objects.routes || {}).flatMap(route => {
+ if (route.segments.length === 0) return;
+ return route.segments[0].tracks.flatMap((routeTrack) => {
+ return routeTrack.connections
+ .map((connection) => {
+ if (connection.otherTrack.type === 'RouteTrack') return null;
+ return { track: connection.otherTrack, routeTrack };
+ })
+ .filter((adjacent) => adjacent !== null);
});
});
-
- if(results.length < routePoints.length) {
- ElectrificationResolver._passWarn(
- 'electrificationMissingRouteTracks',
- `While resolving electrification, found ${results.length} tracks connected to routes, but expected ${routePoints.length}`
- );
- }
-
- return results;
}
static _markNEVPTracks(scenery) {
@@ -103,7 +64,7 @@ export default class ElectrificationResolver {
if(!trackObject.track) {
ElectrificationResolver._passWarn(
- 'electrificationNevpNotApplied',
+ 'electrificationNevpNotApplied',
`NEVP object ${trackObject.name} (${trackObject.id}) at track ${trackObject.track_id} has not been applied. Electrification resolution may fail`
);
return;
@@ -115,16 +76,16 @@ export default class ElectrificationResolver {
static _runResolution() {
while(ElectrificationResolver.propagationQueue.length > 0) {
- const { track, skipTrackIds, status } = ElectrificationResolver.propagationQueue.shift();
- ElectrificationResolver._resolveTrack(track, skipTrackIds, status);
+ const { track, skipTrackId, status } = ElectrificationResolver.propagationQueue.pop();
+ ElectrificationResolver._resolveTrack(track, skipTrackId, status);
}
}
- static _propagate(track, skipTrackIds, status) {
- ElectrificationResolver.propagationQueue.push({ track, skipTrackIds, status });
+ static _propagate(track, skipTrackId, status) {
+ ElectrificationResolver.propagationQueue.push({ track, skipTrackId, status });
}
- static _resolveTrack(track, skipTrackIds, status) {
+ static _resolveTrack(track, skipTrackId, status) {
if (track.electrificationStatus === status) return; // already set
if (Constants.parser.skipElectrificationErrorsPropagation && status === ElectrificationStatus.CONFLICT) {
return;
@@ -137,38 +98,24 @@ export default class ElectrificationResolver {
const fsne = status === ElectrificationStatus.NON_ELECTRIFIED;
const tse = track.electrificationStatus === ElectrificationStatus.ELECTRIFIED;
const tsne = track.electrificationStatus === ElectrificationStatus.NON_ELECTRIFIED;
-
+
track.electrificationStatus = status;
-
+
if (track.hasNEVP) {
track.electrificationStatus = ElectrificationStatus.NON_ELECTRIFIED;
if(!fse) return;
} else if ((tse && fsne) || (tsne && fse)) {
track.electrificationStatus = ElectrificationStatus.CONFLICT;
ElectrificationResolver._passWarn(
- 'electrificationConflict',
+ 'electrificationConflict',
`Track ${track.id} has conflicting electrification status (=>${status})`
);
return;
}
- const propagation = track.connections.map(conn => conn.otherTrack);
- const nextSkipIds = [track.id];
-
- if (track.switch) {
- nextSkipIds.push(track.switch.trackA.id, track.switch.trackB.id);
- }
-
- propagation.forEach(nextTrack => {
- if (skipTrackIds.includes(nextTrack.id)) return;
-
- this._propagate(nextTrack, nextSkipIds, track.electrificationStatus);
+ track.connections.forEach(connection => {
+ if (skipTrackId === connection.otherTrackId) return;
+ this._propagate(connection.otherTrack, track.id, track.electrificationStatus);
});
-
- // Additional propagation for double switches
- if (!track.switch || track.switch.def?.[2]?.length <= 2 || track.hasNEVP) return;
-
- const otherSwitchTrack = track.switch.trackA === track ? track.switch.trackB : track.switch.trackA;
- this._propagate(otherSwitchTrack, [track.id, ...propagation.map(track => track.id)], track.electrificationStatus);
}
-};
\ No newline at end of file
+};
diff --git a/src/model/route.js b/src/model/route.js
index 511692a..c3a4324 100644
--- a/src/model/route.js
+++ b/src/model/route.js
@@ -1,5 +1,9 @@
import SceneryObject from "./scenery-object";
import Vector3 from "./vector3";
+import SceneryParserLog from "./scenery-parser-log";
+import RouteTrack from "./tracks/route-track";
+import TrackConnection, {TrackConnectionEnd} from "./track-connection";
+import AngleHelper from "../helpers/angleHelper";
export default class Route extends SceneryObject {
track_count;
@@ -8,7 +12,12 @@ export default class Route extends SceneryObject {
track_offset;
category = "routes";
type = "Route";
- points = [];
+ points;
+ offsets;
+ segments = [];
+ end_center;
+ end_points;
+ end_angle_rad;
constructor(prefab_name, pos, rot, track_count, route_name, track_offset, electrified) {
super(route_name, pos, rot);
@@ -21,10 +30,14 @@ export default class Route extends SceneryObject {
electrified,
});
- this.points = this._getConnectionPoints();
+ this.offsets = this._getOffsets();
+ this.end_angle_rad = this.rot.y * Math.PI / 180;
+ this.end_center = this.pos.clone();
+ this.end_points = this._getConnectionPoints();
+ this.points = [...this.end_points];
}
- static fromText(text) {
+ static fromText(text) {
const values = text.split(";");
const route = new Route(
values[2], // prefab_name
@@ -32,25 +45,104 @@ export default class Route extends SceneryObject {
Vector3.fromValuesArray(values, 6), // rot
parseInt(values[9]), // track_count
values[10], // route_name
- parseFloat(values[11]) || 0, // track_offset
+ parseFloat(values[11]) || 0, // center_offset
values[13] === "True" || values[12] === "1" // electrified
);
return route;
}
- _getConnectionPoints() {
- if (this.track_count === 1) {
- return [this.pos.clone()];
+ addSegment(length, radius, trackIds) {
+ if (trackIds.length !== this.track_count) {
+ SceneryParserLog.warn('routeInvalidSegment', `Route segment has invalid track count: expected ${this.track_count}, got ${trackIds.length}`);
+ return;
+ }
+ const startAngleRad = this.end_angle_rad;
+ const startRot = new Vector3(0, AngleHelper.radToDeg(startAngleRad), 0);
+
+ const startPoints = this.end_points;
+ this.end_points = [];
+
+ const radii = [];
+ const lengths = [];
+
+ if (radius === 0) {
+ this.end_center = this.end_center.add(Vector3.fromAngleY(startAngleRad, length));
+ this.offsets.forEach(offset => {
+ radii.push(0);
+ lengths.push(length);
+ const end_point = this.end_center
+ .add(Vector3.fromAngleY(this.end_angle_rad + Math.PI / 2, offset));
+ this.end_points.push(end_point);
+ });
+ } else {
+ this.end_angle_rad -= length / radius;
+ const startToCenter = Vector3.fromAngleY(startAngleRad + Math.sign(radius) * Math.PI / 2, Math.abs(radius));
+ const circleCenter = this.end_center.add(startToCenter.negate());
+ const centerToEndUnit = Vector3.fromAngleY(this.end_angle_rad + Math.sign(radius) * Math.PI / 2, 1).multiply(Math.sign(radius));
+ this.end_center = circleCenter.add(centerToEndUnit.multiply(radius));
+
+ this.offsets.forEach(offset => {
+ const trackRadius = radius + offset;
+ const trackLength = length * trackRadius / radius;
+ radii.push(trackRadius);
+ lengths.push(trackLength);
+ const end_point = circleCenter.add(centerToEndUnit.multiply(trackRadius));
+ this.end_points.push(end_point);
+ });
}
- const angle = (this.rot.y + 90) * Math.PI / 180;
- const rightTrackOffset = Vector3.fromAngleY(angle, this.track_offset - 2);
- const leftTrackOffset = Vector3.fromAngleY(angle, this.track_offset + 2);
+ this.points.push(...this.end_points);
- return [
- this.pos.add(rightTrackOffset),
- this.pos.add(leftTrackOffset)
- ];
+ const tracks = trackIds.map((trackId, index) => {
+ return new RouteTrack(
+ trackId,
+ startPoints[index],
+ this.end_points[index],
+ startRot,
+ lengths[index],
+ radii[index],
+ [], // connections
+ this.electrified,
+ this, // route
+ );
+ });
+
+ if (this.segments.length >= 2) {
+ const prevSegment = this.segments[this.segments.length - 2];
+ const newSegment = this.segments[this.segments.length - 1];
+ newSegment.tracks.forEach((track, index) => {
+ const prevTrack = prevSegment.tracks[index];
+ track.connections.push(new TrackConnection(prevTrack, TrackConnectionEnd.START));
+ prevTrack.connections.push(new TrackConnection(track, TrackConnectionEnd.END));
+ });
+ }
+
+ this.segments.push({ length, radius, trackIds, tracks });
}
-}
\ No newline at end of file
+
+ // This method is future-proofed for more than 2 tracks
+ _getOffsets() {
+ const leftmost_offset = (this.track_count - 1) * 2 + this.track_offset;
+ const result = [];
+ for (let i = 0; i < this.track_count; i++) {
+ result.push(leftmost_offset - i * 4);
+ }
+ return result;
+ }
+
+ _getConnectionPoints() {
+ return this.offsets.map((offset) => {
+ const offsetVec = Vector3.fromAngleY(this.end_angle_rad + Math.PI / 2, offset);
+ return this.pos.add(offsetVec);
+ });
+ }
+
+ applyObject(scenery) {
+ this.segments.forEach((segment) => {
+ segment.tracks.forEach((track) => {
+ scenery.addObject(track);
+ });
+ });
+ }
+}
diff --git a/src/model/scenery-parser.js b/src/model/scenery-parser.js
index 51292a0..77136ea 100644
--- a/src/model/scenery-parser.js
+++ b/src/model/scenery-parser.js
@@ -1,5 +1,4 @@
import Scenery from './scenery';
-import { tracksConnectionTest } from './tracks-connection-test';
import Switch from './switch';
import StandardTrack from './tracks/standard-track';
import BezierTrack from './tracks/bezier-track';
@@ -19,7 +18,7 @@ import NEVP from './track-objects/nevp';
import Derailer from './track-objects/derailer';
import SpawnPoint from './track-objects/spawn-point';
import { attachSigns } from './attach-signs';
-import { connectTracks } from './connect-tracks';
+import {connectTracks} from "./connect-tracks";
/*
TODO: add support for WorldRotation and WorldTranslation
@@ -30,23 +29,36 @@ export default class SceneryParser {
const lines = scText.split("\n").map(line => line.trim());
const scenery = new Scenery();
+ let currentRoute = null;
+
lines.forEach((line, index) => {
if(index === 0) {
const sceneryInfo = SceneryParser._parseSceneryInfo(line);
if(sceneryInfo) scenery.addObject(sceneryInfo);
return;
}
- if(!line?.length) return;
+ if(!line?.length) return;
- const object = SceneryParser._parseObject(line);
- if(!object) return; // skip null objects
- scenery.addObject(object);
+ if (currentRoute !== null) {
+ const foundRouteEnd = SceneryParser._parseRouteLine(line, currentRoute);
+ if (foundRouteEnd) {
+ scenery.addObject(currentRoute);
+ currentRoute = null;
+ }
+ } else {
+ const object = SceneryParser._parseObject(line);
+ if(!object) return; // skip null objects
+ if (object.type === 'Route') {
+ currentRoute = object;
+ } else {
+ scenery.addObject(object);
+ }
+ }
});
scenery.applyObjects();
+ connectTracks(scenery);
- if(Constants.parser.runTracksConnectionTest) tracksConnectionTest(scenery);
- if(Constants.parser.connectTracks) connectTracks(scenery);
if(Constants.parser.resolveElectrification) ElectrificationResolver.resolveScenery(scenery);
if(Constants.parser.attachSigns) attachSigns(scenery);
if(Constants.parser.logSceneryAfterFinished) console.log(scenery);
@@ -66,12 +78,13 @@ export default class SceneryParser {
}
return SceneryInfo.fromText(text);
- }
+ }
static _parseObject(text) {
const type = text.split(";", 2)[0];
- if(type.indexOf("Forest") !== -1 || type.indexOf("Empty") !== -1) {
+ if (type === 'EndRoute' || type.indexOf("Forest") !== -1 || type.indexOf("Empty") !== -1) {
+ SceneryParserLog.warn('unknownObjectType', `Unexpected entry of type ${type} without a preceding Route object`);
return null;
}
@@ -90,7 +103,6 @@ export default class SceneryParser {
return CameraHome.fromText(text);
case 'MainCamera':
return MainCamera.fromText(text);
- case 'EndRoute':
case 'MiscGroup':
case 'EndMiscGroup':
case 'Wires':
@@ -108,7 +120,7 @@ export default class SceneryParser {
default:
SceneryParserLog.warn('unknownObjectType', `Unknown object type: ${type}`);
return null;
- };
+ }
}
static _parseTrack(text) {
@@ -149,4 +161,31 @@ export default class SceneryParser {
return Misc.fromText(Scenery.nextMiscId++, text);
}
}
-}
\ No newline at end of file
+
+ // Returns true if EndRoute was found, false otherwise
+ static _parseRouteLine(text, route) {
+ if (text === 'EndRoute') return true;
+ const fields = text.split(';');
+ if (fields.length < 4) {
+ SceneryParserLog.warn('routeInvalidSegment', `Invalid route segment: "${text}", not enough fields`);
+ return;
+ }
+ const trackIds = fields[3]
+ .split(',')
+ .map(segment => {
+ const id = segment.split(':')[0].trim();
+ if (id === '') return null;
+ return id;
+ });
+ if (trackIds.some(id => id === null || isNaN(id))) {
+ SceneryParserLog.warn('routeInvalidSegment', `Invalid route segment track IDs: ${fields[3]}`);
+ return;
+ }
+ route.addSegment(
+ parseInt(fields[1]), // length
+ parseInt(fields[2]), // radius
+ trackIds,
+ );
+ return false;
+ }
+}
diff --git a/src/model/scenery.js b/src/model/scenery.js
index 08737da..b3a69d5 100644
--- a/src/model/scenery.js
+++ b/src/model/scenery.js
@@ -1,5 +1,4 @@
import { ElectrificationResolutionStatus } from './electrification-status.js';
-import SceneryParserLog from './scenery-parser-log.js';
export default class Scenery
{
@@ -7,7 +6,6 @@ export default class Scenery
signalBoxes = [];
spawnPoints = [];
bounds = { minX: Infinity, minZ: Infinity, maxX: -Infinity, maxZ: -Infinity };
- trackAliases = {};
electrificationResolved = ElectrificationResolutionStatus.NOT_RESOLVED;
static nextMiscId = 1;
@@ -36,14 +34,30 @@ export default class Scenery
this._updateBounds(object.getRenderBounds());
}
}
-
+
applyObjects() {
+ const categories_order = [
+ 'switches', 'routes', // switches and routes create tracks
+ 'tracks',
+ 'track-objects', // track objects might refer to tracks
+ ];
+
+ categories_order.forEach((category) => this._applyCategory(category));
+
+ // Remaining categories
Object.keys(this.objects).forEach(category => {
- Object.values(this.objects[category]).forEach(object => {
- if(object.applyObject) {
- object.applyObject(this);
- }
- });
+ if (categories_order.includes(category)) return;
+ this._applyCategory(category);
+ });
+ }
+
+ _applyCategory(category) {
+ if(!this.objects[category]) return;
+
+ Object.values(this.objects[category]).forEach(object => {
+ if (object.applyObject) {
+ object.applyObject(this);
+ }
});
}
@@ -52,26 +66,6 @@ export default class Scenery
return this.objects[category][id] ?? null;
}
- addTrackAlias(id, alias) {
- if(this.trackAliases[alias]) {
- SceneryParserLog.warn('trackAliasAlreadyExists', `Track alias "${alias}" already exists for track "${this.trackAliases[alias]}". Cannot add alias for "${id}"`);
- return;
- }
-
- const track = this.getObject('tracks', id);
- if(!track) {
- SceneryParserLog.warn('trackAliasNoTrack', `Cannot add alias "${alias}" for non-existing track "${id}"`);
- return;
- }
-
- track.aliases.push(alias);
- this.trackAliases[alias] = id;
- }
-
- getTrackIdByAlias(idOrAlias) {
- return this.trackAliases[idOrAlias] || idOrAlias;
- }
-
_updateBounds(objBounds) {
if(!objBounds || !Array.isArray(objBounds)) return;
@@ -80,7 +74,6 @@ export default class Scenery
if(point.z < this.bounds.minZ) this.bounds.minZ = point.z;
if(point.x > this.bounds.maxX) this.bounds.maxX = point.x;
if(point.z > this.bounds.maxZ) this.bounds.maxZ = point.z;
- }
- );
+ });
}
-}
\ No newline at end of file
+}
diff --git a/src/model/switch-descriptions/switch-prefab-track.js b/src/model/switch-descriptions/switch-prefab-track.js
new file mode 100644
index 0000000..7dc8c40
--- /dev/null
+++ b/src/model/switch-descriptions/switch-prefab-track.js
@@ -0,0 +1,32 @@
+export const SwitchTrackConnectionType = {
+ INTERNAL: Symbol('SwitchTrackConnectionType.INTERNAL'),
+ EXTERNAL: Symbol('SwitchTrackConnectionType.EXTERNAL'),
+};
+
+export default class SwitchPrefabTrack {
+ startPos;
+ endPos;
+ radius;
+ dataIndex;
+ connections;
+
+ constructor(startPos, endPos, radius, dataIndex, connections = []) {
+ Object.assign(this, {
+ startPos,
+ endPos,
+ radius,
+ dataIndex,
+ connections,
+ });
+ }
+
+ static point(pos, dataIndex, connections = []) {
+ return new SwitchPrefabTrack(
+ pos,
+ pos,
+ 0,
+ dataIndex,
+ connections,
+ );
+ }
+}
diff --git a/src/model/switch-descriptions/switch-prefab.js b/src/model/switch-descriptions/switch-prefab.js
new file mode 100644
index 0000000..3bfa580
--- /dev/null
+++ b/src/model/switch-descriptions/switch-prefab.js
@@ -0,0 +1,467 @@
+import Vector3 from "../vector3";
+import SwitchPrefabTrack, {SwitchTrackConnectionType} from "./switch-prefab-track";
+import SceneryParserLog from "../scenery-parser-log";
+import {TrackConnectionEnd} from "../track-connection";
+
+export default class SwitchPrefab {
+ tracks = {};
+ isolation_id_offset;
+
+ constructor(tracks, isolation_id_offset) {
+ Object.assign(this, {
+ tracks,
+ isolation_id_offset,
+ });
+ this._verifyConnections();
+ }
+
+ _verifyConnections() {
+ // TODO: Should SceneryParserLog be used here?
+ Object.entries(this.tracks).forEach(([internalId, track]) => {
+ track.connections.forEach((connection) => {
+ if (connection.type === SwitchTrackConnectionType.INTERNAL) {
+ const otherTrack = this.tracks[connection.internalId];
+ if (!otherTrack) {
+ SceneryParserLog.warn(
+ 'switchInvalidInternalConnection',
+ `In a switch prefab the referenced internal track ${connection.internalId} in a connection of track ${internalId} not found`,
+ );
+ return;
+ }
+
+ const reverseConnection = otherTrack.connections.find(
+ (otherConnection) => otherConnection.type === SwitchTrackConnectionType.INTERNAL && otherConnection.internalId === internalId,
+ );
+ if (!reverseConnection) {
+ SceneryParserLog.warn(
+ 'switchInvalidInternalConnection',
+ `In a switch prefab the internal track ${internalId} is connected to ${connection.internalId} but there is no reverse connection`,
+ );
+ return;
+ }
+
+ if (reverseConnection.end !== connection.otherEnd) {
+ SceneryParserLog.warn(
+ 'switchInvalidInternalConnection',
+ `In a switch prefab the connection between tracks internal tracks ${internalId} and ${connection.internalId} has mismatched ends`,
+ );
+ }
+ }
+ });
+ });
+ }
+
+ static fork(radiusA, radiusB, curveLength, addedLength = 0.0) {
+ const tracks = {
+ start: new SwitchPrefabTrack(
+ Vector3.zero(),
+ Vector3.zero(),
+ radiusA,
+ 0,
+ [
+ {
+ type: SwitchTrackConnectionType.EXTERNAL,
+ end: TrackConnectionEnd.START,
+ },
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: TrackConnectionEnd.END,
+ otherEnd: TrackConnectionEnd.START,
+ internalId: 'curve_a',
+ },
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: TrackConnectionEnd.END,
+ otherEnd: TrackConnectionEnd.START,
+ internalId: 'curve_b',
+ },
+ ],
+ ),
+ ...SwitchPrefab._createForkSwitchTracks(
+ radiusA,
+ curveLength,
+ addedLength,
+ 'a',
+ ),
+ ...SwitchPrefab._createForkSwitchTracks(
+ radiusB,
+ curveLength,
+ addedLength,
+ 'b',
+ ),
+ };
+
+ const midpointA = this._calculateCurveEnd(Vector3.zero(), 0, radiusA, curveLength / 2).endPos;
+ const midpointB = this._calculateCurveEnd(Vector3.zero(), 0, radiusB, curveLength / 2).endPos;
+ const isolationIdOffset = midpointA.lerp(midpointB, 0.5);
+ return new SwitchPrefab(tracks, isolationIdOffset);
+ }
+
+ static _calculateCurveEnd(startPos, startAngle, radius, curveLength) {
+ if (radius === 0) {
+ return { endPos: Vector3.fromAngleY(startAngle, curveLength), endAngle: startAngle };
+ }
+ const centerToStart = Vector3.fromAngleY(startAngle + Math.sign(radius) * Math.PI / 2, Math.abs(radius));
+ const circleCenter = startPos.add(centerToStart.negate());
+ const endAngle = startAngle -curveLength / radius;
+ const centerToEnd = Vector3.fromAngleY(endAngle + Math.sign(radius) * Math.PI / 2, Math.abs(radius));
+ const endPos = circleCenter.add(centerToEnd);
+ return { endPos, endAngle };
+ }
+
+ static _createForkSwitchTracks(radius, curveLength, addedLength, side) {
+ const { endPos: curveEnd, endAngle } = SwitchPrefab._calculateCurveEnd(Vector3.zero(), 0, radius, curveLength);
+
+ const tracks = {
+ [`curve_${side}`]: new SwitchPrefabTrack(
+ Vector3.zero(),
+ curveEnd,
+ radius,
+ side === 'a' ? 1 : 2,
+ [
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: TrackConnectionEnd.START,
+ otherEnd: TrackConnectionEnd.END,
+ internalId: 'start',
+ },
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: TrackConnectionEnd.END,
+ otherEnd: TrackConnectionEnd.START,
+ internalId: addedLength > 0 ? `added_${side}` : `end_${side}`,
+ },
+ ],
+ ),
+ };
+
+ let end = curveEnd;
+ if (addedLength > 0) {
+ end = end.add(Vector3.fromAngleY(endAngle, addedLength));
+ tracks[`added_${side}`] = new SwitchPrefabTrack(
+ curveEnd,
+ end,
+ 0,
+ side === 'a' ? 3 : 4,
+ [
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: TrackConnectionEnd.START,
+ otherEnd: TrackConnectionEnd.END,
+ internalId: `curve_${side}`,
+ },
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: TrackConnectionEnd.END,
+ otherEnd: TrackConnectionEnd.START,
+ internalId: `end_${side}`,
+ },
+ ],
+ );
+ }
+
+ tracks[`end_${side}`] = SwitchPrefabTrack.point(
+ end,
+ addedLength > 0 ?
+ (side === 'a' ? 5 : 6) : // A fork switch with added length has the track A before B
+ (side === 'a' ? 4 : 3), // and without any added length, track B before A,
+ [
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: TrackConnectionEnd.START,
+ otherEnd: TrackConnectionEnd.END,
+ internalId: addedLength > 0 ? `added_${side}` : `curve_${side}`,
+ },
+ {
+ type: SwitchTrackConnectionType.EXTERNAL,
+ end: TrackConnectionEnd.END,
+ },
+ ]
+ );
+
+ return tracks;
+ }
+
+ static crossing(length, tangentInv) {
+ const vectorA = Vector3.fromAngleY(Math.atan(1 / tangentInv / 2), length / 2);
+ const vectorB = Vector3.fromAngleY(-Math.atan(1 / tangentInv / 2), length / 2);
+ const tracks = {
+ a: new SwitchPrefabTrack(
+ vectorA.negate(),
+ vectorA,
+ 0,
+ 0,
+ [
+ {
+ type: SwitchTrackConnectionType.EXTERNAL,
+ end: TrackConnectionEnd.START,
+ },
+ {
+ type: SwitchTrackConnectionType.EXTERNAL,
+ end: TrackConnectionEnd.END,
+ },
+ ],
+ ),
+ b: new SwitchPrefabTrack(
+ vectorB.negate(),
+ vectorB,
+ 0,
+ 1,
+ [
+ {
+ type: SwitchTrackConnectionType.EXTERNAL,
+ end: TrackConnectionEnd.START,
+ },
+ {
+ type: SwitchTrackConnectionType.EXTERNAL,
+ end: TrackConnectionEnd.END,
+ },
+ ],
+ ),
+ };
+
+ return new SwitchPrefab(tracks, Vector3.zero());
+ }
+
+ static slip(totalLength, outerLength, transitionLength, radius, tangentInv, leftSlipEnabled, rightSlipEnabled) {
+ const aAngle = Math.atan(1 / tangentInv / 2);
+ const bAngle = -aAngle;
+ const unitVectorA = Vector3.fromAngleY(aAngle);
+ const unitVectorB = Vector3.fromAngleY(bAngle);
+
+ const outerStart = totalLength / 2;
+ const transitionStart = outerStart - outerLength;
+ const innerStart = transitionStart - transitionLength;
+
+ const makeSlipConfig = (radiusSign, unitVector, startAngle) => {
+ const startPos = unitVector.multiply(transitionStart);
+ const slipRadius = radiusSign * radius;
+ const { endPos } = SwitchPrefab._calculateCurveEnd(startPos, startAngle, slipRadius, transitionLength);
+ return {
+ startPos,
+ slipRadius,
+ endPos,
+ };
+ };
+ const slipPoints = {
+ a_enter: makeSlipConfig(1, unitVectorA.negate(), aAngle),
+ a_exit: makeSlipConfig(1, unitVectorA, aAngle + Math.PI),
+ b_enter: makeSlipConfig(-1, unitVectorB.negate(), bAngle),
+ b_exit: makeSlipConfig(-1, unitVectorB, bAngle + Math.PI),
+ };
+
+ let dataIndex = 0;
+ const outerTrack = (unitVector, name) => [
+ `outer_${name}`,
+ new SwitchPrefabTrack(
+ unitVector.multiply(outerStart),
+ unitVector.multiply(transitionStart),
+ 0,
+ dataIndex++,
+ [
+ {
+ type: SwitchTrackConnectionType.EXTERNAL,
+ end: TrackConnectionEnd.START,
+ },
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: TrackConnectionEnd.END,
+ otherEnd: TrackConnectionEnd.START,
+ internalId: `transition_${name}`,
+ },
+ ],
+ ),
+ ];
+ const transitionTrack = (unitVector, path, end) => [
+ `transition_${path}_${end}`,
+ new SwitchPrefabTrack(
+ unitVector.multiply(transitionStart),
+ unitVector.multiply(innerStart),
+ 0,
+ dataIndex++,
+ [
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: TrackConnectionEnd.START,
+ otherEnd: TrackConnectionEnd.END,
+ internalId: `outer_${path}_${end}`,
+ },
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: TrackConnectionEnd.END,
+ otherEnd: end === 'enter' ? TrackConnectionEnd.START : TrackConnectionEnd.END,
+ internalId: `crossing_${path}`,
+ },
+ ],
+ ),
+ ];
+ const slipTransitionTrack = (path, side, end) => {
+ const { startPos, slipRadius, endPos } = slipPoints[`${path}_${end}`];
+ return [
+ `slip_transition_${path}_${end}`,
+ new SwitchPrefabTrack(
+ startPos,
+ endPos,
+ slipRadius,
+ dataIndex++,
+ [
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: TrackConnectionEnd.START,
+ otherEnd: TrackConnectionEnd.END,
+ internalId: `outer_${path}_${end}`,
+ },
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: TrackConnectionEnd.END,
+ otherEnd: end === 'enter' ? TrackConnectionEnd.START : TrackConnectionEnd.END,
+ internalId: `side_${side}`,
+ },
+ ],
+ ),
+ ];
+ };
+ const crossingTrack = (unitVector, path) => [
+ `crossing_${path}`,
+ new SwitchPrefabTrack(
+ unitVector.multiply(-innerStart),
+ unitVector.multiply(innerStart),
+ 0,
+ dataIndex++,
+ [
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: TrackConnectionEnd.START,
+ otherEnd: TrackConnectionEnd.END,
+ internalId: `transition_${path}_enter`,
+ },
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: TrackConnectionEnd.END,
+ otherEnd: TrackConnectionEnd.END,
+ internalId: `transition_${path}_exit`,
+ },
+ ],
+ ),
+ ];
+ const sideTrack = (unitVectorEnter, unitVectorExit, enterPath, exitPath, side, slipEnabled) => {
+ if (slipEnabled) {
+ const enterConfig = slipPoints[`${enterPath}_enter`];
+ const exitConfig = slipPoints[`${exitPath}_exit`];
+ return [
+ `side_${side}`,
+ new SwitchPrefabTrack(
+ enterConfig.endPos,
+ exitConfig.endPos,
+ enterConfig.slipRadius,
+ dataIndex++,
+ [
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: TrackConnectionEnd.START,
+ otherEnd: TrackConnectionEnd.END,
+ internalId: `slip_transition_${enterPath}_enter`,
+ },
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: TrackConnectionEnd.END,
+ otherEnd: TrackConnectionEnd.END,
+ internalId: `slip_transition_${exitPath}_exit`,
+ },
+ ]
+ ),
+ ];
+ } else {
+ return [
+ `side_${side}`,
+ new SwitchPrefabTrack(
+ unitVectorEnter.multiply(innerStart),
+ unitVectorExit.multiply(innerStart),
+ 0,
+ dataIndex++,
+ [
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: TrackConnectionEnd.START,
+ otherEnd: TrackConnectionEnd.END,
+ internalId: `transition_${enterPath}_enter`,
+ },
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: TrackConnectionEnd.END,
+ otherEnd: TrackConnectionEnd.END,
+ internalId: `transition_${exitPath}_exit`,
+ },
+ ]
+ ),
+ ];
+ }
+ };
+
+ // the order is important, each function call increments dataIndex
+ const trackList = [
+ outerTrack(unitVectorA.negate(), 'a_enter'),
+ outerTrack(unitVectorB.negate(), 'b_enter'),
+ outerTrack(unitVectorA, 'a_exit'),
+ outerTrack(unitVectorB, 'b_exit'),
+ transitionTrack(unitVectorA.negate(), 'a', 'enter'),
+ transitionTrack(unitVectorB.negate(), 'b', 'enter'),
+ ];
+ if (leftSlipEnabled) {
+ trackList.push(slipTransitionTrack('a', 'left', 'enter'));
+ }
+ if (rightSlipEnabled) {
+ trackList.push(slipTransitionTrack('b', 'right', 'enter'));
+ }
+ trackList.push(
+ crossingTrack(unitVectorA, 'a'),
+ crossingTrack(unitVectorB, 'b'),
+ sideTrack(unitVectorA.negate(), unitVectorB, 'a', 'b', 'left', leftSlipEnabled),
+ sideTrack(unitVectorB.negate(), unitVectorA, 'b', 'a', 'right', rightSlipEnabled),
+ transitionTrack(unitVectorA, 'a', 'exit'),
+ transitionTrack(unitVectorB, 'b', 'exit'),
+ );
+ if (rightSlipEnabled) {
+ trackList.push(slipTransitionTrack('a', 'right', 'exit'));
+ }
+ if (leftSlipEnabled) {
+ trackList.push(slipTransitionTrack('b', 'left', 'exit'));
+ }
+
+ const tracks = Object.fromEntries(trackList);
+
+ const addSideConnection = (side, enter_path, exit_path, slipEnabled) => {
+ if (slipEnabled) {
+ tracks[`outer_${enter_path}_enter`].connections.push({
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: TrackConnectionEnd.END,
+ otherEnd: TrackConnectionEnd.START,
+ internalId: `slip_transition_${enter_path}_enter`,
+ });
+ tracks[`outer_${exit_path}_exit`].connections.push({
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: TrackConnectionEnd.END,
+ otherEnd: TrackConnectionEnd.START,
+ internalId: `slip_transition_${exit_path}_exit`,
+ });
+ } else {
+ tracks[`transition_${enter_path}_enter`].connections.push({
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: TrackConnectionEnd.END,
+ otherEnd: TrackConnectionEnd.START,
+ internalId: `side_${side}`,
+ });
+ tracks[`transition_${exit_path}_exit`].connections.push({
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: TrackConnectionEnd.END,
+ otherEnd: TrackConnectionEnd.END,
+ internalId: `side_${side}`,
+ });
+ }
+ };
+ addSideConnection('left', 'a', 'b', leftSlipEnabled);
+ addSideConnection('right', 'b', 'a', rightSlipEnabled);
+
+ return new SwitchPrefab(tracks, Vector3.zero());
+ }
+}
diff --git a/src/model/switch.js b/src/model/switch.js
index b19cc39..2ac3536 100644
--- a/src/model/switch.js
+++ b/src/model/switch.js
@@ -3,7 +3,8 @@ import SceneryObject from "./scenery-object.js";
import SceneryParserLog from "./scenery-parser-log.js";
import Vector3 from "./vector3.js";
import DefinedSwitches from "./defs/defined-switches.js";
-import Constants from "../helpers/constants.js";
+import {SwitchTrackConnectionType} from "./switch-descriptions/switch-prefab-track";
+import TrackConnection, {TrackConnectionEnd} from "./track-connection";
export default class Switch extends SceneryObject {
model;
@@ -17,9 +18,9 @@ export default class Switch extends SceneryObject {
type = "Switch";
applied = false;
track_prefab_name;
- trackA;
- trackB;
+ tracks = [];
def = null;
+ isolation_id_pos = null;
constructor(id, model, pos, rot, data, id_isolation, id_switch, maxspeed, derailspeed, track_prefab_name) {
super(id, pos, rot);
@@ -64,188 +65,104 @@ export default class Switch extends SceneryObject {
let def = DefinedSwitches[this.bare_model] || null;
- if(!def && !Constants.parser.forceAutoSwitches) {
- SceneryParserLog.warn('switchUndefinedModel', `Switch ${this.id} has an undefined model "${this.bare_model}", trying to generate auto model definition`);
+ if (!def) {
+ SceneryParserLog.warn('switchUndefinedModel', `Switch ${this.id} has an undefined model "${this.bare_model}"`);
+ return;
}
- if(!def || Constants.parser.forceAutoSwitches) {
- def = this._tryGetAutoDef(scenery);
- }
-
- if(def) {
- this._applySwitchFromDef(scenery, def);
- } else {
- SceneryParserLog.warn('switchAutoDefFailed', `Could not generate auto definition for switch ${this.id} with model "${this.bare_model}"`);
- }
+ this._applySwitchFromDef(scenery, def);
}
_applySwitchFromDef(scenery, def) {
- const ids = this._getTrackIds(def[2]);
- if(!ids) return;
-
+ const trackIds = this._getTrackIds();
+ if (!trackIds) return;
+
+ const tracks = Object.entries(def.tracks).map(([key, track]) => {
+ return this._createSwitchTrackFromDef(scenery, def, track, trackIds);
+ });
+
+ if (tracks.some(track => !track)) return;
+
+ this.tracks = tracks;
this.def = def;
- const trackAAliases = this._getTrackAliases(ids);
- this.trackA = this._createSwitchTrackFromDef(scenery, this.id+"A", def[0], ids[0][1], ids[2][1], def[3], def[5], trackAAliases);
-
- const trackBAliases = this._getTrackAliases(ids, 1, trackAAliases);
- this.trackB = this._createSwitchTrackFromDef(scenery, this.id+"B", def[1], ids[1][1], ids[3][1], def[4], def[6], trackBAliases);
- this.trackB.hide_isolation = true; // hide isolation for the second track
- }
-
- _getTrackAliases(ids, startingId = 0, exclude = []) {
- const aliases = new Set();
- for(let i = startingId; i < ids.length; i+=2) {
- aliases.add(ids[i][0]);
- }
-
- exclude.forEach(alias => {
- aliases.delete(alias);
- });
-
- return Array.from(aliases);
- }
-
- _getTrackIds(outs) {
- const dataValues = this.data.split(",");
- const isCrossing = outs.length === 2;
- if(isCrossing) outs = [outs[0], outs[1], outs[0], outs[1]];
-
- return outs.map((out, index) => {
- const parts = dataValues[out]?.split(":");
- if(parts.length !== 3) {
- SceneryParserLog.warn('switchInvalidDataFormat', `Switch ${this.id} has invalid data: ${this.data}`);
- return null;
- }
- const trackId = parts[0].trim();
- let connectedId = parts[1].trim() || parts[2].trim() || null;
- if(isCrossing) connectedId = parts[~~(index / 2) + 1].trim();
-
- return [trackId, connectedId];
- });
- }
-
- _createSwitchTrackFromDef(scenery, id, r, previd, nextid, startPos, endPos, aliases = []) {
const rotRad = this.rot.multiply(Math.PI / 180);
+ this.isolation_id_pos = this.pos.add(def.isolation_id_offset.rotate(rotRad));
+ super.applyObject(scenery);
+ }
+
+ _getTrackIds() {
+ const ids = this.data
+ .split(",")
+ .filter(part => part.trim() !== '')
+ .map(
+ (value) => value.split(":").map((part) => part.trim()),
+ );
+ if (ids.some((part) => part.length !== 3)) {
+ SceneryParserLog.warn('switchInvalidDataFormat', `Switch ${this.id} has invalid data: ${this.data}`);
+ }
+ return ids;
+ }
+
+ _createSwitchTrackFromDef(scenery, switchDef, trackDef, ids) {
+ const rotRad = this.rot.multiply(Math.PI / 180);
+ if (trackDef.dataIndex >= ids.length) {
+ SceneryParserLog.warn(
+ 'switchMissingTrackId',
+ `Switch ${this.id} with model ${this.bare_model} is missing an id for the track at index ${trackDef.dataIndex}`,
+ );
+ return null;
+ }
+ const [trackId, prevId, nextId] = ids[trackDef.dataIndex];
+
+ const connections = [];
+ trackDef.connections.forEach((connection) => {
+ if (connection.type === SwitchTrackConnectionType.INTERNAL) {
+ const otherTrackDef = switchDef.tracks[connection.internalId];
+ if (!otherTrackDef) {
+ SceneryParserLog.warn(
+ 'switchMissingTrackId',
+ `Switch prefab for model with model ${this.bare_model} of track ${this.id} does not have the internal track ${connection.internalId}`,
+ );
+ return;
+ }
+ if (otherTrackDef.dataIndex >= ids.length) {
+ SceneryParserLog.warn(
+ 'switchMissingTrackId',
+ `Switch ${this.id} with model ${this.bare_model} is missing an id for the track at index ${otherTrackDef.dataIndex}`,
+ );
+ return;
+ }
+ const otherTrackId = ids[otherTrackDef.dataIndex][0];
+ if (otherTrackId) {
+ connections.push(new TrackConnection(otherTrackId, connection.end));
+ }
+ } else if (connection.type === SwitchTrackConnectionType.EXTERNAL) {
+ const otherTrackId = connection.end === TrackConnectionEnd.START ? prevId : nextId;
+ if (otherTrackId) {
+ connections.push(new TrackConnection(otherTrackId, connection.end));
+ }
+ }
+ });
const trackObj = new PointTrack(
- id,
- this.pos.add(startPos.rotate(rotRad)),
- this.pos.add(endPos.rotate(rotRad)),
- r,
- nextid,
- previd,
- this.id_switch,
+ trackId,
+ this.pos.add(trackDef.startPos.rotate(rotRad)),
+ this.pos.add(trackDef.endPos.rotate(rotRad)),
+ trackDef.radius,
+ connections,
+ this.id_switch,
0, // start_slope
0, // end_slope
- this.id_isolation,
+ this.id_isolation,
this.track_prefab_name,
- this.maxspeed,
+ this.maxspeed,
this.derailspeed
);
trackObj.switch = this;
scenery.addObject(trackObj);
- aliases.forEach(alias => {
- scenery.addTrackAlias(id, alias);
- });
return trackObj;
}
-
- _tryGetAutoDef(scenery) {
- const outs = Switch.autoGetOutsFromData(this.data);
- if(!outs) return null;
-
- const radiuses = Switch.autoGetRadiusesFromModel(this.bare_model);
- if(!radiuses) return null;
- const [ra, rb] = radiuses;
-
- const ids = this._getTrackIds(outs);
- if(!ids) return null;
-
- const trackPoints = ids.map(idsVal =>
- this._autoFindClosestTrackEnd(scenery, idsVal[1])
- );
-
- if(trackPoints.length !== 4 || trackPoints.some(point => !point)) return null;
-
- const rotRadNeg = this.rot.multiply(-Math.PI / 180);
- const transformedPoints = trackPoints.map(point => point.sub(this.pos).rotate(rotRadNeg).toPrecision(3));
-
- const newDef = [
- ra,
- rb,
- outs,
- ...transformedPoints
- ];
-
- if(!DefinedSwitches[this.bare_model]) DefinedSwitches[this.bare_model] = newDef;
- if(Constants.parser.logNewAutoSwitches) console.log(this.bare_model, newDef);
- return newDef;
- }
-
- static autoGetOutsFromData(data) {
- const tracks = data.split(",");
-
- if(tracks.length === 3) return [0, 1]; // Crossing
-
- if(tracks.length < 4) {
- SceneryParserLog.warn('switchInvalidDataFormat', `Switch data "${data}" is invalid, expected at least 4 tracks`);
- return null;
- }
-
- const outs = [];
- tracks.forEach((ct, i) => {
- const ctv = ct.split(":");
- outs.push(ctv[1] || ctv[2]);
- });
-
- return [
- 0,
- outs[1] ? 1 : 0, // if no second track, use first track
- outs[2] ? 2 : (outs[3] ? 3 : 5), // if no third track, use fourth or sixth track
- outs[4] ? 4 : (outs[6] ? 6 : 3) // if no fourth track, use sixth or third track
- ];
- }
-
- static autoGetRadiusesFromModel(model) {
- const bareModel = (model.split(',')[0] || model).trim();
- if(/Rkp|Crossing/.test(bareModel)) return [0, 0];
- const right = bareModel.at(-1) === "R";
- const [prefix, rText, rldsR2Text] = bareModel.split("-");
- const r = parseFloat(rText) || 0;
- const [r1, r2] = rText.split('_').map(value => parseFloat(value) || 0);
-
- switch(prefix) {
- case 'Rz 60E1':
- return [0, right ? -r : r];
- case 'Rld 60E1':
- return right ? [r1, -r2] : [-r1, r2];
- case 'Rlds 60E1':
- const rldsR2 = parseFloat(rldsR2Text) || 0;
- return [-r1, rldsR2];
- case 'Rlj 60E1':
- return right ? [-r1, -r2] : [r1, r2];
- default:
- SceneryParserLog.warn('switchCannotResolveModel', `Switch model ${model} cannot be auto-resolved to radiuses`);
- return null;
- }
- }
-
- _autoFindClosestTrackEnd(scenery, trackId) {
- if(!trackId) {
- SceneryParserLog.warn('switchInvalidTrackConnection', `Switch ${this.id}, trackId of one of the required connections is empty`);
- return null;
- }
-
- const track = scenery.getObject("tracks", trackId);
-
- if(!track) {
- SceneryParserLog.warn('switchInvalidTrackConnection', `Switch ${this.id}, track ${trackId} that the switch connects to not found`);
- return null;
- }
-
- return track.getCloserEndPos(this.pos);
- }
}
diff --git a/src/model/track-connection.js b/src/model/track-connection.js
index 7aab742..4f21817 100644
--- a/src/model/track-connection.js
+++ b/src/model/track-connection.js
@@ -1,20 +1,25 @@
-export const TrackConnectionType = {
- END: 0,
- START: 1,
-}
+export const TrackConnectionEnd = {
+ START: Symbol('TrackConnectionEnd.START'),
+ END: Symbol('TrackConnectionEnd.END'),
+};
export default class TrackConnection {
- tracks;
- otherTrack;
- type;
- otherType;
+ otherTrackId;
+ otherTrack = null;
+ end;
- constructor(track, otherTrack, type, otherType) {
- Object.assign(this, {
- track,
- otherTrack,
- type,
- otherType
- });
+ /**
+ * If only an ID is passed as `otherTrack`,
+ * it needs to be resolved later in the `connectTracks` function.
+ */
+ constructor(otherTrack, end) {
+ this.end = end;
+ if (typeof otherTrack === 'string') {
+ this.otherTrackId = otherTrack;
+ this.otherTrack = null;
+ } else {
+ this.otherTrackId = otherTrack.id;
+ this.otherTrack = otherTrack;
+ }
}
}
diff --git a/src/model/track-objects/track-object.js b/src/model/track-objects/track-object.js
index 051fc34..5a60440 100644
--- a/src/model/track-objects/track-object.js
+++ b/src/model/track-objects/track-object.js
@@ -15,7 +15,7 @@ export default class TrackObject extends SceneryObject {
super(id, pos, rot);
Object.assign(this, {
prefab_name,
- track_id,
+ track_id,
name
});
}
@@ -38,13 +38,11 @@ export default class TrackObject extends SceneryObject {
if(this.applied) return; // already applied
this.applied = true;
- const trackId = scenery.getTrackIdByAlias(this.track_id);
- const track = scenery.getObject('tracks', trackId);
+ const track = scenery.getObject('tracks', this.track_id);
if (!track) {
- SceneryParserLog.warn('trackObjectCannotBeApplied', `TrackObject ${this.id} cannot be applied: track ${trackId} not found`)
+ SceneryParserLog.warn('trackObjectCannotBeApplied', `TrackObject ${this.id} cannot be applied: track ${this.track_id} not found`)
return;
}
-
this.track = track;
}
}
diff --git a/src/model/tracks-connection-test.js b/src/model/tracks-connection-test.js
deleted file mode 100644
index dec8fc9..0000000
--- a/src/model/tracks-connection-test.js
+++ /dev/null
@@ -1,52 +0,0 @@
-import SceneryParserLog from './scenery-parser-log';
-
-const connectionThresholdDistanceSq = 0.05;
-
-function trackEndConnectionTest(scenery, track, isPrev) {
- const vNext = isPrev ? track.previd : track.nextid;
- if (!vNext) return;
-
- const vEndPos = isPrev ? track.points.start : track.points.end;
- const vNextTrackId = scenery.getTrackIdByAlias(vNext);
- const vNextTrack = scenery.getObject('tracks', vNextTrackId);
-
- if (!vNextTrack) {
- SceneryParserLog.warn('tracksConnectionTest', `Track ${track.id} has non-existing ${isPrev ? 'previous' : 'next'} track: ${isPrev ? track.previd : track.nextid} (${vNextTrackId})`);
- return;
- }
-
- const [vNextTrackNext, vNextTrackPrev] = isPrev ? [vNextTrack.previd, vNextTrack.nextid] : [vNextTrack.nextid, vNextTrack.previd];
- const vNextTrackNextId = scenery.getTrackIdByAlias(vNextTrackNext);
- const vNextTrackPrevId = scenery.getTrackIdByAlias(vNextTrackPrev);
- const directlyConnected = track.aliases.includes(vNextTrackPrevId) || vNextTrackPrevId === track.id;
- const reverseConnected = track.aliases.includes(vNextTrackNextId) || vNextTrackNextId === track.id;
- const isSwitchBTrackPrev = isPrev && track.id.at(-1) === 'B';
-
- if (directlyConnected) {
- const vNextTrackStartPos = isPrev ? vNextTrack.points.end : vNextTrack.points.start;
- const distSq = vEndPos.distanceSq(vNextTrackStartPos);
- if (distSq > connectionThresholdDistanceSq) {
- SceneryParserLog.warn('tracksConnectionTest', `Track ${track.id} with ${isPrev ? 'start' : 'end'} position ${vEndPos.toString()} is too far from the ${isPrev ? 'previous' : 'next'} track ${vNextTrackId} with ${isPrev ? 'end' : 'start'} position ${vNextTrackStartPos.toString()}. Distance: ${Math.sqrt(distSq).toFixed(3)}`);
- return;
- }
- } else if (reverseConnected) {
- const vNextTrackEndPos = isPrev ? vNextTrack.points.start : vNextTrack.points.end;
- const distSq = vEndPos.distanceSq(vNextTrackEndPos);
- if (distSq > connectionThresholdDistanceSq) {
- SceneryParserLog.warn('tracksConnectionTest', `Track ${track.id} with ${isPrev ? 'start' : 'end'} position ${vEndPos.toString()} is too far from the ${isPrev ? 'previous' : 'next'} track ${vNextTrackId} with ${isPrev ? 'start' : 'end'} position ${vNextTrackEndPos.toString()}. Distance: ${Math.sqrt(distSq).toFixed(3)}`);
- return;
- }
- } else if(!isSwitchBTrackPrev) {
- SceneryParserLog.warn('tracksConnectionTest', `Track ${track.id} ${isPrev ? 'previous' : 'next'} track ${vNextTrackId} does not match its ${isPrev ? 'next' : 'previous'} track id: ${vNextTrackNext} (${vNextTrackNextId}), ${vNextTrackPrev} (${vNextTrackPrevId})`);
- return;
- }
-}
-
-export function tracksConnectionTest(scenery) {
- const tracks = scenery.objects['tracks'] || [];
-
- Object.values(tracks).forEach(track => {
- trackEndConnectionTest(scenery, track, false);
- trackEndConnectionTest(scenery, track, true);
- });
-}
\ No newline at end of file
diff --git a/src/model/tracks/bezier-track.js b/src/model/tracks/bezier-track.js
index 33abd81..9850b68 100644
--- a/src/model/tracks/bezier-track.js
+++ b/src/model/tracks/bezier-track.js
@@ -1,5 +1,6 @@
import Track from "./track";
import Vector3 from "../vector3";
+import TrackConnection, {TrackConnectionEnd} from "../track-connection";
export default class BezierTrack extends Track
{
@@ -8,18 +9,32 @@ export default class BezierTrack extends Track
start: Vector3.zero(),
control1: Vector3.zero(),
end: Vector3.zero(),
- control2: Vector3.zero()
+ control2: Vector3.zero(),
+ middle: Vector3.zero(),
};
- constructor(id, start, control1, end, control2, rot, len, r, nextid, previd, id_station, start_slope, end_slope, id_isolation, prefab_name, maxspeed, derailspeed) {
- super(id, start, rot, len, r, nextid, previd, id_station, start_slope, end_slope, id_isolation, prefab_name, maxspeed, derailspeed);
+ constructor(id, start, control1, end, control2, rot, len, r, connections, id_station, start_slope, end_slope, id_isolation, prefab_name, maxspeed, derailspeed) {
+ super(id, start, rot, len, r, connections, id_station, start_slope, end_slope, id_isolation, prefab_name, maxspeed, derailspeed);
Object.assign(this.points, {
start,
control1: start.add(control1),
end,
- control2: end.add(control2)
+ control2: end.add(control2),
});
+ // This is not the the actual middle point, but it's close enough
+ this.points.middle = this._pointAtT(0.5);
+ }
+
+ _pointAtT(t) {
+ const a1 = this.points.start.lerp(this.points.control1, t);
+ const a2 = this.points.control1.lerp(this.points.control2, t);
+ const a3 = this.points.control2.lerp(this.points.end, t);
+
+ const b1 = a1.lerp(a2, t);
+ const b2 = a2.lerp(a3, t);
+
+ return b1.lerp(b2, t);
}
getStartAngleXZ() {
@@ -32,6 +47,11 @@ export default class BezierTrack extends Track
static fromText(text) {
const values = text.split(";");
+
+ const connections = [];
+ if (values[15]) connections.push(new TrackConnection(values[15], TrackConnectionEnd.END));
+ if (values[16]) connections.push(new TrackConnection(values[16], TrackConnectionEnd.START));
+
const track = new BezierTrack(
values[1], // id
Vector3.fromValuesArray(values, 3), // start
@@ -42,8 +62,7 @@ export default class BezierTrack extends Track
Vector3.zero(),
0, // len ??
0, // r ??
- values[15], // nextid
- values[16], // previd
+ connections,
values[17], // id_station
...Track.slopesFromText(values[18]), // start_slope, end_slope
values[21], // id_isolation
@@ -54,4 +73,8 @@ export default class BezierTrack extends Track
return track;
}
+
+ applyObject(scenery) {
+ super.applyObject(scenery);
+ }
}
diff --git a/src/model/tracks/point-track.js b/src/model/tracks/point-track.js
index 6ba7aaa..64f2b38 100644
--- a/src/model/tracks/point-track.js
+++ b/src/model/tracks/point-track.js
@@ -10,9 +10,9 @@ export default class PointTrack extends Track
end: Vector3.zero()
};
- constructor(id, start, end, r, nextid, previd, id_station, start_slope, end_slope, id_isolation, prefab_name, maxspeed, derailspeed) {
+ constructor(id, start, end, r, connections, id_station, start_slope, end_slope, id_isolation, prefab_name, maxspeed, derailspeed) {
const [rot, len] = PointTrack._getRotLen(start, end, r);
- super(id, start, rot, len, r, nextid, previd, id_station, start_slope, end_slope, id_isolation, prefab_name, maxspeed, derailspeed);
+ super(id, start, rot, len, r, connections, id_station, start_slope, end_slope, id_isolation, prefab_name, maxspeed, derailspeed);
Object.assign(this.points, {
start, end
diff --git a/src/model/tracks/route-track.js b/src/model/tracks/route-track.js
new file mode 100644
index 0000000..ac93dfa
--- /dev/null
+++ b/src/model/tracks/route-track.js
@@ -0,0 +1,42 @@
+import AngleHelper from "../../helpers/angleHelper";
+import Track from "./track";
+import {ElectrificationStatus} from "../electrification-status";
+
+export default class RouteTrack extends Track {
+ type = "RouteTrack";
+ points;
+ route;
+
+ constructor(id, start, end, rot, len, r, connections, electrified, route) {
+ super(
+ id, start, rot, len, r, connections,
+ null, // id_station,
+ 0, // start_slope,
+ 0, // end_slope,
+ null, // id_isolation
+ null, // prefab_name
+ null, // maxspeed,
+ null, // derailspeed,
+ );
+ this.electrificationStatus = electrified ? ElectrificationStatus.ELECTRIFIED : ElectrificationStatus.NON_ELECTRIFIED;
+ this.points = {
+ start,
+ end,
+ };
+ this.route = route;
+ }
+
+ getStartAngleXZ() {
+ return AngleHelper.degToRad(this.rot.y);
+ }
+
+ getEndAngleXZ() {
+ const startAngle = AngleHelper.degToRad(this.rot.y);
+
+ if(this.r === 0) {
+ return startAngle
+ }
+
+ return startAngle - this.len / this.r;
+ }
+}
diff --git a/src/model/tracks/standard-track.js b/src/model/tracks/standard-track.js
index 27c64b2..d359411 100644
--- a/src/model/tracks/standard-track.js
+++ b/src/model/tracks/standard-track.js
@@ -1,6 +1,7 @@
import AngleHelper from "../../helpers/angleHelper";
import Track from "./track";
import Vector3 from "../vector3";
+import TrackConnection, {TrackConnectionEnd} from "../track-connection";
export default class StandardTrack extends Track
{
@@ -8,11 +9,12 @@ export default class StandardTrack extends Track
points = {
start: Vector3.zero(),
end: Vector3.zero(),
- circleCenter: Vector3.zero()
+ circleCenter: Vector3.zero(),
+ middle: Vector3.zero(),
};
- constructor(id, start, rot, len, r, nextid, previd, id_station, start_slope, end_slope, id_isolation, prefab_name, maxspeed, derailspeed) {
- super(id, start, rot, len, r, nextid, previd, id_station, start_slope, end_slope, id_isolation, prefab_name, maxspeed, derailspeed);
+ constructor(id, start, rot, len, r, connections, id_station, start_slope, end_slope, id_isolation, prefab_name, maxspeed, derailspeed) {
+ super(id, start, rot, len, r, connections, id_station, start_slope, end_slope, id_isolation, prefab_name, maxspeed, derailspeed);
this._calcPoints();
}
@@ -32,14 +34,18 @@ export default class StandardTrack extends Track
static fromText(text) {
const values = text.split(";");
+
+ const connections = [];
+ if (values[11]) connections.push(new TrackConnection(values[11], TrackConnectionEnd.END));
+ if (values[12]) connections.push(new TrackConnection(values[12], TrackConnectionEnd.START));
+
const track = new StandardTrack(
values[1], // id
Vector3.fromValuesArray(values, 3), // start
Vector3.fromValuesArray(values, 6), // rot
parseFloat(values[9]), // len
parseFloat(values[10]), // r
- values[11], // nextid
- values[12], // previd
+ connections,
values[13], // id_station
...Track.slopesFromText(values[14]), // start_slope, end_slope
values[17], // id_isolation
@@ -55,16 +61,22 @@ export default class StandardTrack extends Track
const rotRad = AngleHelper.degToRad(this.rot.y);
this.points.start = this.pos.clone();
- this.points.end = this.pos.add(Vector3.fromAngleY(rotRad, this.len));
- this.points.circleCenter = this.pos.clone(); // default circle center
- if (this.r !== 0) {
+ if (this.r === 0) {
+ this.points.end = this.pos.add(Vector3.fromAngleY(rotRad, this.len));
+ this.points.middle = this.points.start.lerp(this.points.end, 0.5);
+ this.points.circleCenter = this.pos.clone(); // default circle center
+ } else {
const centerAngle = rotRad + Math.PI / 2;
this.points.circleCenter = this.pos.sub(Vector3.fromAngleY(centerAngle, this.r));
+
+ const middleAngle = centerAngle - (this.len / this.r) / 2;
+ this.points.middle = this.points.circleCenter.add(Vector3.fromAngleY(middleAngle, this.r));
+
const endAngle = centerAngle - this.len / this.r;
this.points.end = this.points.circleCenter.add(Vector3.fromAngleY(endAngle, this.r));
}
-
+
// adjust end point by the slope
if (this.end_slope !== 0) {
const startHeightDiff = this.start_slope * this.len / 1000;
@@ -73,4 +85,8 @@ export default class StandardTrack extends Track
this.points.end.y += startHeightDiff + (endHeightDiff - startHeightDiff) / 2;
}
}
+
+ applyObject(scenery) {
+ super.applyObject(scenery);
+ }
}
diff --git a/src/model/tracks/track.js b/src/model/tracks/track.js
index b209c29..926425b 100644
--- a/src/model/tracks/track.js
+++ b/src/model/tracks/track.js
@@ -1,5 +1,6 @@
import SceneryObject from "../scenery-object";
import { ElectrificationStatus } from "../electrification-status";
+import {TrackConnectionEnd} from "../track-connection";
export default class Track extends SceneryObject {
len;
@@ -10,28 +11,24 @@ export default class Track extends SceneryObject {
derailspeed;
category = "tracks";
type = "Track";
- nextid;
- previd;
+ connections = [];
start_slope;
end_slope;
prefab_name;
- hide_isolation = false;
- aliases = [];
switch = null;
electrificationStatus = ElectrificationStatus.NOT_CHECKED;
hasNEVP = false;
- connections = [];
- constructor(id, pos, rot, len, r, nextid, previd, id_station, start_slope, end_slope, id_isolation, prefab_name, maxspeed, derailspeed) {
+ constructor(id, pos, rot, len, r, connections, id_station, start_slope, end_slope, id_isolation, prefab_name, maxspeed, derailspeed) {
super(id, pos, rot);
Object.assign(this, {
len, r,
- nextid, previd,
- id_station,
+ connections,
+ id_station,
start_slope, end_slope,
id_isolation,
- prefab_name,
- maxspeed, derailspeed
+ prefab_name,
+ maxspeed, derailspeed,
});
}
@@ -43,10 +40,19 @@ export default class Track extends SceneryObject {
throw new Error("getEndAngleXZ() must be implemented in subclass");
}
+ getAngleXZForEnd(end) {
+ if (end === TrackConnectionEnd.START) return this.getStartAngleXZ();
+ else return this.getEndAngleXZ();
+ }
+
static slopesFromText(text) {
return text.split(",", 2);
}
+ getEndPos(end) {
+ return end === TrackConnectionEnd.START ? this.points.start : this.points.end;
+ }
+
getCloserEndPos(pos) {
const distStartSq = pos.distanceSq(this.points.start);
const distEndSq = pos.distanceSq(this.points.end);
@@ -56,7 +62,7 @@ export default class Track extends SceneryObject {
return this.points.end;
}
}
-
+
getRenderBounds() {
return Object.values(this.points);
}
diff --git a/tools/scp.py b/tools/scp.py
index bbd814a..8cbc5c5 100644
--- a/tools/scp.py
+++ b/tools/scp.py
@@ -7,22 +7,24 @@ import sys
# v1.4
#
-BAD_WORDS = ['Empty,Empty', 'Forest Start,Forest Start', 'EndMiscGroup', 'Fence', 'TerrainPoint', 'Wires']
+BAD_WORDS = ['EndMiscGroup', 'Fence', 'TerrainPoint', 'Wires']
def should_exclude(line: str) -> bool:
for word in BAD_WORDS:
if line.startswith(word):
return True
-
+
if line.startswith("Misc"):
values = line.split(';')
return not values[2].startswith("SignalBox")
+ return False
+
def process_file(file_path: str):
if not file_path.endswith(".sc"):
print(f"Skipping non-.sc file: {file_path}")
return
-
+
if file_path.endswith(".lite.sc"):
print(f"Skipping already processed file: {file_path}")
return