fix(preview): align traffic-aware vehicles to roads

This commit is contained in:
2026-08-06 17:00:51 +08:00
parent 2afab19463
commit 950cd1c4cd

View File

@@ -15,6 +15,11 @@
const toggleFps = document.getElementById("toggleFps");
const toggleDiagnostics = document.getElementById("toggleDiagnostics");
const assetToggles = document.getElementById("assetToggles");
// The exported road surface sits at the 0.35m scene anchor plus 0.03m.
// Vehicle models have their wheels at local Y=0, so keep them just clear
// of the asphalt instead of using the old visibly floating 1.15m height.
const VEHICLE_HEIGHT_METERS = 0.40;
const ROUTE_LINE_HEIGHT_METERS = 0.42;
const semanticToggles = document.getElementById("semanticToggles");
const vehicleSelect = document.getElementById("vehicleSelect");
const speedControl = document.getElementById("speedControl");
@@ -591,10 +596,11 @@
function addCruiseVehicle(viewer, segment, index, start, speed, signalData, vehicleModelName) {
const route = prepareRoute(segment, signalData);
const positions = createTrafficAwarePositions(viewer, route, start, speed);
const trafficMotion = createTrafficAwarePositions(viewer, route, start, speed);
const positions = trafficMotion.positions;
const flat = [];
for (const coord of segment.coordinates) {
flat.push(coord[0], coord[1], 1.05);
flat.push(coord[0], coord[1], ROUTE_LINE_HEIGHT_METERS);
}
const routeColor = [
Cesium.Color.CYAN,
@@ -616,7 +622,7 @@
const vehicle = viewer.entities.add({
name: "Cruise vehicle " + (index + 1),
position: positions,
orientation: routeOrientationFromPositions(positions),
orientation: routeOrientationFromState(route, trafficMotion.state, speed, 0.0),
model: {
uri: vehicleModelName,
// Keep the library's world scale visible. A minimum screen size made
@@ -686,7 +692,6 @@
function createTrafficAwarePositions(viewer, route, start, speed) {
const state = { distance: 0, lastTime: start.clone() };
const scratchTime = new Cesium.JulianDate();
viewer.clock.onTick.addEventListener((clock) => {
const elapsed = Cesium.JulianDate.secondsDifference(clock.currentTime, state.lastTime);
Cesium.JulianDate.clone(clock.currentTime, state.lastTime);
@@ -702,7 +707,10 @@
}
state.distance = (state.distance + advance) % route.length;
});
return new Cesium.CallbackProperty((time, result) => routePositionAtDistance(route, state.distance, result), false);
return {
state,
positions: new Cesium.CallbackProperty((time, result) => routePositionAtDistance(route, state.distance, result), false),
};
}
function nextRouteStop(route, distance) {
@@ -727,7 +735,7 @@
const b = route.coordinates[index];
const lon = a[0] + (b[0] - a[0]) * t;
const lat = a[1] + (b[1] - a[1]) * t;
return Cesium.Cartesian3.fromDegrees(lon, lat, 1.15, Cesium.Ellipsoid.WGS84, result);
return Cesium.Cartesian3.fromDegrees(lon, lat, VEHICLE_HEIGHT_METERS, Cesium.Ellipsoid.WGS84, result);
}
function routeOrientation(route, start, speed, yawDegrees) {
@@ -776,8 +784,41 @@
}, false);
}
function routeOrientationFromPositions(positions) {
return new Cesium.VelocityOrientationProperty(positions);
function routeOrientationFromState(route, state, speed, yawDegrees) {
const correction = Cesium.Quaternion.fromAxisAngle(
Cesium.Cartesian3.UNIT_Z,
Cesium.Math.toRadians(yawDegrees)
);
const current = new Cesium.Cartesian3();
const ahead = new Cesium.Cartesian3();
const direction = new Cesium.Cartesian3();
const up = new Cesium.Cartesian3();
const east = new Cesium.Cartesian3();
const north = new Cesium.Cartesian3();
const hpr = new Cesium.HeadingPitchRoll(0.0, 0.0, 0.0);
const base = new Cesium.Quaternion();
return new Cesium.CallbackProperty((time, result) => {
routePositionAtDistance(route, state.distance, current);
// Sample a small distance ahead, rather than using velocity. This keeps
// the car aligned while stopped and preserves the model's route heading.
const lookAhead = Math.max(0.8, speed * 0.8);
routePositionAtDistance(route, (state.distance + lookAhead) % route.length, ahead);
Cesium.Cartesian3.subtract(ahead, current, direction);
if (Cesium.Cartesian3.magnitudeSquared(direction) < 0.0001) return result;
Cesium.Cartesian3.normalize(direction, direction);
Cesium.Cartesian3.normalize(current, up);
Cesium.Cartesian3.cross(Cesium.Cartesian3.UNIT_Z, up, east);
if (Cesium.Cartesian3.magnitudeSquared(east) < 0.0001) {
Cesium.Cartesian3.clone(Cesium.Cartesian3.UNIT_X, east);
} else {
Cesium.Cartesian3.normalize(east, east);
}
Cesium.Cartesian3.cross(up, east, north);
Cesium.Cartesian3.normalize(north, north);
hpr.heading = Math.atan2(Cesium.Cartesian3.dot(direction, east), Cesium.Cartesian3.dot(direction, north));
Cesium.Transforms.headingPitchRollQuaternion(current, hpr, undefined, undefined, base);
return Cesium.Quaternion.multiply(base, correction, result || new Cesium.Quaternion());
}, false);
}
function createChaseFollow(viewer, positionsProvider) {