Create IsolationId class, split IsolationEndRenderer and IsolationIdRenderer

This commit is contained in:
dominik-korsa 2025-07-12 19:58:35 +02:00
parent 5546601b67
commit 3ae1827d10
No known key found for this signature in database
GPG Key ID: 5A24D76EE0A30974
9 changed files with 128 additions and 61 deletions

View File

@ -10,6 +10,7 @@ import NEVPRenderer from './object-renderers/NEVPRenderer';
import DerailerRenderer from './object-renderers/DerailerRenderer'; import DerailerRenderer from './object-renderers/DerailerRenderer';
import ElectrificationStatusPopup from './additional-layer-components/ElectrificationStatusPopup'; import ElectrificationStatusPopup from './additional-layer-components/ElectrificationStatusPopup';
import SpawnPointRenderer from './object-renderers/SpawnPointRenderer'; import SpawnPointRenderer from './object-renderers/SpawnPointRenderer';
import IsolationEndRenderer from "./object-renderers/IsolationEndRenderer";
const ObjectRendererQueue = [ const ObjectRendererQueue = [
{ {
@ -23,8 +24,14 @@ const ObjectRendererQueue = [
'pointerEvents': true 'pointerEvents': true
}, },
{ {
'name': 'isolations-ids', 'name': 'isolations-ends',
'category': 'tracks', 'category': 'tracks',
'renderer': IsolationEndRenderer,
'cond': (layers) => layers['isolations-ids']
},
{
'name': 'isolations-ids',
'category': 'isolation-ids',
'renderer': IsolationIdRenderer, 'renderer': IsolationIdRenderer,
'cond': (layers) => layers['isolations-ids'] 'cond': (layers) => layers['isolations-ids']
}, },

View File

@ -0,0 +1,40 @@
import { useMemo } from "react";
import { TrackConnectionEnd } from "../../../model/track-connection";
export default function IsolationEndRenderer(props) {
const { object } = props;
return (<>
<IsolationEndMarker object={object} end={TrackConnectionEnd.START} />
<IsolationEndMarker object={object} end={TrackConnectionEnd.END} />
</>);
}
function IsolationEndMarker(props) {
const { object, end = false } = props;
const isShown = useMemo(() => {
return object.connections.some((conn) => {
const isCorrectEnd = conn.end === end;
const isolationIdChanged = conn.otherTrack.id_isolation !== object.id_isolation;
// Prevent the marker from showing twice for both tracks
const isPreferred = object.id < conn.otherTrackId;
return isCorrectEnd && isolationIdChanged && isPreferred;
});
}, [object, end]);
if (!isShown) return null;
const pos = object.getEndPos(end);
const [x, y] = pos.toSVGCoords();
const angle = object.getAngleXZForEnd(end);
const cos = Math.cos(angle);
const sin = Math.sin(angle);
return <path
className="isolation-end-marker"
d={`M ${x - 1.5 * cos} ${y - 1.5 * sin} L ${x + 1.5 * cos} ${y + 1.5 * sin}`}
/>
}

View File

@ -1,55 +1,17 @@
import { useMemo } from "react";
import SimpleLabelText from "../text/SimpleLabelText"; import SimpleLabelText from "../text/SimpleLabelText";
import { TrackConnectionEnd } from "../../../model/track-connection";
export default function IsolationIdRenderer(props) { export default function IsolationIdRenderer(props) {
const { object } = props; const { object } = props;
const pos = object.points.start.lerp(object.points.end, 0.5); const [x, y] = object.pos.toSVGCoords();
const [x, y] = pos.toSVGCoords();
const text = object.id_isolation ?? ''; const text = object.id_isolation ?? '';
if (!text) return null;
const showText = (!object.hide_isolation && !!text); return (<SimpleLabelText
text={text}
return (<> x={x}
{showText && <SimpleLabelText y={y}
text={text} textProps={{
x={x} className: "isolation-id"
y={y} }}
textProps={{ />);
className: "isolation-id"
}}
/>}
<IsolationEndMarker object={object} end={TrackConnectionEnd.START} />
<IsolationEndMarker object={object} end={TrackConnectionEnd.END} />
</>);
}
function IsolationEndMarker(props) {
const { object, end = false } = props;
const isShown = useMemo(() => {
return object.connections.some((conn) => {
const isCorrectEnd = conn.end === end;
const isolationIdChanged = conn.otherTrack.id_isolation !== object.id_isolation;
// Prevent the marker from showing twice for both tracks
const isPreferred = object.id < conn.otherTrackId;
return isCorrectEnd && isolationIdChanged && isPreferred;
});
}, [object, end]);
if (!isShown) return null;
const pos = object.getEndPos(end);
const [x, y] = pos.toSVGCoords();
const angle = object.getAngleXZForEnd(end);
const cos = Math.cos(angle);
const sin = Math.sin(angle);
return <path
className="isolation-end-marker"
d={`M ${x - 1.5 * cos} ${y - 1.5 * sin} L ${x + 1.5 * cos} ${y + 1.5 * sin}`}
/>
} }

15
src/model/isolation-id.js Normal file
View File

@ -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,
});
}
}

View File

@ -5,10 +5,12 @@ import {TrackConnectionEnd} from "../track-connection";
export default class SwitchPrefab { export default class SwitchPrefab {
tracks = {}; tracks = {};
isolation_id_offset;
constructor(tracks) { constructor(tracks, isolation_id_offset) {
Object.assign(this, { Object.assign(this, {
tracks, tracks,
isolation_id_offset,
}); });
this._verifyConnections(); 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) { 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) { static slip(totalLength, outerLength, transitionLength, radius, tangentInv, leftSlipEnabled, rightSlipEnabled) {
@ -457,6 +462,6 @@ export default class SwitchPrefab {
addSideConnection('left', 'a', 'b', leftSlipEnabled); addSideConnection('left', 'a', 'b', leftSlipEnabled);
addSideConnection('right', 'b', 'a', rightSlipEnabled); addSideConnection('right', 'b', 'a', rightSlipEnabled);
return new SwitchPrefab(tracks); return new SwitchPrefab(tracks, Vector3.zero());
} }
} }

View File

@ -5,6 +5,7 @@ import Vector3 from "./vector3.js";
import DefinedSwitches from "./defs/defined-switches.js"; import DefinedSwitches from "./defs/defined-switches.js";
import {SwitchTrackConnectionType} from "./switch-descriptions/switch-prefab-track"; import {SwitchTrackConnectionType} from "./switch-descriptions/switch-prefab-track";
import TrackConnection, {TrackConnectionEnd} from "./track-connection"; import TrackConnection, {TrackConnectionEnd} from "./track-connection";
import IsolationId from "./isolation-id";
export default class Switch extends SceneryObject { export default class Switch extends SceneryObject {
model; model;
@ -84,6 +85,11 @@ export default class Switch extends SceneryObject {
this.tracks = tracks; this.tracks = tracks;
this.def = def; 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() { _getTrackIds() {

View File

@ -1,6 +1,7 @@
import Track from "./track"; import Track from "./track";
import Vector3 from "../vector3"; import Vector3 from "../vector3";
import TrackConnection, {TrackConnectionEnd} from "../track-connection"; import TrackConnection, {TrackConnectionEnd} from "../track-connection";
import IsolationId from "../isolation-id";
export default class BezierTrack extends Track export default class BezierTrack extends Track
{ {
@ -9,7 +10,8 @@ export default class BezierTrack extends Track
start: Vector3.zero(), start: Vector3.zero(),
control1: Vector3.zero(), control1: Vector3.zero(),
end: 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) { 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, start,
control1: start.add(control1), control1: start.add(control1),
end, 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() { getStartAngleXZ() {
@ -59,4 +74,9 @@ export default class BezierTrack extends Track
return track; return track;
} }
applyObject(scenery) {
super.applyObject(scenery);
scenery.addObject(new IsolationId(this.category, this.id, this.points.middle, this.id_isolation));
}
} }

View File

@ -2,6 +2,7 @@ import AngleHelper from "../../helpers/angleHelper";
import Track from "./track"; import Track from "./track";
import Vector3 from "../vector3"; import Vector3 from "../vector3";
import TrackConnection, {TrackConnectionEnd} from "../track-connection"; import TrackConnection, {TrackConnectionEnd} from "../track-connection";
import IsolationId from "../isolation-id";
export default class StandardTrack extends Track export default class StandardTrack extends Track
{ {
@ -9,7 +10,8 @@ export default class StandardTrack extends Track
points = { points = {
start: Vector3.zero(), start: Vector3.zero(),
end: 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) { 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); const rotRad = AngleHelper.degToRad(this.rot.y);
this.points.start = this.pos.clone(); 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; const centerAngle = rotRad + Math.PI / 2;
this.points.circleCenter = this.pos.sub(Vector3.fromAngleY(centerAngle, this.r)); 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; const endAngle = centerAngle - this.len / this.r;
this.points.end = this.points.circleCenter.add(Vector3.fromAngleY(endAngle, 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; 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));
}
} }

View File

@ -15,7 +15,6 @@ export default class Track extends SceneryObject {
start_slope; start_slope;
end_slope; end_slope;
prefab_name; prefab_name;
hide_isolation = false;
switch = null; switch = null;
electrificationStatus = ElectrificationStatus.NOT_CHECKED; electrificationStatus = ElectrificationStatus.NOT_CHECKED;
hasNEVP = false; hasNEVP = false;