Added Vector3 class, code cleanup

+Added Vector3 class and replaced all position and angle values with it to clean up the code
*Minor bugfixes and code cleanup
This commit is contained in:
izawartka 2025-06-01 01:54:26 +02:00
parent b952b9c26d
commit b76752e40d
16 changed files with 206 additions and 194 deletions

View File

@ -1,18 +1,13 @@
export default function IsolationIdRenderer(props) {
const { object } = props;
const x1 = object.points?.x1;
const z1 = -object.points?.z1;
const x2 = object.points?.x2 ?? x1;
const z2 = -object.points?.z2 ?? z1;
const x = (x1 + x2) / 2;
const z = (z1 + z2) / 2;
const pos = object.points.start.lerp(object.points.end, 0.5);
const [x, y] = pos.toSVGCoords();
const text = object.id_isolation ?? '';
return (
<text
x={x}
y={z}
y={y}
textAnchor='middle'
id={`isolation-id-${object.id}`}
style={{ userSelect: "none" }}

View File

@ -3,12 +3,10 @@ import { ReactSVG } from "react-svg";
export default function RouteRenderer(props) {
const { object } = props;
const x = object.x;
const z = -object.z;
const [x, y] = object.pos.toSVGCoords();
const upsideDown = useMemo(() => {
let rot = object.ry;
let rot = object.rot.y;
if (rot < 0) {
rot += 360 * Math.ceil(Math.abs(rot) / 360);
} else if (rot >= 360) {
@ -16,7 +14,7 @@ export default function RouteRenderer(props) {
}
return rot > 180;
}, [object.ry]);
}, [object.rot.y]);
const upsideDownRot = upsideDown ? 90 : -90;
const offY = object.track_count === 2 ? -22 : -20;
@ -24,7 +22,7 @@ export default function RouteRenderer(props) {
const arrowOffset = - object.track_offset - 37.8;
return (
<g className="route" transform={`translate(${x}, ${z}) rotate(${object.ry})`}>
<g className="route" transform={`translate(${x}, ${y}) rotate(${object.rot.y})`}>
<g transform={`rotate(90) translate(-80, ${arrowOffset})`}>
<ReactSVG
src="/assets/route.svg"

View File

@ -2,13 +2,11 @@ import { ReactSVG } from 'react-svg'
export default function SignalRenderer(props) {
const { object } = props;
const x = object.x;
const z = -object.z;
const [x, y] = object.pos.toSVGCoords();
return (
<g className="signal" transform={`translate(${x}, ${z})`}>
<g transform={`rotate(${object.ry}) translate(-2, -2)`}>
<g className="signal" transform={`translate(${x}, ${y})`}>
<g transform={`rotate(${object.rot.y}) translate(-2, -2)`}>
<ReactSVG
src="/assets/signal.svg"
wrapper='svg'

View File

@ -1,14 +1,12 @@
export default function SwitchNameRenderer(props) {
const { object } = props;
const x = object.x;
const z = -object.z;
const [x, y] = object.pos.toSVGCoords();
const text = object.id_switch;
return (
<text
x={x}
y={z}
y={y}
textAnchor='middle'
id={`switch-${object.id}`}
style={{ userSelect: "none" }}

View File

@ -1,20 +1,18 @@
export default function TrackObjectRenderer(props) {
const { object } = props;
const x = object.x;
const z = -object.z;
const [x, y] = object.pos.toSVGCoords();
const text = `${object.prefab_name};${object.name}`
return (
<g className="track-object">
<circle
cx={x}
cy={z}
cy={y}
r='1px'
></circle>
<text
x={x}
y={z}
y={y}
id={`track-object-${object.id}`}
style={{ userSelect: "none" }}
enableBackground="new 0 0 100 100"

View File

@ -18,24 +18,21 @@ export default function TrackRenderer(props) {
function StandardTrackR(props) {
const { object } = props;
const x1 = object.points.x1;
const z1 = -object.points.z1;
const x2 = object.points.x2;
const z2 = -object.points.z2;
const [x1, y1] = object.points.start.toSVGCoords();
const [x2, y2] = object.points.end.toSVGCoords();
const ra = Math.abs(object.r);
let d;
if (object.r < 0) {
d = `M${x1},${z1} A${ra} ${ra} 0 0 1 ${x2},${z2}`;
d = `M${x1},${y1} A${ra} ${ra} 0 0 1 ${x2},${y2}`;
} else if (object.r > 0) {
d = `M${x2},${z2} A${ra} ${ra} 0 0 1 ${x1},${z1}`;
d = `M${x2},${y2} A${ra} ${ra} 0 0 1 ${x1},${y1}`;
} else {
d = `M${x1},${z1} L${x2},${z2}`;
d = `M${x1},${y1} L${x2},${y2}`;
}
const color = Constants.map.useTrackColors ? (
object.r === 0 ? "rgb(255, 255, 255)" : "rgb(255, 0, 255)"
object.r === 0 ? "rgb(0, 255, 255)" : "rgb(0, 0, 255)"
) : undefined;
return (
@ -51,16 +48,12 @@ function StandardTrackR(props) {
function BezierTrackR(props) {
const { object } = props;
const x1 = object.points.x1;
const z1 = -object.points.z1;
const cx1 = object.points.cx1+object.points.x1;
const cz1 = -object.points.cz1-object.points.z1;
const cx2 = object.points.cx2+object.points.x2;
const cz2 = -object.points.cz2-object.points.z2;
const x2 = object.points.x2;
const z2 = -object.points.z2;
const [x1, y1] = object.points.start.toSVGCoords();
const [cx1, cy1] = object.points.control1.toSVGCoords();
const [x2, y2] = object.points.end.toSVGCoords();
const [cx2, cy2] = object.points.control2.toSVGCoords();
const d = `M${x1},${z1} C${cx1},${cz1} ${cx2},${cz2} ${x2},${z2}`;
const d = `M${x1},${y1} C${cx1},${cy1} ${cx2},${cy2} ${x2},${y2}`;
const color = Constants.map.useTrackColors ? "rgb(0, 255, 0)" : undefined;
return (

View File

@ -1,23 +1,24 @@
import Track from "./track";
import Vector3 from "./vector3";
export default class BezierTrack extends Track
{
type = "BezierTrack";
points = {
x1: 0, y1: 0, z1: 0, // start
cx1: 0, cy1: 0, cz1: 0, // control 1
cx2: 0, cy2: 0, cz2: 0, // control 2
x2: 0, y2: 0, z2: 0 // end
start: Vector3.zero(),
control1: Vector3.zero(),
end: Vector3.zero(),
control2: Vector3.zero()
};
constructor(id, x1, y1, z1, cx1, cy1, cz1, x2, y2, z2, cx2, cy2, cz2, rx, ry, rz, len, r, previd, nextid, id_station, id_isolation, maxspeed, derailspeed) {
super(id, x1, y1, z1, rx, ry, rz, len, r, previd, nextid, id_station, id_isolation, maxspeed, derailspeed);
constructor(id, start, control1, end, control2, rot, len, r, previd, nextid, id_station, id_isolation, maxspeed, derailspeed) {
super(id, start, rot, len, r, previd, nextid, id_station, id_isolation, maxspeed, derailspeed);
Object.assign(this.points, {
x1, y1, z1, // start
cx1, cy1, cz1, // control 1
cx2, cy2, cz2, // control 2
x2, y2, z2 // end
start,
control1: start.add(control1),
end,
control2: end.add(control2)
});
}
@ -25,22 +26,12 @@ export default class BezierTrack extends Track
const values = text.split(";");
const track = new BezierTrack(
values[1], // id
parseFloat(values[3]), // x1
parseFloat(values[4]), // y1
parseFloat(values[5]), // z1
parseFloat(values[6]), // cx1
parseFloat(values[7]), // cy1
parseFloat(values[8]), // cz1
parseFloat(values[9]), // x2
parseFloat(values[10]), // y2
parseFloat(values[11]), // z2
parseFloat(values[12]), // cx2
parseFloat(values[13]), // cy2
parseFloat(values[14]), // cz2
Vector3.fromValuesArray(values, 3), // start
Vector3.fromValuesArray(values, 6), // control1
Vector3.fromValuesArray(values, 9), // end
Vector3.fromValuesArray(values, 12), // control2
// TODO: values below
0, // rx ??
0, // ry ??
0, // rz ??
Vector3.zero(),
0, // len ??
0, // r ??
values[15], // previd
@ -53,13 +44,4 @@ export default class BezierTrack extends Track
return track;
}
getRenderBounds() {
return [
{ x: this.points.x1, z: this.points.z1 }, // start
{ x: this.points.x2, z: this.points.z2 }, // end
{ x: this.points.cx1, z: this.points.cz1 }, // control 1
{ x: this.points.cx2, z: this.points.cz2 } // control 2
];
}
}

View File

@ -1,31 +1,23 @@
import Track from './track';
import Vector3 from './vector3';
export default class PointTrack extends Track
{
type = "PointTrack";
points = {
x1: 0, y1: 0, z1: 0, // start
x2: 0, y2: 0, z2: 0 // end
start: Vector3.zero(),
end: Vector3.zero()
};
constructor(id, x1, y1, z1, x2, y2, z2, r, previd, nextid, id_station, id_isolation, maxspeed, derailspeed) {
const ry = Math.atan2(x2-x1, z2-z1);
// TODO: calculate length
constructor(id, start, end, r, previd, nextid, id_station, id_isolation, maxspeed, derailspeed) {
const ry = start.atanY(end);
const rot = new Vector3(0, ry, 0);
// TODO: fix rx and rz = 0
super(id, x1, y1, z1, 0, ry, 0, null, r, previd, nextid, id_station, id_isolation, maxspeed, derailspeed);
// TODO: calculate length
super(id, start, rot, null, r, previd, nextid, id_station, id_isolation, maxspeed, derailspeed);
Object.assign(this.points, {
x1, y1, z1,
x2, y2, z2
start, end
});
this.type="PointTrack";
}
getRenderBounds() {
return [
{ x: this.points.x1, z: this.points.z1 }, // start
{ x: this.points.x2, z: this.points.z2 } // end
];
}
}

View File

@ -1,4 +1,5 @@
import SceneryObject from "./scenery-object";
import Vector3 from "./vector3";
export default class Route extends SceneryObject {
track_count;
@ -8,12 +9,11 @@ export default class Route extends SceneryObject {
category = "routes";
type = "Route";
constructor(prefab_name, x, y, z, rx, ry, rz, track_count, route_name, track_offset, electrified) {
super(route_name, x, y, z, rx, ry, rz);
constructor(prefab_name, pos, rot, track_count, route_name, track_offset, electrified) {
super(route_name, pos, rot);
Object.assign(this, {
prefab_name,
rx, ry, rz,
track_count,
route_name,
track_offset,
@ -21,18 +21,12 @@ export default class Route extends SceneryObject {
});
}
// Route;;ConnectionForest;14418.61;-6.663832;8637.426;0;33.34996;0;2;Ol;2;;True
static fromText(text) {
const values = text.split(";");
const route = new Route(
values[2], // prefab_name
parseFloat(values[3]), // x
parseFloat(values[4]), // y
parseFloat(values[5]), // z
parseFloat(values[6]), // rx
parseFloat(values[7]), // ry
parseFloat(values[8]), // rz
Vector3.fromValuesArray(values, 3), // pos
Vector3.fromValuesArray(values, 6), // rot
parseInt(values[9]), // track_count
values[10], // route_name
parseFloat(values[11]), // track_offset

View File

@ -1,17 +1,13 @@
export default class SceneryObject {
id;
x;
y;
z;
rx;
ry;
rz;
pos;
rot;
constructor(id, x, y, z, rx, ry, rz) {
constructor(id, pos, rot) {
Object.assign(this, {
id,
x, y, z,
rx, ry, rz
pos,
rot
});
}
@ -25,8 +21,6 @@ export default class SceneryObject {
}
getRenderBounds() {
return [
{ x: this.x, z: this.z }
];
return this.pos;
}
}

View File

@ -1,5 +1,6 @@
import SpawnInfo from "./spawn-info";
import TrackObject from "./track-object";
import Vector3 from "./vector3";
export default class Signal extends TrackObject {
is_spawn;
@ -7,8 +8,8 @@ export default class Signal extends TrackObject {
signal_name;
type = "Signal";
constructor(id, prefab_name, x, y, z, rx, ry, rz, track_id, name, is_spawn, spawn_info, signal_name) {
super(id, prefab_name, x, y, z, rx, ry, rz, track_id, name);
constructor(id, prefab_name, pos, rot, track_id, name, is_spawn, spawn_info, signal_name) {
super(id, prefab_name, pos, rot, track_id, name);
Object.assign(this, {
is_spawn,
@ -51,12 +52,8 @@ export default class Signal extends TrackObject {
const signal = new Signal(
values[1], // id
values[2], // prefab_name
parseFloat(values[3]), // x
parseFloat(values[4]), // y
parseFloat(values[5]), // z
parseFloat(values[6]), // rx
parseFloat(values[7]), // ry
parseFloat(values[8]), // rz
Vector3.fromValuesArray(values, 3), // pos
Vector3.fromValuesArray(values, 6), // rot
values[9], // track_id
values[11], // name,
values[12] === "Spawn Signal", // is_spawn

View File

@ -1,16 +1,17 @@
import Track from "./track";
import Vector3 from "./vector3";
export default class StandardTrack extends Track
{
type = "StandardTrack";
points = {
x1: 0, z1: 0, // start
x2: 0, z2: 0, // end
cx: 0, cz: 0 // circle center
start: Vector3.zero(),
end: Vector3.zero(),
circleCenter: Vector3.zero()
};
constructor(id, x, y, z, rx, ry, rz, len, r, previd, nextid, id_station, id_isolation, maxspeed, derailspeed) {
super(id, x, y, z, rx, ry, rz, len, r, previd, nextid, id_station, id_isolation, maxspeed, derailspeed);
constructor(id, start, rot, len, r, previd, nextid, id_station, id_isolation, maxspeed, derailspeed) {
super(id, start, rot, len, r, previd, nextid, id_station, id_isolation, maxspeed, derailspeed);
this._calcPoints();
}
@ -18,12 +19,8 @@ export default class StandardTrack extends Track
const values = text.split(";");
const track = new StandardTrack(
values[1], // id
parseFloat(values[3]), // x
parseFloat(values[4]), // y
parseFloat(values[5]), // z
parseFloat(values[6]), // rx
parseFloat(values[7]), // ry
parseFloat(values[8]), // rz
Vector3.fromValuesArray(values, 3), // start
Vector3.fromValuesArray(values, 6), // rot
parseFloat(values[9]), // len
parseFloat(values[10]), // r
values[11], // previd
@ -38,30 +35,17 @@ export default class StandardTrack extends Track
}
_calcPoints() {
const rotRad = this.ry * Math.PI / 180;
const rotRad = this.rot.y * Math.PI / 180;
this.points = {
x1: this.x, // start
z1: this.z,
x2: this.x + this.len * Math.sin(rotRad), // end
z2: this.z + this.len * Math.cos(rotRad),
cx: this.x, // circle center (default)
cz: this.z // circle center (default)
};
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) {
let trp2 = rotRad + Math.PI / 2;
this.points.cx = this.x - this.r * Math.sin(trp2); // circle center
this.points.cz = this.z - this.r * Math.cos(trp2);
this.points.x2 = this.points.cx + this.r * Math.sin(trp2 - this.len / this.r); // end (modify)
this.points.z2 = this.points.cz + this.r * Math.cos(trp2 - this.len / this.r);
const centerAngle = rotRad + Math.PI / 2;
this.points.circleCenter = this.pos.sub(Vector3.fromAngleY(centerAngle, this.r));
const endAngle = centerAngle - this.len / this.r;
this.points.end = this.points.circleCenter.add(Vector3.fromAngleY(endAngle, this.r));
}
}
gerRenderBounds() {
return [
{ x: this.points.x1, z: this.points.z1 }, // start
{ x: this.points.x2, z: this.points.z2 }, // end
];
}
}

View File

@ -1,7 +1,7 @@
import Constants from "../helpers/constants.js";
import PointTrack from "./point-track.js";
import SceneryObject from "./scenery-object.js";
import SceneryParserLog from "./scenery-parser-log.js";
import Vector3 from "./vector3.js";
export default class Switch extends SceneryObject {
model;
@ -17,8 +17,8 @@ export default class Switch extends SceneryObject {
trackA;
trackB;
constructor(id, model, x, y, z, rx, ry, rz, data, id_isolation, id_switch, maxspeed, derailspeed) {
super(id, x, y, z, rx, ry, rz);
constructor(id, model, pos, rot, data, id_isolation, id_switch, maxspeed, derailspeed) {
super(id, pos, rot);
Object.assign(this, {
model,
data,
@ -33,12 +33,8 @@ export default class Switch extends SceneryObject {
const sw = new Switch(
values[1], // id
values[2], // model
parseFloat(values[3]), // x
parseFloat(values[4]), // y
parseFloat(values[5]), // z
parseFloat(values[6]), // rx
parseFloat(values[7]), // ry
parseFloat(values[8]), // rz
Vector3.fromValuesArray(values, 3), // pos
Vector3.fromValuesArray(values, 6), // rot
values[9], // data
values[10], // id_isolation
values[11], // id_switch
@ -115,13 +111,13 @@ export default class Switch extends SceneryObject {
return null;
}
const startTrackEnd = startTrack.getCloserEnd(this.x, this.y, this.z);
const endTrackEnd = endTrack.getCloserEnd(this.x, this.y, this.z);
const startTrackEnd = startTrack.getCloserEndPos(this.pos);
const endTrackEnd = endTrack.getCloserEndPos(this.pos);
let trackObj = new PointTrack(
name,
...startTrackEnd,
...endTrackEnd,
startTrackEnd,
endTrackEnd,
r,
from,
to,

View File

@ -1,6 +1,6 @@
import Constants from '../helpers/constants';
import SceneryObject from './scenery-object';
import SceneryParserLog from './scenery-parser-log';
import Vector3 from './vector3';
export default class TrackObject extends SceneryObject {
prefab_name;
@ -10,8 +10,8 @@ export default class TrackObject extends SceneryObject {
type = "TrackObject";
track;
constructor(id, prefab_name, x, y, z, rx, ry, rz, track_id, name) {
super(id, x, y, z, rx, ry, rz);
constructor(id, prefab_name, pos, rot, track_id, name) {
super(id, pos, rot);
Object.assign(this, {
prefab_name,
track_id,
@ -24,12 +24,8 @@ export default class TrackObject extends SceneryObject {
const obj = new TrackObject(
values[1], // id
values[2], // prefab_name
parseFloat(values[3]), // x
parseFloat(values[4]), // y
parseFloat(values[5]), // z
parseFloat(values[6]), // rx
parseFloat(values[7]), // ry
parseFloat(values[8]), // rz
Vector3.fromValuesArray(values, 3), // pos
Vector3.fromValuesArray(values, 6), // rot
values[9], // track_id
values[11] // name
);

View File

@ -11,8 +11,8 @@ export default class Track extends SceneryObject {
type = "Track";
outs = [];
constructor(id, x, y, z, rx, ry, rz, len, r, previd, nextid, id_station, id_isolation, maxspeed, derailspeed) {
super(id, x, y, z, rx, ry, rz);
constructor(id, pos, rot, len, r, previd, nextid, id_station, id_isolation, maxspeed, derailspeed) {
super(id, pos, rot);
Object.assign(this, {
len, r,
id_station, id_isolation,
@ -22,15 +22,17 @@ export default class Track extends SceneryObject {
this.outs = [previd, nextid];
}
getCloserEnd(x, y, z) {
function pow2(val) {
return val*val;
getCloserEndPos(pos) {
const distStartSq = pos.distanceSq(this.points.start);
const distEndSq = pos.distanceSq(this.points.end);
if (distStartSq < distEndSq) {
return this.points.start;
} else {
return this.points.end;
}
}
const dist1 = pow2(x-this.points.x1)+pow2(z-this.points.z1);
const dist2 = pow2(x-this.points.x2)+pow2(z-this.points.z2);
return (dist1 < dist2) ?
[this.points.x1, y, this.points.z1] :
[this.points.x2, y, this.points.z2];
getRenderBounds() {
return Object.values(this.points);
}
}

95
src/model/vector3.js Normal file
View File

@ -0,0 +1,95 @@
export default class Vector3 {
x;
y;
z;
constructor(x = 0, y = 0, z = 0) {
Object.assign(this, { x, y, z });
}
static fromText(text) {
const values = text.split(";");
return new Vector3(
parseFloat(values[0]), // x
parseFloat(values[1]), // y
parseFloat(values[2]) // z
);
}
static fromValuesArray(values, startIndex = 0) {
return new Vector3(
parseFloat(values[startIndex]), // x
parseFloat(values[startIndex + 1]), // y
parseFloat(values[startIndex + 2]) // z
);
}
static zero() {
return new Vector3(0, 0, 0);
}
static fromAngleY(angleRad, length = 1) {
return new Vector3(
Math.sin(angleRad) * length,
0,
Math.cos(angleRad) * length
);
}
lerp(other, t) {
return new Vector3(
this.x + (other.x - this.x) * t,
this.y + (other.y - this.y) * t,
this.z + (other.z - this.z) * t
);
}
atanY(other) {
const dx = other.x - this.x;
const dz = other.z - this.z;
return Math.atan2(dz, dx);
}
add(other) {
return new Vector3(
this.x + other.x,
this.y + other.y,
this.z + other.z
);
}
sub(other) {
return new Vector3(
this.x - other.x,
this.y - other.y,
this.z - other.z
);
}
distanceSq(other) {
const dx = this.x - other.x;
const dy = this.y - other.y;
const dz = this.z - other.z;
return dx * dx + dy * dy + dz * dz;
}
distance(other) {
return Math.sqrt(this.distanceSq(other));
}
clone() {
return new Vector3(this.x, this.y, this.z);
}
toText() {
return `${this.x};${this.y};${this.z}`;
}
toArray() {
return [this.x, this.y, this.z];
}
toSVGCoords() {
return [this.x, -this.z];
}
}