@adatechnology/game-vehicles-three 0.6.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-MRUPKE7J.js → chunk-L63R2JQA.js} +542 -29
- package/dist/chunk-L63R2JQA.js.map +1 -0
- package/dist/index.d.ts +148 -5
- package/dist/index.global.js +1401 -261
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +11 -1
- package/dist/{testing-C-BgV7gj.d.ts → testing-DI0_R8dH.d.ts} +4 -5
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/package.json +9 -9
- package/dist/chunk-MRUPKE7J.js.map +0 -1
|
@@ -1271,7 +1271,33 @@ var VEHICLE_SOUNDS = [
|
|
|
1271
1271
|
recipe("car_trunk_close", [tone({ f: 90, f2: 55, d: 0.15, vol: 0.65 }), noise({ type: "bandpass", f: 500, q: 1, d: 0.12, vol: 0.5 }), ...latch(0.01, 0.28), noise({ type: "bandpass", f: 1800, q: 3, d: 0.04, vol: 0.1, delay: 0.06 })], "trunk or hatch shut: a lighter thump, the latch and a small rattle"),
|
|
1272
1272
|
recipe("car_hood_close", [tone({ f: 70, f2: 40, d: 0.22, vol: 0.8 }), noise({ type: "bandpass", f: 850, q: 1.4, d: 0.2, vol: 0.6, wet: 0.3 }), tone({ f: 900, f2: 860, d: 0.18, vol: 0.05 }), ...latch(0.015, 0.3)], "hood slammed: a big sheet of metal ringing a little, the latch")
|
|
1273
1273
|
];
|
|
1274
|
-
var
|
|
1274
|
+
var CAR_RECORDED_RPM = { idle: 1e3, high: 2520 };
|
|
1275
|
+
var CAR_START_RUN_STOP = {
|
|
1276
|
+
/** The starter and the engine catching (at 0.58 s) and flaring; the loops take over at the crank time of the vehicle. */
|
|
1277
|
+
start: { offset: 0, duration: 1.3 },
|
|
1278
|
+
/** The ignition off: the engine running down to the end of the file. */
|
|
1279
|
+
stop: { offset: 9.96, duration: 0.64 }
|
|
1280
|
+
};
|
|
1281
|
+
var carLayer = (id, file, rpm, harmonic, offset) => ({
|
|
1282
|
+
id,
|
|
1283
|
+
file,
|
|
1284
|
+
loop: true,
|
|
1285
|
+
// the layer's oscillator plays the firing frequency times its harmonic: the file sounds at that frequency for the RPM it was recorded at
|
|
1286
|
+
baseFrequency: firingHz(CAR_ENGINE, rpm) * harmonic,
|
|
1287
|
+
falloff: VEHICLE_LOOP_FALLOFF,
|
|
1288
|
+
...offset === void 0 ? {} : { offset }
|
|
1289
|
+
});
|
|
1290
|
+
var VEHICLE_SAMPLE_ALIASES = [
|
|
1291
|
+
carLayer("engine_car_low", "engine_loop_car_idle", CAR_RECORDED_RPM.idle, 1),
|
|
1292
|
+
carLayer("engine_car_mid", "engine_loop_car_high", CAR_RECORDED_RPM.high, 1),
|
|
1293
|
+
// the same file as the mid layer, read from two seconds in, so the two do not sum in phase where they cross
|
|
1294
|
+
carLayer("engine_car_high", "engine_loop_car_high", CAR_RECORDED_RPM.high, 2, 2),
|
|
1295
|
+
{ id: "engine_start", file: "engine_start_run_stop", ...CAR_START_RUN_STOP.start, falloff: VEHICLE_LOOP_FALLOFF },
|
|
1296
|
+
{ id: "engine_stop", file: "engine_start_run_stop", ...CAR_START_RUN_STOP.stop, falloff: VEHICLE_LOOP_FALLOFF },
|
|
1297
|
+
{ id: "car_door_close", file: "car_door_shut" },
|
|
1298
|
+
{ id: "car_trunk_close", file: "car_trunk_shut" }
|
|
1299
|
+
];
|
|
1300
|
+
var VEHICLE_SOUND_BANK = { id: "vehicles_three", sounds: VEHICLE_SOUNDS, loops: VEHICLE_LOOPS, sampleAliases: VEHICLE_SAMPLE_ALIASES };
|
|
1275
1301
|
|
|
1276
1302
|
// src/test-area/module.ts
|
|
1277
1303
|
var VEHICLES_AREA_ID = "vehicles_area";
|
|
@@ -1280,6 +1306,7 @@ var ITEM_OF = { compact_hatch: "hatch", midsize_sedan: "sedan", midsize_pickup:
|
|
|
1280
1306
|
var itemId = (key) => ITEM_OF[key] ?? key;
|
|
1281
1307
|
var LANE = { z: 11, from: -12, wall: 13 };
|
|
1282
1308
|
var SPARK_EVERY_S = 0.08;
|
|
1309
|
+
var TYRE_DAMAGE = /* @__PURE__ */ new Set(["flat_tyre", "torn_tyre", "run_flat"]);
|
|
1283
1310
|
var LEAF_REST_RADIANS = 0.02;
|
|
1284
1311
|
var MATERIAL_COLOURS = { metal: 13421772, glass: 4500223, rubber: 16750848, fuel: 16724736 };
|
|
1285
1312
|
var LANE_START_S = 1.2;
|
|
@@ -1575,7 +1602,8 @@ function createVehiclesTestModule(options = {}) {
|
|
|
1575
1602
|
state.time += dt;
|
|
1576
1603
|
const braking = settings.damage !== "into_wall" && gap() < brakeAt() && state.time > 0.5;
|
|
1577
1604
|
const moving = state.time > LANE_START_S;
|
|
1578
|
-
const
|
|
1605
|
+
const straight = settings.damage === "into_wall" || TYRE_DAMAGE.has(settings.damage) || !moving;
|
|
1606
|
+
const input = { ...idle, throttle: braking || !moving ? 0 : 1, brake: braking ? 1 : 0, steer: straight ? 0 : Math.sin(state.time * 1.4) * 0.15 };
|
|
1579
1607
|
if (mode === "arcade") {
|
|
1580
1608
|
const { events } = arcade.step(input, dt, { blocked: (x, z, yaw) => vehicleCorners2(arcade.spec, x, z, yaw).some(([px]) => px >= LANE.wall) });
|
|
1581
1609
|
for (const e of events) {
|
|
@@ -1814,16 +1842,139 @@ function loadVehiclesTestModule(room, options = {}) {
|
|
|
1814
1842
|
}
|
|
1815
1843
|
|
|
1816
1844
|
// src/test-area/proving/module.ts
|
|
1817
|
-
import { createVehicleCamera, mirrorPoses } from "@adatechnology/game-camera";
|
|
1845
|
+
import { createVehicleCamera, defaultEye, mirrorPoses } from "@adatechnology/game-camera";
|
|
1818
1846
|
import { markKitOrigin as markKitOrigin3 } from "@adatechnology/game-contracts";
|
|
1819
|
-
import { CABIN_BANK, LIBRARY_BANK as LIBRARY_BANK2 } from "@adatechnology/game-audio";
|
|
1847
|
+
import { CABIN_BANK, LIBRARY_BANK as LIBRARY_BANK2, RADIO_BANK } from "@adatechnology/game-audio";
|
|
1820
1848
|
import { createSemiSimDriver as createSemiSimDriver4 } from "@adatechnology/game-vehicles-driving";
|
|
1821
1849
|
import { certify, createProvingTests, startRun, statusOf, subjectSpec as subjectSpec2, withResult } from "@adatechnology/game-vehicles-proving";
|
|
1822
1850
|
import { discardObject3D as discardObject3D3 } from "@adatechnology/game-shapes-three";
|
|
1823
1851
|
|
|
1824
1852
|
// src/test-area/proving/cockpit.ts
|
|
1825
|
-
import {
|
|
1853
|
+
import { RADIO_TRACKS } from "@adatechnology/game-audio";
|
|
1854
|
+
import { bumperDistance, createSemiSimDriver as createSemiSimDriver3, createVehicleControls, createVehicleSystems, parkingBeepInterval } from "@adatechnology/game-vehicles-driving";
|
|
1826
1855
|
import { subjectSpec } from "@adatechnology/game-vehicles-proving";
|
|
1856
|
+
import { createVehicleDestructible as createVehicleDestructible3 } from "@adatechnology/game-vehicles";
|
|
1857
|
+
|
|
1858
|
+
// src/body.ts
|
|
1859
|
+
var OPEN_SOUND = "car_door_open";
|
|
1860
|
+
var SHUT_SOUND = { door: "car_door_close", hood: "car_hood_close", lid: "car_trunk_close" };
|
|
1861
|
+
var clamp = (x, a, b) => Math.max(a, Math.min(b, x));
|
|
1862
|
+
function createVehicleBody(built, options) {
|
|
1863
|
+
const swing = options.swingSeconds ?? 1.2;
|
|
1864
|
+
const windowSeconds = options.windowSeconds ?? 3.5;
|
|
1865
|
+
const travel = options.windowTravel ?? 0.92;
|
|
1866
|
+
const root = built.context?.vehicle ?? built.group;
|
|
1867
|
+
const poses = /* @__PURE__ */ new Map();
|
|
1868
|
+
for (const clip of options.clips) {
|
|
1869
|
+
if (!clip.id.startsWith("open_")) continue;
|
|
1870
|
+
const last = clip.keys[clip.keys.length - 1];
|
|
1871
|
+
for (const [id, pose] of Object.entries(last?.parts ?? {})) if (pose.rotation) poses.set(id, pose.rotation);
|
|
1872
|
+
}
|
|
1873
|
+
const hinges = /* @__PURE__ */ new Map();
|
|
1874
|
+
for (const [id, open] of poses) {
|
|
1875
|
+
const pivot = root.getObjectByName(id);
|
|
1876
|
+
if (pivot) hinges.set(id, { pivot, open, now: 0, target: 0 });
|
|
1877
|
+
}
|
|
1878
|
+
const kind = (part) => part.startsWith("door") ? "door" : part === "hood" ? "hood" : "lid";
|
|
1879
|
+
const panes = /* @__PURE__ */ new Map();
|
|
1880
|
+
for (const glass of built.context?.glass ?? []) {
|
|
1881
|
+
const g = glass.userData.glass;
|
|
1882
|
+
if (!g || g.key !== "window_front" && g.key !== "window_rear") continue;
|
|
1883
|
+
const id = `${g.key === "window_front" ? "front" : "rear"}_${(g.side ?? 1) < 0 ? "left" : "right"}`;
|
|
1884
|
+
if (panes.has(id) || !glass.geometry) continue;
|
|
1885
|
+
glass.geometry.computeBoundingBox();
|
|
1886
|
+
const box = glass.geometry.boundingBox;
|
|
1887
|
+
if (box) panes.set(id, { pane: glass, drop: (box.max.y - box.min.y) * travel, now: 0, target: 0, applied: 0 });
|
|
1888
|
+
}
|
|
1889
|
+
const sunroofs = [];
|
|
1890
|
+
const wipers = [];
|
|
1891
|
+
const T = options.THREE;
|
|
1892
|
+
const turn = new T.Quaternion();
|
|
1893
|
+
root.traverse((o) => {
|
|
1894
|
+
const sun = o.userData.sunroof;
|
|
1895
|
+
if (sun) sunroofs.push({ obj: o, slide: sun.slide, base: o.position.x });
|
|
1896
|
+
});
|
|
1897
|
+
for (const { part, tag } of built.context?.wipers ?? [])
|
|
1898
|
+
wipers.push({ obj: part, arm: tag.arm, sweep: tag.sweep, spindle: new T.Vector3(...tag.spindle), axis: new T.Vector3(...tag.axis), rest: part.position.clone(), restQ: part.quaternion.clone() });
|
|
1899
|
+
let sunNow = 0;
|
|
1900
|
+
let sunTarget = 0;
|
|
1901
|
+
let wipePhase = 0;
|
|
1902
|
+
let locked = false;
|
|
1903
|
+
const placeWiper = (w, angle) => {
|
|
1904
|
+
if (angle === 0) {
|
|
1905
|
+
w.obj.position.copy(w.rest);
|
|
1906
|
+
w.obj.quaternion.copy(w.restQ);
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
turn.setFromAxisAngle(w.axis, angle);
|
|
1910
|
+
w.obj.position.copy(w.rest).sub(w.spindle).applyQuaternion(turn).add(w.spindle);
|
|
1911
|
+
w.obj.quaternion.copy(turn).multiply(w.restQ);
|
|
1912
|
+
};
|
|
1913
|
+
const ease = (now, target, seconds, dt) => now + clamp(target - now, -dt / seconds, dt / seconds);
|
|
1914
|
+
return {
|
|
1915
|
+
parts: [...hinges.keys()],
|
|
1916
|
+
windows: [...panes.keys()],
|
|
1917
|
+
hasSunroof: sunroofs.length > 0,
|
|
1918
|
+
wipers: new Set(wipers.map((w) => w.arm)).size,
|
|
1919
|
+
get locked() {
|
|
1920
|
+
return locked;
|
|
1921
|
+
},
|
|
1922
|
+
openness: (part) => hinges.get(part)?.now ?? 0,
|
|
1923
|
+
toggle(part) {
|
|
1924
|
+
const h = hinges.get(part);
|
|
1925
|
+
if (!h) return [{ type: "refused", part, reason: "missing" }];
|
|
1926
|
+
if (locked && kind(part) !== "hood" && h.target === 0) return [{ type: "refused", part, reason: "locked" }];
|
|
1927
|
+
h.target = h.target > 0 ? 0 : 1;
|
|
1928
|
+
return h.target > 0 ? [{ type: "sound", key: OPEN_SOUND, part }] : [];
|
|
1929
|
+
},
|
|
1930
|
+
window: (id) => panes.get(id)?.now ?? 0,
|
|
1931
|
+
toggleWindow(id) {
|
|
1932
|
+
const w = panes.get(id);
|
|
1933
|
+
if (w) w.target = w.target > 0.5 ? 0 : 1;
|
|
1934
|
+
},
|
|
1935
|
+
setWindow(id, open) {
|
|
1936
|
+
const w = panes.get(id);
|
|
1937
|
+
if (w) w.target = clamp(open, 0, 1);
|
|
1938
|
+
},
|
|
1939
|
+
toggleSunroof() {
|
|
1940
|
+
sunTarget = sunTarget > 0.5 ? 0 : 1;
|
|
1941
|
+
},
|
|
1942
|
+
lock(on) {
|
|
1943
|
+
locked = on;
|
|
1944
|
+
return [{ type: "sound", key: "blinker_tick", part: "doors" }];
|
|
1945
|
+
},
|
|
1946
|
+
update(dt, wipe = 0) {
|
|
1947
|
+
const events = [];
|
|
1948
|
+
for (const [id, h] of hinges) {
|
|
1949
|
+
if (h.now === h.target) continue;
|
|
1950
|
+
h.now = ease(h.now, h.target, swing, dt);
|
|
1951
|
+
h.pivot.rotation.set(h.open[0] * h.now, h.open[1] * h.now, h.open[2] * h.now);
|
|
1952
|
+
if (h.now === 0) events.push({ type: "sound", key: SHUT_SOUND[kind(id)] ?? "car_door_close", part: id });
|
|
1953
|
+
}
|
|
1954
|
+
for (const w of panes.values()) {
|
|
1955
|
+
if (w.now === w.target) continue;
|
|
1956
|
+
w.now = ease(w.now, w.target, windowSeconds, dt);
|
|
1957
|
+
const offset = -w.drop * w.now;
|
|
1958
|
+
w.pane.position.y += offset - w.applied;
|
|
1959
|
+
w.applied = offset;
|
|
1960
|
+
}
|
|
1961
|
+
if (sunNow !== sunTarget) {
|
|
1962
|
+
sunNow = ease(sunNow, sunTarget, 2.5, dt);
|
|
1963
|
+
for (const s of sunroofs) s.obj.position.x = s.base - s.slide * sunNow;
|
|
1964
|
+
}
|
|
1965
|
+
wipePhase = wipe > 0 ? (wipePhase + dt * wipe) % 1 : wipePhase > 0 ? Math.min(1, wipePhase + dt * 1.5) % 1 : 0;
|
|
1966
|
+
const sweep = Math.sin(Math.PI * wipePhase);
|
|
1967
|
+
for (const w of wipers) placeWiper(w, w.sweep * sweep);
|
|
1968
|
+
return events;
|
|
1969
|
+
},
|
|
1970
|
+
dispose() {
|
|
1971
|
+
for (const h of hinges.values()) h.pivot.rotation.set(0, 0, 0);
|
|
1972
|
+
for (const w of panes.values()) w.pane.position.y -= w.applied;
|
|
1973
|
+
for (const s of sunroofs) s.obj.position.x = s.base;
|
|
1974
|
+
for (const w of wipers) placeWiper(w, 0);
|
|
1975
|
+
}
|
|
1976
|
+
};
|
|
1977
|
+
}
|
|
1827
1978
|
|
|
1828
1979
|
// src/dashboard.ts
|
|
1829
1980
|
import { discardObject3D as discardObject3D2 } from "@adatechnology/game-shapes-three";
|
|
@@ -2000,6 +2151,8 @@ function createVehicleDashboard(THREE, built, options) {
|
|
|
2000
2151
|
lamp(g, 104, 214, r.highBeam, DASHBOARD_LOOK.blue, beam);
|
|
2001
2152
|
lamp(g, 152, 214, r.lowBeam || r.fog, r.fog ? DASHBOARD_LOOK.amber : DASHBOARD_LOOK.green, beam);
|
|
2002
2153
|
bar(g, 40, r.fuel, false, "F");
|
|
2154
|
+
lamp(g, 104, 244, !!r.cruise, DASHBOARD_LOOK.green, letter("CRZ"));
|
|
2155
|
+
if (r.profile && r.profile !== "normal") lamp(g, 152, 244, true, r.profile === "eco" ? DASHBOARD_LOOK.green : DASHBOARD_LOOK.amber, letter(r.profile === "eco" ? "E" : "S"));
|
|
2003
2156
|
} else {
|
|
2004
2157
|
dial(g, r.rpm / 1e3, Math.ceil(redline / 1e3) + 1, 1, redline / 1e3, (v) => String(Math.round(v)));
|
|
2005
2158
|
g.fillStyle = DASHBOARD_LOOK.marks;
|
|
@@ -2008,7 +2161,9 @@ function createVehicleDashboard(THREE, built, options) {
|
|
|
2008
2161
|
g.fillText(r.gear < 0 ? "R" : r.gear === 0 ? "N" : String(r.gear), SIZE / 2, SIZE / 2 + 54);
|
|
2009
2162
|
lamp(g, 86, 178, r.parkingBrake, DASHBOARD_LOOK.red, ring("P"));
|
|
2010
2163
|
lamp(g, 128, 214, r.ignition && !r.seatbelt, DASHBOARD_LOOK.red, belt);
|
|
2011
|
-
lamp(g, 170, 178, warn, DASHBOARD_LOOK.red, letter("+\u2212"));
|
|
2164
|
+
lamp(g, 170, 178, warn || r.engineOn && r.charging === false, DASHBOARD_LOOK.red, letter("+\u2212"));
|
|
2165
|
+
lamp(g, 104, 244, r.ignition && !!r.tractionOff, DASHBOARD_LOOK.amber, letter("TC"));
|
|
2166
|
+
lamp(g, 152, 244, r.ignition && !!r.absOff, DASHBOARD_LOOK.amber, letter("ABS"));
|
|
2012
2167
|
lamp(g, 86, 214, warn, DASHBOARD_LOOK.red, letter("OIL"));
|
|
2013
2168
|
lamp(g, 170, 214, r.fourWheel, DASHBOARD_LOOK.green, letter("4x4"));
|
|
2014
2169
|
bar(g, SIZE - 40, r.temperature, true, "T");
|
|
@@ -2197,7 +2352,18 @@ function createVehicleLamps(THREE, built, options = {}) {
|
|
|
2197
2352
|
}
|
|
2198
2353
|
|
|
2199
2354
|
// src/test-area/proving/cockpit.ts
|
|
2200
|
-
var DEFAULT_COCKPIT_SOUNDS = { horn: "horn", blinker: "blinker_tick", starter: "starter_crank", alarm: "car_alarm", chirp: "alarm_chirp", wiper: "wiper_swish", chime: "seatbelt_chime", radio: "radio_static" };
|
|
2355
|
+
var DEFAULT_COCKPIT_SOUNDS = { horn: "horn", blinker: "blinker_tick", starter: "starter_crank", alarm: "car_alarm", chirp: "alarm_chirp", wiper: "wiper_swish", chime: "seatbelt_chime", radio: "radio_static", beep: "parking_beep" };
|
|
2356
|
+
var BODY_KEYS = {
|
|
2357
|
+
Digit1: "door_front_left",
|
|
2358
|
+
Digit2: "door_front_right",
|
|
2359
|
+
Digit3: "door_rear_left",
|
|
2360
|
+
Digit4: "door_rear_right",
|
|
2361
|
+
Digit5: "hood",
|
|
2362
|
+
Digit6: "lid"
|
|
2363
|
+
};
|
|
2364
|
+
var WINDOW_KEYS = { Digit1: "front_left", Digit2: "front_right", Digit3: "rear_left", Digit4: "rear_right" };
|
|
2365
|
+
var LOCK_KEY = "KeyV";
|
|
2366
|
+
var WIPE_RATE = { off: 0, intermittent: 0.25, slow: 0.7, fast: 1.4 };
|
|
2201
2367
|
function createCockpit(o) {
|
|
2202
2368
|
const sounds = { ...DEFAULT_COCKPIT_SOUNDS, ...o.sounds };
|
|
2203
2369
|
const spec = subjectSpec(o.subject);
|
|
@@ -2214,19 +2380,38 @@ function createCockpit(o) {
|
|
|
2214
2380
|
let wipeAgain = 0;
|
|
2215
2381
|
let chimeAgain = 0;
|
|
2216
2382
|
let radioAgain = 0;
|
|
2383
|
+
let beepAgain = 0;
|
|
2384
|
+
let bar = 0;
|
|
2217
2385
|
let exitAsked = false;
|
|
2218
2386
|
let gas = 0;
|
|
2387
|
+
const systems = createVehicleSystems();
|
|
2388
|
+
const context = o.built.context;
|
|
2389
|
+
const destructible = o.kit && context ? createVehicleDestructible3(o.kit, context, { id: "driven", frame: o.built.group }) : null;
|
|
2390
|
+
const body = destructible ? createVehicleBody(o.built, { THREE: o.THREE, clips: destructible.animations() }) : null;
|
|
2391
|
+
const bodyEvents = (events) => {
|
|
2392
|
+
for (const event of events) {
|
|
2393
|
+
if (event.type === "sound") play(event.key, 0.9);
|
|
2394
|
+
o.onEvent?.(event);
|
|
2395
|
+
}
|
|
2396
|
+
};
|
|
2219
2397
|
const controls = createVehicleControls({
|
|
2398
|
+
canCrank: systems.canCrank,
|
|
2399
|
+
onCrank: systems.crank,
|
|
2400
|
+
powerLimit: systems.powerLimit,
|
|
2220
2401
|
gears: spec.gears?.length ?? 5,
|
|
2221
2402
|
fourWheelDrive: !!o.subject.body.fourWheelDrive,
|
|
2222
2403
|
fogLamps: lamps.has.fog,
|
|
2404
|
+
clutchPedal: true,
|
|
2405
|
+
radioTracks: RADIO_TRACKS.length,
|
|
2223
2406
|
onEvent: (event) => {
|
|
2224
2407
|
if (event.type === "command") {
|
|
2225
|
-
if (event.command === "ignition" && event.state.ignition && sounds.starter) play(sounds.starter);
|
|
2226
2408
|
if (event.command === "ignition" && !event.state.ignition) driver.state.engineOn = false;
|
|
2227
2409
|
if (event.command === "alarm" && event.state.alarm === "armed") play(sounds.chirp);
|
|
2228
2410
|
if (event.command === "alarm") alarmAgain = 0;
|
|
2229
|
-
if (event.command === "radio" || event.command === "radio_source"
|
|
2411
|
+
if (event.command === "radio" || event.command === "radio_source" || event.command === "radio_track") {
|
|
2412
|
+
radioAgain = 0;
|
|
2413
|
+
bar = 0;
|
|
2414
|
+
}
|
|
2230
2415
|
if (event.command === "exit") exitAsked = true;
|
|
2231
2416
|
}
|
|
2232
2417
|
o.onEvent?.(event);
|
|
@@ -2234,7 +2419,20 @@ function createCockpit(o) {
|
|
|
2234
2419
|
});
|
|
2235
2420
|
driver.state.engineOn = false;
|
|
2236
2421
|
const target = typeof window !== "undefined" ? window : null;
|
|
2422
|
+
const bodyKey = (e) => {
|
|
2423
|
+
if (!body) return false;
|
|
2424
|
+
if (e.code === LOCK_KEY) bodyEvents(body.lock(!body.locked));
|
|
2425
|
+
else if (e.shiftKey && e.code === "Digit5") body.toggleSunroof();
|
|
2426
|
+
else if (e.shiftKey && WINDOW_KEYS[e.code]) body.toggleWindow(WINDOW_KEYS[e.code]);
|
|
2427
|
+
else if (!e.shiftKey && BODY_KEYS[e.code]) bodyEvents(body.toggle(BODY_KEYS[e.code]));
|
|
2428
|
+
else return false;
|
|
2429
|
+
return true;
|
|
2430
|
+
};
|
|
2237
2431
|
const down = (e) => {
|
|
2432
|
+
if (!e.repeat && bodyKey(e)) {
|
|
2433
|
+
e.stopImmediatePropagation();
|
|
2434
|
+
return;
|
|
2435
|
+
}
|
|
2238
2436
|
if (!controls.key(e.code, true)) return;
|
|
2239
2437
|
e.stopImmediatePropagation();
|
|
2240
2438
|
if (e.code.startsWith("Arrow") || e.code === "Space") e.preventDefault();
|
|
@@ -2246,9 +2444,13 @@ function createCockpit(o) {
|
|
|
2246
2444
|
target?.addEventListener("keydown", down, true);
|
|
2247
2445
|
target?.addEventListener("keyup", up, true);
|
|
2248
2446
|
target?.addEventListener("blur", blur);
|
|
2249
|
-
|
|
2447
|
+
const cockpit = {
|
|
2448
|
+
radiator: 1,
|
|
2250
2449
|
driver,
|
|
2251
2450
|
controls,
|
|
2451
|
+
systems,
|
|
2452
|
+
body,
|
|
2453
|
+
destructible,
|
|
2252
2454
|
lamps,
|
|
2253
2455
|
dashboard,
|
|
2254
2456
|
get throttle() {
|
|
@@ -2257,11 +2459,16 @@ function createCockpit(o) {
|
|
|
2257
2459
|
step(dt) {
|
|
2258
2460
|
const input = controls.input(dt, driver);
|
|
2259
2461
|
gas = input.throttle;
|
|
2260
|
-
|
|
2462
|
+
if (input.start && sounds.starter) play(sounds.starter);
|
|
2463
|
+
const engine = driver.step(input, dt).events;
|
|
2261
2464
|
controls.after(driver, dt);
|
|
2262
2465
|
const s = controls.state;
|
|
2466
|
+
const report = [...engine.filter((e) => e.type !== "shift"), ...systems.step({ driver, controls: s, throttle: input.throttle, dt, radiator: cockpit.radiator })];
|
|
2467
|
+
for (const event of report) o.onEvent?.(event);
|
|
2468
|
+
const powered = driver.state.engineOn || systems.state.charge > 0;
|
|
2469
|
+
const high = s.lamps.mode === "high" || s.lamps.flash;
|
|
2263
2470
|
lamps.set({
|
|
2264
|
-
mode: s.lamps.mode,
|
|
2471
|
+
mode: !powered ? "off" : s.lamps.flash ? "high" : s.lamps.mode,
|
|
2265
2472
|
fog: s.lamps.fog,
|
|
2266
2473
|
interior: s.lamps.interior,
|
|
2267
2474
|
brake: s.brakeLights,
|
|
@@ -2278,7 +2485,14 @@ function createCockpit(o) {
|
|
|
2278
2485
|
engineOn: driver.state.engineOn,
|
|
2279
2486
|
left: s.blinkOn && (s.indicator === "left" || s.indicator === "hazard"),
|
|
2280
2487
|
right: s.blinkOn && (s.indicator === "right" || s.indicator === "hazard"),
|
|
2281
|
-
highBeam:
|
|
2488
|
+
highBeam: high,
|
|
2489
|
+
fuel: driver.state.fuel / spec.tank.capacity,
|
|
2490
|
+
temperature: systems.state.temperature,
|
|
2491
|
+
cruise: s.cruise.on,
|
|
2492
|
+
profile: s.profile,
|
|
2493
|
+
tractionOff: !s.tractionControl,
|
|
2494
|
+
absOff: !s.abs,
|
|
2495
|
+
charging: systems.state.netW >= 0,
|
|
2282
2496
|
lowBeam: s.lamps.mode === "low" || s.lamps.mode === "position",
|
|
2283
2497
|
fog: s.lamps.fog,
|
|
2284
2498
|
parkingBrake: s.parkingBrake,
|
|
@@ -2287,7 +2501,19 @@ function createCockpit(o) {
|
|
|
2287
2501
|
},
|
|
2288
2502
|
dt
|
|
2289
2503
|
);
|
|
2504
|
+
if (body) bodyEvents(body.update(dt, s.washer ? 1.2 : WIPE_RATE[s.wipers] ?? 0));
|
|
2290
2505
|
if (s.tick) play(sounds.blinker, 0.8);
|
|
2506
|
+
const near = o.obstacles?.() ?? [];
|
|
2507
|
+
if (s.ignition && near.length && Math.abs(driver.state.u) < 3) {
|
|
2508
|
+
const g = o.built.group;
|
|
2509
|
+
const reversing = driver.state.gear < 0 || driver.state.u < -0.05;
|
|
2510
|
+
const distance = bumperDistance({ x: g.position.x, z: g.position.z, heading: [Math.sin(g.rotation.y), Math.cos(g.rotation.y)], length: o.built.length, width: o.built.width, forward: !reversing, obstacles: near });
|
|
2511
|
+
const every = parkingBeepInterval(distance);
|
|
2512
|
+
if (every != null && (beepAgain -= dt) <= 0) {
|
|
2513
|
+
play(sounds.beep, 0.7);
|
|
2514
|
+
beepAgain = every === 0 ? 0.09 : every;
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2291
2517
|
if (s.horn && (hornAgain -= dt) <= 0) {
|
|
2292
2518
|
play(sounds.horn);
|
|
2293
2519
|
hornAgain = 0.5;
|
|
@@ -2296,9 +2522,9 @@ function createCockpit(o) {
|
|
|
2296
2522
|
play(sounds.alarm);
|
|
2297
2523
|
alarmAgain = 2.9;
|
|
2298
2524
|
}
|
|
2299
|
-
if (s.wipers !== "off" && (wipeAgain -= dt) <= 0) {
|
|
2525
|
+
if ((s.wipers !== "off" || s.washer) && (wipeAgain -= dt) <= 0) {
|
|
2300
2526
|
play(sounds.wiper, 0.7);
|
|
2301
|
-
wipeAgain = s.wipers === "fast" ? 0.6 : 1.4;
|
|
2527
|
+
wipeAgain = s.washer || s.wipers === "fast" ? 0.6 : s.wipers === "slow" ? 1.4 : 4;
|
|
2302
2528
|
}
|
|
2303
2529
|
const kmh = Math.abs(driver.state.u) * 3.6;
|
|
2304
2530
|
if (!s.seatbelt && s.ignition && kmh > 10 && (chimeAgain -= dt) <= 0) {
|
|
@@ -2306,8 +2532,13 @@ function createCockpit(o) {
|
|
|
2306
2532
|
chimeAgain = 6;
|
|
2307
2533
|
}
|
|
2308
2534
|
if (s.radio.on && (radioAgain -= dt) <= 0) {
|
|
2309
|
-
|
|
2310
|
-
|
|
2535
|
+
const song = RADIO_TRACKS[s.radio.track % RADIO_TRACKS.length];
|
|
2536
|
+
if (song) {
|
|
2537
|
+
play(song.bars[bar % song.bars.length], s.radio.source === "tape" ? 0.8 : 0.7);
|
|
2538
|
+
bar++;
|
|
2539
|
+
}
|
|
2540
|
+
if (s.radio.source === "radio") play(sounds.radio, 0.12);
|
|
2541
|
+
radioAgain = song?.barSeconds ?? 2;
|
|
2311
2542
|
}
|
|
2312
2543
|
if (exitAsked) {
|
|
2313
2544
|
exitAsked = false;
|
|
@@ -2321,10 +2552,151 @@ function createCockpit(o) {
|
|
|
2321
2552
|
controls.release();
|
|
2322
2553
|
lamps.dispose();
|
|
2323
2554
|
dashboard?.dispose();
|
|
2555
|
+
body?.dispose();
|
|
2556
|
+
destructible?.dispose?.();
|
|
2557
|
+
}
|
|
2558
|
+
};
|
|
2559
|
+
return cockpit;
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
// src/wheel-roll.ts
|
|
2563
|
+
var DEFAULT_RADIUS = 0.32;
|
|
2564
|
+
function createWheelRoll(THREE, built) {
|
|
2565
|
+
const wheels = (built.context?.wheels ?? []).map((wheel) => ({
|
|
2566
|
+
wheel,
|
|
2567
|
+
rest: wheel.quaternion.clone(),
|
|
2568
|
+
radius: Number(wheel.userData.r) || DEFAULT_RADIUS,
|
|
2569
|
+
// in the kit's frame the front is +x: the wheels ahead of the middle steer
|
|
2570
|
+
front: wheel.position.x > 0,
|
|
2571
|
+
angle: 0
|
|
2572
|
+
}));
|
|
2573
|
+
const axle = new THREE.Vector3(0, 0, 1);
|
|
2574
|
+
const up = new THREE.Vector3(0, 1, 0);
|
|
2575
|
+
const spin = new THREE.Quaternion();
|
|
2576
|
+
const steering = new THREE.Quaternion();
|
|
2577
|
+
return {
|
|
2578
|
+
count: wheels.length,
|
|
2579
|
+
roll(distance, steer) {
|
|
2580
|
+
for (const w of wheels) {
|
|
2581
|
+
w.angle = (w.angle - distance / w.radius) % (Math.PI * 2);
|
|
2582
|
+
spin.setFromAxisAngle(axle, w.angle);
|
|
2583
|
+
steering.setFromAxisAngle(up, w.front ? steer : 0);
|
|
2584
|
+
w.wheel.quaternion.copy(w.rest).premultiply(spin).premultiply(steering);
|
|
2585
|
+
}
|
|
2586
|
+
},
|
|
2587
|
+
dispose() {
|
|
2588
|
+
for (const w of wheels) w.wheel.quaternion.copy(w.rest);
|
|
2589
|
+
}
|
|
2590
|
+
};
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
// src/test-area/proving/crash.ts
|
|
2594
|
+
import { launchBody } from "@adatechnology/game-contracts";
|
|
2595
|
+
import { ejectionOf } from "@adatechnology/game-vehicles-driving";
|
|
2596
|
+
var GLASS_BLOW = 6e3;
|
|
2597
|
+
function createProvingCrash(host, occupant) {
|
|
2598
|
+
const thrown = [];
|
|
2599
|
+
const breakGlass = (destructible, exit, forward) => {
|
|
2600
|
+
if (!destructible) return;
|
|
2601
|
+
const want = exit === "windshield" ? "windshield" : exit === "rear_window" ? "rear" : exit.startsWith("left") ? "left" : "right";
|
|
2602
|
+
const pane = destructible.describe().parts.find((p) => p.id.includes(exit === "windshield" || exit === "rear_window" ? want : "window") && (exit === "windshield" || exit === "rear_window" || p.id.includes(want)));
|
|
2603
|
+
if (!pane) return;
|
|
2604
|
+
destructible.hit({ type: "blunt", partId: pane.id, point: destructible.partWorldCenter(pane.id), direction: forward, energy: GLASS_BLOW, radius: 0.35, source: "occupant" });
|
|
2605
|
+
};
|
|
2606
|
+
return {
|
|
2607
|
+
crash({ car, u, w, belted, destructible }) {
|
|
2608
|
+
const ejection = ejectionOf({ before: { u, w }, after: { u: 0, w: 0 } }, belted);
|
|
2609
|
+
if (!ejection) return null;
|
|
2610
|
+
const s = Math.sin(car.rotationY);
|
|
2611
|
+
const c = Math.cos(car.rotationY);
|
|
2612
|
+
const forward = [s, 0, c];
|
|
2613
|
+
const right = [-c, 0, s];
|
|
2614
|
+
breakGlass(destructible, ejection.exit, forward);
|
|
2615
|
+
const [vf, vr, vu] = ejection.velocity;
|
|
2616
|
+
const start = [car.position.x + forward[0] * 1.2, 1.1, car.position.z + forward[2] * 1.2];
|
|
2617
|
+
const placed = occupant ? host.addObject?.(occupant, { position: start, rotationY: car.rotationY }) : null;
|
|
2618
|
+
if (placed) {
|
|
2619
|
+
launchBody(placed.root, [forward[0] * vf + right[0] * vr, vu, forward[2] * vf + right[2] * vr]);
|
|
2620
|
+
thrown.push(placed.remove);
|
|
2621
|
+
}
|
|
2622
|
+
return ejection;
|
|
2623
|
+
},
|
|
2624
|
+
dispose() {
|
|
2625
|
+
for (const remove of thrown.splice(0)) remove();
|
|
2324
2626
|
}
|
|
2325
2627
|
};
|
|
2326
2628
|
}
|
|
2327
2629
|
|
|
2630
|
+
// src/test-area/proving/systems-text.ts
|
|
2631
|
+
var SYSTEMS_TEXT = {
|
|
2632
|
+
"pt-BR": {
|
|
2633
|
+
keys: "C embreagem (c\xE2mbio manual) \xB7 X lampejo do farol alto \xB7 Z esguicho do limpador \xB7 7 modo econ\xF4mico/normal/esporte \xB7 8 controle de tra\xE7\xE3o \xB7 9 ABS \xB7 0 piloto autom\xE1tico \xB7 - ar-condicionado \xB7 = desemba\xE7ador \xB7 ] troca a faixa do r\xE1dio ou da fita",
|
|
2634
|
+
profile: "Modo de condu\xE7\xE3o",
|
|
2635
|
+
profiles: { eco: "econ\xF4mico", normal: "normal", sport: "esporte" },
|
|
2636
|
+
traction: "Controle de tra\xE7\xE3o",
|
|
2637
|
+
abs: "ABS",
|
|
2638
|
+
cruise: "Piloto autom\xE1tico",
|
|
2639
|
+
cruiseSet: "Piloto autom\xE1tico: segurando {0} km/h",
|
|
2640
|
+
climate: "Ar-condicionado",
|
|
2641
|
+
demist: "Desemba\xE7ador",
|
|
2642
|
+
wiperModes: { off: "desligado", intermittent: "intermitente", slow: "lento", fast: "r\xE1pido" },
|
|
2643
|
+
refused: { battery_flat: "Bateria descarregada: o motor de partida n\xE3o gira", too_slow: "Piloto autom\xE1tico s\xF3 acima de 30 km/h", locked: "Porta travada", missing: "Este ve\xEDculo n\xE3o tem essa pe\xE7a" },
|
|
2644
|
+
events: {
|
|
2645
|
+
stall: "O motor morreu",
|
|
2646
|
+
out_of_fuel: "Acabou o combust\xEDvel",
|
|
2647
|
+
bogged: "Atolou",
|
|
2648
|
+
aquaplaning: "Aquaplanagem",
|
|
2649
|
+
battery_flat: "A bateria descarregou",
|
|
2650
|
+
overheating: "Motor superaquecendo: perdendo pot\xEAncia",
|
|
2651
|
+
seized: "O motor fundiu de tanto esquentar",
|
|
2652
|
+
spare_fitted: "Estepe colocado",
|
|
2653
|
+
no_spare: "N\xE3o h\xE1 estepe",
|
|
2654
|
+
cruise_cancelled: "Piloto autom\xE1tico desligado"
|
|
2655
|
+
},
|
|
2656
|
+
actions: { puncture: "Furar pneu (dianteiro esquerdo)", fit_spare: "Trocar pelo estepe", refuel: "Abastecer", drain_battery: "Descarregar bateria", jump_start: "Chupeta (carregar bateria)", break_radiator: "Furar o radiador", empty_tank: "Deixar o tanque na reserva", remote_lock: "Controle: travar (arma o alarme)", remote_unlock: "Controle: destravar", force_door: "For\xE7ar a porta do motorista", crash_wall: "Bater no muro (parada seca)", park_behind: "Estacionar um carro atr\xE1s (sensor de r\xE9)" },
|
|
2657
|
+
parking: { placed: "Carro parado 3 m atr\xE1s: d\xEA r\xE9 e escute o sensor", none: "Sem cat\xE1logo de ve\xEDculos nesta sala: o sensor n\xE3o tem o que ver" },
|
|
2658
|
+
track: "faixa {0}",
|
|
2659
|
+
adjust: { seat_forward: "Banco: frente/tr\xE1s (m)", seat_height: "Banco: altura (m)", mirror_left: "Retrovisor esquerdo: \xE2ngulo", mirror_right: "Retrovisor direito: \xE2ngulo" },
|
|
2660
|
+
crash: { mild: "Batida a {0} km/h: fraca demais para lan\xE7ar quem est\xE1 sem cinto", stayed: "Batida a {0} km/h: o cinto segurou o motorista", ejected: "Batida a {0} km/h sem cinto: o motorista saiu pelo {1}", exits: { windshield: "para-brisa", left_window: "vidro esquerdo", right_window: "vidro direito", rear_window: "vidro traseiro" } },
|
|
2661
|
+
bodyKeys: "1 a 4 portas \xB7 5 cap\xF4 \xB7 6 porta-malas \xB7 Shift+1 a 4 vidros \xB7 Shift+5 teto solar \xB7 V trava as portas",
|
|
2662
|
+
remote: { locked: "Travado pelo controle: alarme armado", unlocked: "Destravado pelo controle", forced: "Porta for\xE7ada" },
|
|
2663
|
+
done: { puncture: "Pneu dianteiro esquerdo furado", refuel: "Tanque cheio", drain_battery: "Bateria quase sem carga", jump_start: "Bateria carregada", break_radiator: "Radiador furado: o motor vai esquentar", empty_tank: "Tanque na reserva" }
|
|
2664
|
+
},
|
|
2665
|
+
en: {
|
|
2666
|
+
keys: "C clutch (manual gearbox) \xB7 X flash the high beam \xB7 Z washer \xB7 7 eco/normal/sport \xB7 8 traction control \xB7 9 ABS \xB7 0 cruise control \xB7 - air conditioning \xB7 = demister \xB7 ] next radio or tape track",
|
|
2667
|
+
profile: "Drive profile",
|
|
2668
|
+
profiles: { eco: "eco", normal: "normal", sport: "sport" },
|
|
2669
|
+
traction: "Traction control",
|
|
2670
|
+
abs: "ABS",
|
|
2671
|
+
cruise: "Cruise control",
|
|
2672
|
+
cruiseSet: "Cruise control: holding {0} km/h",
|
|
2673
|
+
climate: "Air conditioning",
|
|
2674
|
+
demist: "Demister",
|
|
2675
|
+
wiperModes: { off: "off", intermittent: "intermittent", slow: "slow", fast: "fast" },
|
|
2676
|
+
refused: { battery_flat: "Flat battery: the starter does not turn", too_slow: "Cruise control only above 30 km/h", locked: "The door is locked", missing: "This vehicle has no such part" },
|
|
2677
|
+
events: {
|
|
2678
|
+
stall: "The engine stalled",
|
|
2679
|
+
out_of_fuel: "Out of fuel",
|
|
2680
|
+
bogged: "Bogged down",
|
|
2681
|
+
aquaplaning: "Aquaplaning",
|
|
2682
|
+
battery_flat: "The battery went flat",
|
|
2683
|
+
overheating: "Engine overheating: losing power",
|
|
2684
|
+
seized: "The engine seized from the heat",
|
|
2685
|
+
spare_fitted: "Spare tyre fitted",
|
|
2686
|
+
no_spare: "No spare tyre left",
|
|
2687
|
+
cruise_cancelled: "Cruise control off"
|
|
2688
|
+
},
|
|
2689
|
+
actions: { puncture: "Puncture a tyre (front left)", fit_spare: "Fit the spare", refuel: "Refuel", drain_battery: "Drain the battery", jump_start: "Jump start (charge the battery)", break_radiator: "Puncture the radiator", empty_tank: "Leave the tank on reserve", remote_lock: "Remote: lock (arms the alarm)", remote_unlock: "Remote: unlock", force_door: "Force the driver's door", crash_wall: "Hit a wall (dead stop)", park_behind: "Park a car behind (rear sensor)" },
|
|
2690
|
+
parking: { placed: "A car parked 3 m behind: reverse and listen to the sensor", none: "No vehicle catalogue in this room: the sensor has nothing to see" },
|
|
2691
|
+
track: "track {0}",
|
|
2692
|
+
adjust: { seat_forward: "Seat: forward/back (m)", seat_height: "Seat: height (m)", mirror_left: "Left mirror: angle", mirror_right: "Right mirror: angle" },
|
|
2693
|
+
crash: { mild: "Crash at {0} km/h: too mild to throw an unbelted occupant out", stayed: "Crash at {0} km/h: the belt held the driver", ejected: "Crash at {0} km/h without a belt: the driver went out through the {1}", exits: { windshield: "windshield", left_window: "left window", right_window: "right window", rear_window: "rear window" } },
|
|
2694
|
+
bodyKeys: "1 to 4 doors \xB7 5 hood \xB7 6 trunk \xB7 Shift+1 to 4 windows \xB7 Shift+5 sunroof \xB7 V locks the doors",
|
|
2695
|
+
remote: { locked: "Locked by the remote: alarm armed", unlocked: "Unlocked by the remote", forced: "Door forced" },
|
|
2696
|
+
done: { puncture: "Front left tyre punctured", refuel: "Tank full", drain_battery: "Battery almost flat", jump_start: "Battery charged", break_radiator: "Radiator punctured: the engine will heat up", empty_tank: "Tank on reserve" }
|
|
2697
|
+
}
|
|
2698
|
+
};
|
|
2699
|
+
|
|
2328
2700
|
// src/test-area/proving/text.ts
|
|
2329
2701
|
var PROVING_TEXT = {
|
|
2330
2702
|
"pt-BR": {
|
|
@@ -2469,9 +2841,11 @@ var MIRROR_RECTS = {
|
|
|
2469
2841
|
right: { x: 0.79, y: 0.72, width: 0.19, height: 0.13, rounded: 0.3 },
|
|
2470
2842
|
reverse: { x: 0.36, y: 0.13, width: 0.28, height: 0.18, rounded: 0.06 }
|
|
2471
2843
|
};
|
|
2844
|
+
var ADJUST_KEYS = { seat_forward: "seatForward", seat_height: "seatHeight", mirror_left: "mirrorLeft", mirror_right: "mirrorRight" };
|
|
2472
2845
|
function createProvingModule(options) {
|
|
2473
2846
|
const locale = options.locale ?? "pt-BR";
|
|
2474
2847
|
const text = PROVING_TEXT[locale] ?? PROVING_TEXT["pt-BR"];
|
|
2848
|
+
const sysText = SYSTEMS_TEXT[locale] ?? SYSTEMS_TEXT["pt-BR"];
|
|
2475
2849
|
const tests = options.tests ?? createProvingTests();
|
|
2476
2850
|
const vehicles = options.vehicles;
|
|
2477
2851
|
const sounds = { ...defaultSoundIds, ...options.sounds };
|
|
@@ -2488,6 +2862,15 @@ function createProvingModule(options) {
|
|
|
2488
2862
|
let run = null;
|
|
2489
2863
|
let parking = null;
|
|
2490
2864
|
let driving = null;
|
|
2865
|
+
let crashes = null;
|
|
2866
|
+
let wheels = null;
|
|
2867
|
+
let lastAt = null;
|
|
2868
|
+
let parked = null;
|
|
2869
|
+
const adjust = { seatForward: 0, seatHeight: 0, mirrorLeft: 0, mirrorRight: 0 };
|
|
2870
|
+
const aimed = (pose2) => {
|
|
2871
|
+
const turn = pose2.id === "left" ? adjust.mirrorLeft : pose2.id === "right" ? -adjust.mirrorRight : 0;
|
|
2872
|
+
return turn ? { ...pose2, look: [pose2.look[0] + turn, pose2.look[1], pose2.look[2]] } : pose2;
|
|
2873
|
+
};
|
|
2491
2874
|
let gearbox = "automatic";
|
|
2492
2875
|
let lastGear = null;
|
|
2493
2876
|
let driveClock = 0;
|
|
@@ -2649,6 +3032,8 @@ function createProvingModule(options) {
|
|
|
2649
3032
|
};
|
|
2650
3033
|
const removeCar = () => {
|
|
2651
3034
|
if (!car) return;
|
|
3035
|
+
wheels = null;
|
|
3036
|
+
lastAt = null;
|
|
2652
3037
|
discardObject3D3(car.built.group);
|
|
2653
3038
|
car = null;
|
|
2654
3039
|
};
|
|
@@ -2662,6 +3047,7 @@ function createProvingModule(options) {
|
|
|
2662
3047
|
built.group.add(guides);
|
|
2663
3048
|
host.scene.add(built.group);
|
|
2664
3049
|
car = { built, guides, subject };
|
|
3050
|
+
wheels = createWheelRoll(three, built);
|
|
2665
3051
|
pose({ x: 0, z: TRACK.start, yaw: 0, pitch: 0, roll: 0, height: 0 }, 0);
|
|
2666
3052
|
rig.vehicle?.reset();
|
|
2667
3053
|
drawBoard();
|
|
@@ -2712,11 +3098,18 @@ function createProvingModule(options) {
|
|
|
2712
3098
|
car.built.group.position.set(TRACK.x0 + wrapped, 0, TRACK.z + s.z);
|
|
2713
3099
|
car.built.group.rotation.set(-s.pitch, s.yaw + Math.PI / 2, s.roll, "YXZ");
|
|
2714
3100
|
bendGuides(steer);
|
|
3101
|
+
if (lastAt) {
|
|
3102
|
+
const along = (s.x - lastAt.x) * Math.cos(s.yaw) - (s.z - lastAt.z) * Math.sin(s.yaw);
|
|
3103
|
+
if (Math.abs(along) < 5) wheels?.roll(along, steer);
|
|
3104
|
+
}
|
|
3105
|
+
lastAt = { x: s.x, z: s.z };
|
|
2715
3106
|
};
|
|
2716
3107
|
const frameOf = (speed) => {
|
|
2717
3108
|
if (!car) return null;
|
|
2718
3109
|
const g = car.built.group;
|
|
2719
|
-
|
|
3110
|
+
const size = { length: car.built.length, width: car.built.width, height: car.built.height };
|
|
3111
|
+
const [ex, ey, ez] = car.built.driverEye ?? defaultEye(size);
|
|
3112
|
+
return { x: g.position.x, y: g.position.y, z: g.position.z, yaw: g.rotation.y, pitch: -g.rotation.x, roll: g.rotation.z, speed, ...size, eye: [ex, ey + adjust.seatHeight, ez + adjust.seatForward] };
|
|
2720
3113
|
};
|
|
2721
3114
|
const applyCamera = () => {
|
|
2722
3115
|
if (!host?.setViewCamera) return;
|
|
@@ -2805,19 +3198,26 @@ function createProvingModule(options) {
|
|
|
2805
3198
|
built: car.built,
|
|
2806
3199
|
subject: car.subject,
|
|
2807
3200
|
place: [0, 0, 0],
|
|
3201
|
+
kit: kitFor(three),
|
|
3202
|
+
obstacles: () => parked ? [[parked.root.position.x, parked.root.position.z]] : [],
|
|
2808
3203
|
gauge: kitFor(three).materials?.gauge,
|
|
2809
3204
|
needle: kitFor(three).materials?.amber,
|
|
2810
3205
|
// the engine's own sound turns the key (starter, catch, idle by RPM)
|
|
2811
3206
|
sounds: { starter: "" },
|
|
2812
3207
|
onExit: () => stopDriving(),
|
|
2813
3208
|
onEvent: (event) => {
|
|
2814
|
-
if (event.type === "refused") return host?.log(text.cabin.refused[event.reason] ?? event.reason);
|
|
3209
|
+
if (event.type === "refused") return host?.log(text.cabin.refused[event.reason] ?? sysText.refused[event.reason] ?? event.reason);
|
|
3210
|
+
if (event.type === "sound") return;
|
|
3211
|
+
if (event.type !== "command") {
|
|
3212
|
+
if (event.type === "out_of_fuel" || event.type === "stall" || event.type === "seized") engine?.stop([car?.built.group.position.x ?? 0, 0.5, car?.built.group.position.z ?? 0]);
|
|
3213
|
+
return host?.log(sysText.events[event.type] ?? event.type, { vehicle: settings.vehicle, ...event }, "proving_system");
|
|
3214
|
+
}
|
|
2815
3215
|
const st = event.state;
|
|
2816
3216
|
const line = cabinLine(event.command, st);
|
|
2817
3217
|
if (line) host?.log(line);
|
|
2818
3218
|
if (event.command === "ignition" && car) {
|
|
2819
3219
|
const at = [car.built.group.position.x, 0.5, car.built.group.position.z];
|
|
2820
|
-
if (st.ignition) engine?.start(at);
|
|
3220
|
+
if (st.ignition && driving?.systems.canCrank() !== false) engine?.start(at);
|
|
2821
3221
|
else engine?.stop(at);
|
|
2822
3222
|
}
|
|
2823
3223
|
if (event.command === "gearbox") {
|
|
@@ -2836,7 +3236,7 @@ function createProvingModule(options) {
|
|
|
2836
3236
|
rig.vehicle?.reset();
|
|
2837
3237
|
stopEngine();
|
|
2838
3238
|
engine = createVehicleSound(host.audio, { sounds });
|
|
2839
|
-
host.bench?.sheet?.({ kicker: text.name, title: fillText(text.log.drive, name(settings.vehicle)), body: text.cabin.keys });
|
|
3239
|
+
host.bench?.sheet?.({ kicker: text.name, title: fillText(text.log.drive, name(settings.vehicle)), body: `${text.cabin.keys} \xB7 ${sysText.keys} \xB7 ${sysText.bodyKeys}` });
|
|
2840
3240
|
host.log(fillText(text.log.drive, name(settings.vehicle)));
|
|
2841
3241
|
};
|
|
2842
3242
|
const onOff = (on) => on ? text.cabin.on : text.cabin.off;
|
|
@@ -2865,19 +3265,109 @@ function createProvingModule(options) {
|
|
|
2865
3265
|
return `${c.alarm}: ${c.alarmStates[st.alarm]}`;
|
|
2866
3266
|
case "radio":
|
|
2867
3267
|
case "radio_source":
|
|
2868
|
-
|
|
3268
|
+
case "radio_track":
|
|
3269
|
+
return `${c.radio}: ${st.radio.on ? `${c.sources[st.radio.source]} \xB7 ${fillText(sysText.track, st.radio.track + 1)}` : c.off}`;
|
|
2869
3270
|
case "indicator_left":
|
|
2870
3271
|
case "indicator_right":
|
|
2871
3272
|
case "hazard":
|
|
2872
3273
|
return `${c.indicator}: ${c.indicators[st.indicator]}`;
|
|
2873
3274
|
case "wipers":
|
|
2874
|
-
return `${c.wipers}: ${
|
|
3275
|
+
return `${c.wipers}: ${sysText.wiperModes[st.wipers]}`;
|
|
3276
|
+
case "drive_profile":
|
|
3277
|
+
return `${sysText.profile}: ${sysText.profiles[st.profile]}`;
|
|
3278
|
+
case "traction_control":
|
|
3279
|
+
return `${sysText.traction}: ${onOff(st.tractionControl)}`;
|
|
3280
|
+
case "abs":
|
|
3281
|
+
return `${sysText.abs}: ${onOff(st.abs)}`;
|
|
3282
|
+
case "cruise":
|
|
3283
|
+
return st.cruise.on ? fillText(sysText.cruiseSet, Math.round(st.cruise.speed * 3.6)) : `${sysText.cruise}: ${c.off}`;
|
|
3284
|
+
case "climate":
|
|
3285
|
+
return `${sysText.climate}: ${onOff(st.climate.on)}`;
|
|
3286
|
+
case "demist":
|
|
3287
|
+
return `${sysText.demist}: ${onOff(st.climate.demist)}`;
|
|
2875
3288
|
case "seatbelt":
|
|
2876
3289
|
return `${c.seatbelt}: ${onOff(st.seatbelt)}`;
|
|
2877
3290
|
default:
|
|
2878
3291
|
return null;
|
|
2879
3292
|
}
|
|
2880
3293
|
};
|
|
3294
|
+
const systemsAction = (id) => {
|
|
3295
|
+
if (!driving) startDriving();
|
|
3296
|
+
const cockpit = driving;
|
|
3297
|
+
if (!cockpit) return;
|
|
3298
|
+
const d = cockpit.driver;
|
|
3299
|
+
const done = (key) => host?.log(sysText.done[key], { vehicle: settings.vehicle, action: key }, "proving_system");
|
|
3300
|
+
switch (id) {
|
|
3301
|
+
case "puncture": {
|
|
3302
|
+
const tyres = d.spec.wheels.map((_, i) => d.condition.tyres?.[i] ?? 1);
|
|
3303
|
+
tyres[0] = 0;
|
|
3304
|
+
d.condition = { ...d.condition, tyres };
|
|
3305
|
+
return done("puncture");
|
|
3306
|
+
}
|
|
3307
|
+
case "fit_spare": {
|
|
3308
|
+
const flat = d.spec.wheels.findIndex((_, i) => (d.condition.tyres?.[i] ?? 1) < 0.2);
|
|
3309
|
+
const event = cockpit.systems.fitSpare(d, Math.max(0, flat));
|
|
3310
|
+
host?.log(sysText.events[event.type], { vehicle: settings.vehicle, ...event }, "proving_system");
|
|
3311
|
+
return;
|
|
3312
|
+
}
|
|
3313
|
+
case "refuel":
|
|
3314
|
+
d.refuel(d.spec.tank.capacity);
|
|
3315
|
+
return done("refuel");
|
|
3316
|
+
case "empty_tank":
|
|
3317
|
+
d.state.fuel = Math.min(d.state.fuel, d.spec.tank.capacity * 0.04);
|
|
3318
|
+
return done("empty_tank");
|
|
3319
|
+
case "drain_battery":
|
|
3320
|
+
cockpit.systems.charge(-(cockpit.systems.state.charge - 0.15));
|
|
3321
|
+
return done("drain_battery");
|
|
3322
|
+
case "jump_start":
|
|
3323
|
+
cockpit.systems.charge(1);
|
|
3324
|
+
return done("jump_start");
|
|
3325
|
+
case "break_radiator":
|
|
3326
|
+
cockpit.radiator = 0.05;
|
|
3327
|
+
return done("break_radiator");
|
|
3328
|
+
case "remote_lock":
|
|
3329
|
+
case "remote_unlock": {
|
|
3330
|
+
const lock = id === "remote_lock";
|
|
3331
|
+
cockpit.body?.lock(lock);
|
|
3332
|
+
cockpit.controls.state.alarm = lock ? "armed" : "off";
|
|
3333
|
+
host?.audio?.play?.({ sound: "alarm_chirp", position: [car?.built.group.position.x ?? 0, 1, car?.built.group.position.z ?? 0] });
|
|
3334
|
+
host?.log(sysText.remote[lock ? "locked" : "unlocked"], { vehicle: settings.vehicle, locked: lock }, "proving_system");
|
|
3335
|
+
return;
|
|
3336
|
+
}
|
|
3337
|
+
case "crash_wall": {
|
|
3338
|
+
const g = car?.built.group;
|
|
3339
|
+
if (!g || !host) return;
|
|
3340
|
+
crashes ?? (crashes = createProvingCrash(host, options.occupant));
|
|
3341
|
+
const speed = d.state.u;
|
|
3342
|
+
const ejection = crashes.crash({ car: { position: g.position, rotationY: g.rotation.y }, u: d.state.u, w: d.state.w, belted: cockpit.controls.state.seatbelt, destructible: cockpit.destructible });
|
|
3343
|
+
d.state.u = d.state.w = d.state.yawRate = 0;
|
|
3344
|
+
const kmh = Math.round(Math.abs(speed) * 3.6);
|
|
3345
|
+
const belted = cockpit.controls.state.seatbelt;
|
|
3346
|
+
host.log(ejection ? fillText(sysText.crash.ejected, kmh, sysText.crash.exits[ejection.exit]) : fillText(belted ? sysText.crash.stayed : sysText.crash.mild, kmh), { vehicle: settings.vehicle, kmh, belted: cockpit.controls.state.seatbelt, ejection }, "proving_crash");
|
|
3347
|
+
return;
|
|
3348
|
+
}
|
|
3349
|
+
case "park_behind": {
|
|
3350
|
+
const g = car?.built.group;
|
|
3351
|
+
if (!g || !host) return;
|
|
3352
|
+
parked?.remove();
|
|
3353
|
+
const back = (car?.built.length ?? 4.5) / 2 + 3 + 2.3;
|
|
3354
|
+
const at = [g.position.x - Math.sin(g.rotation.y) * back, 0, g.position.z - Math.cos(g.rotation.y) * back];
|
|
3355
|
+
const placed = host.addObject?.(options.parkedCar ?? "sedan", { position: at, rotationY: g.rotation.y });
|
|
3356
|
+
parked = placed ? { root: placed.root, remove: placed.remove } : null;
|
|
3357
|
+
host.log(placed ? sysText.parking.placed : sysText.parking.none, { vehicle: settings.vehicle }, "proving_system");
|
|
3358
|
+
return;
|
|
3359
|
+
}
|
|
3360
|
+
case "force_door": {
|
|
3361
|
+
const body = cockpit.body;
|
|
3362
|
+
if (!body) return;
|
|
3363
|
+
if (body.locked) body.lock(false);
|
|
3364
|
+
body.toggle("door_front_left");
|
|
3365
|
+
if (cockpit.controls.state.alarm === "armed") cockpit.controls.state.alarm = "ringing";
|
|
3366
|
+
host?.log(sysText.remote.forced, { vehicle: settings.vehicle, alarm: cockpit.controls.state.alarm }, "proving_system");
|
|
3367
|
+
return;
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
};
|
|
2881
3371
|
const gearName = (gear) => gear < 0 ? "R" : gear === 0 ? "N" : String(gear);
|
|
2882
3372
|
const onChange = (change) => {
|
|
2883
3373
|
if (change.kind === "layer") {
|
|
@@ -2886,6 +3376,7 @@ function createProvingModule(options) {
|
|
|
2886
3376
|
}
|
|
2887
3377
|
if (change.kind === "action") {
|
|
2888
3378
|
if (change.id === "drive") return driving ? stopDriving() : startDriving();
|
|
3379
|
+
if (change.id in sysText.actions) return systemsAction(change.id);
|
|
2889
3380
|
if (change.id === "run_test" || change.id === "run_all" || change.id === "reverse_park") stopDriving();
|
|
2890
3381
|
if (change.id === "run_test") {
|
|
2891
3382
|
queue = [];
|
|
@@ -2932,6 +3423,12 @@ function createProvingModule(options) {
|
|
|
2932
3423
|
settings.camera = String(change.value);
|
|
2933
3424
|
applyCamera();
|
|
2934
3425
|
break;
|
|
3426
|
+
case "seat_forward":
|
|
3427
|
+
case "seat_height":
|
|
3428
|
+
case "mirror_left":
|
|
3429
|
+
case "mirror_right":
|
|
3430
|
+
adjust[ADJUST_KEYS[change.id]] = Number(change.value);
|
|
3431
|
+
break;
|
|
2935
3432
|
case "time_scale":
|
|
2936
3433
|
settings.timeScale = Number(change.value);
|
|
2937
3434
|
break;
|
|
@@ -2981,7 +3478,7 @@ function createProvingModule(options) {
|
|
|
2981
3478
|
if (car) engine?.update({ dt, position: [car.built.group.position.x, 0.5, car.built.group.position.z], speed, throttle: run ? 1 : driving?.throttle ?? 0, rpm: driving ? driving.driver.state.rpm : void 0 });
|
|
2982
3479
|
const f = frameOf(speed);
|
|
2983
3480
|
if (f && rig.vehicle && settings.camera !== "room") rig.vehicle.update(rig.camera, f, dt);
|
|
2984
|
-
if (f && rig.vehicle) for (const m of rig.mirrors) rig.vehicle.placeMirror(m.camera, m.pose, f);
|
|
3481
|
+
if (f && rig.vehicle) for (const m of rig.mirrors) rig.vehicle.placeMirror(m.camera, aimed(m.pose), f);
|
|
2985
3482
|
void steer;
|
|
2986
3483
|
const key = `${host.bench?.layer("mirrors")}|${host.bench?.layer("reverse_camera")}`;
|
|
2987
3484
|
if (key !== mirrorKey) {
|
|
@@ -3000,6 +3497,10 @@ function createProvingModule(options) {
|
|
|
3000
3497
|
const speedNow = () => Math.abs(run?.driver.state.u ?? driving?.driver.state.u ?? parking?.driver.state.u ?? 0) * 3.6;
|
|
3001
3498
|
const counts = (verdict) => certificateOf(settings.vehicle)?.results.filter((r) => r.verdict === verdict).length ?? 0;
|
|
3002
3499
|
const teardown = () => {
|
|
3500
|
+
crashes?.dispose();
|
|
3501
|
+
crashes = null;
|
|
3502
|
+
parked?.remove();
|
|
3503
|
+
parked = null;
|
|
3003
3504
|
stopDriving();
|
|
3004
3505
|
stopEngine();
|
|
3005
3506
|
for (const m of rig.mirrors) m.remove();
|
|
@@ -3030,9 +3531,14 @@ function createProvingModule(options) {
|
|
|
3030
3531
|
{ kind: "choice", id: "vehicle", name: text.controls.vehicle, hint: text.hints.vehicle, options: vehicles.map((id) => ({ id, name: name(id) })), default: settings.vehicle },
|
|
3031
3532
|
{ kind: "choice", id: "test", name: text.controls.test, hint: text.hints.test, options: measured.map((id) => ({ id, name: text.tests[id] ?? id })), default: settings.test },
|
|
3032
3533
|
{ kind: "choice", id: "camera", name: text.controls.camera, hint: text.hints.camera, options: ["room", "chase", "cockpit", "hood"].map((id) => ({ id, name: text.cameras[id] })), default: settings.camera },
|
|
3534
|
+
{ kind: "number", id: "seat_forward", name: sysText.adjust.seat_forward, min: -0.2, max: 0.2, step: 0.02, default: 0 },
|
|
3535
|
+
{ kind: "number", id: "seat_height", name: sysText.adjust.seat_height, min: -0.1, max: 0.1, step: 0.01, default: 0 },
|
|
3536
|
+
{ kind: "number", id: "mirror_left", name: sysText.adjust.mirror_left, min: -0.3, max: 0.3, step: 0.02, default: 0 },
|
|
3537
|
+
{ kind: "number", id: "mirror_right", name: sysText.adjust.mirror_right, min: -0.3, max: 0.3, step: 0.02, default: 0 },
|
|
3033
3538
|
{ kind: "number", id: "time_scale", name: text.controls.time_scale, hint: text.hints.time_scale, min: 1, max: 20, step: 1, default: settings.timeScale },
|
|
3034
3539
|
{ kind: "choice", id: "visual_check", name: text.controls.visual_check, hint: text.hints.visual_check, options: VISUAL_CHECKS.map((id) => ({ id, name: text.tests[id] ?? id })), default: settings.visual },
|
|
3035
3540
|
{ kind: "action", id: "drive", name: text.actions.drive },
|
|
3541
|
+
...Object.keys(sysText.actions).map((id) => ({ kind: "action", id, name: sysText.actions[id] })),
|
|
3036
3542
|
{ kind: "choice", id: "gearbox", name: text.controls.gearbox, hint: text.hints.gearbox, options: ["automatic", "manual"].map((id) => ({ id, name: text.gearbox[id] })), default: gearbox },
|
|
3037
3543
|
{ kind: "action", id: "run_test", name: text.actions.run_test },
|
|
3038
3544
|
{ kind: "action", id: "run_all", name: text.actions.run_all },
|
|
@@ -3066,6 +3572,8 @@ function createProvingModule(options) {
|
|
|
3066
3572
|
rig.vehicle = createVehicleCamera(three);
|
|
3067
3573
|
lent.audio?.register?.(LIBRARY_BANK2);
|
|
3068
3574
|
lent.audio?.register?.(CABIN_BANK);
|
|
3575
|
+
lent.audio?.register?.(VEHICLE_SOUND_BANK);
|
|
3576
|
+
lent.audio?.register?.(RADIO_BANK);
|
|
3069
3577
|
const stopChange = lent.bench?.onChange(onChange);
|
|
3070
3578
|
const stopFrame = lent.onFrame(frame);
|
|
3071
3579
|
const stopClear = lent.onClear?.(() => {
|
|
@@ -3098,10 +3606,6 @@ export {
|
|
|
3098
3606
|
liteMeshes,
|
|
3099
3607
|
createVehiclesThree,
|
|
3100
3608
|
createCabinSmoke,
|
|
3101
|
-
VEHICLE_LOOP_FALLOFF,
|
|
3102
|
-
VEHICLE_LOOPS,
|
|
3103
|
-
VEHICLE_SOUNDS,
|
|
3104
|
-
VEHICLE_SOUND_BANK,
|
|
3105
3609
|
firingHz,
|
|
3106
3610
|
rpmShare,
|
|
3107
3611
|
layerWeight,
|
|
@@ -3113,6 +3617,13 @@ export {
|
|
|
3113
3617
|
TRUCK_ENGINE,
|
|
3114
3618
|
BIKE_ENGINE,
|
|
3115
3619
|
ENGINE_PROFILES,
|
|
3620
|
+
VEHICLE_LOOP_FALLOFF,
|
|
3621
|
+
VEHICLE_LOOPS,
|
|
3622
|
+
VEHICLE_SOUNDS,
|
|
3623
|
+
CAR_RECORDED_RPM,
|
|
3624
|
+
CAR_START_RUN_STOP,
|
|
3625
|
+
VEHICLE_SAMPLE_ALIASES,
|
|
3626
|
+
VEHICLE_SOUND_BANK,
|
|
3116
3627
|
createVehicleSound,
|
|
3117
3628
|
VEHICLE_LOD_DISTANCE,
|
|
3118
3629
|
createVehicleLod,
|
|
@@ -3126,13 +3637,15 @@ export {
|
|
|
3126
3637
|
DEFAULT_TEST_VEHICLES,
|
|
3127
3638
|
createVehiclesTestModule,
|
|
3128
3639
|
loadVehiclesTestModule,
|
|
3640
|
+
createVehicleBody,
|
|
3129
3641
|
DASHBOARD_LOOK,
|
|
3130
3642
|
createVehicleDashboard,
|
|
3131
3643
|
LAMP_RULES,
|
|
3132
3644
|
createVehicleLamps,
|
|
3645
|
+
createWheelRoll,
|
|
3133
3646
|
PROVING_TEXT,
|
|
3134
3647
|
PROVING_AREA_ID,
|
|
3135
3648
|
createProvingModule,
|
|
3136
3649
|
loadProvingModule
|
|
3137
3650
|
};
|
|
3138
|
-
//# sourceMappingURL=chunk-
|
|
3651
|
+
//# sourceMappingURL=chunk-L63R2JQA.js.map
|