Download static/watch.js from Death242/void: direct link, hf CLI and curl.
- Browser
- Download file 8.01 kB
-
https://huggingface.co/spaces/Death242/void/resolve/main/static/watch.js
- Command line
-
hf download hf://spaces/Death242/void/static/watch.js
-
curl -L -o watch.js https://huggingface.co/spaces/Death242/void/resolve/main/static/watch.js
8.01 kB
| // Watch: the 48 s film. Its clock is the song's clock (26 bars at 130 bpm) whenever sound is on, | |
| // so the loop lands on the beat, the ramp gets the build-up and lift-off gets the drop. | |
| import * as THREE from "three"; | |
| import { clamp, lerp, smooth, keyed, G, R, LZ, frame, loc, zonesFrom } from "./world.js"; | |
| import { BAR, SONG_BARS } from "./sound.js"; | |
| import { HEAD } from "./interior.js"; | |
| export const TOTAL = SONG_BARS * BAR; | |
| export const CHAPTERS = [ | |
| [0, "Build", "Blueprint to carbon."], [2, "Roll out", "Flat out from a standstill."], [4.5, "The loop", "A full 360°. Real loop physics."], | |
| [6, "The ramp", "Foot down. 33° kicker."], [8, "Lift-off", "Four thrusters fire straight down."], [11, "Wings out", "Thrusters swing back. It glides over Dammam."], | |
| [17.5, "Front seat", "Inside the Hydra LTE242."], [20.2, "The screen", "Dashcam and a landing scan within 5 km."], [22, "", ""], | |
| ]; | |
| export function createWatch(W, car, interior) { | |
| const T = W.track, fr = frame(), basis = new THREE.Matrix4(), q = new THREE.Quaternion(); | |
| const dStart = T.D[T.idxX(0)], dX = x => T.D[T.idxX(x, T.roadEnd)]; | |
| const RIDE = [[0, dStart], [.065, T.D[T.loopIn]], [.245, T.D[T.loopOut]], [.33, T.D[T.rampIn]], [.385, T.D[T.roadEnd]], [.5, dX(77)], [.64, dX(180)], [.78, dX(420)], [1, dX(880)]]; | |
| const rideP = b => b < 2 ? 0 : keyed([[2, 0], [4.5, .065], [6, .245], [7.2, .33], [8, .385], [11, .5], [14, .64], [17.5, .735], [24, 1]], b); | |
| const speedMS = (p, y) => { | |
| if (p < .065) return 30 * Math.pow(smooth(0, .065, p), .6); | |
| if (p < .245) return Math.sqrt(Math.max(30 * 30 - 2 * G * y, 9)); | |
| if (p < .33) return lerp(30, 48, smooth(.245, .33, p)); | |
| if (p < .385) return Math.sqrt(Math.max(48 * 48 - 2 * G * y, 9)); | |
| return keyed([[.385, 47], [.5, 72], [.64, 88], [.78, 84], [1, 81]], p); | |
| }; | |
| const thrustDeploy = p => smooth(.355, .395, p), thrustPower = p => keyed([[.37, 0], [.392, 1], [.5, 1], [.56, .5], [.66, .35], [1, .3]], p); | |
| const thrustAngle = p => keyed([[.39, 0], [.47, .12], [.56, .85], [.62, 1]], p), wingsOut = p => smooth(.5, .585, p); | |
| // where the car is at each bar, with real speed changes inside each section: it pulls away from | |
| // rest, slows over the top of the loop and whips through the bottom, builds up the run-up and | |
| // ramp, surges on the thrusters, then glides. w(s) is the speed shape over the section. | |
| const tmp = frame(), V0 = 30, dFilm = T.D[T.idxX(-55)]; | |
| const SECTIONS = [ | |
| [2, 4.5, dFilm, T.D[T.loopIn], s => Math.sqrt(s + .02)], | |
| [4.5, 6, T.D[T.loopIn], T.D[T.loopOut], s => Math.sqrt(Math.max(.05, 1 - 2 * G * T.sample(lerp(T.D[T.loopIn], T.D[T.loopOut], s), tmp).p.y / (V0 * V0)))], | |
| [6, 8, T.D[T.loopOut], T.D[T.roadEnd], s => .75 + .35 * s], | |
| [8, 11, T.D[T.roadEnd], dX(T.P[T.roadEnd].x + 110), s => Math.sqrt(.25 + 1.4 * s)], | |
| [11, 17.5, dX(T.P[T.roadEnd].x + 110), dX(T.P[T.roadEnd].x + 1150), () => 1], | |
| [17.5, 24, dX(T.P[T.roadEnd].x + 1150), dX(T.P[T.roadEnd].x + 2100), () => 1], | |
| ].map(([b0, b1, d0, d1, w]) => { | |
| const N = 240, tau = new Float32Array(N + 1); | |
| for (let i = 1; i <= N; i++) tau[i] = tau[i - 1] + 2 / (w((i - 1) / N) + w(i / N)) / N; | |
| for (let i = 1; i <= N; i++) tau[i] /= tau[N]; | |
| return { b0, b1, d0, d1, tau, N }; | |
| }); | |
| const distAt = b => { | |
| if (b <= 2) return dFilm; | |
| const S = SECTIONS.find(x => b <= x.b1) || SECTIONS[SECTIONS.length - 1]; | |
| const u = clamp((b - S.b0) / (S.b1 - S.b0)); | |
| let lo = 0, hi = S.N; while (hi - lo > 1) { const m = (lo + hi) >> 1; if (S.tau[m] <= u) lo = m; else hi = m; } | |
| const s = (lo + clamp((u - S.tau[lo]) / Math.max(1e-6, S.tau[hi] - S.tau[lo]))) / S.N; | |
| return lerp(S.d0, S.d1, s); | |
| }; | |
| const FILM = 1; // real time | |
| const UP = new THREE.Vector3(0, 1, 0); | |
| const SHOTS = [ | |
| [0, .05, f => ({ pos: loc(f, -1, 1.3, 8), look: loc(f, 0, .6, 0), fov: 40 })], | |
| [.06, .065, f => ({ pos: loc(f, -7.5, 2.3, 4.5), look: loc(f, 4, .6, 0), fov: 42 })], | |
| [.085, .23, f => ({ pos: new THREE.Vector3(16, 9, LZ + 25), look: new THREE.Vector3(16, 9, 2).lerp(f.p, .3), fov: 44 })], | |
| [.255, .37, f => ({ pos: f.p.clone().add(new THREE.Vector3(-7, 2, 9)), look: f.p.clone().add(new THREE.Vector3(5, 1.4, 0)), fov: 46 })], | |
| [.395, .48, f => ({ pos: loc(f, -9, -1.8, 7), look: loc(f, 2, 1, 0), fov: 50 })], | |
| [.52, .62, f => ({ pos: loc(f, -4, 2.2, 12), look: loc(f, 1.5, .5, 0), fov: 42 })], | |
| [.665, 1, f => ({ pos: loc(f, -11, 3.2, 2), look: loc(f, 8, 1, 0), fov: 48 })], | |
| ]; | |
| const shotAt = (p, f) => { | |
| for (let i = 0; i < SHOTS.length; i++) { | |
| const [a, b, fn] = SHOTS[i]; | |
| if (p <= b || i === SHOTS.length - 1) { | |
| if (p >= a || i === 0) return fn(f); | |
| const prev = SHOTS[i - 1], w = smooth(prev[1], a, p), A = prev[2](f), B = fn(f); | |
| return { pos: A.pos.lerp(B.pos, w), look: A.look.lerp(B.look, w), fov: lerp(A.fov, B.fov, w) }; | |
| } | |
| } | |
| }; | |
| const blend = (A, B, w) => ({ pos: A.pos.clone().lerp(B.pos, w), look: A.look.clone().lerp(B.look, w), fov: lerp(A.fov, B.fov, w), up: (A.up || UP).clone().lerp(B.up || UP, w).normalize() }); | |
| let prevB = 0; | |
| return { | |
| // everything about the film at time t (seconds) | |
| at(t, time, aspect, fovFor = f => f) { | |
| const b = clamp(t / BAR, 0, SONG_BARS), p = rideP(b); | |
| T.sample(distAt(b), fr); | |
| basis.makeBasis(fr.f, fr.u, fr.s); q.setFromRotationMatrix(basis); | |
| const v = b < 2 ? 0 : Math.min(93, (distAt(b + .02) - distAt(b)) / (.02 * BAR) * FILM), onLoop = fr.i >= T.loopIn && fr.i <= T.loopOut; | |
| const g = onLoop ? v * v / (R * G) + fr.u.y : (p > .385 ? 1 + thrustPower(p) * .25 : 1); | |
| const speed = v * 3.6, dep = thrustDeploy(p), power = thrustPower(p); | |
| // camera | |
| let cam; | |
| if (b < 2) { | |
| const h = b / 2, a = lerp(.75, 1.45, smooth(0, 1, h)); | |
| cam = { pos: fr.p.clone().add(new THREE.Vector3(Math.cos(a) * 7.4, lerp(2.2, 1.3, h), Math.sin(a) * 8.2)), look: fr.p.clone().add(new THREE.Vector3(0, .55, 0)), fov: 40 }; | |
| cam = blend(cam, shotAt(0, fr), smooth(.8, 1, h)); | |
| } else { | |
| cam = shotAt(p, fr); | |
| if (b > 17.2) { | |
| car.group.position.copy(fr.p); car.group.quaternion.copy(q); car.group.updateMatrixWorld(true); | |
| const head = { pos: HEAD.clone().applyMatrix4(car.group.matrixWorld), look: loc(fr, 30, .5, HEAD.z), up: fr.u.clone(), fov: 72 }; | |
| const glance = { pos: head.pos, look: interior.screen.getWorldPosition(new THREE.Vector3()), up: fr.u.clone(), fov: 66 }; | |
| const sv = interior.screenView(aspect, fovFor(46)), scr = { pos: sv.pos, look: sv.look, up: sv.up, fov: 46 }; | |
| if (b < 18.4) cam = blend(cam, head, smooth(17.5, 18.4, b)); | |
| else if (b < 20.2) cam = blend(head, glance, smooth(19.4, 20.2, b)); | |
| else cam = blend(glance, scr, smooth(20.2, 21.4, b)); | |
| } | |
| } | |
| if (b >= 8 && prevB < 8) this.onDrop && this.onDrop(); | |
| prevB = b; | |
| let gear = 1; const GEARS = [0, 60, 100, 140, 185, 235, 285, 340]; | |
| for (let i = 1; i < GEARS.length; i++) if (speed >= GEARS[i]) gear = i + 1; | |
| gear = Math.min(7, gear); | |
| const rpm = b < 8 && b >= 2 ? 2400 + clamp((speed - GEARS[gear - 1]) / ((GEARS[gear] || 340) - GEARS[gear - 1])) * 5200 : 900; | |
| return { | |
| bar: b, p, reveal: smooth(0, 1.8, b), pos: fr.p.clone(), quat: q.clone(), fr, speed, g, alt: Math.max(0, fr.p.y), vs: v * fr.f.y, | |
| thrust: { power: power * dep, angle: thrustAngle(p), deploy: dep }, wings: wingsOut(p), air: p > .385, onLoop, | |
| cam, inside: b > 18.1, screenOn: b > 18.2, mode: thrustAngle(p) > .9 ? "GLIDE" : p > .385 ? "THRUST" : "DRIVE", | |
| rpm, gear, load: b < 8 ? .8 : .15, engine: b < 8 ? 1 : .15, | |
| zones: zonesFrom(fr.p, fr.f), dream: smooth(22, 22.6, b), fade: smooth(23.4, 24, b), | |
| chapter: CHAPTERS.reduce((c, ch) => (b >= ch[0] ? ch : c), CHAPTERS[0]), | |
| }; | |
| }, | |
| }; | |
| } | |