Download www/js/track.js from 1024m/HF-Racing: direct link, hf CLI and curl.
- Browser
- Download file 4.05 kB
-
https://huggingface.co/spaces/1024m/HF-Racing/resolve/main/www/js/track.js
- Command line
-
hf download hf://spaces/1024m/HF-Racing/www/js/track.js
-
curl -L -o track.js https://huggingface.co/spaces/1024m/HF-Racing/resolve/main/www/js/track.js
4.05 kB
| /** | |
| * 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, | |
| }; | |
| } | |
| } | |