Improved Platform transforms & rendering

*Improved Platform (MiscGroups) transforms. Positions are now 100% accurate, rotations ~90%
*Platforms won't be rendered if their tilt is too big
*Misc objects now use yawData.yaw as their "y rotation" in rendering
*Renamed Platforms layer
This commit is contained in:
izawartka 2025-07-22 16:37:37 +02:00
parent 0c656f3ca6
commit 872d44a3f0
9 changed files with 100 additions and 40 deletions

View File

@ -130,5 +130,5 @@
}
.map svg g.platform rect {
fill: #333;
fill: #444;
}

View File

@ -1,14 +1,17 @@
import Constants from "../../../helpers/constants";
export default function PlatformRenderer(props) {
const { object } = props;
const [x, y] = object.pos.toSVGCoords();
if(!object.def) return null;
const tiltNotOk = object.yawData.tilt > Constants.map.platformMaxTilt;
if(!object.def || tiltNotOk) return null;
const [centerX, centerY] = object.def.center.toSVGCoords();
const sizeX = object.def.size.x;
const sizeY = object.def.size.z;
return (
<g className="platform" transform={`translate(${x}, ${y}) rotate(${object.rot.y})`}>
<g className="platform" transform={`translate(${x}, ${y}) rotate(${object.yawData.yaw})`}>
<rect
x={-sizeX / 2 + centerX}
y={-sizeY / 2 + centerY}

View File

@ -6,8 +6,9 @@ export default function SignalBoxRenderer(props) {
const [x, y] = object.pos.toSVGCoords();
const text = object.getPrintableSignalBoxName();
const rotDiff = object.def.undef ? Math.round(object.rot.y / 90) * (-90) : object.def.rot * 90;
const rot = object.rot.y + rotDiff;
const yaw = object.yawData?.yaw || object.rot.y;
const rotDiff = object.def.undef ? Math.round(yaw / 90) * (-90) : object.def.rot * 90;
const rot = yaw + rotDiff;
return (
<g className="signalbox" transform={`translate(${x}, ${y}) rotate(${rot})`}>

View File

@ -5,7 +5,8 @@ const Constants = {
zoomMin: 0.03,
zoomMax: 200.0,
rotationSensitivity: 0.2,
forcePointerEvents: false
forcePointerEvents: false,
platformMaxTilt: 0.5
},
parser: {
logSceneryAfterFinished: true,
@ -66,7 +67,7 @@ const Constants = {
layers: [
{
id: 'platforms',
name: 'Platforms (WIP)',
name: 'Platforms',
default: true,
cond: () => !Constants.parser.skipPlatforms,
},

View File

@ -23,7 +23,8 @@ export default class MiscGroup {
getQuaternion() {
if (!this.quaternion) {
this.quaternion = Quaternion.fromEulerAngles(this.rot.multiply(Math.PI / 180));
const rotRad = this.rot.multiply(Math.PI / 180);
this.quaternion = Quaternion.fromEulerAngles(rotRad).normalize();
}
return this.quaternion;

View File

@ -1,18 +1,22 @@
import AngleHelper from "../../helpers/angleHelper";
import Quaternion from "../quaternion";
import SceneryObject from "../scenery-object";
import Vector3 from "../vector3";
export default class Misc extends SceneryObject {
prefab_name;
name;
yawData;
category = "misc";
type = "Misc";
constructor(id, misc_id, prefab_name, pos, rot, name) {
constructor(id, misc_id, prefab_name, pos, rot, yawData, name) {
super(id, pos, rot);
Object.assign(this, {
misc_id,
prefab_name,
name
name,
yawData
});
}
@ -35,14 +39,19 @@ export default class Misc extends SceneryObject {
}
static applyGroupTransforms(localPos, localRot, miscGroups) {
const localRotRad = localRot.multiply(Math.PI / 180);
let worldQuat = Quaternion.fromEulerAngles(localRotRad).normalize();
let worldPos = localPos.clone();
let worldRot = localRot.clone();
for (const group of miscGroups) {
worldPos = group.getQuaternion().rotateVector(worldPos).add(group.pos);
worldRot = worldRot.add(group.rot);
const groupQuat = group.getQuaternion();
worldPos = groupQuat.rotateVector(worldPos).add(group.pos);
worldQuat = groupQuat.multiply(worldQuat).normalize();
}
const worldRot = worldQuat.toEulerAngles().multiply(180 / Math.PI);
const yawData = worldQuat.getMiscYawData();
return [ worldPos, worldRot ];
return [ worldPos, worldRot, yawData ];
}
}

View File

@ -7,8 +7,8 @@ export default class Platform extends Misc {
type = "Platform";
def = null;
constructor(id, misc_id, prefab_name, pos, rot, name) {
super(id, misc_id, prefab_name, pos, rot, name);
constructor(id, misc_id, prefab_name, pos, rot, yawData, name) {
super(id, misc_id, prefab_name, pos, rot, yawData, name);
this.def = Platform.getDef(name, prefab_name);
}

View File

@ -8,8 +8,8 @@ export default class SignalBox extends Misc {
applied = false;
def = null;
constructor(id, misc_id, prefab_name, pos, rot, name) {
super(id, misc_id, prefab_name, pos, rot, name);
constructor(id, misc_id, prefab_name, pos, rot, yawData, name) {
super(id, misc_id, prefab_name, pos, rot, yawData, name);
this.def = SignalBox.getDef(name, prefab_name);
}

View File

@ -14,27 +14,50 @@ export default class Quaternion {
}
static fromEulerAngles(vector) {
const { x: rotX, y: rotY, z: rotZ } = vector;
const halfZ = vector.z * 0.5;
const halfX = vector.x * 0.5;
const halfY = vector.y * 0.5;
const cz = Math.cos(rotZ * 0.5);
const sz = Math.sin(rotZ * 0.5);
const cx = Math.cos(rotX * 0.5);
const sx = Math.sin(rotX * 0.5);
const cy = Math.cos(rotY * 0.5);
const sy = Math.sin(rotY * 0.5);
const w = cx * cy * cz + sx * sy * sz;
const x = sx * cy * cz + cx * sy * sz;
const y = cx * sy * cz - sx * cy * sz;
const z = cx * cy * sz - sx * sy * cz;
return new Quaternion(w, x, y, z);
const sz = Math.sin(halfZ);
const cz = Math.cos(halfZ);
const sx = Math.sin(halfX);
const cx = Math.cos(halfX);
const sy = Math.sin(halfY);
const cy = Math.cos(halfY);
return new Quaternion(
sx * sy * sz + cx * cy * cz,
sx * cy * cz + cx * sy * sz,
cx * sy * cz - sx * cy * sz,
cx * cy * sz - sx * sy * cz
)
}
toEulerAngles() {
const sinr_cosp = 2 * (this.w * this.x + this.y * this.z);
const cosr_cosp = 1 - 2 * (this.x * this.x + this.y * this.y);
const roll = Math.atan2(sinr_cosp, cosr_cosp);
const sinp = 2 * (this.w * this.y - this.z * this.x);
let pitch;
if (Math.abs(sinp) >= 1) {
pitch = Math.sign(sinp) * Math.PI / 2; // use 90 degrees if out of range
} else {
pitch = -Math.asin(sinp);
}
const siny_cosp = 2 * (this.w * this.z + this.x * this.y);
const cosy_cosp = 1 - 2 * (this.y * this.y + this.z * this.z);
const yaw = Math.atan2(siny_cosp, cosy_cosp);
return new Vector3(roll, pitch, yaw);
}
rotateVector(v) {
const vecQuat = new Quaternion(0, v.x, v.y, v.z);
const resultQuat = this.multiply(vecQuat).multiply(this.conjugate());
return new Vector3(resultQuat.x, resultQuat.y, resultQuat.z);
const qv = new Quaternion(0, v.x, v.y, v.z);
const conjugate = this.conjugate();
const rotated = this.multiply(qv).multiply(conjugate);
return new Vector3(rotated.x, rotated.y, rotated.z);
}
conjugate() {
@ -48,11 +71,33 @@ export default class Quaternion {
}
multiply(q) {
const w = this.w * q.w - this.x * q.x - this.y * q.y - this.z * q.z;
const x = this.w * q.x + this.x * q.w + this.y * q.z - this.z * q.y;
const y = this.w * q.y - this.x * q.z + this.y * q.w + this.z * q.x;
const z = this.w * q.z + this.x * q.y - this.y * q.x + this.z * q.w;
return new Quaternion(
this.w * q.w - this.x * q.x - this.y * q.y - this.z * q.z,
this.w * q.x + this.x * q.w + this.y * q.z - this.z * q.y,
this.w * q.y - this.x * q.z + this.y * q.w + this.z * q.x,
this.w * q.z + this.x * q.y - this.y * q.x + this.z * q.w
);
}
return new Quaternion(w, x, y, z);
multiplyScalar(scalar) {
return new Quaternion(
this.w * scalar,
this.x * scalar,
this.y * scalar,
this.z * scalar
);
}
getMiscYawData() {
const forward = new Vector3(0, 0, -1);
const rotatedForward = this.rotateVector(forward);
const up = new Vector3(0, 1, 0);
const rotatedUp = this.rotateVector(up);
return {
yaw: Math.atan2(rotatedForward.x, rotatedForward.z) * 180 / Math.PI,
tilt: rotatedUp.x * rotatedUp.x + rotatedUp.z * rotatedUp.z
}
}
};