Download www/js/main.js from 1024m/HF-Racing: direct link, hf CLI and curl.
- Browser
- Download file 6.76 kB
-
https://huggingface.co/spaces/1024m/HF-Racing/resolve/main/www/js/main.js
- Command line
-
hf download hf://spaces/1024m/HF-Racing/www/js/main.js
-
curl -L -o main.js https://huggingface.co/spaces/1024m/HF-Racing/resolve/main/www/js/main.js
6.76 kB
| import * as THREE from 'three'; | |
| import { GameMenu } from './ui-menu.js'; | |
| import { NetClient, SPAWN_OFFSETS } from './net.js'; | |
| import { DriveControls } from './controls.js'; | |
| import { LocalCar } from './vehicles.js'; | |
| import { Track } from './track.js'; | |
| import { DEFAULT_CAR, CAR_LABELS } from './cars.js'; | |
| const SPACE_URL = 'https://1024m-hf-racing.hf.space'; | |
| const footer = document.getElementById('footer-text'); | |
| const hud = document.getElementById('hud'); | |
| const speedEl = document.getElementById('speed-value'); | |
| const playerHud = document.getElementById('player-hud'); | |
| const playerName = document.getElementById('player-name'); | |
| const playerAvatar = document.getElementById('player-avatar'); | |
| const spectateBar = document.getElementById('spectate-bar'); | |
| const scene = new THREE.Scene(); | |
| scene.background = new THREE.Color(0x87a0b5); | |
| scene.fog = new THREE.Fog(0x87a0b5, 80, 400); | |
| const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 800); | |
| camera.position.set(0, 6, -12); | |
| const renderer = new THREE.WebGLRenderer({ antialias: true }); | |
| renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| renderer.shadowMap.enabled = true; | |
| renderer.domElement.id = 'game-canvas'; | |
| document.body.prepend(renderer.domElement); | |
| const hemi = new THREE.HemisphereLight(0xddeeff, 0x443322, 0.85); | |
| scene.add(hemi); | |
| const sun = new THREE.DirectionalLight(0xfff2dd, 1.1); | |
| sun.position.set(30, 50, 10); | |
| sun.castShadow = true; | |
| scene.add(sun); | |
| const track = new Track(scene); | |
| const controls = new DriveControls(); | |
| let car = null; | |
| let inRace = false; | |
| let spectating = false; | |
| let specIndex = 0; | |
| let trackReady = null; | |
| const net = new NetClient({ | |
| scene, | |
| onStatus: (msg) => { | |
| if (footer) footer.textContent = msg; | |
| }, | |
| onLobbyUpdate: (board, mode, err) => menu.renderBoard(board, mode, err), | |
| onMatchStart: enterRace, | |
| onMatchEnd: () => { | |
| if (footer) footer.textContent = 'Race finished — Esc for menu'; | |
| }, | |
| onMatchConnectionLost: () => { | |
| leaveRaceToMenu(); | |
| }, | |
| }); | |
| const menu = new GameMenu({ | |
| root: document.getElementById('menu-root'), | |
| onOpenMode: (mode) => net.startLobbyPoll(mode), | |
| onBack: async () => { | |
| net.stopLobbyPoll(); | |
| await net.leaveLobby(); | |
| }, | |
| onClaim: (mode, lobbyId, seat) => net.claim(mode, lobbyId, seat), | |
| onLeaveSeat: () => net.leaveLobby(), | |
| onEnterMatch: () => net.connectMatch(), | |
| onSpectate: (mode, lobbyId) => { | |
| menu.hide(); | |
| hud.hidden = false; | |
| net.connectSpectate(mode, lobbyId); | |
| }, | |
| }); | |
| function setIdentityHud(identity) { | |
| if (!identity?.username) return; | |
| if (playerName) playerName.textContent = identity.username; | |
| if (playerAvatar && identity.avatarUrl) { | |
| playerAvatar.src = identity.avatarUrl; | |
| playerAvatar.hidden = false; | |
| } | |
| if (playerHud) playerHud.hidden = false; | |
| } | |
| async function ensureTrack() { | |
| if (!trackReady) { | |
| trackReady = track.loadShanghai(); | |
| } | |
| return trackReady; | |
| } | |
| async function enterRace(info) { | |
| inRace = true; | |
| spectating = !!info.spectating; | |
| menu.hide(); | |
| hud.hidden = false; | |
| if (spectateBar) spectateBar.hidden = !spectating; | |
| const mapOk = await ensureTrack(); | |
| const grid = track.getGridPose(info.spawnIndex || 0); | |
| if (!spectating) { | |
| const carId = info.carId || (info.mode === 'sandbox' ? DEFAULT_CAR : DEFAULT_CAR); | |
| if (car) { | |
| car.dispose(); | |
| car = null; | |
| } | |
| car = new LocalCar(scene); | |
| const loaded = await car.loadModel(carId); | |
| car.setPose(grid.x, grid.y, grid.z, grid.yaw); | |
| const label = CAR_LABELS[carId] || carId; | |
| if (footer) { | |
| footer.textContent = loaded | |
| ? `Race live · ${label}${mapOk ? ' · Shanghai' : ' · map fallback'}` | |
| : `Race live · car missing (${carId})`; | |
| } | |
| } else if (footer) { | |
| footer.textContent = `Spectating · ${info.mode}`; | |
| } | |
| } | |
| function leaveRaceToMenu() { | |
| inRace = false; | |
| spectating = false; | |
| net.disconnectMatch(); | |
| if (car) { | |
| car.dispose(); | |
| car = null; | |
| } | |
| hud.hidden = true; | |
| if (spectateBar) spectateBar.hidden = true; | |
| menu.show(); | |
| net.startLobbyPoll(menu.mode || 'sandbox'); | |
| } | |
| document.addEventListener('keydown', (e) => { | |
| if (e.code === 'Escape' && inRace) { | |
| leaveRaceToMenu(); | |
| } | |
| }); | |
| document.getElementById('spec-prev')?.addEventListener('click', () => { | |
| specIndex = Math.max(0, specIndex - 1); | |
| }); | |
| document.getElementById('spec-next')?.addEventListener('click', () => { | |
| specIndex += 1; | |
| }); | |
| window.addEventListener('resize', () => { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| }); | |
| /** GTA-style chase cam — behind and above, looking at car rear/cabin. */ | |
| function followCar(target, delta) { | |
| if (!target) return; | |
| const yaw = target.yaw ?? target.mesh?.rotation.y ?? 0; | |
| const pos = target.position; | |
| const dist = 9.5; | |
| const height = 3.2; | |
| const lookLift = 1.15; | |
| const behind = new THREE.Vector3( | |
| -Math.sin(yaw) * dist, | |
| height, | |
| -Math.cos(yaw) * dist, | |
| ); | |
| const desired = pos.clone().add(behind); | |
| camera.position.lerp(desired, Math.min(1, delta * 6)); | |
| const look = new THREE.Vector3(pos.x, pos.y + lookLift, pos.z); | |
| camera.lookAt(look); | |
| } | |
| let last = performance.now(); | |
| function frame(now) { | |
| const delta = Math.min(0.05, (now - last) / 1000); | |
| last = now; | |
| if (inRace && !spectating && car) { | |
| car.update(delta, controls.sample()); | |
| if (speedEl) speedEl.textContent = String(car.speedKmh()); | |
| followCar(car, delta); | |
| } else if (inRace && spectating) { | |
| const names = net.peers.list(); | |
| if (names.length) { | |
| specIndex = ((specIndex % names.length) + names.length) % names.length; | |
| const peer = net.peers.get(names[specIndex]); | |
| if (peer) { | |
| followCar({ position: peer.mesh.position, yaw: peer.mesh.rotation.y, mesh: peer.mesh }, delta); | |
| const nameEl = document.getElementById('spec-name'); | |
| if (nameEl) nameEl.textContent = names[specIndex]; | |
| } | |
| } | |
| } | |
| net.update(delta, car); | |
| renderer.render(scene, camera); | |
| requestAnimationFrame(frame); | |
| } | |
| async function boot() { | |
| if (footer) footer.textContent = `Space: ${SPACE_URL} · loading…`; | |
| ensureTrack(); // warm map load in background | |
| const identity = await net.initLocal(); | |
| menu.setIdentity({ | |
| username: identity.username, | |
| spaceUrl: identity.spaceUrl || SPACE_URL, | |
| authError: identity.ok ? null : identity.error, | |
| playAllowed: identity.playAllowed !== false, | |
| }); | |
| if (identity.ok) setIdentityHud(identity); | |
| if (footer) { | |
| footer.textContent = identity.ok | |
| ? `Signed in · Space ${SPACE_URL}` | |
| : (identity.error || 'Set HF_TOKEN in .env.local'); | |
| } | |
| requestAnimationFrame(frame); | |
| } | |
| boot(); | |