From db273d24201317ecbb2f23a1c7841a20c039ef82 Mon Sep 17 00:00:00 2001
From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com>
Date: Sat, 12 Jul 2025 00:22:38 +0200
Subject: [PATCH 01/24] Change switch prefab definition format
---
.gitignore | 5 +-
src/helpers/constants.js | 7 +-
src/model/defs/defined-switches.js | 659 +++---------------
.../switch-prefab-track.js | 37 +
.../switch-descriptions/switch-prefab.js | 176 +++++
src/model/switch.js | 240 +++----
6 files changed, 405 insertions(+), 719 deletions(-)
create mode 100644 src/model/switch-descriptions/switch-prefab-track.js
create mode 100644 src/model/switch-descriptions/switch-prefab.js
diff --git a/.gitignore b/.gitignore
index 34fe017..830909f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,4 +22,7 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
-public/sceneries/*
\ No newline at end of file
+public/sceneries/*
+
+# IDEs
+.idea
diff --git a/src/helpers/constants.js b/src/helpers/constants.js
index 5665913..b36cca1 100644
--- a/src/helpers/constants.js
+++ b/src/helpers/constants.js
@@ -8,8 +8,6 @@ const Constants = {
forcePointerEvents: false
},
parser: {
- forceAutoSwitches: false,
- logNewAutoSwitches: true,
logSceneryAfterFinished: true,
sceneryInfoVersion: 29,
alwaysShowLogDialog: false,
@@ -35,9 +33,10 @@ const Constants = {
trackAliasAlreadyExists: true,
trackAliasNoTrack: true,
switchUndefinedModel: true,
- switchAutoDefFailed: true,
switchInvalidDataFormat: true,
switchNoModel: true,
+ switchMissingTrackId: true,
+ switchInvalidInternalConnection: true,
signalBoxUndefinedPrefabName: true,
invalidSceneryInfoVersion: true,
signUndefinedPrefabName: true,
@@ -175,4 +174,4 @@ const Constants = {
}
};
-export default Constants;
\ No newline at end of file
+export default Constants;
diff --git a/src/model/defs/defined-switches.js b/src/model/defs/defined-switches.js
index a9ea06f..f8f1c25 100644
--- a/src/model/defs/defined-switches.js
+++ b/src/model/defs/defined-switches.js
@@ -1,555 +1,110 @@
-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-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": [
+ // 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)
+ // ],
+ // Rkp 60E1-190-1_9 ab
+ // Rkp 60E1-190-1_9 ba
+ // Rkpd 60E1-190-1_9
+ "Rld 60E1-1200_600-1_15 L": SwitchPrefab.fork(600.0, -1200.0, 39.95566, 0.0),
+ "Rld 60E1-1200_600-1_15 R": SwitchPrefab.fork(-600.0, 1200.0, 39.95566, 0.0),
+ "Rld 60E1-1200_900-1_18.5 L": SwitchPrefab.fork(900.0, -1200.0, 48.61317, 0.0),
+ "Rld 60E1-1200_900-1_18.5 R": SwitchPrefab.fork(-900.0, 1200.0, 48.61317, 0.0),
+ "Rld 60E1-1800_300-1_9 L": SwitchPrefab.fork(300.0, -1800.0, 33.23108, 0.0),
+ "Rld 60E1-1800_300-1_9 R": SwitchPrefab.fork(-300.0, 1800.0, 33.23108, 0.0),
+ "Rld 60E1-1800_600-1_14 L": SwitchPrefab.fork(600.0, -1800.0, 42.80262, 0.0),
+ "Rld 60E1-1800_600-1_14 R": SwitchPrefab.fork(-600.0, 1800.0, 42.80262, 0.0),
+ "Rld 60E1-1800_900-1_18.5 L": SwitchPrefab.fork(900.0, -1800.0, 48.61317, 0.0),
+ "Rld 60E1-1800_900-1_18.5 R": SwitchPrefab.fork(-900.0, 1800.0, 48.61317, 0.0),
+ "Rld 60E1-2500_1200-1_22 L": SwitchPrefab.fork(1200.0, -2500.0, 54.51731, 0.0),
+ "Rld 60E1-2500_1200-1_22 R": SwitchPrefab.fork(-1200.0, 2500.0, 54.51731, 0.0),
+ "Rld 60E1-2500_250-1_8.5 L": SwitchPrefab.fork(250.0, -2500.0, 29.31069, 0.0),
+ "Rld 60E1-2500_250-1_8.5 R": SwitchPrefab.fork(-250.0, 2500.0, 29.31069, 0.0),
+ "Rld 60E1-2500_400-1_10.5 L": SwitchPrefab.fork(400.0, -2500.0, 38.00925, 0.0),
+ "Rld 60E1-2500_400-1_10.5 R": SwitchPrefab.fork(-400.0, 2500.0, 38.00925, 0.0),
+ "Rld 60E1-2500_600-1_14 L": SwitchPrefab.fork(600.0, -2500.0, 42.80262, 0.0),
+ "Rld 60E1-2500_600-1_14 R": SwitchPrefab.fork(-600.0, 2500.0, 42.80262, 0.0),
+ "Rld 60E1-2500_900-1_17 L": SwitchPrefab.fork(900.0, -2500.0, 52.14928, 0.0),
+ "Rld 60E1-2500_900-1_17 R": SwitchPrefab.fork(-900.0, 2500.0, 52.14928, 0.0),
+ "Rld 60E1-600_300-1_9 L": SwitchPrefab.fork(300.0, -600.0, 29.92537, 0.0),
+ "Rld 60E1-600_300-1_9 R": SwitchPrefab.fork(-300.0, 600.0, 29.92537, 0.0),
+ "Rld 60E1-650_450-1_15 L": SwitchPrefab.fork(450.0, -650.0, 29.96674, 0.0),
+ "Rld 60E1-650_450-1_15 R": SwitchPrefab.fork(-450.0, 650.0, 29.96674, 0.0),
+ "Rld 60E1-700_190-1_7.5 L": SwitchPrefab.fork(190.0, -700.0, 25.22173, 0.0),
+ "Rld 60E1-700_190-1_7.5 R": SwitchPrefab.fork(-190.0, 700.0, 25.22173, 0.0),
+ "Rld 60E1-700_300-1_10 L": SwitchPrefab.fork(300.0, -700.0, 29.38637, 0.0),
+ "Rld 60E1-700_300-1_10 R": SwitchPrefab.fork(-300.0, 700.0, 29.38637, 0.0),
+ "Rld 60E1-700_500-1_14 L": SwitchPrefab.fork(500.0, -700.0, 35.66885, 0.0),
+ "Rld 60E1-700_500-1_14 R": SwitchPrefab.fork(-500.0, 700.0, 35.66885, 0.0),
+ "Rld 60E1-900_300-1_9 L": SwitchPrefab.fork(300.0, -900.0, 33.23108, 0.0),
+ "Rld 60E1-900_300-1_9 R": SwitchPrefab.fork(-300.0, 900.0, 33.23108, 0.0),
+ "Rld 60E1-900_450-1_12 L": SwitchPrefab.fork(450.0, -900.0, 37.43512, 0.0),
+ "Rld 60E1-900_450-1_12 R": SwitchPrefab.fork(-450.0, 900.0, 37.43512, 0.0),
+ "Rld 60E1-900_600-1_15 L": SwitchPrefab.fork(600.0, -900.0, 39.95566, 0.0),
+ "Rld 60E1-900_600-1_15 R": SwitchPrefab.fork(-600.0, 900.0, 39.95566, 0.0),
+ "Rlds 60E1-1000-1000-1_23": SwitchPrefab.fork(1000.0, -1000.0, 43.45773, 0.0),
+ "Rlds 60E1-190-190-1_9": SwitchPrefab.fork(190.0, -190.0, 21.23, 0.0),
+ "Rlds 60E1-600-600-1_18.5": SwitchPrefab.fork(599.9205, -600.0, 33.22262, 0.0),
+ "Rlj 60E1-1200_300-1_7 L": SwitchPrefab.fork(300.0, 1200.0, 42.64069, 0.0),
+ "Rlj 60E1-1200_300-1_7 R": SwitchPrefab.fork(-300.0, -1200.0, 42.64069, 0.0),
+ "Rlj 60E1-1200_600-1_9 L": SwitchPrefab.fork(600.0, 1200.0, 68.0, 0.0),
+ "Rlj 60E1-1200_600-1_9 R": SwitchPrefab.fork(-600.0, -1200.0, 68.0, 0.0),
+ "Rlj 60E1-1500_450-1_9 L": SwitchPrefab.fork(450.0, 1500.0, 49.84663, 0.0),
+ "Rlj 60E1-1500_450-1_9 R": SwitchPrefab.fork(-450.0, -1500.0, 49.84663, 0.0),
+ "Rlj 60E1-1800_300-1_7.5 L": SwitchPrefab.fork(300.0, 1800.0, 39.0, 0.0),
+ "Rlj 60E1-1800_300-1_7.5 R": SwitchPrefab.fork(-300.0, -1800.0, 39.0, 0.0),
+ "Rlj 60E1-1800_450-1_9 L": SwitchPrefab.fork(450.0, 1800.0, 49.84663, 0.0),
+ "Rlj 60E1-1800_450-1_9 R": SwitchPrefab.fork(-450.0, -1800.0, 49.84663, 0.0),
+ "Rlj 60E1-1800_600-1_10 L": SwitchPrefab.fork(599.48096, 1800.0, 59.85075, 0.0),
+ "Rlj 60E1-1800_600-1_10 R": SwitchPrefab.fork(-599.48096, -1800.0, 59.85075, 0.0),
+ "Rlj 60E1-600_300-1_6 L": SwitchPrefab.fork(300.0, 600.0, 48.0, 0.0),
+ "Rlj 60E1-600_300-1_6 R": SwitchPrefab.fork(-300.0, -600.0, 48.0, 0.0),
+ "Rlj 60E1-650_190-1_20 L": SwitchPrefab.fork(190.0, 650.0, 32.47971, 0.0),
+ "Rlj 60E1-650_190-1_20 R": SwitchPrefab.fork(-190.0, -650.0, 32.47971, 0.0),
+ "Rlj 60E1-750_190-1_6 L": SwitchPrefab.fork(190.0, 750.0, 31.44976, 0.0),
+ "Rlj 60E1-750_190-1_6 R": SwitchPrefab.fork(-190.0, -750.0, 31.44976, 0.0),
+ "Rlj 60E1-800_250-1_6.5 L": SwitchPrefab.fork(250.0, 800.0, 38.23661, 0.0),
+ "Rlj 60E1-800_250-1_6.5 R": SwitchPrefab.fork(-250.0, -800.0, 38.23661, 0.0),
+ "Rlj 60E1-900_300-1_7.5 L": SwitchPrefab.fork(300.0, 900.0, 39.82378, 0.0),
+ "Rlj 60E1-900_300-1_7.5 R": SwitchPrefab.fork(-300.0, -900.0, 39.82378, 0.0),
+ "Rlj 60E1-900_300-1_9 L": SwitchPrefab.fork(300.0, 900.0, 49.84663, 0.0),
+ "Rlj 60E1-900_300-1_9 R": SwitchPrefab.fork(-300.0, -900.0, 49.84663, 0.0),
+ "Rlj 60E1-900_450-1_7.5 L": SwitchPrefab.fork(450.0, 900.0, 59.0, 0.0),
+ "Rlj 60E1-900_450-1_7.5 R": SwitchPrefab.fork(-450.0, -900.0, 59.0, 0.0),
+ "Rz 60E1-1200-1_18.5 L": SwitchPrefab.fork(1200.0, 0.0, 64.81756, 0.0),
+ "Rz 60E1-1200-1_18.5 R": SwitchPrefab.fork(-1200.0, 0.0, 64.81756, 0.0),
+ "Rz 60E1-190-1_7.5 L": SwitchPrefab.fork(190.0, 0.0, 25.221731, 0.0),
+ "Rz 60E1-190-1_7.5 R": SwitchPrefab.fork(-190.0, 0.0, 25.221731, 0.0),
+ "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, 0.0),
+ "Rz 60E1-2500-1_26.5 R": SwitchPrefab.fork(-2500.0, 0.0, 94.30607, 0.0),
+ "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, 0.0),
+ "Rz 60E1-300-1_9 R": SwitchPrefab.fork(-300.0, 0.0, 33.231083, 0.0),
+ "Rz 60E1-500-1_12 L": SwitchPrefab.fork(500.0, 0.0, 41.59458, 0.0),
+ "Rz 60E1-500-1_12 R": SwitchPrefab.fork(-500.0, 0.0, 41.59458, 0.0),
+ "Rz 60E1-760-1_14 L": SwitchPrefab.fork(760.0, 0.0, 54.21665, 0.0),
+ "Rz 60E1-760-1_14 R": SwitchPrefab.fork(-760.0, 0.0, 54.21665, 0.0),
+};
-export default DefinedSwitches;
\ No newline at end of file
+export default DefinedSwitches;
diff --git a/src/model/switch-descriptions/switch-prefab-track.js b/src/model/switch-descriptions/switch-prefab-track.js
new file mode 100644
index 0000000..61808be
--- /dev/null
+++ b/src/model/switch-descriptions/switch-prefab-track.js
@@ -0,0 +1,37 @@
+export const SwitchTrackConnectionEnd = {
+ PREV: Symbol('SwitchTrackConnectionEnd.PREV'),
+ NEXT: Symbol('SwitchTrackConnectionEnd.NEXT'),
+};
+
+export const SwitchTrackConnectionType = {
+ INTERNAL: Symbol('SwitchTrackConnectionType.INTERNAL'),
+ EXTERNAL: Symbol('SwitchTrackConnectionType.EXTERNAL'),
+};
+
+export default class SwitchPrefabTrack {
+ startPos;
+ endPos;
+ radius;
+ dataIndex;
+ connections;
+
+ constructor(startPos, endPos, radius, dataIndex, connections = []) {
+ Object.assign(this, {
+ startPos,
+ endPos,
+ radius,
+ dataIndex,
+ connections,
+ });
+ }
+
+ static point(pos, dataIndex, connections = []) {
+ return new SwitchPrefabTrack(
+ pos,
+ pos,
+ 0,
+ dataIndex,
+ connections,
+ );
+ }
+}
diff --git a/src/model/switch-descriptions/switch-prefab.js b/src/model/switch-descriptions/switch-prefab.js
new file mode 100644
index 0000000..0791b76
--- /dev/null
+++ b/src/model/switch-descriptions/switch-prefab.js
@@ -0,0 +1,176 @@
+import Vector3 from "../vector3";
+import SwitchPrefabTrack, {SwitchTrackConnectionEnd, SwitchTrackConnectionType} from "./switch-prefab-track";
+import SceneryParserLog from "../scenery-parser-log";
+
+export default class SwitchPrefab {
+ tracks = {};
+
+ constructor(tracks) {
+ Object.assign(this, {
+ tracks,
+ });
+ 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 not 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) {
+ const tracks = {
+ start: new SwitchPrefabTrack(
+ Vector3.zero(),
+ Vector3.zero(),
+ radiusA,
+ 0,
+ [
+ {
+ type: SwitchTrackConnectionType.EXTERNAL,
+ end: SwitchTrackConnectionEnd.PREV,
+ },
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: SwitchTrackConnectionEnd.NEXT,
+ otherEnd: SwitchTrackConnectionEnd.PREV,
+ internalId: 'curve_a',
+ },
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: SwitchTrackConnectionEnd.NEXT,
+ otherEnd: SwitchTrackConnectionEnd.PREV,
+ internalId: 'curve_b',
+ },
+ ],
+ ),
+ ...SwitchPrefab._createSwitchTracks(
+ radiusA,
+ curveLength,
+ addedLength,
+ 'a',
+ ),
+ ...SwitchPrefab._createSwitchTracks(
+ radiusB,
+ curveLength,
+ addedLength,
+ 'b',
+ ),
+ };
+
+ return new SwitchPrefab(tracks);
+ }
+
+ static _createSwitchTracks(radius, curveLength, addedLength, side) {
+ let curveEnd;
+ let endAngle;
+ if (radius === 0) {
+ curveEnd = new Vector3(0, 0, curveLength);
+ endAngle = 0;
+ } else {
+ const circleCenter = new Vector3(-radius, 0, 0);
+ endAngle = -curveLength / radius;
+ const centerToEnd = Vector3.fromAngleY(endAngle + Math.sign(radius) * Math.PI / 2, Math.abs(radius));
+ curveEnd = circleCenter.add(centerToEnd);
+ }
+
+ const tracks = {
+ [`curve_${side}`]: new SwitchPrefabTrack(
+ Vector3.zero(),
+ curveEnd,
+ radius,
+ side === 'a' ? 1 : 2,
+ [
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: SwitchTrackConnectionEnd.PREV,
+ otherEnd: SwitchTrackConnectionEnd.NEXT,
+ internalId: 'start',
+ },
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: SwitchTrackConnectionEnd.NEXT,
+ otherEnd: SwitchTrackConnectionEnd.PREV,
+ 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: SwitchTrackConnectionEnd.PREV,
+ otherEnd: SwitchTrackConnectionEnd.NEXT,
+ internalId: `curve_${side}`,
+ },
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: SwitchTrackConnectionEnd.NEXT,
+ otherEnd: SwitchTrackConnectionEnd.PREV,
+ internalId: `end_${side}`,
+ },
+ ],
+ );
+ }
+
+ tracks[`end_${side}`] = SwitchPrefabTrack.point(
+ end,
+ addedLength > 0 ?
+ (side === 'a' ? 5 : 6) :
+ (side === 'a' ? 3 : 4),
+ [
+ {
+ type: SwitchTrackConnectionType.INTERNAL,
+ end: SwitchTrackConnectionEnd.PREV,
+ otherEnd: SwitchTrackConnectionEnd.NEXT,
+ internalId: addedLength > 0 ? `added_${side}` : `curve_${side}`,
+ },
+ {
+ type: SwitchTrackConnectionType.EXTERNAL,
+ end: SwitchTrackConnectionEnd.NEXT,
+ },
+ ]
+ );
+
+ return tracks;
+ }
+}
diff --git a/src/model/switch.js b/src/model/switch.js
index b19cc39..22c8797 100644
--- a/src/model/switch.js
+++ b/src/model/switch.js
@@ -3,7 +3,7 @@ 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 {SwitchTrackConnectionEnd, SwitchTrackConnectionType} from "./switch-descriptions/switch-prefab-track";
export default class Switch extends SceneryObject {
model;
@@ -17,8 +17,7 @@ export default class Switch extends SceneryObject {
type = "Switch";
applied = false;
track_prefab_name;
- trackA;
- trackB;
+ tracks = [];
def = null;
constructor(id, model, pos, rot, data, id_isolation, id_switch, maxspeed, derailspeed, track_prefab_name) {
@@ -64,188 +63,105 @@ 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]);
+ _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}`);
}
-
- exclude.forEach(alias => {
- aliases.delete(alias);
- });
-
- return Array.from(aliases);
+ return ids;
}
- _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 = []) {
+ _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 endIds = {
+ [SwitchTrackConnectionEnd.PREV]: [],
+ [SwitchTrackConnectionEnd.NEXT]: [],
+ };
+ 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) {
+ endIds[connection.end].push(otherTrackId);
+ }
+ } else if (connection.type === SwitchTrackConnectionType.EXTERNAL) {
+ const otherTrackId = connection.end === SwitchTrackConnectionEnd.PREV ? prevId : nextId;
+ if (otherTrackId) {
+ endIds[connection.end].push(otherTrackId);
+ }
+ }
+ });
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,
+ endIds[SwitchTrackConnectionEnd.PREV][0], // TODO: Pass the array
+ endIds[SwitchTrackConnectionEnd.NEXT][0], // TODO: --||--
+ 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);
- });
+ scenery.addTrackAlias(trackId, trackId);
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);
- }
}
From b79530ca71bbe7c1400eaa3dc3dde92bb963804f Mon Sep 17 00:00:00 2001
From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com>
Date: Sat, 12 Jul 2025 01:19:55 +0200
Subject: [PATCH 02/24] Change purpose of TrackConnection
---
.../object-renderers/IsolationIdRenderer.js | 8 +--
src/helpers/constants.js | 2 -
src/model/connect-tracks.js | 70 -------------------
src/model/scenery-parser.js | 8 +--
.../switch-prefab-track.js | 5 --
.../switch-descriptions/switch-prefab.js | 35 +++++-----
src/model/switch.js | 19 +++--
src/model/track-connection.js | 21 +++---
src/model/track-objects/track-object.js | 3 +-
src/model/tracks-connection-test.js | 70 +++++++++----------
src/model/tracks/bezier-track.js | 11 +--
src/model/tracks/point-track.js | 4 +-
src/model/tracks/standard-track.js | 13 ++--
src/model/tracks/track.js | 21 +++---
14 files changed, 106 insertions(+), 184 deletions(-)
delete mode 100644 src/model/connect-tracks.js
diff --git a/src/components/map/object-renderers/IsolationIdRenderer.js b/src/components/map/object-renderers/IsolationIdRenderer.js
index 3a6088f..acb6cba 100644
--- a/src/components/map/object-renderers/IsolationIdRenderer.js
+++ b/src/components/map/object-renderers/IsolationIdRenderer.js
@@ -1,6 +1,6 @@
import { useMemo } from "react";
import SimpleLabelText from "../text/SimpleLabelText";
-import { TrackConnectionType } from "../../../model/track-connection";
+import { TrackConnectionEnd } from "../../../model/track-connection";
export default function IsolationIdRenderer(props) {
const { object } = props;
@@ -15,7 +15,7 @@ export default function IsolationIdRenderer(props) {
text={text}
x={x}
y={y}
- textProps={{
+ textProps={{
className: "isolation-id"
}}
/>}
@@ -29,7 +29,7 @@ function IsolationEndMarker(props) {
const isShown = useMemo(() => {
return object.connections.some(conn =>
- (isStart ? conn.type === TrackConnectionType.START : conn.type === TrackConnectionType.END) &&
+ (isStart ? conn.type === TrackConnectionEnd.START : conn.type === TrackConnectionEnd.END) &&
conn.otherTrack.id_isolation !== object.id_isolation
);
}, [object, isStart]);
@@ -46,5 +46,5 @@ function IsolationEndMarker(props) {
return
+ />
}
diff --git a/src/helpers/constants.js b/src/helpers/constants.js
index b36cca1..1e66345 100644
--- a/src/helpers/constants.js
+++ b/src/helpers/constants.js
@@ -21,7 +21,6 @@ const Constants = {
attachSignsMaxDistanceX: 0.2,
attachSignsGridSize: 10,
logAttachedSigns: false,
- connectTracks: true,
},
warnings: {
all: false, // enable all warnings
@@ -51,7 +50,6 @@ const Constants = {
attachSignsOutOfBounds: true,
signalElemsUnknownSign: true,
signalElemsUnknownSignText: true,
- connectTracksFailed: true,
},
errors: {
invalidSceneryInfo: true
diff --git a/src/model/connect-tracks.js b/src/model/connect-tracks.js
deleted file mode 100644
index 477da55..0000000
--- a/src/model/connect-tracks.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import SceneryParserLog from "./scenery-parser-log";
-import TrackConnection, { TrackConnectionType } from "./track-connection";
-
-export function connectTracks(scenery) {
- const tracks = scenery.objects['tracks'] || {};
-
- Object.values(tracks).forEach(track => {
- connectTrack(scenery, track, false);
- connectTrack(scenery, track, true);
- });
-}
-
-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})`);
- 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 mainConnectionType = isPrev ? TrackConnectionType.START : TrackConnectionType.END;
-
- track.connections.push(new TrackConnection(
- track,
- vNextTrack,
- mainConnectionType,
- otherConnectionType
- ));
-
- if(!addReverseConnection) return;
-
- vNextTrack.connections.push(new TrackConnection(
- vNextTrack,
- track,
- otherConnectionType,
- mainConnectionType
- ));
-}
\ No newline at end of file
diff --git a/src/model/scenery-parser.js b/src/model/scenery-parser.js
index 51292a0..dbeb723 100644
--- a/src/model/scenery-parser.js
+++ b/src/model/scenery-parser.js
@@ -19,7 +19,6 @@ 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';
/*
TODO: add support for WorldRotation and WorldTranslation
@@ -36,7 +35,7 @@ export default class SceneryParser {
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
@@ -46,7 +45,6 @@ export default class SceneryParser {
scenery.applyObjects();
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,7 +64,7 @@ export default class SceneryParser {
}
return SceneryInfo.fromText(text);
- }
+ }
static _parseObject(text) {
const type = text.split(";", 2)[0];
@@ -149,4 +147,4 @@ export default class SceneryParser {
return Misc.fromText(Scenery.nextMiscId++, text);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/model/switch-descriptions/switch-prefab-track.js b/src/model/switch-descriptions/switch-prefab-track.js
index 61808be..7dc8c40 100644
--- a/src/model/switch-descriptions/switch-prefab-track.js
+++ b/src/model/switch-descriptions/switch-prefab-track.js
@@ -1,8 +1,3 @@
-export const SwitchTrackConnectionEnd = {
- PREV: Symbol('SwitchTrackConnectionEnd.PREV'),
- NEXT: Symbol('SwitchTrackConnectionEnd.NEXT'),
-};
-
export const SwitchTrackConnectionType = {
INTERNAL: Symbol('SwitchTrackConnectionType.INTERNAL'),
EXTERNAL: Symbol('SwitchTrackConnectionType.EXTERNAL'),
diff --git a/src/model/switch-descriptions/switch-prefab.js b/src/model/switch-descriptions/switch-prefab.js
index 0791b76..ace1e1b 100644
--- a/src/model/switch-descriptions/switch-prefab.js
+++ b/src/model/switch-descriptions/switch-prefab.js
@@ -1,6 +1,7 @@
import Vector3 from "../vector3";
-import SwitchPrefabTrack, {SwitchTrackConnectionEnd, SwitchTrackConnectionType} from "./switch-prefab-track";
+import SwitchPrefabTrack, {SwitchTrackConnectionType} from "./switch-prefab-track";
import SceneryParserLog from "../scenery-parser-log";
+import {TrackConnectionEnd} from "../track-connection";
export default class SwitchPrefab {
tracks = {};
@@ -58,18 +59,18 @@ export default class SwitchPrefab {
[
{
type: SwitchTrackConnectionType.EXTERNAL,
- end: SwitchTrackConnectionEnd.PREV,
+ end: TrackConnectionEnd.START,
},
{
type: SwitchTrackConnectionType.INTERNAL,
- end: SwitchTrackConnectionEnd.NEXT,
- otherEnd: SwitchTrackConnectionEnd.PREV,
+ end: TrackConnectionEnd.END,
+ otherEnd: TrackConnectionEnd.START,
internalId: 'curve_a',
},
{
type: SwitchTrackConnectionType.INTERNAL,
- end: SwitchTrackConnectionEnd.NEXT,
- otherEnd: SwitchTrackConnectionEnd.PREV,
+ end: TrackConnectionEnd.END,
+ otherEnd: TrackConnectionEnd.START,
internalId: 'curve_b',
},
],
@@ -113,14 +114,14 @@ export default class SwitchPrefab {
[
{
type: SwitchTrackConnectionType.INTERNAL,
- end: SwitchTrackConnectionEnd.PREV,
- otherEnd: SwitchTrackConnectionEnd.NEXT,
+ end: TrackConnectionEnd.START,
+ otherEnd: TrackConnectionEnd.END,
internalId: 'start',
},
{
type: SwitchTrackConnectionType.INTERNAL,
- end: SwitchTrackConnectionEnd.NEXT,
- otherEnd: SwitchTrackConnectionEnd.PREV,
+ end: TrackConnectionEnd.END,
+ otherEnd: TrackConnectionEnd.START,
internalId: addedLength > 0 ? `added_${side}` : `end_${side}`,
},
],
@@ -138,14 +139,14 @@ export default class SwitchPrefab {
[
{
type: SwitchTrackConnectionType.INTERNAL,
- end: SwitchTrackConnectionEnd.PREV,
- otherEnd: SwitchTrackConnectionEnd.NEXT,
+ end: TrackConnectionEnd.START,
+ otherEnd: TrackConnectionEnd.END,
internalId: `curve_${side}`,
},
{
type: SwitchTrackConnectionType.INTERNAL,
- end: SwitchTrackConnectionEnd.NEXT,
- otherEnd: SwitchTrackConnectionEnd.PREV,
+ end: TrackConnectionEnd.END,
+ otherEnd: TrackConnectionEnd.START,
internalId: `end_${side}`,
},
],
@@ -160,13 +161,13 @@ export default class SwitchPrefab {
[
{
type: SwitchTrackConnectionType.INTERNAL,
- end: SwitchTrackConnectionEnd.PREV,
- otherEnd: SwitchTrackConnectionEnd.NEXT,
+ end: TrackConnectionEnd.START,
+ otherEnd: TrackConnectionEnd.END,
internalId: addedLength > 0 ? `added_${side}` : `curve_${side}`,
},
{
type: SwitchTrackConnectionType.EXTERNAL,
- end: SwitchTrackConnectionEnd.NEXT,
+ end: TrackConnectionEnd.END,
},
]
);
diff --git a/src/model/switch.js b/src/model/switch.js
index 22c8797..1352265 100644
--- a/src/model/switch.js
+++ b/src/model/switch.js
@@ -3,7 +3,8 @@ import SceneryObject from "./scenery-object.js";
import SceneryParserLog from "./scenery-parser-log.js";
import Vector3 from "./vector3.js";
import DefinedSwitches from "./defs/defined-switches.js";
-import {SwitchTrackConnectionEnd, SwitchTrackConnectionType} from "./switch-descriptions/switch-prefab-track";
+import {SwitchTrackConnectionType} from "./switch-descriptions/switch-prefab-track";
+import TrackConnection, {TrackConnectionEnd} from "./track-connection";
export default class Switch extends SceneryObject {
model;
@@ -76,7 +77,7 @@ export default class Switch extends SceneryObject {
if (!trackIds) return;
const tracks = Object.entries(def.tracks).map(([key, track]) => {
- return this._createSwitchTrackFromDef(scenery, def, track, trackIds)
+ return this._createSwitchTrackFromDef(scenery, def, track, trackIds);
});
if (tracks.some(track => !track)) return;
@@ -109,10 +110,7 @@ export default class Switch extends SceneryObject {
}
const [trackId, prevId, nextId] = ids[trackDef.dataIndex];
- const endIds = {
- [SwitchTrackConnectionEnd.PREV]: [],
- [SwitchTrackConnectionEnd.NEXT]: [],
- };
+ const connections = [];
trackDef.connections.forEach((connection) => {
if (connection.type === SwitchTrackConnectionType.INTERNAL) {
const otherTrackDef = switchDef.tracks[connection.internalId];
@@ -132,12 +130,12 @@ export default class Switch extends SceneryObject {
}
const otherTrackId = ids[otherTrackDef.dataIndex][0];
if (otherTrackId) {
- endIds[connection.end].push(otherTrackId);
+ connections.push(new TrackConnection(otherTrackId, connection.end));
}
} else if (connection.type === SwitchTrackConnectionType.EXTERNAL) {
- const otherTrackId = connection.end === SwitchTrackConnectionEnd.PREV ? prevId : nextId;
+ const otherTrackId = connection.end === TrackConnectionEnd.START ? prevId : nextId;
if (otherTrackId) {
- endIds[connection.end].push(otherTrackId);
+ connections.push(new TrackConnection(otherTrackId, connection.end));
}
}
});
@@ -147,8 +145,7 @@ export default class Switch extends SceneryObject {
this.pos.add(trackDef.startPos.rotate(rotRad)),
this.pos.add(trackDef.endPos.rotate(rotRad)),
trackDef.radius,
- endIds[SwitchTrackConnectionEnd.PREV][0], // TODO: Pass the array
- endIds[SwitchTrackConnectionEnd.NEXT][0], // TODO: --||--
+ connections,
this.id_switch,
0, // start_slope
0, // end_slope
diff --git a/src/model/track-connection.js b/src/model/track-connection.js
index 7aab742..2949714 100644
--- a/src/model/track-connection.js
+++ b/src/model/track-connection.js
@@ -1,20 +1,15 @@
-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;
+ end;
- constructor(track, otherTrack, type, otherType) {
+ constructor(otherTrackId, end) {
Object.assign(this, {
- track,
- otherTrack,
- type,
- otherType
+ otherTrackId, end,
});
}
}
diff --git a/src/model/track-objects/track-object.js b/src/model/track-objects/track-object.js
index 051fc34..2070be2 100644
--- a/src/model/track-objects/track-object.js
+++ b/src/model/track-objects/track-object.js
@@ -15,7 +15,7 @@ export default class TrackObject extends SceneryObject {
super(id, pos, rot);
Object.assign(this, {
prefab_name,
- track_id,
+ track_id,
name
});
}
@@ -38,6 +38,7 @@ export default class TrackObject extends SceneryObject {
if(this.applied) return; // already applied
this.applied = true;
+ // TODO: Is the scenery.getTrackIdByAlias needed?
const trackId = scenery.getTrackIdByAlias(this.track_id);
const track = scenery.getObject('tracks', trackId);
if (!track) {
diff --git a/src/model/tracks-connection-test.js b/src/model/tracks-connection-test.js
index dec8fc9..62dc1db 100644
--- a/src/model/tracks-connection-test.js
+++ b/src/model/tracks-connection-test.js
@@ -1,52 +1,50 @@
import SceneryParserLog from './scenery-parser-log';
+import {TrackConnectionEnd} from "./track-connection";
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})`);
+function trackEndConnectionTest(scenery, track, connection) {
+ const otherTrack = scenery.getObject('tracks', connection.otherTrackId);
+ if (!otherTrack) {
+ SceneryParserLog.warn('tracksConnectionTest', `Track ${track.id} has a non-existing ${connection.end === TrackConnectionEnd.START ? 'previous' : 'next'} track: ${connection.otherTrackId}`);
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})`);
+ const reverseConnections = otherTrack.connections.filter(
+ (otherConnection) => otherConnection.type === otherConnection.otherTrackId === track.id,
+ );
+ if (reverseConnections.length > 1) {
+ SceneryParserLog.warn(
+ 'tracksConnectionTest',
+ `Track ${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 not reverse connection`,
+ );
return;
}
+ const reverseConnection = reverseConnections[0];
+
+ const ownPosition = track.getEndPos(connection.end);
+ const otherPosition = 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)}`,
+ );
+ }
}
export function tracksConnectionTest(scenery) {
const tracks = scenery.objects['tracks'] || [];
Object.values(tracks).forEach(track => {
- trackEndConnectionTest(scenery, track, false);
- trackEndConnectionTest(scenery, track, true);
+ track.connections.forEach((connection) => {
+ trackEndConnectionTest(scenery, track, connection);
+ });
});
-}
\ No newline at end of file
+}
diff --git a/src/model/tracks/bezier-track.js b/src/model/tracks/bezier-track.js
index 33abd81..bcd66fd 100644
--- a/src/model/tracks/bezier-track.js
+++ b/src/model/tracks/bezier-track.js
@@ -1,5 +1,6 @@
import Track from "./track";
import Vector3 from "../vector3";
+import TrackConnection, {TrackConnectionEnd} from "../track-connection";
export default class BezierTrack extends Track
{
@@ -11,8 +12,8 @@ export default class BezierTrack extends Track
control2: 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,
@@ -42,8 +43,10 @@ export default class BezierTrack extends Track
Vector3.zero(),
0, // len ??
0, // r ??
- values[15], // nextid
- values[16], // previd
+ [
+ new TrackConnection(values[15], TrackConnectionEnd.END),
+ new TrackConnection(values[16], TrackConnectionEnd.START),
+ ],
values[17], // id_station
...Track.slopesFromText(values[18]), // start_slope, end_slope
values[21], // id_isolation
diff --git a/src/model/tracks/point-track.js b/src/model/tracks/point-track.js
index 6ba7aaa..64f2b38 100644
--- a/src/model/tracks/point-track.js
+++ b/src/model/tracks/point-track.js
@@ -10,9 +10,9 @@ export default class PointTrack extends Track
end: Vector3.zero()
};
- constructor(id, start, end, r, nextid, previd, id_station, start_slope, end_slope, id_isolation, prefab_name, maxspeed, derailspeed) {
+ constructor(id, start, end, r, connections, id_station, start_slope, end_slope, id_isolation, prefab_name, maxspeed, derailspeed) {
const [rot, len] = PointTrack._getRotLen(start, end, r);
- super(id, start, rot, len, r, nextid, previd, id_station, start_slope, end_slope, id_isolation, prefab_name, maxspeed, derailspeed);
+ super(id, start, rot, len, r, connections, id_station, start_slope, end_slope, id_isolation, prefab_name, maxspeed, derailspeed);
Object.assign(this.points, {
start, end
diff --git a/src/model/tracks/standard-track.js b/src/model/tracks/standard-track.js
index 27c64b2..3600dce 100644
--- a/src/model/tracks/standard-track.js
+++ b/src/model/tracks/standard-track.js
@@ -1,6 +1,7 @@
import AngleHelper from "../../helpers/angleHelper";
import Track from "./track";
import Vector3 from "../vector3";
+import TrackConnection, {TrackConnectionEnd} from "../track-connection";
export default class StandardTrack extends Track
{
@@ -11,8 +12,8 @@ export default class StandardTrack extends Track
circleCenter: 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();
}
@@ -38,8 +39,10 @@ export default class StandardTrack extends Track
Vector3.fromValuesArray(values, 6), // rot
parseFloat(values[9]), // len
parseFloat(values[10]), // r
- values[11], // nextid
- values[12], // previd
+ [
+ new TrackConnection(values[11], TrackConnectionEnd.END),
+ new TrackConnection(values[12], TrackConnectionEnd.START),
+ ],
values[13], // id_station
...Track.slopesFromText(values[14]), // start_slope, end_slope
values[17], // id_isolation
@@ -64,7 +67,7 @@ export default class StandardTrack extends Track
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;
diff --git a/src/model/tracks/track.js b/src/model/tracks/track.js
index b209c29..5f91868 100644
--- a/src/model/tracks/track.js
+++ b/src/model/tracks/track.js
@@ -1,5 +1,6 @@
import SceneryObject from "../scenery-object";
import { ElectrificationStatus } from "../electrification-status";
+import {TrackConnectionEnd} from "../track-connection";
export default class Track extends SceneryObject {
len;
@@ -10,8 +11,7 @@ export default class Track extends SceneryObject {
derailspeed;
category = "tracks";
type = "Track";
- nextid;
- previd;
+ connections = [];
start_slope;
end_slope;
prefab_name;
@@ -20,18 +20,17 @@ export default class Track extends SceneryObject {
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,
});
}
@@ -47,6 +46,10 @@ export default class Track extends SceneryObject {
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 +59,7 @@ export default class Track extends SceneryObject {
return this.points.end;
}
}
-
+
getRenderBounds() {
return Object.values(this.points);
}
From 5d7cef846fc069fd028d9d7e54921228d2089298 Mon Sep 17 00:00:00 2001
From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com>
Date: Sat, 12 Jul 2025 02:02:53 +0200
Subject: [PATCH 03/24] Run track connection test and resolve some errors
---
src/model/electrification-resolver.js | 34 +++++++------------
.../switch-descriptions/switch-prefab.js | 6 ++--
src/model/tracks-connection-test.js | 4 +--
src/model/tracks/bezier-track.js | 10 +++---
src/model/tracks/standard-track.js | 10 +++---
5 files changed, 30 insertions(+), 34 deletions(-)
diff --git a/src/model/electrification-resolver.js b/src/model/electrification-resolver.js
index dea0702..b3bf5d2 100644
--- a/src/model/electrification-resolver.js
+++ b/src/model/electrification-resolver.js
@@ -26,14 +26,15 @@ export default class ElectrificationResolver {
routeTracks.forEach(routeTrack => {
this._propagate(
- routeTrack.track,
- [],
+ routeTrack.track,
+ [],
routeTrack.electrified ? ElectrificationStatus.ELECTRIFIED : ElectrificationStatus.NON_ELECTRIFIED
);
});
this._runResolution();
} catch (error) {
+ // TODO: There is no such field, should it be RESOLVING_ERROR?
scenery.electrificationResolved = ElectrificationResolutionStatus.ERROR;
SceneryParserLog.warn('electrificationResolverError', `Error resolving electrification: ${error.message}`);
return;
@@ -89,7 +90,7 @@ export default class ElectrificationResolver {
if(results.length < routePoints.length) {
ElectrificationResolver._passWarn(
- 'electrificationMissingRouteTracks',
+ 'electrificationMissingRouteTracks',
`While resolving electrification, found ${results.length} tracks connected to routes, but expected ${routePoints.length}`
);
}
@@ -103,7 +104,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;
@@ -124,7 +125,7 @@ export default class ElectrificationResolver {
ElectrificationResolver.propagationQueue.push({ track, skipTrackIds, status });
}
- static _resolveTrack(track, skipTrackIds, status) {
+ static _resolveTrack(scenery, track, skipTrackIds, status) {
if (track.electrificationStatus === status) return; // already set
if (Constants.parser.skipElectrificationErrorsPropagation && status === ElectrificationStatus.CONFLICT) {
return;
@@ -137,38 +138,29 @@ 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 propagation = track.connections
+ .map(conn => scenery.getObject('tracks', conn.otherTrackId))
+ .filter((track) => !!track);
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);
});
-
- // Additional propagation for double switches
- if (!track.switch || track.switch.def?.[2]?.length <= 2 || track.hasNEVP) return;
-
- const otherSwitchTrack = track.switch.trackA === track ? track.switch.trackB : track.switch.trackA;
- this._propagate(otherSwitchTrack, [track.id, ...propagation.map(track => track.id)], track.electrificationStatus);
}
-};
\ No newline at end of file
+};
diff --git a/src/model/switch-descriptions/switch-prefab.js b/src/model/switch-descriptions/switch-prefab.js
index ace1e1b..7469fcd 100644
--- a/src/model/switch-descriptions/switch-prefab.js
+++ b/src/model/switch-descriptions/switch-prefab.js
@@ -33,7 +33,7 @@ export default class SwitchPrefab {
if (!reverseConnection) {
SceneryParserLog.warn(
'switchInvalidInternalConnection',
- `In a switch prefab the internal track ${internalId} is connected to ${connection.internalId} but there is not reverse connection`,
+ `In a switch prefab the internal track ${internalId} is connected to ${connection.internalId} but there is no reverse connection`,
);
return;
}
@@ -156,8 +156,8 @@ export default class SwitchPrefab {
tracks[`end_${side}`] = SwitchPrefabTrack.point(
end,
addedLength > 0 ?
- (side === 'a' ? 5 : 6) :
- (side === 'a' ? 3 : 4),
+ (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,
diff --git a/src/model/tracks-connection-test.js b/src/model/tracks-connection-test.js
index 62dc1db..87fb2a7 100644
--- a/src/model/tracks-connection-test.js
+++ b/src/model/tracks-connection-test.js
@@ -11,7 +11,7 @@ function trackEndConnectionTest(scenery, track, connection) {
}
const reverseConnections = otherTrack.connections.filter(
- (otherConnection) => otherConnection.type === otherConnection.otherTrackId === track.id,
+ (otherConnection) => otherConnection.otherTrackId === track.id,
);
if (reverseConnections.length > 1) {
SceneryParserLog.warn(
@@ -22,7 +22,7 @@ function trackEndConnectionTest(scenery, track, connection) {
if (reverseConnections.length === 0) {
SceneryParserLog.warn(
'tracksConnectionTest',
- `Track ${track.id} is connected to ${connection.otherTrackId} but there is not reverse connection`,
+ `Track ${track.id} is connected to ${connection.otherTrackId} but there is no reverse connection`,
);
return;
}
diff --git a/src/model/tracks/bezier-track.js b/src/model/tracks/bezier-track.js
index bcd66fd..ff79db7 100644
--- a/src/model/tracks/bezier-track.js
+++ b/src/model/tracks/bezier-track.js
@@ -33,6 +33,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
@@ -43,10 +48,7 @@ export default class BezierTrack extends Track
Vector3.zero(),
0, // len ??
0, // r ??
- [
- new TrackConnection(values[15], TrackConnectionEnd.END),
- new TrackConnection(values[16], TrackConnectionEnd.START),
- ],
+ connections,
values[17], // id_station
...Track.slopesFromText(values[18]), // start_slope, end_slope
values[21], // id_isolation
diff --git a/src/model/tracks/standard-track.js b/src/model/tracks/standard-track.js
index 3600dce..81cc799 100644
--- a/src/model/tracks/standard-track.js
+++ b/src/model/tracks/standard-track.js
@@ -33,16 +33,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
- [
- new TrackConnection(values[11], TrackConnectionEnd.END),
- new TrackConnection(values[12], TrackConnectionEnd.START),
- ],
+ connections,
values[13], // id_station
...Track.slopesFromText(values[14]), // start_slope, end_slope
values[17], // id_isolation
From 4342bb41e8a896c736138476c65a348945752e15 Mon Sep 17 00:00:00 2001
From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com>
Date: Sat, 12 Jul 2025 10:48:06 +0200
Subject: [PATCH 04/24] Add crossing prefab definitions
---
src/model/defs/defined-switches.js | 20 +-------
.../switch-descriptions/switch-prefab.js | 47 +++++++++++++++++--
2 files changed, 46 insertions(+), 21 deletions(-)
diff --git a/src/model/defs/defined-switches.js b/src/model/defs/defined-switches.js
index f8f1c25..e917203 100644
--- a/src/model/defs/defined-switches.js
+++ b/src/model/defs/defined-switches.js
@@ -1,24 +1,8 @@
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)
- // ],
+ "Crossing": SwitchPrefab.crossing(33.2294, 9.0),
+ "Crossing4.444": SwitchPrefab.crossing(20.0, 4.444),
// Rkp 60E1-190-1_9 ab
// Rkp 60E1-190-1_9 ba
// Rkpd 60E1-190-1_9
diff --git a/src/model/switch-descriptions/switch-prefab.js b/src/model/switch-descriptions/switch-prefab.js
index 7469fcd..4b0ef2e 100644
--- a/src/model/switch-descriptions/switch-prefab.js
+++ b/src/model/switch-descriptions/switch-prefab.js
@@ -75,13 +75,13 @@ export default class SwitchPrefab {
},
],
),
- ...SwitchPrefab._createSwitchTracks(
+ ...SwitchPrefab._createForkSwitchTracks(
radiusA,
curveLength,
addedLength,
'a',
),
- ...SwitchPrefab._createSwitchTracks(
+ ...SwitchPrefab._createForkSwitchTracks(
radiusB,
curveLength,
addedLength,
@@ -92,7 +92,7 @@ export default class SwitchPrefab {
return new SwitchPrefab(tracks);
}
- static _createSwitchTracks(radius, curveLength, addedLength, side) {
+ static _createForkSwitchTracks(radius, curveLength, addedLength, side) {
let curveEnd;
let endAngle;
if (radius === 0) {
@@ -174,4 +174,45 @@ export default class SwitchPrefab {
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);
+ }
}
From d93eae6985ecbdf30929a525685637958edcaf54 Mon Sep 17 00:00:00 2001
From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com>
Date: Sat, 12 Jul 2025 13:12:27 +0200
Subject: [PATCH 05/24] Add slip switch prefab definitions
---
src/model/defs/defined-switches.js | 162 +++++------
.../switch-descriptions/switch-prefab.js | 266 +++++++++++++++++-
2 files changed, 337 insertions(+), 91 deletions(-)
diff --git a/src/model/defs/defined-switches.js b/src/model/defs/defined-switches.js
index e917203..48a2260 100644
--- a/src/model/defs/defined-switches.js
+++ b/src/model/defs/defined-switches.js
@@ -3,92 +3,94 @@ import SwitchPrefab from "../switch-descriptions/switch-prefab";
const DefinedSwitches = {
"Crossing": SwitchPrefab.crossing(33.2294, 9.0),
"Crossing4.444": SwitchPrefab.crossing(20.0, 4.444),
- // Rkp 60E1-190-1_9 ab
- // Rkp 60E1-190-1_9 ba
- // Rkpd 60E1-190-1_9
- "Rld 60E1-1200_600-1_15 L": SwitchPrefab.fork(600.0, -1200.0, 39.95566, 0.0),
- "Rld 60E1-1200_600-1_15 R": SwitchPrefab.fork(-600.0, 1200.0, 39.95566, 0.0),
- "Rld 60E1-1200_900-1_18.5 L": SwitchPrefab.fork(900.0, -1200.0, 48.61317, 0.0),
- "Rld 60E1-1200_900-1_18.5 R": SwitchPrefab.fork(-900.0, 1200.0, 48.61317, 0.0),
- "Rld 60E1-1800_300-1_9 L": SwitchPrefab.fork(300.0, -1800.0, 33.23108, 0.0),
- "Rld 60E1-1800_300-1_9 R": SwitchPrefab.fork(-300.0, 1800.0, 33.23108, 0.0),
- "Rld 60E1-1800_600-1_14 L": SwitchPrefab.fork(600.0, -1800.0, 42.80262, 0.0),
- "Rld 60E1-1800_600-1_14 R": SwitchPrefab.fork(-600.0, 1800.0, 42.80262, 0.0),
- "Rld 60E1-1800_900-1_18.5 L": SwitchPrefab.fork(900.0, -1800.0, 48.61317, 0.0),
- "Rld 60E1-1800_900-1_18.5 R": SwitchPrefab.fork(-900.0, 1800.0, 48.61317, 0.0),
- "Rld 60E1-2500_1200-1_22 L": SwitchPrefab.fork(1200.0, -2500.0, 54.51731, 0.0),
- "Rld 60E1-2500_1200-1_22 R": SwitchPrefab.fork(-1200.0, 2500.0, 54.51731, 0.0),
- "Rld 60E1-2500_250-1_8.5 L": SwitchPrefab.fork(250.0, -2500.0, 29.31069, 0.0),
- "Rld 60E1-2500_250-1_8.5 R": SwitchPrefab.fork(-250.0, 2500.0, 29.31069, 0.0),
- "Rld 60E1-2500_400-1_10.5 L": SwitchPrefab.fork(400.0, -2500.0, 38.00925, 0.0),
- "Rld 60E1-2500_400-1_10.5 R": SwitchPrefab.fork(-400.0, 2500.0, 38.00925, 0.0),
- "Rld 60E1-2500_600-1_14 L": SwitchPrefab.fork(600.0, -2500.0, 42.80262, 0.0),
- "Rld 60E1-2500_600-1_14 R": SwitchPrefab.fork(-600.0, 2500.0, 42.80262, 0.0),
- "Rld 60E1-2500_900-1_17 L": SwitchPrefab.fork(900.0, -2500.0, 52.14928, 0.0),
- "Rld 60E1-2500_900-1_17 R": SwitchPrefab.fork(-900.0, 2500.0, 52.14928, 0.0),
- "Rld 60E1-600_300-1_9 L": SwitchPrefab.fork(300.0, -600.0, 29.92537, 0.0),
- "Rld 60E1-600_300-1_9 R": SwitchPrefab.fork(-300.0, 600.0, 29.92537, 0.0),
- "Rld 60E1-650_450-1_15 L": SwitchPrefab.fork(450.0, -650.0, 29.96674, 0.0),
- "Rld 60E1-650_450-1_15 R": SwitchPrefab.fork(-450.0, 650.0, 29.96674, 0.0),
- "Rld 60E1-700_190-1_7.5 L": SwitchPrefab.fork(190.0, -700.0, 25.22173, 0.0),
- "Rld 60E1-700_190-1_7.5 R": SwitchPrefab.fork(-190.0, 700.0, 25.22173, 0.0),
- "Rld 60E1-700_300-1_10 L": SwitchPrefab.fork(300.0, -700.0, 29.38637, 0.0),
- "Rld 60E1-700_300-1_10 R": SwitchPrefab.fork(-300.0, 700.0, 29.38637, 0.0),
- "Rld 60E1-700_500-1_14 L": SwitchPrefab.fork(500.0, -700.0, 35.66885, 0.0),
- "Rld 60E1-700_500-1_14 R": SwitchPrefab.fork(-500.0, 700.0, 35.66885, 0.0),
- "Rld 60E1-900_300-1_9 L": SwitchPrefab.fork(300.0, -900.0, 33.23108, 0.0),
- "Rld 60E1-900_300-1_9 R": SwitchPrefab.fork(-300.0, 900.0, 33.23108, 0.0),
- "Rld 60E1-900_450-1_12 L": SwitchPrefab.fork(450.0, -900.0, 37.43512, 0.0),
- "Rld 60E1-900_450-1_12 R": SwitchPrefab.fork(-450.0, 900.0, 37.43512, 0.0),
- "Rld 60E1-900_600-1_15 L": SwitchPrefab.fork(600.0, -900.0, 39.95566, 0.0),
- "Rld 60E1-900_600-1_15 R": SwitchPrefab.fork(-600.0, 900.0, 39.95566, 0.0),
- "Rlds 60E1-1000-1000-1_23": SwitchPrefab.fork(1000.0, -1000.0, 43.45773, 0.0),
- "Rlds 60E1-190-190-1_9": SwitchPrefab.fork(190.0, -190.0, 21.23, 0.0),
- "Rlds 60E1-600-600-1_18.5": SwitchPrefab.fork(599.9205, -600.0, 33.22262, 0.0),
- "Rlj 60E1-1200_300-1_7 L": SwitchPrefab.fork(300.0, 1200.0, 42.64069, 0.0),
- "Rlj 60E1-1200_300-1_7 R": SwitchPrefab.fork(-300.0, -1200.0, 42.64069, 0.0),
- "Rlj 60E1-1200_600-1_9 L": SwitchPrefab.fork(600.0, 1200.0, 68.0, 0.0),
- "Rlj 60E1-1200_600-1_9 R": SwitchPrefab.fork(-600.0, -1200.0, 68.0, 0.0),
- "Rlj 60E1-1500_450-1_9 L": SwitchPrefab.fork(450.0, 1500.0, 49.84663, 0.0),
- "Rlj 60E1-1500_450-1_9 R": SwitchPrefab.fork(-450.0, -1500.0, 49.84663, 0.0),
- "Rlj 60E1-1800_300-1_7.5 L": SwitchPrefab.fork(300.0, 1800.0, 39.0, 0.0),
- "Rlj 60E1-1800_300-1_7.5 R": SwitchPrefab.fork(-300.0, -1800.0, 39.0, 0.0),
- "Rlj 60E1-1800_450-1_9 L": SwitchPrefab.fork(450.0, 1800.0, 49.84663, 0.0),
- "Rlj 60E1-1800_450-1_9 R": SwitchPrefab.fork(-450.0, -1800.0, 49.84663, 0.0),
- "Rlj 60E1-1800_600-1_10 L": SwitchPrefab.fork(599.48096, 1800.0, 59.85075, 0.0),
- "Rlj 60E1-1800_600-1_10 R": SwitchPrefab.fork(-599.48096, -1800.0, 59.85075, 0.0),
- "Rlj 60E1-600_300-1_6 L": SwitchPrefab.fork(300.0, 600.0, 48.0, 0.0),
- "Rlj 60E1-600_300-1_6 R": SwitchPrefab.fork(-300.0, -600.0, 48.0, 0.0),
- "Rlj 60E1-650_190-1_20 L": SwitchPrefab.fork(190.0, 650.0, 32.47971, 0.0),
- "Rlj 60E1-650_190-1_20 R": SwitchPrefab.fork(-190.0, -650.0, 32.47971, 0.0),
- "Rlj 60E1-750_190-1_6 L": SwitchPrefab.fork(190.0, 750.0, 31.44976, 0.0),
- "Rlj 60E1-750_190-1_6 R": SwitchPrefab.fork(-190.0, -750.0, 31.44976, 0.0),
- "Rlj 60E1-800_250-1_6.5 L": SwitchPrefab.fork(250.0, 800.0, 38.23661, 0.0),
- "Rlj 60E1-800_250-1_6.5 R": SwitchPrefab.fork(-250.0, -800.0, 38.23661, 0.0),
- "Rlj 60E1-900_300-1_7.5 L": SwitchPrefab.fork(300.0, 900.0, 39.82378, 0.0),
- "Rlj 60E1-900_300-1_7.5 R": SwitchPrefab.fork(-300.0, -900.0, 39.82378, 0.0),
- "Rlj 60E1-900_300-1_9 L": SwitchPrefab.fork(300.0, 900.0, 49.84663, 0.0),
- "Rlj 60E1-900_300-1_9 R": SwitchPrefab.fork(-300.0, -900.0, 49.84663, 0.0),
- "Rlj 60E1-900_450-1_7.5 L": SwitchPrefab.fork(450.0, 900.0, 59.0, 0.0),
- "Rlj 60E1-900_450-1_7.5 R": SwitchPrefab.fork(-450.0, -900.0, 59.0, 0.0),
- "Rz 60E1-1200-1_18.5 L": SwitchPrefab.fork(1200.0, 0.0, 64.81756, 0.0),
- "Rz 60E1-1200-1_18.5 R": SwitchPrefab.fork(-1200.0, 0.0, 64.81756, 0.0),
- "Rz 60E1-190-1_7.5 L": SwitchPrefab.fork(190.0, 0.0, 25.221731, 0.0),
- "Rz 60E1-190-1_7.5 R": SwitchPrefab.fork(-190.0, 0.0, 25.221731, 0.0),
+
+ "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, 0.0),
- "Rz 60E1-2500-1_26.5 R": SwitchPrefab.fork(-2500.0, 0.0, 94.30607, 0.0),
+ "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, 0.0),
- "Rz 60E1-300-1_9 R": SwitchPrefab.fork(-300.0, 0.0, 33.231083, 0.0),
- "Rz 60E1-500-1_12 L": SwitchPrefab.fork(500.0, 0.0, 41.59458, 0.0),
- "Rz 60E1-500-1_12 R": SwitchPrefab.fork(-500.0, 0.0, 41.59458, 0.0),
- "Rz 60E1-760-1_14 L": SwitchPrefab.fork(760.0, 0.0, 54.21665, 0.0),
- "Rz 60E1-760-1_14 R": SwitchPrefab.fork(-760.0, 0.0, 54.21665, 0.0),
+ "Rz 60E1-300-1_9 L": SwitchPrefab.fork(300.0, 0.0, 33.231083),
+ "Rz 60E1-300-1_9 R": SwitchPrefab.fork(-300.0, 0.0, 33.231083),
+ "Rz 60E1-500-1_12 L": SwitchPrefab.fork(500.0, 0.0, 41.59458),
+ "Rz 60E1-500-1_12 R": SwitchPrefab.fork(-500.0, 0.0, 41.59458),
+ "Rz 60E1-760-1_14 L": SwitchPrefab.fork(760.0, 0.0, 54.21665),
+ "Rz 60E1-760-1_14 R": SwitchPrefab.fork(-760.0, 0.0, 54.21665),
};
export default DefinedSwitches;
diff --git a/src/model/switch-descriptions/switch-prefab.js b/src/model/switch-descriptions/switch-prefab.js
index 4b0ef2e..a6161b3 100644
--- a/src/model/switch-descriptions/switch-prefab.js
+++ b/src/model/switch-descriptions/switch-prefab.js
@@ -49,7 +49,7 @@ export default class SwitchPrefab {
});
}
- static fork(radiusA, radiusB, curveLength, addedLength) {
+ static fork(radiusA, radiusB, curveLength, addedLength = 0.0) {
const tracks = {
start: new SwitchPrefabTrack(
Vector3.zero(),
@@ -92,18 +92,20 @@ export default class SwitchPrefab {
return new SwitchPrefab(tracks);
}
- static _createForkSwitchTracks(radius, curveLength, addedLength, side) {
- let curveEnd;
- let endAngle;
+ static _calculateCurveEnd(startPos, startAngle, radius, curveLength) {
if (radius === 0) {
- curveEnd = new Vector3(0, 0, curveLength);
- endAngle = 0;
- } else {
- const circleCenter = new Vector3(-radius, 0, 0);
- endAngle = -curveLength / radius;
- const centerToEnd = Vector3.fromAngleY(endAngle + Math.sign(radius) * Math.PI / 2, Math.abs(radius));
- curveEnd = circleCenter.add(centerToEnd);
+ return { endPos: startPos, 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(
@@ -215,4 +217,246 @@ export default class SwitchPrefab {
return new SwitchPrefab(tracks);
}
+
+ 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);
+ }
}
From 0b6514a6f820bc23f77de57fc3e3c3beaa3f05d8 Mon Sep 17 00:00:00 2001
From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com>
Date: Sat, 12 Jul 2025 13:19:54 +0200
Subject: [PATCH 06/24] Fix straight switch tracks
---
src/model/switch-descriptions/switch-prefab.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/model/switch-descriptions/switch-prefab.js b/src/model/switch-descriptions/switch-prefab.js
index a6161b3..6d2f623 100644
--- a/src/model/switch-descriptions/switch-prefab.js
+++ b/src/model/switch-descriptions/switch-prefab.js
@@ -94,7 +94,7 @@ export default class SwitchPrefab {
static _calculateCurveEnd(startPos, startAngle, radius, curveLength) {
if (radius === 0) {
- return { endPos: startPos, endAngle: startAngle };
+ 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());
From c4eac723ba42636889a0baa59f8fdc367aaf1439 Mon Sep 17 00:00:00 2001
From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com>
Date: Sat, 12 Jul 2025 14:00:50 +0200
Subject: [PATCH 07/24] Add route parsing
---
src/helpers/constants.js | 1 +
src/model/route.js | 9 ++++--
src/model/scenery-parser.js | 58 ++++++++++++++++++++++++++++++++-----
3 files changed, 59 insertions(+), 9 deletions(-)
diff --git a/src/helpers/constants.js b/src/helpers/constants.js
index 1e66345..ac8e816 100644
--- a/src/helpers/constants.js
+++ b/src/helpers/constants.js
@@ -50,6 +50,7 @@ const Constants = {
attachSignsOutOfBounds: true,
signalElemsUnknownSign: true,
signalElemsUnknownSignText: true,
+ routeInvalidSegment: true,
},
errors: {
invalidSceneryInfo: true
diff --git a/src/model/route.js b/src/model/route.js
index 511692a..11676af 100644
--- a/src/model/route.js
+++ b/src/model/route.js
@@ -9,6 +9,7 @@ export default class Route extends SceneryObject {
category = "routes";
type = "Route";
points = [];
+ segments = [];
constructor(prefab_name, pos, rot, track_count, route_name, track_offset, electrified) {
super(route_name, pos, rot);
@@ -24,7 +25,7 @@ export default class Route extends SceneryObject {
this.points = this._getConnectionPoints();
}
- static fromText(text) {
+ static fromText(text) {
const values = text.split(";");
const route = new Route(
values[2], // prefab_name
@@ -39,6 +40,10 @@ export default class Route extends SceneryObject {
return route;
}
+ addSegment(length, radius, trackIds) {
+ this.segments.push({ length, radius, trackIds });
+ }
+
_getConnectionPoints() {
if (this.track_count === 1) {
return [this.pos.clone()];
@@ -53,4 +58,4 @@ export default class Route extends SceneryObject {
this.pos.add(leftTrackOffset)
];
}
-}
\ No newline at end of file
+}
diff --git a/src/model/scenery-parser.js b/src/model/scenery-parser.js
index dbeb723..0e940cd 100644
--- a/src/model/scenery-parser.js
+++ b/src/model/scenery-parser.js
@@ -29,6 +29,8 @@ 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);
@@ -37,9 +39,21 @@ export default class SceneryParser {
}
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();
@@ -69,7 +83,8 @@ export default class SceneryParser {
static _parseObject(text) {
const type = text.split(";", 2)[0];
- if(type.indexOf("Forest") !== -1 || type.indexOf("Empty") !== -1) {
+ if (type === '' || 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;
}
@@ -88,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':
@@ -101,12 +115,11 @@ export default class SceneryParser {
case 'SSPController':
case 'TerrainGroup':
case 'EndTerrainGroup':
- case '':
return null;
default:
SceneryParserLog.warn('unknownObjectType', `Unknown object type: ${type}`);
return null;
- };
+ }
}
static _parseTrack(text) {
@@ -147,4 +160,35 @@ 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];
+ if (!id) return null;
+ return parseInt(id);
+ });
+ if (trackIds.some(id => id === null || isNaN(id))) {
+ SceneryParserLog.warn('routeInvalidSegment', `Invalid route segment track IDs: ${fields[3]}`);
+ return;
+ }
+ if (trackIds.length !== route.track_count) {
+ SceneryParserLog.warn('routeInvalidSegment', `Route segment has invalid track count: expected ${route.track_count}, got ${trackIds.length}`);
+ return;
+ }
+ route.addSegment(
+ parseInt(fields[1]), // length
+ parseInt(fields[2]), // radius
+ trackIds,
+ );
+ return false;
+ }
}
From e78c1ccde50941917ae94e771db21f8c0616bac8 Mon Sep 17 00:00:00 2001
From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com>
Date: Sat, 12 Jul 2025 15:36:13 +0200
Subject: [PATCH 08/24] Add route track rendering
---
.../map/object-renderers/TrackRenderer.js | 13 ++-
.../track-hover-info/TrackHoverInfoConsts.js | 1 +
src/helpers/constants.js | 1 +
src/model/connect-routes.js | 24 ++++
src/model/route.js | 103 +++++++++++++++---
src/model/scenery-parser.js | 6 +-
src/model/tracks/route-track.js | 42 +++++++
7 files changed, 166 insertions(+), 24 deletions(-)
create mode 100644 src/model/connect-routes.js
create mode 100644 src/model/tracks/route-track.js
diff --git a/src/components/map/object-renderers/TrackRenderer.js b/src/components/map/object-renderers/TrackRenderer.js
index 8f85636..cc8045a 100644
--- a/src/components/map/object-renderers/TrackRenderer.js
+++ b/src/components/map/object-renderers/TrackRenderer.js
@@ -56,7 +56,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 +85,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 +103,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:
@@ -133,13 +136,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 (
{
+ 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.id, TrackConnectionEnd.START));
+ });
+ });
+}
diff --git a/src/model/route.js b/src/model/route.js
index 11676af..119c263 100644
--- a/src/model/route.js
+++ b/src/model/route.js
@@ -1,5 +1,7 @@
import SceneryObject from "./scenery-object";
import Vector3 from "./vector3";
+import SceneryParserLog from "./scenery-parser-log";
+import RouteTrack from "./tracks/route-track";
export default class Route extends SceneryObject {
track_count;
@@ -8,8 +10,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);
@@ -22,7 +28,11 @@ 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) {
@@ -33,7 +43,7 @@ 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
);
@@ -41,21 +51,84 @@ export default class Route extends SceneryObject {
}
addSegment(length, radius, trackIds) {
- this.segments.push({ 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 start_rot = new Vector3(0, startAngleRad * 180 / Math.PI, 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);
+ });
+ }
+
+ this.points.push(...this.end_points);
+
+ const tracks = trackIds.map((trackId, index) => {
+ return new RouteTrack(
+ trackId,
+ startPoints[index],
+ this.end_points[index],
+ start_rot,
+ lengths[index],
+ radii[index],
+ [], // connections
+ this, // route
+ );
+ });
+ 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() {
- if (this.track_count === 1) {
- return [this.pos.clone()];
- }
+ return this.offsets.map((offset) => {
+ const offsetVec = Vector3.fromAngleY(this.end_angle_rad + Math.PI / 2, offset);
+ return this.pos.add(offsetVec);
+ });
+ }
- 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);
-
- return [
- this.pos.add(rightTrackOffset),
- this.pos.add(leftTrackOffset)
- ];
+ applyObject(scenery) {
+ this.segments.forEach((segment) => {
+ segment.tracks.forEach((track) => {
+ scenery.addObject(track);
+ });
+ });
}
}
diff --git a/src/model/scenery-parser.js b/src/model/scenery-parser.js
index 0e940cd..9aad6de 100644
--- a/src/model/scenery-parser.js
+++ b/src/model/scenery-parser.js
@@ -19,6 +19,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 {connectRoutes} from "./connect-routes";
/*
TODO: add support for WorldRotation and WorldTranslation
@@ -57,6 +58,7 @@ export default class SceneryParser {
});
scenery.applyObjects();
+ connectRoutes(scenery);
if(Constants.parser.runTracksConnectionTest) tracksConnectionTest(scenery);
if(Constants.parser.resolveElectrification) ElectrificationResolver.resolveScenery(scenery);
@@ -180,10 +182,6 @@ export default class SceneryParser {
SceneryParserLog.warn('routeInvalidSegment', `Invalid route segment track IDs: ${fields[3]}`);
return;
}
- if (trackIds.length !== route.track_count) {
- SceneryParserLog.warn('routeInvalidSegment', `Route segment has invalid track count: expected ${route.track_count}, got ${trackIds.length}`);
- return;
- }
route.addSegment(
parseInt(fields[1]), // length
parseInt(fields[2]), // radius
diff --git a/src/model/tracks/route-track.js b/src/model/tracks/route-track.js
new file mode 100644
index 0000000..b4236d9
--- /dev/null
+++ b/src/model/tracks/route-track.js
@@ -0,0 +1,42 @@
+import AngleHelper from "../../helpers/angleHelper";
+import Track from "./track";
+import {ElectrificationStatus} from "../electrification-status";
+
+export default class RouteTrack extends Track {
+ type = "RouteTrack";
+ points;
+ route;
+
+ constructor(id, start, end, rot, len, r, connections, route, electrified) {
+ 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;
+ }
+}
From 60e2f1f8fa2197f24a39f3809333f8933f6d2b6f Mon Sep 17 00:00:00 2001
From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com>
Date: Sat, 12 Jul 2025 16:04:58 +0200
Subject: [PATCH 09/24] Properly connect route tracks
---
src/model/route.js | 17 +++++++++++++++--
src/model/scenery-parser.js | 6 +++---
src/model/tracks/route-track.js | 2 +-
3 files changed, 19 insertions(+), 6 deletions(-)
diff --git a/src/model/route.js b/src/model/route.js
index 119c263..522b92b 100644
--- a/src/model/route.js
+++ b/src/model/route.js
@@ -2,6 +2,7 @@ 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";
export default class Route extends SceneryObject {
track_count;
@@ -101,18 +102,30 @@ export default class Route extends SceneryObject {
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.id, TrackConnectionEnd.START));
+ prevTrack.connections.push(new TrackConnection(track.id, 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 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);
+ result.push(leftmost_offset - i * 4);
}
return result;
}
diff --git a/src/model/scenery-parser.js b/src/model/scenery-parser.js
index 9aad6de..3ce7890 100644
--- a/src/model/scenery-parser.js
+++ b/src/model/scenery-parser.js
@@ -174,9 +174,9 @@ export default class SceneryParser {
const trackIds = fields[3]
.split(',')
.map(segment => {
- const id = segment.split(':')[0];
- if (!id) return null;
- return parseInt(id);
+ 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]}`);
diff --git a/src/model/tracks/route-track.js b/src/model/tracks/route-track.js
index b4236d9..ac93dfa 100644
--- a/src/model/tracks/route-track.js
+++ b/src/model/tracks/route-track.js
@@ -7,7 +7,7 @@ export default class RouteTrack extends Track {
points;
route;
- constructor(id, start, end, rot, len, r, connections, route, electrified) {
+ constructor(id, start, end, rot, len, r, connections, electrified, route) {
super(
id, start, rot, len, r, connections,
null, // id_station,
From 68f4504a4ade040ce22f1aec75f98e112002a4b8 Mon Sep 17 00:00:00 2001
From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com>
Date: Sat, 12 Jul 2025 16:47:55 +0200
Subject: [PATCH 10/24] Update electrification resolution
---
src/helpers/constants.js | 1 -
src/model/electrification-resolver.js | 94 ++++++++-------------------
2 files changed, 27 insertions(+), 68 deletions(-)
diff --git a/src/helpers/constants.js b/src/helpers/constants.js
index 18e3837..467c8d5 100644
--- a/src/helpers/constants.js
+++ b/src/helpers/constants.js
@@ -42,7 +42,6 @@ const Constants = {
tracksConnectionTest: true,
electrificationNevpNotApplied: false,
electrificationConflict: false,
- electrificationMissingRouteTracks: true,
electrificationResolverError: true,
electrificationResolverWarnings: true,
signalElemsUnknownPrefab: true,
diff --git a/src/model/electrification-resolver.js b/src/model/electrification-resolver.js
index b3bf5d2..34f0037 100644
--- a/src/model/electrification-resolver.js
+++ b/src/model/electrification-resolver.js
@@ -2,16 +2,6 @@ import Constants from "../helpers/constants";
import SceneryParserLog from "./scenery-parser-log";
import {ElectrificationStatus, ElectrificationResolutionStatus} from "./electrification-status";
-class RouteTrack {
- route;
- track;
- electrified;
-
- constructor(route, track, electrified) {
- Object.assign(this, { route, track, electrified });
- }
-}
-
export default class ElectrificationResolver {
static hasWarnings = false;
static propagationQueue = [];
@@ -21,22 +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();
+ this._runResolution(scenery);
} catch (error) {
- // TODO: There is no such field, should it be RESOLVING_ERROR?
- scenery.electrificationResolved = ElectrificationResolutionStatus.ERROR;
+ scenery.electrificationResolved = ElectrificationResolutionStatus.RESOLVING_ERROR;
SceneryParserLog.warn('electrificationResolverError', `Error resolving electrification: ${error.message}`);
+ console.warn(error);
return;
}
@@ -54,48 +44,19 @@ 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) => {
+ const otherTrack = scenery.getObject('tracks', connection.otherTrackId);
+ if (!otherTrack || otherTrack.type === 'RouteTrack') return null;
+ return { track: 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) {
@@ -114,18 +75,18 @@ export default class ElectrificationResolver {
});
}
- static _runResolution() {
+ static _runResolution(scenery) {
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(scenery, 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(scenery, track, skipTrackIds, status) {
+ static _resolveTrack(scenery, track, skipTrackId, status) {
if (track.electrificationStatus === status) return; // already set
if (Constants.parser.skipElectrificationErrorsPropagation && status === ElectrificationStatus.CONFLICT) {
return;
@@ -156,11 +117,10 @@ export default class ElectrificationResolver {
const propagation = track.connections
.map(conn => scenery.getObject('tracks', conn.otherTrackId))
.filter((track) => !!track);
- const nextSkipIds = [track.id];
propagation.forEach(nextTrack => {
- if (skipTrackIds.includes(nextTrack.id)) return;
- this._propagate(nextTrack, nextSkipIds, track.electrificationStatus);
+ if (skipTrackId === nextTrack.id) return;
+ this._propagate(nextTrack, track.id, track.electrificationStatus);
});
}
};
From d6c0582c8448427db809cf96f66c3164e639a7f9 Mon Sep 17 00:00:00 2001
From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com>
Date: Sat, 12 Jul 2025 16:58:17 +0200
Subject: [PATCH 11/24] Remove track aliases
---
src/helpers/constants.js | 2 --
src/model/scenery.js | 26 ++-----------------------
src/model/switch.js | 1 -
src/model/track-objects/track-object.js | 7 ++-----
src/model/tracks/track.js | 1 -
5 files changed, 4 insertions(+), 33 deletions(-)
diff --git a/src/helpers/constants.js b/src/helpers/constants.js
index 467c8d5..4785357 100644
--- a/src/helpers/constants.js
+++ b/src/helpers/constants.js
@@ -29,8 +29,6 @@ const Constants = {
unknownObjectType: true,
unknownTrackType: true,
spawnWithoutSpawnInfo: true,
- trackAliasAlreadyExists: true,
- trackAliasNoTrack: true,
switchUndefinedModel: true,
switchInvalidDataFormat: true,
switchNoModel: true,
diff --git a/src/model/scenery.js b/src/model/scenery.js
index 08737da..5a4eb40 100644
--- a/src/model/scenery.js
+++ b/src/model/scenery.js
@@ -1,5 +1,4 @@
import { ElectrificationResolutionStatus } from './electrification-status.js';
-import SceneryParserLog from './scenery-parser-log.js';
export default class Scenery
{
@@ -7,7 +6,6 @@ export default class Scenery
signalBoxes = [];
spawnPoints = [];
bounds = { minX: Infinity, minZ: Infinity, maxX: -Infinity, maxZ: -Infinity };
- trackAliases = {};
electrificationResolved = ElectrificationResolutionStatus.NOT_RESOLVED;
static nextMiscId = 1;
@@ -36,7 +34,7 @@ export default class Scenery
this._updateBounds(object.getRenderBounds());
}
}
-
+
applyObjects() {
Object.keys(this.objects).forEach(category => {
Object.values(this.objects[category]).forEach(object => {
@@ -52,26 +50,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;
@@ -83,4 +61,4 @@ export default class Scenery
}
);
}
-}
\ No newline at end of file
+}
diff --git a/src/model/switch.js b/src/model/switch.js
index 1352265..0950a52 100644
--- a/src/model/switch.js
+++ b/src/model/switch.js
@@ -157,7 +157,6 @@ export default class Switch extends SceneryObject {
trackObj.switch = this;
scenery.addObject(trackObj);
- scenery.addTrackAlias(trackId, trackId);
return trackObj;
}
diff --git a/src/model/track-objects/track-object.js b/src/model/track-objects/track-object.js
index 2070be2..5a60440 100644
--- a/src/model/track-objects/track-object.js
+++ b/src/model/track-objects/track-object.js
@@ -38,14 +38,11 @@ export default class TrackObject extends SceneryObject {
if(this.applied) return; // already applied
this.applied = true;
- // TODO: Is the scenery.getTrackIdByAlias needed?
- const trackId = scenery.getTrackIdByAlias(this.track_id);
- const track = scenery.getObject('tracks', trackId);
+ const track = scenery.getObject('tracks', this.track_id);
if (!track) {
- SceneryParserLog.warn('trackObjectCannotBeApplied', `TrackObject ${this.id} cannot be applied: track ${trackId} not found`)
+ SceneryParserLog.warn('trackObjectCannotBeApplied', `TrackObject ${this.id} cannot be applied: track ${this.track_id} not found`)
return;
}
-
this.track = track;
}
}
diff --git a/src/model/tracks/track.js b/src/model/tracks/track.js
index 5f91868..71c35ca 100644
--- a/src/model/tracks/track.js
+++ b/src/model/tracks/track.js
@@ -16,7 +16,6 @@ export default class Track extends SceneryObject {
end_slope;
prefab_name;
hide_isolation = false;
- aliases = [];
switch = null;
electrificationStatus = ElectrificationStatus.NOT_CHECKED;
hasNEVP = false;
From f9ef9de7ef1b4335cbeda9ddf20d4276c69ab226 Mon Sep 17 00:00:00 2001
From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com>
Date: Sat, 12 Jul 2025 17:07:40 +0200
Subject: [PATCH 12/24] Do not render 0-length tracks
---
src/components/map/object-renderers/TrackRenderer.js | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/components/map/object-renderers/TrackRenderer.js b/src/components/map/object-renderers/TrackRenderer.js
index cc8045a..c027890 100644
--- a/src/components/map/object-renderers/TrackRenderer.js
+++ b/src/components/map/object-renderers/TrackRenderer.js
@@ -22,6 +22,10 @@ const MemoizedTrackRenderer = React.memo(StatelessTrackRenderer);
function StatelessTrackRenderer(props) {
const { object, trackColorMode } = props;
+ if (object.points.start.distanceSq(object.points.end) < 0.001) {
+ return null;
+ }
+
const onMouseEnter = () => {
setHoveredTrack(object);
};
From 5eca3d4558708ad433f06836b8440e251cd57c72 Mon Sep 17 00:00:00 2001
From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com>
Date: Sat, 12 Jul 2025 17:13:19 +0200
Subject: [PATCH 13/24] Change position of the route arrow
---
src/components/map/object-renderers/RouteRenderer.js | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/components/map/object-renderers/RouteRenderer.js b/src/components/map/object-renderers/RouteRenderer.js
index 474ead5..a208bb9 100644
--- a/src/components/map/object-renderers/RouteRenderer.js
+++ b/src/components/map/object-renderers/RouteRenderer.js
@@ -3,13 +3,14 @@ import AlwaysUpText from "../text/AlwaysUpText";
export default function RouteRenderer(props) {
const { object } = props;
- const [x, y] = object.pos.toSVGCoords();
+ const [x, y] = object.end_center.toSVGCoords();
+ const angle = object.end_angle_rad * 180 / Math.PI;
const offY = object.track_count === 2 ? -22 : -20;
const arrowOffset = - object.track_offset - 37.8;
return (
-
+
Date: Sat, 12 Jul 2025 18:19:55 +0200
Subject: [PATCH 14/24] Store reference to otherTrack in TrackConnection
---
.../object-renderers/IsolationIdRenderer.js | 25 +++---
src/helpers/constants.js | 1 +
src/model/connect-routes.js | 24 ------
src/model/connect-tracks.js | 83 +++++++++++++++++++
src/model/electrification-resolver.js | 25 +++---
src/model/route.js | 4 +-
src/model/scenery-parser.js | 6 +-
src/model/track-connection.js | 18 +++-
src/model/tracks-connection-test.js | 50 -----------
src/model/tracks/track.js | 5 ++
10 files changed, 132 insertions(+), 109 deletions(-)
delete mode 100644 src/model/connect-routes.js
create mode 100644 src/model/connect-tracks.js
delete mode 100644 src/model/tracks-connection-test.js
diff --git a/src/components/map/object-renderers/IsolationIdRenderer.js b/src/components/map/object-renderers/IsolationIdRenderer.js
index acb6cba..d802f65 100644
--- a/src/components/map/object-renderers/IsolationIdRenderer.js
+++ b/src/components/map/object-renderers/IsolationIdRenderer.js
@@ -19,26 +19,31 @@ export default function IsolationIdRenderer(props) {
className: "isolation-id"
}}
/>}
-
-
+
+
>);
}
function IsolationEndMarker(props) {
- const { object, isStart = false } = props;
+ const { object, end = false } = props;
const isShown = useMemo(() => {
- return object.connections.some(conn =>
- (isStart ? conn.type === TrackConnectionEnd.START : conn.type === TrackConnectionEnd.END) &&
- conn.otherTrack.id_isolation !== object.id_isolation
- );
- }, [object, isStart]);
+ 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 = isStart ? object.points.start : object.points.end;
+ const pos = object.getEndPos(end);
const [x, y] = pos.toSVGCoords();
- const angle = isStart ? object.getStartAngleXZ() : object.getEndAngleXZ();
+ const angle = object.getAngleXZForEnd(end);
const cos = Math.cos(angle);
const sin = Math.sin(angle);
diff --git a/src/helpers/constants.js b/src/helpers/constants.js
index 4785357..e5d2ad4 100644
--- a/src/helpers/constants.js
+++ b/src/helpers/constants.js
@@ -47,6 +47,7 @@ const Constants = {
attachSignsOutOfBounds: true,
signalElemsUnknownSign: true,
signalElemsUnknownSignText: true,
+ connectTracksFailed: true,
routeInvalidSegment: true,
},
errors: {
diff --git a/src/model/connect-routes.js b/src/model/connect-routes.js
deleted file mode 100644
index 12cbadd..0000000
--- a/src/model/connect-routes.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import TrackConnection, {TrackConnectionEnd} from "./track-connection";
-
-export 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.id, TrackConnectionEnd.START));
- });
- });
-}
diff --git a/src/model/connect-tracks.js b/src/model/connect-tracks.js
new file mode 100644
index 0000000..94961de
--- /dev/null
+++ b/src/model/connect-tracks.js
@@ -0,0 +1,83 @@
+import TrackConnection, {TrackConnectionEnd} from "./track-connection";
+import SceneryParserLog from "./scenery-parser-log";
+import Constants from "../helpers/constants";
+
+const connectionThresholdDistanceSq = 0.05;
+
+export function connectTracks(scenery) {
+ _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 _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 ${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 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)}`,
+ );
+ }
+}
diff --git a/src/model/electrification-resolver.js b/src/model/electrification-resolver.js
index 34f0037..9cebb88 100644
--- a/src/model/electrification-resolver.js
+++ b/src/model/electrification-resolver.js
@@ -22,7 +22,7 @@ export default class ElectrificationResolver {
);
});
- this._runResolution(scenery);
+ this._runResolution();
} catch (error) {
scenery.electrificationResolved = ElectrificationResolutionStatus.RESOLVING_ERROR;
SceneryParserLog.warn('electrificationResolverError', `Error resolving electrification: ${error.message}`);
@@ -50,11 +50,10 @@ export default class ElectrificationResolver {
return route.segments[0].tracks.flatMap((routeTrack) => {
return routeTrack.connections
.map((connection) => {
- const otherTrack = scenery.getObject('tracks', connection.otherTrackId);
- if (!otherTrack || otherTrack.type === 'RouteTrack') return null;
- return { track: otherTrack, routeTrack };
+ if (connection.otherTrack.type === 'RouteTrack') return null;
+ return { track: connection.otherTrack, routeTrack };
})
- .filter((adjacent) => adjacent !== null)
+ .filter((adjacent) => adjacent !== null);
});
});
}
@@ -75,10 +74,10 @@ export default class ElectrificationResolver {
});
}
- static _runResolution(scenery) {
+ static _runResolution() {
while(ElectrificationResolver.propagationQueue.length > 0) {
const { track, skipTrackId, status } = ElectrificationResolver.propagationQueue.pop();
- ElectrificationResolver._resolveTrack(scenery, track, skipTrackId, status);
+ ElectrificationResolver._resolveTrack(track, skipTrackId, status);
}
}
@@ -86,7 +85,7 @@ export default class ElectrificationResolver {
ElectrificationResolver.propagationQueue.push({ track, skipTrackId, status });
}
- static _resolveTrack(scenery, track, skipTrackId, status) {
+ static _resolveTrack(track, skipTrackId, status) {
if (track.electrificationStatus === status) return; // already set
if (Constants.parser.skipElectrificationErrorsPropagation && status === ElectrificationStatus.CONFLICT) {
return;
@@ -114,13 +113,9 @@ export default class ElectrificationResolver {
return;
}
- const propagation = track.connections
- .map(conn => scenery.getObject('tracks', conn.otherTrackId))
- .filter((track) => !!track);
-
- propagation.forEach(nextTrack => {
- if (skipTrackId === nextTrack.id) return;
- this._propagate(nextTrack, track.id, track.electrificationStatus);
+ track.connections.forEach(connection => {
+ if (skipTrackId === connection.otherTrackId) return;
+ this._propagate(connection.otherTrack, track.id, track.electrificationStatus);
});
}
};
diff --git a/src/model/route.js b/src/model/route.js
index 522b92b..7136391 100644
--- a/src/model/route.js
+++ b/src/model/route.js
@@ -112,8 +112,8 @@ export default class Route extends SceneryObject {
const newSegment = this.segments[this.segments.length - 1];
newSegment.tracks.forEach((track, index) => {
const prevTrack = prevSegment.tracks[index];
- track.connections.push(new TrackConnection(prevTrack.id, TrackConnectionEnd.START));
- prevTrack.connections.push(new TrackConnection(track.id, TrackConnectionEnd.END));
+ track.connections.push(new TrackConnection(prevTrack, TrackConnectionEnd.START));
+ prevTrack.connections.push(new TrackConnection(track, TrackConnectionEnd.END));
});
}
diff --git a/src/model/scenery-parser.js b/src/model/scenery-parser.js
index 3ce7890..3ca046e 100644
--- a/src/model/scenery-parser.js
+++ b/src/model/scenery-parser.js
@@ -1,5 +1,4 @@
import Scenery from './scenery';
-import { tracksConnectionTest } from './tracks-connection-test';
import Switch from './switch';
import StandardTrack from './tracks/standard-track';
import BezierTrack from './tracks/bezier-track';
@@ -19,7 +18,7 @@ import NEVP from './track-objects/nevp';
import Derailer from './track-objects/derailer';
import SpawnPoint from './track-objects/spawn-point';
import { attachSigns } from './attach-signs';
-import {connectRoutes} from "./connect-routes";
+import {connectTracks} from "./connect-tracks";
/*
TODO: add support for WorldRotation and WorldTranslation
@@ -58,9 +57,8 @@ export default class SceneryParser {
});
scenery.applyObjects();
- connectRoutes(scenery);
+ connectTracks(scenery);
- if(Constants.parser.runTracksConnectionTest) tracksConnectionTest(scenery);
if(Constants.parser.resolveElectrification) ElectrificationResolver.resolveScenery(scenery);
if(Constants.parser.attachSigns) attachSigns(scenery);
if(Constants.parser.logSceneryAfterFinished) console.log(scenery);
diff --git a/src/model/track-connection.js b/src/model/track-connection.js
index 2949714..4f21817 100644
--- a/src/model/track-connection.js
+++ b/src/model/track-connection.js
@@ -5,11 +5,21 @@ export const TrackConnectionEnd = {
export default class TrackConnection {
otherTrackId;
+ otherTrack = null;
end;
- constructor(otherTrackId, end) {
- Object.assign(this, {
- otherTrackId, end,
- });
+ /**
+ * If only an ID is passed as `otherTrack`,
+ * it needs to be resolved later in the `connectTracks` function.
+ */
+ constructor(otherTrack, end) {
+ this.end = end;
+ if (typeof otherTrack === 'string') {
+ this.otherTrackId = otherTrack;
+ this.otherTrack = null;
+ } else {
+ this.otherTrackId = otherTrack.id;
+ this.otherTrack = otherTrack;
+ }
}
}
diff --git a/src/model/tracks-connection-test.js b/src/model/tracks-connection-test.js
deleted file mode 100644
index 87fb2a7..0000000
--- a/src/model/tracks-connection-test.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import SceneryParserLog from './scenery-parser-log';
-import {TrackConnectionEnd} from "./track-connection";
-
-const connectionThresholdDistanceSq = 0.05;
-
-function trackEndConnectionTest(scenery, track, connection) {
- const otherTrack = scenery.getObject('tracks', connection.otherTrackId);
- if (!otherTrack) {
- SceneryParserLog.warn('tracksConnectionTest', `Track ${track.id} has a non-existing ${connection.end === TrackConnectionEnd.START ? 'previous' : 'next'} track: ${connection.otherTrackId}`);
- return;
- }
-
- const reverseConnections = otherTrack.connections.filter(
- (otherConnection) => otherConnection.otherTrackId === track.id,
- );
- if (reverseConnections.length > 1) {
- SceneryParserLog.warn(
- 'tracksConnectionTest',
- `Track ${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 reverseConnection = reverseConnections[0];
-
- const ownPosition = track.getEndPos(connection.end);
- const otherPosition = 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)}`,
- );
- }
-}
-
-export function tracksConnectionTest(scenery) {
- const tracks = scenery.objects['tracks'] || [];
-
- Object.values(tracks).forEach(track => {
- track.connections.forEach((connection) => {
- trackEndConnectionTest(scenery, track, connection);
- });
- });
-}
diff --git a/src/model/tracks/track.js b/src/model/tracks/track.js
index 71c35ca..711517e 100644
--- a/src/model/tracks/track.js
+++ b/src/model/tracks/track.js
@@ -41,6 +41,11 @@ 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);
}
From b137af2843d62d5b3699b766f61e60bdad74a4c7 Mon Sep 17 00:00:00 2001
From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com>
Date: Sat, 12 Jul 2025 18:28:57 +0200
Subject: [PATCH 15/24] Don't mark route tracks as derailing tracks
---
src/components/map/object-renderers/TrackRenderer.js | 3 ++-
src/helpers/constants.js | 1 +
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/components/map/object-renderers/TrackRenderer.js b/src/components/map/object-renderers/TrackRenderer.js
index c027890..b9fa747 100644
--- a/src/components/map/object-renderers/TrackRenderer.js
+++ b/src/components/map/object-renderers/TrackRenderer.js
@@ -130,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);
diff --git a/src/helpers/constants.js b/src/helpers/constants.js
index e5d2ad4..4e0f6f7 100644
--- a/src/helpers/constants.js
+++ b/src/helpers/constants.js
@@ -167,6 +167,7 @@ const Constants = {
},
options: {
'derail': ['#f22', 'Derail track'],
+ 'unknown': ['#aaa', 'Unknown speed'],
}
}
}
From 5546601b67094544aab0097892bc18d36f0b7758 Mon Sep 17 00:00:00 2001
From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com>
Date: Sat, 12 Jul 2025 19:07:45 +0200
Subject: [PATCH 16/24] Implement category application order
---
src/model/scenery.js | 30 +++++++++++++++++++++++-------
1 file changed, 23 insertions(+), 7 deletions(-)
diff --git a/src/model/scenery.js b/src/model/scenery.js
index 5a4eb40..0a79104 100644
--- a/src/model/scenery.js
+++ b/src/model/scenery.js
@@ -36,12 +36,29 @@ export default class Scenery
}
applyObjects() {
+ const categories_order = [
+ 'switches', 'routes', // switches and routes create tracks
+ 'tracks', // tracks create isolation ids
+ 'isolation-ids',
+ '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);
+ }
});
}
@@ -58,7 +75,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;
- }
- );
+ });
}
}
From 3ae1827d10fd4b5f3e6a672657536b32f48766aa Mon Sep 17 00:00:00 2001
From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com>
Date: Sat, 12 Jul 2025 19:58:35 +0200
Subject: [PATCH 17/24] Create IsolationId class, split IsolationEndRenderer
and IsolationIdRenderer
---
src/components/map/ObjectsRenderQueue.js | 11 +++-
.../object-renderers/IsolationEndRenderer.js | 40 +++++++++++++
.../object-renderers/IsolationIdRenderer.js | 58 ++++---------------
src/model/isolation-id.js | 15 +++++
.../switch-descriptions/switch-prefab.js | 13 +++--
src/model/switch.js | 6 ++
src/model/tracks/bezier-track.js | 24 +++++++-
src/model/tracks/standard-track.js | 21 +++++--
src/model/tracks/track.js | 1 -
9 files changed, 128 insertions(+), 61 deletions(-)
create mode 100644 src/components/map/object-renderers/IsolationEndRenderer.js
create mode 100644 src/model/isolation-id.js
diff --git a/src/components/map/ObjectsRenderQueue.js b/src/components/map/ObjectsRenderQueue.js
index a4f84bf..8f98b70 100644
--- a/src/components/map/ObjectsRenderQueue.js
+++ b/src/components/map/ObjectsRenderQueue.js
@@ -10,6 +10,7 @@ import NEVPRenderer from './object-renderers/NEVPRenderer';
import DerailerRenderer from './object-renderers/DerailerRenderer';
import ElectrificationStatusPopup from './additional-layer-components/ElectrificationStatusPopup';
import SpawnPointRenderer from './object-renderers/SpawnPointRenderer';
+import IsolationEndRenderer from "./object-renderers/IsolationEndRenderer";
const ObjectRendererQueue = [
{
@@ -23,8 +24,14 @@ const ObjectRendererQueue = [
'pointerEvents': true
},
{
- 'name': 'isolations-ids',
+ 'name': 'isolations-ends',
'category': 'tracks',
+ 'renderer': IsolationEndRenderer,
+ 'cond': (layers) => layers['isolations-ids']
+ },
+ {
+ 'name': 'isolations-ids',
+ 'category': 'isolation-ids',
'renderer': IsolationIdRenderer,
'cond': (layers) => layers['isolations-ids']
},
@@ -92,4 +99,4 @@ const ObjectRendererQueue = [
}
]
-export default ObjectRendererQueue;
\ No newline at end of file
+export default ObjectRendererQueue;
diff --git a/src/components/map/object-renderers/IsolationEndRenderer.js b/src/components/map/object-renderers/IsolationEndRenderer.js
new file mode 100644
index 0000000..12e3e5e
--- /dev/null
+++ b/src/components/map/object-renderers/IsolationEndRenderer.js
@@ -0,0 +1,40 @@
+import { useMemo } from "react";
+import { TrackConnectionEnd } from "../../../model/track-connection";
+
+export default function IsolationEndRenderer(props) {
+ const { object } = props;
+ return (<>
+
+
+ >);
+}
+
+function IsolationEndMarker(props) {
+ const { object, end = false } = props;
+
+ const isShown = useMemo(() => {
+ return object.connections.some((conn) => {
+ const isCorrectEnd = conn.end === end;
+ const isolationIdChanged = conn.otherTrack.id_isolation !== object.id_isolation;
+
+ // Prevent the marker from showing twice for both tracks
+ const isPreferred = object.id < conn.otherTrackId;
+
+ return isCorrectEnd && isolationIdChanged && isPreferred;
+ });
+ }, [object, end]);
+
+ if (!isShown) return null;
+
+ const pos = object.getEndPos(end);
+ const [x, y] = pos.toSVGCoords();
+ const angle = object.getAngleXZForEnd(end);
+
+ const cos = Math.cos(angle);
+ const sin = Math.sin(angle);
+
+ return
+}
diff --git a/src/components/map/object-renderers/IsolationIdRenderer.js b/src/components/map/object-renderers/IsolationIdRenderer.js
index d802f65..881585b 100644
--- a/src/components/map/object-renderers/IsolationIdRenderer.js
+++ b/src/components/map/object-renderers/IsolationIdRenderer.js
@@ -1,55 +1,17 @@
-import { useMemo } from "react";
import SimpleLabelText from "../text/SimpleLabelText";
-import { TrackConnectionEnd } 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 [x, y] = object.pos.toSVGCoords();
const text = object.id_isolation ?? '';
+ if (!text) return null;
- const showText = (!object.hide_isolation && !!text);
-
- return (<>
- {showText && }
-
-
- >);
-}
-
-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
+ return ();
}
diff --git a/src/model/isolation-id.js b/src/model/isolation-id.js
new file mode 100644
index 0000000..feefa5d
--- /dev/null
+++ b/src/model/isolation-id.js
@@ -0,0 +1,15 @@
+import SceneryObject from "./scenery-object";
+import Vector3 from "./vector3";
+
+export default class IsolationId extends SceneryObject {
+ category = "isolation-ids";
+ type = "IsolationId";
+ id_isolation;
+
+ constructor(object_type, object_id, pos, id_isolation) {
+ super(`${object_type}:${object_id}`, pos, Vector3.zero());
+ Object.assign(this, {
+ id_isolation,
+ });
+ }
+}
diff --git a/src/model/switch-descriptions/switch-prefab.js b/src/model/switch-descriptions/switch-prefab.js
index 6d2f623..3bfa580 100644
--- a/src/model/switch-descriptions/switch-prefab.js
+++ b/src/model/switch-descriptions/switch-prefab.js
@@ -5,10 +5,12 @@ import {TrackConnectionEnd} from "../track-connection";
export default class SwitchPrefab {
tracks = {};
+ isolation_id_offset;
- constructor(tracks) {
+ constructor(tracks, isolation_id_offset) {
Object.assign(this, {
tracks,
+ isolation_id_offset,
});
this._verifyConnections();
}
@@ -89,7 +91,10 @@ export default class SwitchPrefab {
),
};
- return new SwitchPrefab(tracks);
+ 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) {
@@ -215,7 +220,7 @@ export default class SwitchPrefab {
),
};
- return new SwitchPrefab(tracks);
+ return new SwitchPrefab(tracks, Vector3.zero());
}
static slip(totalLength, outerLength, transitionLength, radius, tangentInv, leftSlipEnabled, rightSlipEnabled) {
@@ -457,6 +462,6 @@ export default class SwitchPrefab {
addSideConnection('left', 'a', 'b', leftSlipEnabled);
addSideConnection('right', 'b', 'a', rightSlipEnabled);
- return new SwitchPrefab(tracks);
+ return new SwitchPrefab(tracks, Vector3.zero());
}
}
diff --git a/src/model/switch.js b/src/model/switch.js
index 0950a52..27412e6 100644
--- a/src/model/switch.js
+++ b/src/model/switch.js
@@ -5,6 +5,7 @@ import Vector3 from "./vector3.js";
import DefinedSwitches from "./defs/defined-switches.js";
import {SwitchTrackConnectionType} from "./switch-descriptions/switch-prefab-track";
import TrackConnection, {TrackConnectionEnd} from "./track-connection";
+import IsolationId from "./isolation-id";
export default class Switch extends SceneryObject {
model;
@@ -84,6 +85,11 @@ export default class Switch extends SceneryObject {
this.tracks = tracks;
this.def = def;
+
+ const rotRad = this.rot.multiply(Math.PI / 180);
+ const isolationIdPos = this.pos.add(def.isolation_id_offset.rotate(rotRad));
+ super.applyObject(scenery);
+ scenery.addObject(new IsolationId(this.category, this.id, isolationIdPos, this.id_isolation));
}
_getTrackIds() {
diff --git a/src/model/tracks/bezier-track.js b/src/model/tracks/bezier-track.js
index ff79db7..96b9cc2 100644
--- a/src/model/tracks/bezier-track.js
+++ b/src/model/tracks/bezier-track.js
@@ -1,6 +1,7 @@
import Track from "./track";
import Vector3 from "../vector3";
import TrackConnection, {TrackConnectionEnd} from "../track-connection";
+import IsolationId from "../isolation-id";
export default class BezierTrack extends Track
{
@@ -9,7 +10,8 @@ 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, connections, id_station, start_slope, end_slope, id_isolation, prefab_name, maxspeed, derailspeed) {
@@ -19,8 +21,21 @@ export default class BezierTrack extends Track
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() {
@@ -59,4 +74,9 @@ export default class BezierTrack extends Track
return track;
}
+
+ applyObject(scenery) {
+ super.applyObject(scenery);
+ scenery.addObject(new IsolationId(this.category, this.id, this.points.middle, this.id_isolation));
+ }
}
diff --git a/src/model/tracks/standard-track.js b/src/model/tracks/standard-track.js
index 81cc799..2d0d263 100644
--- a/src/model/tracks/standard-track.js
+++ b/src/model/tracks/standard-track.js
@@ -2,6 +2,7 @@ import AngleHelper from "../../helpers/angleHelper";
import Track from "./track";
import Vector3 from "../vector3";
import TrackConnection, {TrackConnectionEnd} from "../track-connection";
+import IsolationId from "../isolation-id";
export default class StandardTrack extends Track
{
@@ -9,7 +10,8 @@ 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, connections, id_station, start_slope, end_slope, id_isolation, prefab_name, maxspeed, derailspeed) {
@@ -60,12 +62,18 @@ 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));
}
@@ -78,4 +86,9 @@ export default class StandardTrack extends Track
this.points.end.y += startHeightDiff + (endHeightDiff - startHeightDiff) / 2;
}
}
+
+ applyObject(scenery) {
+ super.applyObject(scenery);
+ scenery.addObject(new IsolationId(this.category, this.id, this.points.middle, this.id_isolation));
+ }
}
diff --git a/src/model/tracks/track.js b/src/model/tracks/track.js
index 711517e..926425b 100644
--- a/src/model/tracks/track.js
+++ b/src/model/tracks/track.js
@@ -15,7 +15,6 @@ export default class Track extends SceneryObject {
start_slope;
end_slope;
prefab_name;
- hide_isolation = false;
switch = null;
electrificationStatus = ElectrificationStatus.NOT_CHECKED;
hasNEVP = false;
From 7d36835b28bf7dae519382fc526ef8d08cecef42 Mon Sep 17 00:00:00 2001
From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com>
Date: Sat, 12 Jul 2025 21:43:26 +0200
Subject: [PATCH 18/24] Don't remove route tracks in scenery processor script
---
tools/scp.py | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/tools/scp.py b/tools/scp.py
index bbd814a..8cbc5c5 100644
--- a/tools/scp.py
+++ b/tools/scp.py
@@ -7,22 +7,24 @@ import sys
# v1.4
#
-BAD_WORDS = ['Empty,Empty', 'Forest Start,Forest Start', 'EndMiscGroup', 'Fence', 'TerrainPoint', 'Wires']
+BAD_WORDS = ['EndMiscGroup', 'Fence', 'TerrainPoint', 'Wires']
def should_exclude(line: str) -> bool:
for word in BAD_WORDS:
if line.startswith(word):
return True
-
+
if line.startswith("Misc"):
values = line.split(';')
return not values[2].startswith("SignalBox")
+ return False
+
def process_file(file_path: str):
if not file_path.endswith(".sc"):
print(f"Skipping non-.sc file: {file_path}")
return
-
+
if file_path.endswith(".lite.sc"):
print(f"Skipping already processed file: {file_path}")
return
From b31277e614c7214fc4f05c55d8a24c8f8a8b0fdb Mon Sep 17 00:00:00 2001
From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com>
Date: Sun, 13 Jul 2025 21:28:17 +0200
Subject: [PATCH 19/24] Fix undefined reference in connect-tracks.js
---
src/model/connect-tracks.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/model/connect-tracks.js b/src/model/connect-tracks.js
index 94961de..7a0a22c 100644
--- a/src/model/connect-tracks.js
+++ b/src/model/connect-tracks.js
@@ -59,7 +59,7 @@ function _checkConnection(scenery, track, connection) {
if (reverseConnections.length > 1) {
SceneryParserLog.warn(
'tracksConnectionTest',
- `Track ${otherTrack.id} has multiple connections to ${track.id}`,
+ `Track ${connection.otherTrack.id} has multiple connections to ${track.id}`,
);
}
if (reverseConnections.length === 0) {
From d394e843d7ed6b83811d889cedb0ac63ead7debd Mon Sep 17 00:00:00 2001
From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com>
Date: Sun, 13 Jul 2025 21:30:28 +0200
Subject: [PATCH 20/24] Revert showing warning for the empty object type
---
src/model/scenery-parser.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/model/scenery-parser.js b/src/model/scenery-parser.js
index 3ca046e..77136ea 100644
--- a/src/model/scenery-parser.js
+++ b/src/model/scenery-parser.js
@@ -83,7 +83,7 @@ export default class SceneryParser {
static _parseObject(text) {
const type = text.split(";", 2)[0];
- if (type === '' || type === 'EndRoute' || 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;
}
@@ -115,6 +115,7 @@ export default class SceneryParser {
case 'SSPController':
case 'TerrainGroup':
case 'EndTerrainGroup':
+ case '':
return null;
default:
SceneryParserLog.warn('unknownObjectType', `Unknown object type: ${type}`);
From b0e0c85c6aef978bb9ff5c603a0d63f1551670ff Mon Sep 17 00:00:00 2001
From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com>
Date: Sun, 13 Jul 2025 21:43:26 +0200
Subject: [PATCH 21/24] Remove unused maxRouteConnectionDistance config field
---
src/helpers/constants.js | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/helpers/constants.js b/src/helpers/constants.js
index 4e0f6f7..47b9d03 100644
--- a/src/helpers/constants.js
+++ b/src/helpers/constants.js
@@ -14,7 +14,6 @@ const Constants = {
runTracksConnectionTest: false,
resolveElectrification: true,
skipElectrificationErrorsPropagation: false,
- maxRouteConnectionDistance: 0.2,
attachSigns: true,
attachSignsNeedsSameTrack: false,
attachSignsMaxDistanceZ: 1.0,
From 94554d5200142cee11e1fc09d57d3475506471e3 Mon Sep 17 00:00:00 2001
From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com>
Date: Sun, 13 Jul 2025 22:38:11 +0200
Subject: [PATCH 22/24] Remove isolation-id objects from model and modify
isolation id renderer
---
src/components/map/ObjectsRenderQueue.js | 5 +++--
src/components/map/SceneryLayer.js | 22 +++++++++++--------
.../object-renderers/IsolationIdRenderer.js | 5 ++++-
src/model/isolation-id.js | 15 -------------
src/model/scenery.js | 3 +--
src/model/switch.js | 5 ++---
src/model/tracks/bezier-track.js | 2 --
src/model/tracks/standard-track.js | 2 --
8 files changed, 23 insertions(+), 36 deletions(-)
delete mode 100644 src/model/isolation-id.js
diff --git a/src/components/map/ObjectsRenderQueue.js b/src/components/map/ObjectsRenderQueue.js
index 8f98b70..738951c 100644
--- a/src/components/map/ObjectsRenderQueue.js
+++ b/src/components/map/ObjectsRenderQueue.js
@@ -10,7 +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";
+import IsolationEndRenderer from './object-renderers/IsolationEndRenderer';
const ObjectRendererQueue = [
{
@@ -31,7 +31,8 @@ const ObjectRendererQueue = [
},
{
'name': 'isolations-ids',
- 'category': 'isolation-ids',
+ 'category': ['tracks', 'switches'],
+ 'types': ['StandardTrack', 'BezierTrack', 'Switch'],
'renderer': IsolationIdRenderer,
'cond': (layers) => layers['isolations-ids']
},
diff --git a/src/components/map/SceneryLayer.js b/src/components/map/SceneryLayer.js
index 5ac1b08..3ea50f5 100644
--- a/src/components/map/SceneryLayer.js
+++ b/src/components/map/SceneryLayer.js
@@ -8,19 +8,23 @@ 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 categoryList = typeof category === 'string' ? [category] : category;
+ return categoryList.flatMap((category) => {
+ const categoryObjs = scenery.objects[category];
+ if (!categoryObjs) return [];
+ return Object.values(categoryObjs);
+ });
}, [scenery, category, isVisible]);
-
+
if (!scenery || !isVisible) return null;
const pointerEvents = Constants.map.forcePointerEvents || queueItem.pointerEvents || false;
-
+
return (
);
}
-
+
const MemoizedSceneryLayer = memo(StatelessSceneryLayer);
function StatelessSceneryLayer({ name, Renderer, objects, type, types, pointerEvents = false, AdditionalComponents = [] }) {
return (
-
@@ -53,4 +57,4 @@ function StatelessSceneryLayer({ name, Renderer, objects, type, types, pointerEv
))}
);
-}
\ No newline at end of file
+}
diff --git a/src/components/map/object-renderers/IsolationIdRenderer.js b/src/components/map/object-renderers/IsolationIdRenderer.js
index 881585b..f1be890 100644
--- a/src/components/map/object-renderers/IsolationIdRenderer.js
+++ b/src/components/map/object-renderers/IsolationIdRenderer.js
@@ -2,10 +2,13 @@ import SimpleLabelText from "../text/SimpleLabelText";
export default function IsolationIdRenderer(props) {
const { object } = props;
- const [x, y] = object.pos.toSVGCoords();
+
const text = object.id_isolation ?? '';
if (!text) return null;
+ let pos = (object.type === 'Switch' ? object.isolation_id_pos : object.points.middle) ?? object.pos;
+ const [x, y] = pos.toSVGCoords();
+
return (
Date: Sun, 13 Jul 2025 22:58:55 +0200
Subject: [PATCH 23/24] Place route name label at the start of route
---
src/components/map/Map.css | 4 +-
.../map/object-renderers/RouteRenderer.js | 37 +++++++++++--------
src/model/route.js | 5 ++-
3 files changed, 26 insertions(+), 20 deletions(-)
diff --git a/src/components/map/Map.css b/src/components/map/Map.css
index 7c95820..3e72537 100644
--- a/src/components/map/Map.css
+++ b/src/components/map/Map.css
@@ -63,11 +63,11 @@
fill: #111;
}
-.map svg g.route g {
+.map svg g.route-arrow {
fill: #eee;
}
-.map svg g.route text {
+.map svg g.route-name text {
fill: #eee;
font-size: 72mm;
}
diff --git a/src/components/map/object-renderers/RouteRenderer.js b/src/components/map/object-renderers/RouteRenderer.js
index a208bb9..e42de3c 100644
--- a/src/components/map/object-renderers/RouteRenderer.js
+++ b/src/components/map/object-renderers/RouteRenderer.js
@@ -1,28 +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.end_center.toSVGCoords();
- const angle = object.end_angle_rad * 180 / Math.PI;
+
+ const [startX, startY] = object.pos.toSVGCoords();
+ const startAngle = object.rot.y;
+
+ const [endX, endY] = object.end_center.toSVGCoords();
+ const endAngle = AngleHelper.radToDeg(object.end_angle_rad);
const offY = object.track_count === 2 ? -22 : -20;
const arrowOffset = - object.track_offset - 37.8;
- return (
-
-
- {
- svg.setAttribute('width', '20mm');
- svg.setAttribute('height', '20mm');
- }}
- />
-
+ return (<>
+
+ {
+ svg.setAttribute('width', '20mm');
+ svg.setAttribute('height', '20mm');
+ }}
+ />
+
+
- );
+ >);
}
diff --git a/src/model/route.js b/src/model/route.js
index 7136391..c3a4324 100644
--- a/src/model/route.js
+++ b/src/model/route.js
@@ -3,6 +3,7 @@ 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;
@@ -57,7 +58,7 @@ export default class Route extends SceneryObject {
return;
}
const startAngleRad = this.end_angle_rad;
- const start_rot = new Vector3(0, startAngleRad * 180 / Math.PI, 0);
+ const startRot = new Vector3(0, AngleHelper.radToDeg(startAngleRad), 0);
const startPoints = this.end_points;
this.end_points = [];
@@ -98,7 +99,7 @@ export default class Route extends SceneryObject {
trackId,
startPoints[index],
this.end_points[index],
- start_rot,
+ startRot,
lengths[index],
radii[index],
[], // connections
From 55312b48ae225237d70d2df6cbcb9bdb633fffe3 Mon Sep 17 00:00:00 2001
From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com>
Date: Sun, 13 Jul 2025 23:06:09 +0200
Subject: [PATCH 24/24] Don't use flapMap for a single category in SceneryLayer
---
src/components/map/SceneryLayer.js | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/src/components/map/SceneryLayer.js b/src/components/map/SceneryLayer.js
index 3ea50f5..ee26395 100644
--- a/src/components/map/SceneryLayer.js
+++ b/src/components/map/SceneryLayer.js
@@ -13,12 +13,15 @@ export default function SceneryLayer(props) {
const objects = useMemo(() => {
if (!scenery || !isVisible) return [];
- const categoryList = typeof category === 'string' ? [category] : category;
- return categoryList.flatMap((category) => {
+
+ 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;