fix(city): correct vehicle yaw calculation and expose orientation API
- Extracts yawFromDelta helper to standardize rotation math - Removes incorrect PI/2 offset causing reverse-facing vehicles - Accounts for direction sign in shuttle vehicle rotation - Adds vehicleOrientations() for test validation
This commit is contained in:
+48
-4
@@ -980,6 +980,15 @@ function probeAhead(pts, s, L) {
|
||||
return { distToCorner: 999, axis: 'x', corner: pts[0] };
|
||||
}
|
||||
|
||||
/**
|
||||
* Yaw so a mesh whose nose is local +X faces the world direction (dx, dz).
|
||||
*
|
||||
* Three.js: local +X after rotation.y=θ becomes (cos θ, 0, -sin θ).
|
||||
*/
|
||||
function yawFromDelta(dx, dz) {
|
||||
return Math.atan2(-dz, dx);
|
||||
}
|
||||
|
||||
function updateLoopVehicle(v, dt) {
|
||||
const probe = probeAhead(v.pts, v.s, v.L);
|
||||
const distToCorner = probe.distToCorner;
|
||||
@@ -1008,7 +1017,7 @@ function updateLoopVehicle(v, dt) {
|
||||
posOnPath(v.pts, v.s + 0.6, v.L, tmp);
|
||||
v.mesh.position.copy(pos);
|
||||
if (tmp.distanceToSquared(pos) > 0.0001) {
|
||||
v.mesh.rotation.y = Math.atan2(tmp.x - pos.x, tmp.z - pos.z) + Math.PI / 2;
|
||||
v.mesh.rotation.y = yawFromDelta(tmp.x - pos.x, tmp.z - pos.z);
|
||||
}
|
||||
const wheelSpin = v.speed * dt * 3;
|
||||
for (const w of v.mesh.userData.wheels || []) w.rotation.z += wheelSpin;
|
||||
@@ -1049,7 +1058,7 @@ function updateShuttleVehicle(v, dt) {
|
||||
v.mesh.position.copy(pos);
|
||||
const look = new THREE.Vector3().lerpVectors(v.a, v.b, Math.max(0, Math.min(1, v.t + v.dir * 0.05)));
|
||||
if (look.distanceToSquared(pos) > 0.0001) {
|
||||
v.mesh.rotation.y = Math.atan2(look.x - pos.x, look.z - pos.z) + Math.PI / 2;
|
||||
v.mesh.rotation.y = yawFromDelta(look.x - pos.x, look.z - pos.z);
|
||||
}
|
||||
for (const w of v.mesh.userData.wheels || []) w.rotation.z += v.speed * dt * 3;
|
||||
}
|
||||
@@ -2632,10 +2641,11 @@ function vehicleBoxes() {
|
||||
posOnPath(v.pts, v.s, v.L, p);
|
||||
const ahead = new THREE.Vector3();
|
||||
posOnPath(v.pts, v.s + 0.6, v.L, ahead);
|
||||
rot = Math.atan2(ahead.x - p.x, ahead.z - p.z);
|
||||
rot = yawFromDelta(ahead.x - p.x, ahead.z - p.z);
|
||||
} else {
|
||||
p.lerpVectors(v.a, v.b, v.t);
|
||||
rot = Math.atan2(v.b.x - v.a.x, v.b.z - v.a.z) + Math.PI / 2;
|
||||
const dirSign = v.dir >= 0 ? 1 : -1;
|
||||
rot = yawFromDelta((v.b.x - v.a.x) * dirSign, (v.b.z - v.a.z) * dirSign);
|
||||
}
|
||||
const halfL = v.mesh?.userData?.halfL ?? (v.pts ? 2.1 : 2.0);
|
||||
const halfW = v.mesh?.userData?.halfW ?? 1.0;
|
||||
@@ -3459,6 +3469,40 @@ window.__city10 = {
|
||||
};
|
||||
},
|
||||
vehiclePositions() { return vehicleSystem ? vehicleSystem.positions() : []; },
|
||||
/** Sample vehicle heading vs velocity so tests can detect reverse-facing models. */
|
||||
vehicleOrientations() {
|
||||
if (!vehicleSystem) return [];
|
||||
const out = [];
|
||||
for (const v of vehicleSystem.vehicles) {
|
||||
const p = new THREE.Vector3();
|
||||
const ahead = new THREE.Vector3();
|
||||
if (v.pts) {
|
||||
posOnPath(v.pts, v.s, v.L, p);
|
||||
posOnPath(v.pts, v.s + 0.8, v.L, ahead);
|
||||
} else {
|
||||
p.lerpVectors(v.a, v.b, v.t);
|
||||
const lookT = Math.max(0, Math.min(1, v.t + v.dir * 0.05));
|
||||
ahead.lerpVectors(v.a, v.b, lookT);
|
||||
}
|
||||
const dx = ahead.x - p.x;
|
||||
const dz = ahead.z - p.z;
|
||||
const len = Math.hypot(dx, dz) || 1;
|
||||
const vx = dx / len;
|
||||
const vz = dz / len;
|
||||
// Mesh nose is local +X → world forward = (cos yaw, -sin yaw).
|
||||
const yaw = v.mesh.rotation.y;
|
||||
const fx = Math.cos(yaw);
|
||||
const fz = -Math.sin(yaw);
|
||||
const dot = fx * vx + fz * vz;
|
||||
out.push({
|
||||
kind: v.mesh?.userData?.kind || 'car',
|
||||
speed: +(v.speed || 0).toFixed(2),
|
||||
dot: +dot.toFixed(3),
|
||||
aligned: dot > 0.5,
|
||||
});
|
||||
}
|
||||
return out;
|
||||
},
|
||||
pedestrianPositions() { return pedestrianSystem ? pedestrianSystem.positions() : []; },
|
||||
get me() { return meCharacter; },
|
||||
refreshMe: async function () {
|
||||
|
||||
Reference in New Issue
Block a user