fix(city): correct traffic signal orientation for right-hand traffic
- Fix vehicle signal head rotation to face approaching traffic correctly - Refactor pedestrian signal positioning with descriptive naming - Add getSignalHeads() method for snapshotting signal state - Add cache-busting query parameter to app.js script tag
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 186 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 258 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 239 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 252 KiB |
+85
-18
@@ -556,13 +556,15 @@ function initTrafficLights(g) {
|
|||||||
group.add(pole);
|
group.add(pole);
|
||||||
|
|
||||||
// Mast arm over the vertical road (northbound lane), facing
|
// Mast arm over the vertical road (northbound lane), facing
|
||||||
// south so approaching traffic sees the signal.
|
// south so approaching traffic sees the signal. Northbound
|
||||||
|
// drives on the east half (right-hand traffic); the SE-corner
|
||||||
|
// pole sits on that same (right) side.
|
||||||
const armNS = new THREE.Mesh(new THREE.BoxGeometry(0.12, 0.1, 2.2), poleMat);
|
const armNS = new THREE.Mesh(new THREE.BoxGeometry(0.12, 0.1, 2.2), poleMat);
|
||||||
armNS.position.set(x + sw, 4.0, z + sw - 1.1);
|
armNS.position.set(x + sw, 4.0, z + sw - 1.1);
|
||||||
group.add(armNS);
|
group.add(armNS);
|
||||||
const headNS = makeSignalHead();
|
const headNS = makeSignalHead();
|
||||||
headNS.position.set(x + sw, 3.7, z + sw - 2.2);
|
headNS.position.set(x + sw, 3.7, z + sw - 2.2);
|
||||||
headNS.rotation.y = Math.PI; // face -Z (south)
|
headNS.rotation.y = 0; // face +Z (south — toward northbound traffic)
|
||||||
group.add(headNS);
|
group.add(headNS);
|
||||||
heads.push({ head: headNS, axis: 'z' });
|
heads.push({ head: headNS, axis: 'z' });
|
||||||
|
|
||||||
@@ -578,23 +580,30 @@ function initTrafficLights(g) {
|
|||||||
|
|
||||||
// Pedestrian signals: short posts on the same corner, well
|
// Pedestrian signals: short posts on the same corner, well
|
||||||
// inside the sidewalk, facing the crosswalks they control.
|
// inside the sidewalk, facing the crosswalks they control.
|
||||||
const pedZ = makePedSignal();
|
//
|
||||||
const pedZPole = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.06, 2.4, 6), pedPoleMat);
|
// The east crosswalk crosses the horizontal (x) road, so its
|
||||||
pedZPole.position.set(x + sw + 0.6, 1.2, z + sw - 0.6);
|
// signal follows the x-axis vehicle state. It sits on the east
|
||||||
group.add(pedZPole);
|
// sidewalk facing north, for walkers arriving from the north.
|
||||||
pedZ.position.set(x + sw + 0.6, 2.5, z + sw - 0.6);
|
const pedEast = makePedSignal();
|
||||||
pedZ.rotation.y = Math.PI; // faces south crosswalk
|
const pedEastPole = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.06, 2.4, 6), pedPoleMat);
|
||||||
group.add(pedZ);
|
pedEastPole.position.set(x + sw + 0.6, 1.2, z + sw - 0.6);
|
||||||
pedHeads.push({ head: pedZ, axis: 'z' });
|
group.add(pedEastPole);
|
||||||
|
pedEast.position.set(x + sw + 0.6, 2.5, z + sw - 0.6);
|
||||||
|
pedEast.rotation.y = Math.PI; // faces north (approach from the north)
|
||||||
|
group.add(pedEast);
|
||||||
|
pedHeads.push({ head: pedEast, axis: 'x' });
|
||||||
|
|
||||||
const pedX = makePedSignal();
|
// The south crosswalk crosses the vertical (z) road, so its
|
||||||
const pedXPole = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.06, 2.4, 6), pedPoleMat);
|
// signal follows the z-axis vehicle state. It sits on the south
|
||||||
pedXPole.position.set(x + sw - 0.6, 1.2, z + sw + 0.6);
|
// sidewalk facing west, for walkers arriving from the east.
|
||||||
group.add(pedXPole);
|
const pedSouth = makePedSignal();
|
||||||
pedX.position.set(x + sw - 0.6, 2.5, z + sw + 0.6);
|
const pedSouthPole = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.06, 2.4, 6), pedPoleMat);
|
||||||
pedX.rotation.y = -Math.PI / 2; // faces west crosswalk
|
pedSouthPole.position.set(x + sw - 0.6, 1.2, z + sw + 0.6);
|
||||||
group.add(pedX);
|
group.add(pedSouthPole);
|
||||||
pedHeads.push({ head: pedX, axis: 'x' });
|
pedSouth.position.set(x + sw - 0.6, 2.5, z + sw + 0.6);
|
||||||
|
pedSouth.rotation.y = -Math.PI / 2; // faces west (approach from the east)
|
||||||
|
group.add(pedSouth);
|
||||||
|
pedHeads.push({ head: pedSouth, axis: 'z' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4329,6 +4338,64 @@ window.__city10 = {
|
|||||||
if (trafficSystem) trafficSystem.setPhase(name);
|
if (trafficSystem) trafficSystem.setPhase(name);
|
||||||
return this.trafficPhase;
|
return this.trafficPhase;
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Snapshot of every signal head (position, facing, axis and the lamp
|
||||||
|
* currently lit) so automated tests can assert placement and states.
|
||||||
|
*/
|
||||||
|
trafficLightHeads() {
|
||||||
|
if (!trafficSystem) return { vehicle: [], pedestrian: [] };
|
||||||
|
const vehicle = trafficSystem.heads.map(({ head, axis }) => {
|
||||||
|
const intensities = head.userData.lamps.map(
|
||||||
|
l => +l.material.emissiveIntensity.toFixed(2)
|
||||||
|
);
|
||||||
|
const active = ['red', 'yellow', 'green'][intensities.indexOf(Math.max(...intensities))];
|
||||||
|
return {
|
||||||
|
axis,
|
||||||
|
x: +head.position.x.toFixed(2),
|
||||||
|
y: +head.position.y.toFixed(2),
|
||||||
|
z: +head.position.z.toFixed(2),
|
||||||
|
rotationY: +head.rotation.y.toFixed(3),
|
||||||
|
active,
|
||||||
|
intensities,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const pedestrian = trafficSystem.pedHeads.map(({ head, axis }) => {
|
||||||
|
const red = +head.userData.lamps.red.material.emissiveIntensity.toFixed(2);
|
||||||
|
const green = +head.userData.lamps.green.material.emissiveIntensity.toFixed(2);
|
||||||
|
return {
|
||||||
|
axis,
|
||||||
|
x: +head.position.x.toFixed(2),
|
||||||
|
y: +head.position.y.toFixed(2),
|
||||||
|
z: +head.position.z.toFixed(2),
|
||||||
|
rotationY: +head.rotation.y.toFixed(3),
|
||||||
|
active: red > green ? 'red' : 'green',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return { vehicle, pedestrian };
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Per-vehicle stop-line probe: travel axis, distance to the next stop
|
||||||
|
* line and current speed, for red-light compliance checks.
|
||||||
|
*/
|
||||||
|
trafficProbe() {
|
||||||
|
if (!vehicleSystem) return [];
|
||||||
|
const out = [];
|
||||||
|
for (const v of vehicleSystem.vehicles) {
|
||||||
|
if (!v.pts) continue;
|
||||||
|
const probe = probeAhead(v.pts, v.s, v.L);
|
||||||
|
const cur = new THREE.Vector3();
|
||||||
|
posOnPath(v.pts, v.s, v.L, cur);
|
||||||
|
out.push({
|
||||||
|
x: +cur.x.toFixed(2),
|
||||||
|
z: +cur.z.toFixed(2),
|
||||||
|
speed: +(v.speed || 0).toFixed(2),
|
||||||
|
axis: probe.axis,
|
||||||
|
distToStop: +distanceToStopLine(probe, cur).toFixed(2),
|
||||||
|
state: trafficSystem ? trafficSystem.axisState(probe.axis) : null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
},
|
||||||
playerCrossingAllowed,
|
playerCrossingAllowed,
|
||||||
get barriersDown() { return trainSystem ? trainSystem.blocks : null; },
|
get barriersDown() { return trainSystem ? trainSystem.blocks : null; },
|
||||||
setHour(h) {
|
setHour(h) {
|
||||||
|
|||||||
@@ -372,5 +372,5 @@
|
|||||||
citySlug: @json($city->slug),
|
citySlug: @json($city->slug),
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<script type="module" src="{{ asset('js/city/app.js') }}"></script>
|
<script type="module" src="{{ asset('js/city/app.js') }}?v={{ filemtime(public_path('js/city/app.js')) }}"></script>
|
||||||
@endpush
|
@endpush
|
||||||
|
|||||||
Reference in New Issue
Block a user