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;