feat(preview): add low poly vehicle library

This commit is contained in:
2026-08-05 16:00:04 +08:00
parent 436eb482fd
commit 3108336bf4
53 changed files with 15829 additions and 13 deletions

View File

@@ -13,13 +13,14 @@ function writeCesiumPreviewSupportFiles(outDir) {
}
}
function cesiumPreviewHtml(glbName, metadataName, routeName, vehicleModelName, areaId) {
function cesiumPreviewHtml(glbName, metadataName, routeName, vehicleModelName, areaId, vehicleModelNames = []) {
const previewConfig = {
areaId,
glbName,
metadataName,
routeName,
vehicleModelName,
vehicleModelNames,
};
return `<!doctype html>
<html lang="zh-CN">

View File

@@ -37,7 +37,7 @@
const viewer = createViewer();
setLoadingMessage("Loading model", config.glbName || "");
const assets = await loadSceneAssets(viewer, metadata, placement);
const cruise = addVehicleCruises(viewer, routeData, config.vehicleModelName);
const cruise = addVehicleCruises(viewer, routeData, config.vehicleModelNames, config.vehicleModelName);
const cameras = createCameraPresets(viewer, metadata, placement, cruise);
buildAssetToggles(assets);
@@ -422,7 +422,7 @@
return stopFollow;
}
function addVehicleCruises(viewer, routeData, vehicleModelName) {
function addVehicleCruises(viewer, routeData, vehicleModelNames, fallbackVehicleModelName) {
const segments = ((routeData && (routeData.routes || routeData.segments)) || [])
.filter((segment) => segment.coordinates && segment.coordinates.length >= 2)
.slice(0, 5);
@@ -436,7 +436,8 @@
viewer.clock.shouldAnimate = segments.length > 0;
const vehicles = segments.map((segment, index) => {
const vehicle = addCruiseVehicle(viewer, segment, index, start, speed, vehicleModelName);
const vehicle = addCruiseVehicle(viewer, segment, index, start, speed,
selectedVehicleModelName(vehicleModelNames, fallbackVehicleModelName));
const option = document.createElement("option");
option.value = String(index);
option.textContent = routeLabel(segment, index);
@@ -450,6 +451,14 @@
};
}
function selectedVehicleModelName(modelNames, fallbackModelName) {
const choices = Array.isArray(modelNames) && modelNames.length
? modelNames
: [fallbackModelName];
const usable = choices.filter(Boolean);
return usable[Math.floor(Math.random() * usable.length)] || "";
}
function addCruiseVehicle(viewer, segment, index, start, speed, vehicleModelName) {
const route = prepareRoute(segment);
const positions = new Cesium.CallbackProperty((time, result) => {
@@ -482,9 +491,10 @@
orientation: routeOrientation(route, start, speed, 0.0),
model: {
uri: vehicleModelName,
scale: 1.0,
minimumPixelSize: 24,
maximumScale: 80
// Keep the library's world scale visible. A minimum screen size made
// long trucks balloon in overview and at intersections.
scale: 0.72,
minimumPixelSize: 0
}
});
return {

View File

@@ -0,0 +1,49 @@
"use strict";
const fs = require("fs");
const path = require("path");
const VEHICLE_IDS = [
"car_a01_002",
"car_a02_002",
"car_a03_001",
"truck_a01_001",
"truck_a02_001",
"truck_a03_001",
];
const LIBRARY_ROOT = path.resolve(__dirname, "..", "..", "assets", "models", "custom", "lowpoly_cars");
const TEXTURE_NAME = "color_512x512.jpg";
const REVERSED_MODEL_IDS = new Set(["truck_a03_001"]);
function writePreviewVehicleLibrary(outDir, fileStem) {
const textureOutput = `${fileStem}-vehicle-texture.jpg`;
fs.mkdirSync(outDir, { recursive: true });
fs.copyFileSync(path.join(LIBRARY_ROOT, "textures", TEXTURE_NAME), path.join(outDir, textureOutput));
return VEHICLE_IDS.map((id) => {
const modelName = `${fileStem}-vehicle-${id}.gltf`;
const binName = `${fileStem}-vehicle-${id}.bin`;
const sourceDir = path.join(LIBRARY_ROOT, id);
const gltf = JSON.parse(fs.readFileSync(path.join(sourceDir, "model.gltf"), "utf8"));
for (const image of gltf.images || []) image.uri = textureOutput;
for (const buffer of gltf.buffers || []) buffer.uri = binName;
if (REVERSED_MODEL_IDS.has(id)) {
// The source's longitudinal PCA axis points rearward for this truck.
for (const node of gltf.nodes || []) node.rotation = [0, 1, 0, 0];
}
for (const material of gltf.materials || []) {
const pbr = material.pbrMetallicRoughness || (material.pbrMetallicRoughness = {});
// Match the Cesium foliage profile: lift albedo first, then use the
// texture itself as a modest shadow-side floor.
pbr.baseColorFactor = [2.1, 2.1, 2.1, 1];
if (pbr.baseColorTexture) {
material.emissiveTexture = { ...pbr.baseColorTexture };
material.emissiveFactor = [0.25, 0.25, 0.25];
}
}
fs.writeFileSync(path.join(outDir, modelName), `${JSON.stringify(gltf, null, 2)}\n`);
fs.copyFileSync(path.join(sourceDir, "model.bin"), path.join(outDir, binName));
return modelName;
});
}
module.exports = { VEHICLE_IDS, REVERSED_MODEL_IDS, writePreviewVehicleLibrary };