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) { export default function IsolationIdRenderer(props) {
const { object } = props; const { object } = props;
const pos = object.points.start.lerp(object.points.end, 0.5);
const x1 = object.points?.x1; const [x, y] = pos.toSVGCoords();
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 text = object.id_isolation ?? ''; const text = object.id_isolation ?? '';
return ( return (
<text <text
x={x} x={x}
y={z} y={y}
textAnchor='middle' textAnchor='middle'
id={`isolation-id-${object.id}`} id={`isolation-id-${object.id}`}
style={{ userSelect: "none" }} style={{ userSelect: "none" }}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,23 +1,24 @@
import Track from "./track"; import Track from "./track";
import Vector3 from "./vector3";
export default class BezierTrack extends Track export default class BezierTrack extends Track
{ {
type = "BezierTrack"; type = "BezierTrack";
points = { points = {
x1: 0, y1: 0, z1: 0, // start start: Vector3.zero(),
cx1: 0, cy1: 0, cz1: 0, // control 1 control1: Vector3.zero(),
cx2: 0, cy2: 0, cz2: 0, // control 2 end: Vector3.zero(),
x2: 0, y2: 0, z2: 0 // end 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) { constructor(id, start, control1, end, control2, rot, 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); super(id, start, rot, len, r, previd, nextid, id_station, id_isolation, maxspeed, derailspeed);
Object.assign(this.points, { Object.assign(this.points, {
x1, y1, z1, // start start,
cx1, cy1, cz1, // control 1 control1: start.add(control1),
cx2, cy2, cz2, // control 2 end,
x2, y2, z2 // end control2: end.add(control2)
}); });
} }
@ -25,22 +26,12 @@ export default class BezierTrack extends Track
const values = text.split(";"); const values = text.split(";");
const track = new BezierTrack( const track = new BezierTrack(
values[1], // id values[1], // id
parseFloat(values[3]), // x1 Vector3.fromValuesArray(values, 3), // start
parseFloat(values[4]), // y1 Vector3.fromValuesArray(values, 6), // control1
parseFloat(values[5]), // z1 Vector3.fromValuesArray(values, 9), // end
parseFloat(values[6]), // cx1 Vector3.fromValuesArray(values, 12), // control2
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
// TODO: values below // TODO: values below
0, // rx ?? Vector3.zero(),
0, // ry ??
0, // rz ??
0, // len ?? 0, // len ??
0, // r ?? 0, // r ??
values[15], // previd values[15], // previd
@ -53,13 +44,4 @@ export default class BezierTrack extends Track
return 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 Track from './track';
import Vector3 from './vector3';
export default class PointTrack extends Track export default class PointTrack extends Track
{ {
type = "PointTrack"; type = "PointTrack";
points = { points = {
x1: 0, y1: 0, z1: 0, // start start: Vector3.zero(),
x2: 0, y2: 0, z2: 0 // end end: Vector3.zero()
}; };
constructor(id, x1, y1, z1, x2, y2, z2, r, previd, nextid, id_station, id_isolation, maxspeed, derailspeed) { constructor(id, start, end, r, previd, nextid, id_station, id_isolation, maxspeed, derailspeed) {
const ry = Math.atan2(x2-x1, z2-z1); const ry = start.atanY(end);
// TODO: calculate length const rot = new Vector3(0, ry, 0);
// TODO: fix rx and rz = 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, { Object.assign(this.points, {
x1, y1, z1, start, end
x2, y2, z2
}); });
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 SceneryObject from "./scenery-object";
import Vector3 from "./vector3";
export default class Route extends SceneryObject { export default class Route extends SceneryObject {
track_count; track_count;
@ -8,12 +9,11 @@ export default class Route extends SceneryObject {
category = "routes"; category = "routes";
type = "Route"; type = "Route";
constructor(prefab_name, x, y, z, rx, ry, rz, track_count, route_name, track_offset, electrified) { constructor(prefab_name, pos, rot, track_count, route_name, track_offset, electrified) {
super(route_name, x, y, z, rx, ry, rz); super(route_name, pos, rot);
Object.assign(this, { Object.assign(this, {
prefab_name, prefab_name,
rx, ry, rz,
track_count, track_count,
route_name, route_name,
track_offset, 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) { static fromText(text) {
const values = text.split(";"); const values = text.split(";");
const route = new Route( const route = new Route(
values[2], // prefab_name values[2], // prefab_name
parseFloat(values[3]), // x Vector3.fromValuesArray(values, 3), // pos
parseFloat(values[4]), // y Vector3.fromValuesArray(values, 6), // rot
parseFloat(values[5]), // z
parseFloat(values[6]), // rx
parseFloat(values[7]), // ry
parseFloat(values[8]), // rz
parseInt(values[9]), // track_count parseInt(values[9]), // track_count
values[10], // route_name values[10], // route_name
parseFloat(values[11]), // track_offset parseFloat(values[11]), // track_offset

View File

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

View File

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

View File

@ -1,16 +1,17 @@
import Track from "./track"; import Track from "./track";
import Vector3 from "./vector3";
export default class StandardTrack extends Track export default class StandardTrack extends Track
{ {
type = "StandardTrack"; type = "StandardTrack";
points = { points = {
x1: 0, z1: 0, // start start: Vector3.zero(),
x2: 0, z2: 0, // end end: Vector3.zero(),
cx: 0, cz: 0 // circle center circleCenter: Vector3.zero()
}; };
constructor(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, x, y, z, rx, ry, rz, 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(); this._calcPoints();
} }
@ -18,12 +19,8 @@ export default class StandardTrack extends Track
const values = text.split(";"); const values = text.split(";");
const track = new StandardTrack( const track = new StandardTrack(
values[1], // id values[1], // id
parseFloat(values[3]), // x Vector3.fromValuesArray(values, 3), // start
parseFloat(values[4]), // y Vector3.fromValuesArray(values, 6), // rot
parseFloat(values[5]), // z
parseFloat(values[6]), // rx
parseFloat(values[7]), // ry
parseFloat(values[8]), // rz
parseFloat(values[9]), // len parseFloat(values[9]), // len
parseFloat(values[10]), // r parseFloat(values[10]), // r
values[11], // previd values[11], // previd
@ -38,30 +35,17 @@ export default class StandardTrack extends Track
} }
_calcPoints() { _calcPoints() {
const rotRad = this.ry * Math.PI / 180; const rotRad = this.rot.y * Math.PI / 180;
this.points = { this.points.start = this.pos.clone();
x1: this.x, // start this.points.end = this.pos.add(Vector3.fromAngleY(rotRad, this.len));
z1: this.z, this.points.circleCenter = this.pos.clone(); // default circle center
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)
};
if (this.r !== 0) { if (this.r !== 0) {
let trp2 = rotRad + Math.PI / 2; const centerAngle = rotRad + Math.PI / 2;
this.points.cx = this.x - this.r * Math.sin(trp2); // circle center this.points.circleCenter = this.pos.sub(Vector3.fromAngleY(centerAngle, this.r));
this.points.cz = this.z - this.r * Math.cos(trp2); const endAngle = centerAngle - this.len / this.r;
this.points.x2 = this.points.cx + this.r * Math.sin(trp2 - this.len / this.r); // end (modify) this.points.end = this.points.circleCenter.add(Vector3.fromAngleY(endAngle, this.r));
this.points.z2 = this.points.cz + this.r * Math.cos(trp2 - this.len / 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 PointTrack from "./point-track.js";
import SceneryObject from "./scenery-object.js"; import SceneryObject from "./scenery-object.js";
import SceneryParserLog from "./scenery-parser-log.js"; import SceneryParserLog from "./scenery-parser-log.js";
import Vector3 from "./vector3.js";
export default class Switch extends SceneryObject { export default class Switch extends SceneryObject {
model; model;
@ -17,8 +17,8 @@ export default class Switch extends SceneryObject {
trackA; trackA;
trackB; trackB;
constructor(id, model, x, y, z, rx, ry, rz, data, id_isolation, id_switch, maxspeed, derailspeed) { constructor(id, model, pos, rot, data, id_isolation, id_switch, maxspeed, derailspeed) {
super(id, x, y, z, rx, ry, rz); super(id, pos, rot);
Object.assign(this, { Object.assign(this, {
model, model,
data, data,
@ -33,12 +33,8 @@ export default class Switch extends SceneryObject {
const sw = new Switch( const sw = new Switch(
values[1], // id values[1], // id
values[2], // model values[2], // model
parseFloat(values[3]), // x Vector3.fromValuesArray(values, 3), // pos
parseFloat(values[4]), // y Vector3.fromValuesArray(values, 6), // rot
parseFloat(values[5]), // z
parseFloat(values[6]), // rx
parseFloat(values[7]), // ry
parseFloat(values[8]), // rz
values[9], // data values[9], // data
values[10], // id_isolation values[10], // id_isolation
values[11], // id_switch values[11], // id_switch
@ -115,13 +111,13 @@ export default class Switch extends SceneryObject {
return null; return null;
} }
const startTrackEnd = startTrack.getCloserEnd(this.x, this.y, this.z); const startTrackEnd = startTrack.getCloserEndPos(this.pos);
const endTrackEnd = endTrack.getCloserEnd(this.x, this.y, this.z); const endTrackEnd = endTrack.getCloserEndPos(this.pos);
let trackObj = new PointTrack( let trackObj = new PointTrack(
name, name,
...startTrackEnd, startTrackEnd,
...endTrackEnd, endTrackEnd,
r, r,
from, from,
to, to,

View File

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

View File

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

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];
}
}