Merge pull request #3 from dominik-korsa/switch-tracks
This commit is contained in:
commit
a0b792fb6f
5
.gitignore
vendored
5
.gitignore
vendored
@ -22,4 +22,7 @@ npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
public/sceneries/*
|
||||
public/sceneries/*
|
||||
|
||||
# IDEs
|
||||
.idea
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
export default ObjectRendererQueue;
|
||||
|
||||
@ -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 (
|
||||
<MemoizedSceneryLayer
|
||||
name={name}
|
||||
@ -33,13 +40,13 @@ export default function SceneryLayer(props) {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const MemoizedSceneryLayer = memo(StatelessSceneryLayer);
|
||||
|
||||
|
||||
function StatelessSceneryLayer({ name, Renderer, objects, type, types, pointerEvents = false, AdditionalComponents = [] }) {
|
||||
return (
|
||||
<g
|
||||
<g
|
||||
className={`scenery-layer-${name}`}
|
||||
pointerEvents={pointerEvents ? "all" : "none"}
|
||||
>
|
||||
@ -53,4 +60,4 @@ function StatelessSceneryLayer({ name, Renderer, objects, type, types, pointerEv
|
||||
))}
|
||||
</g>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
40
src/components/map/object-renderers/IsolationEndRenderer.js
Normal file
40
src/components/map/object-renderers/IsolationEndRenderer.js
Normal file
@ -0,0 +1,40 @@
|
||||
import { useMemo } from "react";
|
||||
import { TrackConnectionEnd } from "../../../model/track-connection";
|
||||
|
||||
export default function IsolationEndRenderer(props) {
|
||||
const { object } = props;
|
||||
return (<>
|
||||
<IsolationEndMarker object={object} end={TrackConnectionEnd.START} />
|
||||
<IsolationEndMarker object={object} end={TrackConnectionEnd.END} />
|
||||
</>);
|
||||
}
|
||||
|
||||
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 <path
|
||||
className="isolation-end-marker"
|
||||
d={`M ${x - 1.5 * cos} ${y - 1.5 * sin} L ${x + 1.5 * cos} ${y + 1.5 * sin}`}
|
||||
/>
|
||||
}
|
||||
@ -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 && <SimpleLabelText
|
||||
text={text}
|
||||
x={x}
|
||||
y={y}
|
||||
textProps={{
|
||||
className: "isolation-id"
|
||||
}}
|
||||
/>}
|
||||
<IsolationEndMarker object={object} isStart={true} />
|
||||
<IsolationEndMarker object={object} />
|
||||
</>);
|
||||
}
|
||||
|
||||
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 <path
|
||||
className="isolation-end-marker"
|
||||
d={`M ${x - 1.5 * cos} ${y - 1.5 * sin} L ${x + 1.5 * cos} ${y + 1.5 * sin}`}
|
||||
/>
|
||||
return (<SimpleLabelText
|
||||
text={text}
|
||||
x={x}
|
||||
y={y}
|
||||
textProps={{
|
||||
className: "isolation-id"
|
||||
}}
|
||||
/>);
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
<g className="route" transform={`translate(${x}, ${y}) rotate(${object.rot.y})`}>
|
||||
<g transform={`rotate(90) translate(-80, ${arrowOffset})`}>
|
||||
<ReactSVG
|
||||
src={`${process.env.PUBLIC_URL}/assets/route.svg`}
|
||||
wrapper='svg'
|
||||
beforeInjection={(svg) => {
|
||||
svg.setAttribute('width', '20mm');
|
||||
svg.setAttribute('height', '20mm');
|
||||
}}
|
||||
/>
|
||||
</g>
|
||||
return (<>
|
||||
<g className="route-arrow" transform={`translate(${endX}, ${endY}) rotate(${endAngle + 90}) translate(-80, ${arrowOffset})`}>
|
||||
<ReactSVG
|
||||
src={`${process.env.PUBLIC_URL}/assets/route.svg`}
|
||||
wrapper='svg'
|
||||
beforeInjection={(svg) => {
|
||||
svg.setAttribute('width', '20mm');
|
||||
svg.setAttribute('height', '20mm');
|
||||
}}
|
||||
/>
|
||||
</g>
|
||||
<g className="route-name" transform={`translate(${startX}, ${startY}) rotate(${startAngle})`}>
|
||||
<AlwaysUpText
|
||||
baseRot={object.rot.y}
|
||||
baseRot={startAngle}
|
||||
additionalRot={-90}
|
||||
offsetX={object.track_offset}
|
||||
reverseAnchor={true}
|
||||
@ -32,5 +38,5 @@ export default function RouteRenderer(props) {
|
||||
text={object.route_name}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
</>);
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
<defs>
|
||||
<linearGradient
|
||||
|
||||
@ -11,6 +11,7 @@ const TrackHoverInfoConsts = {
|
||||
'StandardTrack': 'Standard track',
|
||||
'PointTrack': 'Switch track',
|
||||
'BezierTrack': 'Bezier track',
|
||||
'RouteTrack': 'Route track',
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -8,22 +8,18 @@ const Constants = {
|
||||
forcePointerEvents: false
|
||||
},
|
||||
parser: {
|
||||
forceAutoSwitches: false,
|
||||
logNewAutoSwitches: true,
|
||||
logSceneryAfterFinished: true,
|
||||
sceneryInfoVersion: 29,
|
||||
alwaysShowLogDialog: false,
|
||||
runTracksConnectionTest: false,
|
||||
resolveElectrification: true,
|
||||
skipElectrificationErrorsPropagation: false,
|
||||
maxRouteConnectionDistance: 0.2,
|
||||
attachSigns: true,
|
||||
attachSignsNeedsSameTrack: false,
|
||||
attachSignsMaxDistanceZ: 1.0,
|
||||
attachSignsMaxDistanceX: 0.2,
|
||||
attachSignsGridSize: 10,
|
||||
logAttachedSigns: false,
|
||||
connectTracks: true,
|
||||
},
|
||||
warnings: {
|
||||
all: false, // enable all warnings
|
||||
@ -32,19 +28,17 @@ const Constants = {
|
||||
unknownObjectType: true,
|
||||
unknownTrackType: true,
|
||||
spawnWithoutSpawnInfo: true,
|
||||
trackAliasAlreadyExists: true,
|
||||
trackAliasNoTrack: true,
|
||||
switchUndefinedModel: true,
|
||||
switchAutoDefFailed: true,
|
||||
switchInvalidDataFormat: true,
|
||||
switchNoModel: true,
|
||||
switchMissingTrackId: true,
|
||||
switchInvalidInternalConnection: true,
|
||||
signalBoxUndefinedPrefabName: true,
|
||||
invalidSceneryInfoVersion: true,
|
||||
signUndefinedPrefabName: true,
|
||||
tracksConnectionTest: true,
|
||||
electrificationNevpNotApplied: false,
|
||||
electrificationConflict: false,
|
||||
electrificationMissingRouteTracks: true,
|
||||
electrificationResolverError: true,
|
||||
electrificationResolverWarnings: true,
|
||||
signalElemsUnknownPrefab: true,
|
||||
@ -53,6 +47,7 @@ const Constants = {
|
||||
signalElemsUnknownSign: true,
|
||||
signalElemsUnknownSignText: true,
|
||||
connectTracksFailed: true,
|
||||
routeInvalidSegment: true,
|
||||
},
|
||||
errors: {
|
||||
invalidSceneryInfo: true
|
||||
@ -146,6 +141,7 @@ const Constants = {
|
||||
'standard-track': ['#00a', 'Standard track'],
|
||||
'point-track': ['#0a0', 'Switch track'],
|
||||
'bezier-track': ['#aa8', 'Bezier track'],
|
||||
'route-track': ['#a6d', 'Route track'],
|
||||
}
|
||||
},
|
||||
'slope': {
|
||||
@ -170,9 +166,10 @@ const Constants = {
|
||||
},
|
||||
options: {
|
||||
'derail': ['#f22', 'Derail track'],
|
||||
'unknown': ['#aaa', 'Unknown speed'],
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default Constants;
|
||||
export default Constants;
|
||||
|
||||
@ -1,70 +1,83 @@
|
||||
import TrackConnection, {TrackConnectionEnd} from "./track-connection";
|
||||
import SceneryParserLog from "./scenery-parser-log";
|
||||
import TrackConnection, { TrackConnectionType } from "./track-connection";
|
||||
import Constants from "../helpers/constants";
|
||||
|
||||
const connectionThresholdDistanceSq = 0.05;
|
||||
|
||||
export function connectTracks(scenery) {
|
||||
const tracks = scenery.objects['tracks'] || {};
|
||||
|
||||
Object.values(tracks).forEach(track => {
|
||||
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
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
"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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
32
src/model/switch-descriptions/switch-prefab-track.js
Normal file
32
src/model/switch-descriptions/switch-prefab-track.js
Normal file
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
467
src/model/switch-descriptions/switch-prefab.js
Normal file
467
src/model/switch-descriptions/switch-prefab.js
Normal file
@ -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());
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
42
src/model/tracks/route-track.js
Normal file
42
src/model/tracks/route-track.js
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user