Refactor area preview orchestration

This commit is contained in:
2026-08-04 12:55:20 +08:00
parent 0ce7d1ea5e
commit 396df5127c
16 changed files with 541 additions and 455 deletions

View File

@@ -0,0 +1,39 @@
"use strict";
function makeVehicleGltf() {
const meshes = [], nodes = [], bufferParts = [], bufferViews = [], accessors = [];
function align4(bytes) { while (bytes.length % 4 !== 0) bytes.push(0); }
function addBufferView(bytes, target) {
align4(bufferParts); const offset = bufferParts.length; bufferParts.push(...bytes);
const view = { buffer: 0, byteOffset: offset, byteLength: bytes.length }; if (target) view.target = target;
bufferViews.push(view); return bufferViews.length - 1;
}
function floatBytes(values) { const buffer = Buffer.alloc(values.length * 4); values.forEach((value, index) => buffer.writeFloatLE(value, index * 4)); return Array.from(buffer); }
function ushortBytes(values) { const buffer = Buffer.alloc(values.length * 2); values.forEach((value, index) => buffer.writeUInt16LE(value, index * 2)); return Array.from(buffer); }
function addAccessor(bufferView, componentType, count, type, min, max) { const accessor = { bufferView, componentType, count, type }; if (min) accessor.min = min; if (max) accessor.max = max; accessors.push(accessor); return accessors.length - 1; }
function addMesh(name, geometry, materialIndex) {
const positionView = addBufferView(floatBytes(geometry.positions), 34962);
const indexView = addBufferView(ushortBytes(geometry.indices), 34963);
const positionAccessor = addAccessor(positionView, 5126, geometry.positions.length / 3, "VEC3", geometry.min, geometry.max);
const indexAccessor = addAccessor(indexView, 5123, geometry.indices.length, "SCALAR");
meshes.push({ name, primitives: [{ attributes: { POSITION: positionAccessor }, indices: indexAccessor, material: materialIndex }] });
nodes.push({ name, mesh: meshes.length - 1 });
}
addMesh("body", cuboid(0, 0, 0.72, 4.6, 1.9, 0.9), 0);
addMesh("hood", cuboid(1.35, 0, 1.1, 1.25, 1.74, 0.38), 0);
addMesh("cabin", cuboid(-0.55, 0, 1.42, 1.75, 1.55, 0.82), 1);
addMesh("rear", cuboid(-1.65, 0, 1.08, 0.95, 1.78, 0.42), 0);
addMesh("front_windshield", cuboid(0.28, 0, 1.58, 0.12, 1.42, 0.58), 2);
addMesh("left_window", cuboid(-0.55, -0.82, 1.52, 1.25, 0.08, 0.48), 2);
addMesh("right_window", cuboid(-0.55, 0.82, 1.52, 1.25, 0.08, 0.48), 2);
for (const x of [-1.55, 1.45]) for (const y of [-1.02, 1.02]) { addMesh(`wheel_${x}_${y}`, cylinderY(x, y, 0.46, 0.38, 0.32, 16), 3); addMesh(`hub_${x}_${y}`, cylinderY(x, y, 0.46, 0.2, 0.34, 12), 4); }
addMesh("left_headlight", cuboid(2.36, -0.48, 0.9, 0.08, 0.32, 0.16), 5); addMesh("right_headlight", cuboid(2.36, 0.48, 0.9, 0.08, 0.32, 0.16), 5);
addMesh("left_tail", cuboid(-2.36, -0.55, 0.9, 0.08, 0.28, 0.16), 6); addMesh("right_tail", cuboid(-2.36, 0.55, 0.9, 0.08, 0.28, 0.16), 6);
const buffer = Buffer.from(bufferParts);
return { asset: { version: "2.0", generator: "osm-asset-pipeline vehicle preview" }, scene: 0, scenes: [{ nodes: nodes.map((_, index) => index) }], nodes, meshes, buffers: [{ byteLength: buffer.length, uri: `data:application/octet-stream;base64,${buffer.toString("base64")}` }], bufferViews, accessors, materials: [material("paint red", [0.82, 0.05, 0.035, 1], 0.55, 0.25), material("dark roof", [0.08, 0.08, 0.085, 1], 0.45, 0.35), material("glass", [0.04, 0.12, 0.16, 0.82], 0.18, 0.08), material("tire", [0.015, 0.014, 0.013, 1], 0.75, 0.65), material("wheel hub", [0.72, 0.72, 0.68, 1], 0.35, 0.85), material("headlight", [1, 0.92, 0.62, 1], 0.12, 0), material("tail light", [0.95, 0.03, 0.03, 1], 0.25, 0)] };
}
function material(name, color, roughness, metallic) { return { name, pbrMetallicRoughness: { baseColorFactor: color, roughnessFactor: roughness, metallicFactor: metallic } }; }
function cuboid(cx, lateral, up, sx, width, height) { const x0 = cx - sx / 2, x1 = cx + sx / 2, y0 = up - height / 2, y1 = up + height / 2, z0 = lateral - width / 2, z1 = lateral + width / 2; return { positions: [x0,y0,z0,x1,y0,z0,x1,y1,z0,x0,y1,z0,x0,y0,z1,x1,y0,z1,x1,y1,z1,x0,y1,z1], indices: [0,1,2,0,2,3,4,6,5,4,7,6,0,4,5,0,5,1,1,5,6,1,6,2,2,6,7,2,7,3,3,7,4,3,4,0], min: [x0,y0,z0], max: [x1,y1,z1] }; }
function cylinderY(cx, lateral, up, radius, width, segments) { const positions = [], indices = [], z0 = lateral - width / 2, z1 = lateral + width / 2; for (const z of [z0, z1]) { positions.push(cx, up, z); for (let i = 0; i < segments; i += 1) { const angle = 2 * Math.PI * i / segments; positions.push(cx + Math.cos(angle) * radius, up + Math.sin(angle) * radius, z); } } const center0 = 0, center1 = segments + 1; for (let i = 0; i < segments; i += 1) { const a0 = center0 + 1 + i, b0 = center0 + 1 + ((i + 1) % segments), a1 = center1 + 1 + i, b1 = center1 + 1 + ((i + 1) % segments); indices.push(center0,b0,a0,center1,a1,b1,a0,b0,b1,a0,b1,a1); } return { positions, indices, min: [cx - radius, up - radius, z0], max: [cx + radius, up + radius, z1] }; }
module.exports = { makeVehicleGltf };