File size: 4,045 Bytes
d0a5946 0820aaf d0a5946 0820aaf d0a5946 0820aaf d0a5946 0820aaf d0a5946 0820aaf d0a5946 0820aaf d0a5946 0820aaf d0a5946 0820aaf d0a5946 0820aaf d0a5946 0820aaf d0a5946 0820aaf d0a5946 0820aaf d0a5946 0820aaf d0a5946 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | /**
* Shanghai International Circuit loader + start/grid poses.
*/
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const MAP_URL = 'assets/maps/shanghai/track.glb';
export class Track {
constructor(scene) {
this.scene = scene;
this.root = new THREE.Group();
scene.add(this.root);
this._loader = new GLTFLoader();
this.mapId = null;
this.ready = false;
this.start = { x: 0, y: 0.4, z: 0, yaw: 0 };
this._bbox = null;
// Temporary ground until map loads
const ground = new THREE.Mesh(
new THREE.PlaneGeometry(200, 200),
new THREE.MeshStandardMaterial({ color: 0x2a2622, roughness: 0.95 }),
);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
ground.name = '__placeholder';
this.root.add(ground);
}
async loadShanghai() {
try {
const gltf = await this._loader.loadAsync(MAP_URL);
while (this.root.children.length) {
this.root.remove(this.root.children[0]);
}
const model = gltf.scene;
model.traverse((o) => {
if (o.isMesh) {
o.castShadow = true;
o.receiveShadow = true;
}
});
this.root.add(model);
this.mapId = 'shanghai';
this.ready = true;
this._deriveStartFromMap(model);
return true;
} catch (err) {
console.warn('map load failed', err);
this.ready = false;
return false;
}
}
/**
* Find start/finish / pit / grid cues in node names; else use asphalt AABB heuristic.
* Shanghai SIC start/finish sits near the pit straight — we prefer named nodes,
* then fall back to the longest thin asphalt-ish bounds.
*/
_deriveStartFromMap(model) {
const nameHits = [];
const meshes = [];
model.updateWorldMatrix(true, true);
model.traverse((o) => {
if (!o.isMesh) return;
meshes.push(o);
const n = `${o.name || ''} ${o.parent?.name || ''}`.toLowerCase();
if (
n.includes('start') ||
n.includes('finish') ||
n.includes('grid') ||
n.includes('pit') ||
n.includes('s/f') ||
n.includes('sf_')
) {
nameHits.push(o);
}
});
let anchor = null;
let yaw = 0;
if (nameHits.length) {
const box = new THREE.Box3();
for (const m of nameHits) box.expandByObject(m);
const c = box.getCenter(new THREE.Vector3());
const size = box.getSize(new THREE.Vector3());
anchor = c;
// Face along the longer horizontal axis of the start strip.
yaw = size.x >= size.z ? 0 : Math.PI / 2;
} else {
// Fallback: whole-map center shifted toward a long strip (pit straight guess).
const box = new THREE.Box3().setFromObject(model);
this._bbox = box;
const c = box.getCenter(new THREE.Vector3());
const size = box.getSize(new THREE.Vector3());
// Shanghai-like: start straight often near one end of the long axis.
if (size.x >= size.z) {
anchor = new THREE.Vector3(c.x + size.x * 0.12, box.min.y, c.z);
yaw = 0;
} else {
anchor = new THREE.Vector3(c.x, box.min.y, c.z + size.z * 0.12);
yaw = Math.PI / 2;
}
}
const groundY = (() => {
const box = new THREE.Box3().setFromObject(model);
return box.min.y;
})();
this.start = {
x: anchor.x,
y: groundY + 0.35,
z: anchor.z,
yaw,
};
}
/** Grid slot relative to start line (GTA-style staggered pairs). */
getGridPose(index = 0) {
const i = Math.max(0, index | 0);
const row = Math.floor(i / 2);
const side = i % 2 === 0 ? -1 : 1;
const lateral = side * 3.2;
const back = -row * 7.5;
const yaw = this.start.yaw;
const fx = Math.sin(yaw);
const fz = Math.cos(yaw);
const rx = Math.cos(yaw);
const rz = -Math.sin(yaw);
return {
x: this.start.x + fx * back + rx * lateral,
y: this.start.y,
z: this.start.z + fz * back + rz * lateral,
yaw,
};
}
}
|