@adatechnology/game-vehicles-three 0.6.0 → 0.6.2
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-4SUQMRS3.js → chunk-MRUPKE7J.js} +732 -23
- package/dist/chunk-MRUPKE7J.js.map +1 -0
- package/dist/index.d.ts +168 -3
- package/dist/index.global.js +1050 -26
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +9 -1
- package/dist/{testing-gEnSqEF6.d.ts → testing-C-BgV7gj.d.ts} +40 -9
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/package.json +7 -7
- package/dist/chunk-4SUQMRS3.js.map +0 -1
|
@@ -209,6 +209,16 @@ function primaryPaint(c) {
|
|
|
209
209
|
});
|
|
210
210
|
return best;
|
|
211
211
|
}
|
|
212
|
+
var SEATED_EYE = { up: 0.55, back: 0.28 };
|
|
213
|
+
function eyeOf(THREE, c) {
|
|
214
|
+
const tops = c.seatTops ?? [];
|
|
215
|
+
if (!tops.length) return null;
|
|
216
|
+
const points = tops.map((t) => c.vehicle.localToWorld(new THREE.Vector3(t.x, t.y, t.z)));
|
|
217
|
+
const front = Math.max(...points.map((p) => p.z));
|
|
218
|
+
const row = points.filter((p) => p.z > front - 0.25);
|
|
219
|
+
const seat = row.reduce((a, b) => b.x > a.x ? b : a);
|
|
220
|
+
return [+seat.x.toFixed(3), +(seat.y + SEATED_EYE.up).toFixed(3), +(seat.z - SEATED_EYE.back).toFixed(3)];
|
|
221
|
+
}
|
|
212
222
|
function createVehiclesThree(THREE, options) {
|
|
213
223
|
const kit = options.kit;
|
|
214
224
|
const fleet = options.fleet ?? { colorFromSeed: defaultColorFromSeed, fleetDims: defaultFleetDims, paintPalettes: defaultPaintPalettes, paletteOf: defaultPaletteOf };
|
|
@@ -244,6 +254,7 @@ function createVehiclesThree(THREE, options) {
|
|
|
244
254
|
markKitOrigin(group, { owner: "game-vehicles-three", kind: "vehicle", id: key });
|
|
245
255
|
const damageables = kit.damageables(c, pivotMatrix);
|
|
246
256
|
let pruned = 0;
|
|
257
|
+
let driverEye = null;
|
|
247
258
|
if (quality === "high") {
|
|
248
259
|
const pivot = new THREE.Group();
|
|
249
260
|
pivot.name = "vehicle";
|
|
@@ -265,6 +276,7 @@ function createVehiclesThree(THREE, options) {
|
|
|
265
276
|
}
|
|
266
277
|
markDamageables(c, damageables);
|
|
267
278
|
group.updateMatrixWorld(true);
|
|
279
|
+
driverEye = eyeOf(THREE, c);
|
|
268
280
|
} else {
|
|
269
281
|
const fusion = fuse(THREE, c, pivotMatrix, color ? new THREE.Color(color) : null, primary, kit.lodHiddenMaterials());
|
|
270
282
|
for (const mesh of liteMeshes(THREE, fusion, liteMaterials(THREE, kit.studioEnvironment()))) group.add(mesh);
|
|
@@ -290,6 +302,7 @@ function createVehiclesThree(THREE, options) {
|
|
|
290
302
|
pruned,
|
|
291
303
|
expected: fleetDims2(key),
|
|
292
304
|
context: quality === "high" ? c : null,
|
|
305
|
+
driverEye,
|
|
293
306
|
dispose() {
|
|
294
307
|
group.traverse((m) => {
|
|
295
308
|
if (m.geometry) m.geometry.dispose();
|
|
@@ -1803,26 +1816,532 @@ function loadVehiclesTestModule(room, options = {}) {
|
|
|
1803
1816
|
// src/test-area/proving/module.ts
|
|
1804
1817
|
import { createVehicleCamera, mirrorPoses } from "@adatechnology/game-camera";
|
|
1805
1818
|
import { markKitOrigin as markKitOrigin3 } from "@adatechnology/game-contracts";
|
|
1806
|
-
import { LIBRARY_BANK as LIBRARY_BANK2 } from "@adatechnology/game-audio";
|
|
1807
|
-
import { createSemiSimDriver as
|
|
1808
|
-
import { certify, createProvingTests, startRun, statusOf, subjectSpec, withResult } from "@adatechnology/game-vehicles-proving";
|
|
1819
|
+
import { CABIN_BANK, LIBRARY_BANK as LIBRARY_BANK2 } from "@adatechnology/game-audio";
|
|
1820
|
+
import { createSemiSimDriver as createSemiSimDriver4 } from "@adatechnology/game-vehicles-driving";
|
|
1821
|
+
import { certify, createProvingTests, startRun, statusOf, subjectSpec as subjectSpec2, withResult } from "@adatechnology/game-vehicles-proving";
|
|
1822
|
+
import { discardObject3D as discardObject3D3 } from "@adatechnology/game-shapes-three";
|
|
1823
|
+
|
|
1824
|
+
// src/test-area/proving/cockpit.ts
|
|
1825
|
+
import { createSemiSimDriver as createSemiSimDriver3, createVehicleControls } from "@adatechnology/game-vehicles-driving";
|
|
1826
|
+
import { subjectSpec } from "@adatechnology/game-vehicles-proving";
|
|
1827
|
+
|
|
1828
|
+
// src/dashboard.ts
|
|
1809
1829
|
import { discardObject3D as discardObject3D2 } from "@adatechnology/game-shapes-three";
|
|
1830
|
+
var DASHBOARD_LOOK = {
|
|
1831
|
+
face: "#0b1110",
|
|
1832
|
+
ring: "#2f3a36",
|
|
1833
|
+
marks: "#cfe8dc",
|
|
1834
|
+
needle: "#ff6a2a",
|
|
1835
|
+
red: "#e2402f",
|
|
1836
|
+
amber: "#f2a630",
|
|
1837
|
+
green: "#46d16f",
|
|
1838
|
+
blue: "#3f8cff",
|
|
1839
|
+
dim: "rgba(207,232,220,0.12)",
|
|
1840
|
+
/** How far in front of the kit's dial the face sits, metres (the kit's needle sticks out 6 mm). */
|
|
1841
|
+
lift: 0.011
|
|
1842
|
+
};
|
|
1843
|
+
var SIZE = 256;
|
|
1844
|
+
function createVehicleDashboard(THREE, built, options) {
|
|
1845
|
+
const maxKmh = options.maxKmh ?? 220;
|
|
1846
|
+
const redline = options.redlineRpm ?? 6500;
|
|
1847
|
+
const doc = options.document ?? (typeof document !== "undefined" ? document : null);
|
|
1848
|
+
const dials = [];
|
|
1849
|
+
built.group.traverse((o) => {
|
|
1850
|
+
if (o.isMesh && o.material === options.gauge) dials.push(o);
|
|
1851
|
+
});
|
|
1852
|
+
const eye = built.driverEye;
|
|
1853
|
+
const hidden = [];
|
|
1854
|
+
const faces = [];
|
|
1855
|
+
if (doc && eye && dials.length) {
|
|
1856
|
+
built.group.updateMatrixWorld(true);
|
|
1857
|
+
const eyeWorld = built.group.localToWorld(new THREE.Vector3(eye[0], eye[1], eye[2]));
|
|
1858
|
+
const near = dials.map((d) => ({ d, at: d.getWorldPosition(new THREE.Vector3()) })).sort((a, b) => a.at.distanceTo(eyeWorld) - b.at.distanceTo(eyeWorld)).slice(0, 2).sort((a, b) => built.group.worldToLocal(b.at.clone()).x - built.group.worldToLocal(a.at.clone()).x);
|
|
1859
|
+
near.forEach(({ d, at }, i) => {
|
|
1860
|
+
const canvas = doc.createElement("canvas");
|
|
1861
|
+
canvas.width = canvas.height = SIZE;
|
|
1862
|
+
const g = canvas.getContext("2d");
|
|
1863
|
+
if (!g) return;
|
|
1864
|
+
const texture = new THREE.CanvasTexture(canvas);
|
|
1865
|
+
const radius = d.geometry.parameters?.radiusTop ?? 0.055;
|
|
1866
|
+
const mesh = new THREE.Mesh(new THREE.CircleGeometry(radius * 1.12, 40), new THREE.MeshBasicMaterial({ map: texture, transparent: true, toneMapped: false, depthWrite: false }));
|
|
1867
|
+
mesh.name = "dashboard_face";
|
|
1868
|
+
const axis = new THREE.Vector3(0, 1, 0).applyQuaternion(d.getWorldQuaternion(d.quaternion.clone())).normalize();
|
|
1869
|
+
if (axis.dot(eyeWorld.clone().sub(at)) < 0) axis.negate();
|
|
1870
|
+
const front = at.clone().addScaledVector(axis, DASHBOARD_LOOK.lift);
|
|
1871
|
+
mesh.position.copy(built.group.worldToLocal(front.clone()));
|
|
1872
|
+
built.group.add(mesh);
|
|
1873
|
+
mesh.updateMatrixWorld(true);
|
|
1874
|
+
mesh.lookAt(front.add(axis));
|
|
1875
|
+
d.visible = false;
|
|
1876
|
+
hidden.push(d);
|
|
1877
|
+
if (options.needle)
|
|
1878
|
+
built.group.traverse((o) => {
|
|
1879
|
+
if (o.isMesh && o.material === options.needle && o.visible && o.getWorldPosition(new THREE.Vector3()).distanceTo(at) < radius * 1.2) {
|
|
1880
|
+
o.visible = false;
|
|
1881
|
+
hidden.push(o);
|
|
1882
|
+
}
|
|
1883
|
+
});
|
|
1884
|
+
faces.push({ mesh, canvas, g, texture, kind: near.length === 1 || i === 0 ? "speed" : "rpm" });
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
const lamp = (g, x, y, on, colour, draw2) => {
|
|
1888
|
+
g.save();
|
|
1889
|
+
g.translate(x, y);
|
|
1890
|
+
g.fillStyle = g.strokeStyle = on ? colour : DASHBOARD_LOOK.dim;
|
|
1891
|
+
g.lineWidth = 4;
|
|
1892
|
+
draw2(g);
|
|
1893
|
+
g.restore();
|
|
1894
|
+
};
|
|
1895
|
+
const arrow = (dir) => (g) => {
|
|
1896
|
+
g.beginPath();
|
|
1897
|
+
g.moveTo(dir * 14, 0);
|
|
1898
|
+
g.lineTo(-dir * 2, -12);
|
|
1899
|
+
g.lineTo(-dir * 2, -5);
|
|
1900
|
+
g.lineTo(-dir * 14, -5);
|
|
1901
|
+
g.lineTo(-dir * 14, 5);
|
|
1902
|
+
g.lineTo(-dir * 2, 5);
|
|
1903
|
+
g.lineTo(-dir * 2, 12);
|
|
1904
|
+
g.closePath();
|
|
1905
|
+
g.fill();
|
|
1906
|
+
};
|
|
1907
|
+
const beam = (g) => {
|
|
1908
|
+
g.beginPath();
|
|
1909
|
+
g.arc(-4, 0, 10, Math.PI / 2, -Math.PI / 2, true);
|
|
1910
|
+
g.closePath();
|
|
1911
|
+
g.fill();
|
|
1912
|
+
for (let k = -1; k <= 1; k++) g.fillRect(8, k * 6 - 1.5, 10, 3);
|
|
1913
|
+
};
|
|
1914
|
+
const belt = (g) => {
|
|
1915
|
+
g.beginPath();
|
|
1916
|
+
g.arc(0, -10, 5, 0, Math.PI * 2);
|
|
1917
|
+
g.fill();
|
|
1918
|
+
g.fillRect(-6, -3, 12, 16);
|
|
1919
|
+
g.strokeStyle = DASHBOARD_LOOK.face;
|
|
1920
|
+
g.lineWidth = 3;
|
|
1921
|
+
g.beginPath();
|
|
1922
|
+
g.moveTo(-7, -3);
|
|
1923
|
+
g.lineTo(7, 12);
|
|
1924
|
+
g.stroke();
|
|
1925
|
+
};
|
|
1926
|
+
const letter = (text) => (g) => {
|
|
1927
|
+
g.font = "bold 22px sans-serif";
|
|
1928
|
+
g.textAlign = "center";
|
|
1929
|
+
g.textBaseline = "middle";
|
|
1930
|
+
g.fillText(text, 0, 1);
|
|
1931
|
+
};
|
|
1932
|
+
const ring = (text) => (g) => {
|
|
1933
|
+
g.beginPath();
|
|
1934
|
+
g.arc(0, 0, 13, 0, Math.PI * 2);
|
|
1935
|
+
g.stroke();
|
|
1936
|
+
letter(text)(g);
|
|
1937
|
+
};
|
|
1938
|
+
const dial = (g, value, top, step, redFrom, label) => {
|
|
1939
|
+
const c = SIZE / 2;
|
|
1940
|
+
g.clearRect(0, 0, SIZE, SIZE);
|
|
1941
|
+
g.fillStyle = DASHBOARD_LOOK.face;
|
|
1942
|
+
g.beginPath();
|
|
1943
|
+
g.arc(c, c, c - 2, 0, Math.PI * 2);
|
|
1944
|
+
g.fill();
|
|
1945
|
+
g.strokeStyle = DASHBOARD_LOOK.ring;
|
|
1946
|
+
g.lineWidth = 6;
|
|
1947
|
+
g.stroke();
|
|
1948
|
+
const angle = (v) => Math.PI * 0.75 + Math.max(0, Math.min(top, v)) / top * Math.PI * 1.5;
|
|
1949
|
+
for (let v = 0; v <= top + 1e-6; v += step) {
|
|
1950
|
+
const a2 = angle(v);
|
|
1951
|
+
g.strokeStyle = redFrom !== null && v >= redFrom ? DASHBOARD_LOOK.red : DASHBOARD_LOOK.marks;
|
|
1952
|
+
g.lineWidth = 4;
|
|
1953
|
+
g.beginPath();
|
|
1954
|
+
g.moveTo(c + Math.cos(a2) * (c - 12), c + Math.sin(a2) * (c - 12));
|
|
1955
|
+
g.lineTo(c + Math.cos(a2) * (c - 30), c + Math.sin(a2) * (c - 30));
|
|
1956
|
+
g.stroke();
|
|
1957
|
+
g.fillStyle = DASHBOARD_LOOK.marks;
|
|
1958
|
+
g.font = "18px sans-serif";
|
|
1959
|
+
g.textAlign = "center";
|
|
1960
|
+
g.textBaseline = "middle";
|
|
1961
|
+
g.fillText(label(v), c + Math.cos(a2) * (c - 48), c + Math.sin(a2) * (c - 48));
|
|
1962
|
+
}
|
|
1963
|
+
const a = angle(value);
|
|
1964
|
+
g.strokeStyle = DASHBOARD_LOOK.needle;
|
|
1965
|
+
g.lineWidth = 6;
|
|
1966
|
+
g.beginPath();
|
|
1967
|
+
g.moveTo(c - Math.cos(a) * 14, c - Math.sin(a) * 14);
|
|
1968
|
+
g.lineTo(c + Math.cos(a) * (c - 22), c + Math.sin(a) * (c - 22));
|
|
1969
|
+
g.stroke();
|
|
1970
|
+
g.fillStyle = DASHBOARD_LOOK.ring;
|
|
1971
|
+
g.beginPath();
|
|
1972
|
+
g.arc(c, c, 10, 0, Math.PI * 2);
|
|
1973
|
+
g.fill();
|
|
1974
|
+
};
|
|
1975
|
+
const bar = (g, x, share, warnAbove, label) => {
|
|
1976
|
+
if (share === void 0) return;
|
|
1977
|
+
const h = 60;
|
|
1978
|
+
g.strokeStyle = DASHBOARD_LOOK.marks;
|
|
1979
|
+
g.lineWidth = 2;
|
|
1980
|
+
g.strokeRect(x - 5, 150, 10, h);
|
|
1981
|
+
const s = Math.max(0, Math.min(1, share));
|
|
1982
|
+
g.fillStyle = (warnAbove ? s > 0.85 : s < 0.12) ? DASHBOARD_LOOK.red : DASHBOARD_LOOK.marks;
|
|
1983
|
+
g.fillRect(x - 4, 150 + h * (1 - s), 8, h * s);
|
|
1984
|
+
g.font = "14px sans-serif";
|
|
1985
|
+
g.textAlign = "center";
|
|
1986
|
+
g.fillText(label, x, 222);
|
|
1987
|
+
};
|
|
1988
|
+
const draw = (r) => {
|
|
1989
|
+
const warn = r.ignition && !r.engineOn;
|
|
1990
|
+
for (const f of faces) {
|
|
1991
|
+
const g = f.g;
|
|
1992
|
+
if (f.kind === "speed") {
|
|
1993
|
+
dial(g, r.kmh, maxKmh, maxKmh > 160 ? 20 : 10, null, (v) => String(Math.round(v)));
|
|
1994
|
+
g.fillStyle = DASHBOARD_LOOK.marks;
|
|
1995
|
+
g.font = "bold 30px sans-serif";
|
|
1996
|
+
g.textAlign = "center";
|
|
1997
|
+
g.fillText(String(Math.round(r.kmh)), SIZE / 2, SIZE / 2 + 52);
|
|
1998
|
+
lamp(g, 92, 178, r.left, DASHBOARD_LOOK.green, arrow(-1));
|
|
1999
|
+
lamp(g, 164, 178, r.right, DASHBOARD_LOOK.green, arrow(1));
|
|
2000
|
+
lamp(g, 104, 214, r.highBeam, DASHBOARD_LOOK.blue, beam);
|
|
2001
|
+
lamp(g, 152, 214, r.lowBeam || r.fog, r.fog ? DASHBOARD_LOOK.amber : DASHBOARD_LOOK.green, beam);
|
|
2002
|
+
bar(g, 40, r.fuel, false, "F");
|
|
2003
|
+
} else {
|
|
2004
|
+
dial(g, r.rpm / 1e3, Math.ceil(redline / 1e3) + 1, 1, redline / 1e3, (v) => String(Math.round(v)));
|
|
2005
|
+
g.fillStyle = DASHBOARD_LOOK.marks;
|
|
2006
|
+
g.font = "bold 34px sans-serif";
|
|
2007
|
+
g.textAlign = "center";
|
|
2008
|
+
g.fillText(r.gear < 0 ? "R" : r.gear === 0 ? "N" : String(r.gear), SIZE / 2, SIZE / 2 + 54);
|
|
2009
|
+
lamp(g, 86, 178, r.parkingBrake, DASHBOARD_LOOK.red, ring("P"));
|
|
2010
|
+
lamp(g, 128, 214, r.ignition && !r.seatbelt, DASHBOARD_LOOK.red, belt);
|
|
2011
|
+
lamp(g, 170, 178, warn, DASHBOARD_LOOK.red, letter("+\u2212"));
|
|
2012
|
+
lamp(g, 86, 214, warn, DASHBOARD_LOOK.red, letter("OIL"));
|
|
2013
|
+
lamp(g, 170, 214, r.fourWheel, DASHBOARD_LOOK.green, letter("4x4"));
|
|
2014
|
+
bar(g, SIZE - 40, r.temperature, true, "T");
|
|
2015
|
+
}
|
|
2016
|
+
f.texture.needsUpdate = true;
|
|
2017
|
+
}
|
|
2018
|
+
};
|
|
2019
|
+
let wait = 0;
|
|
2020
|
+
let last = "";
|
|
2021
|
+
return {
|
|
2022
|
+
dials: faces.length,
|
|
2023
|
+
update(reading, dt) {
|
|
2024
|
+
if (!faces.length) return;
|
|
2025
|
+
wait -= dt;
|
|
2026
|
+
const key = JSON.stringify(reading);
|
|
2027
|
+
if (wait > 0 || key === last) return;
|
|
2028
|
+
wait = 1 / (options.fps ?? 15);
|
|
2029
|
+
last = key;
|
|
2030
|
+
draw(reading);
|
|
2031
|
+
},
|
|
2032
|
+
dispose() {
|
|
2033
|
+
for (const f of faces) {
|
|
2034
|
+
discardObject3D2(f.mesh);
|
|
2035
|
+
f.texture.dispose();
|
|
2036
|
+
f.canvas.width = f.canvas.height = 0;
|
|
2037
|
+
}
|
|
2038
|
+
faces.length = 0;
|
|
2039
|
+
for (const o of hidden) o.visible = true;
|
|
2040
|
+
hidden.length = 0;
|
|
2041
|
+
}
|
|
2042
|
+
};
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
// src/lamps.ts
|
|
2046
|
+
import { isLightBudget, LIGHT_PRIORITY } from "@adatechnology/game-render-three";
|
|
2047
|
+
var LAMP_RULES = {
|
|
2048
|
+
glow: { head: { off: 0.04, position: 0.3, low: 1, high: 1.35 }, tail: { off: 0.15, on: 0.7, brake: 1.6 }, fog: { off: 0.04, on: 1.1 }, amber: { off: 0.1, on: 2.2 } },
|
|
2049
|
+
low: { color: 16773584, intensity: 2.4, distance: 32, angle: 0.42, penumbra: 0.55, decay: 1.6, drop: 0.09 },
|
|
2050
|
+
high: { color: 16774882, intensity: 3.6, distance: 70, angle: 0.3, penumbra: 0.45, decay: 1.4, drop: 0.015 },
|
|
2051
|
+
fog: { color: 16773320, intensity: 1.6, distance: 16, angle: 0.75, penumbra: 0.7, decay: 1.8, drop: 0.12 },
|
|
2052
|
+
reverse: { color: 16054015, intensity: 0.9, distance: 7, decay: 2 },
|
|
2053
|
+
interior: { color: 16769712, intensity: 0.5, distance: 2.4, decay: 2 }
|
|
2054
|
+
};
|
|
2055
|
+
function createVehicleLamps(THREE, built, options = {}) {
|
|
2056
|
+
const budget = isLightBudget(options.lights) ? options.lights : null;
|
|
2057
|
+
const owner = options.owner ?? "vehicle-lamps";
|
|
2058
|
+
const context = built.context;
|
|
2059
|
+
const lamps = (context?.lights ?? []).filter((l) => l && Array.isArray(l.inner)).map((l) => l.fog ? { ...l, kind: "fog" } : l);
|
|
2060
|
+
const bulbs = (context?.fogBulbs ?? []).filter((o) => o?.material);
|
|
2061
|
+
built.group.traverse((o) => {
|
|
2062
|
+
if (o.userData?.lamp === "fog" && o.material && !bulbs.includes(o)) bulbs.push(o);
|
|
2063
|
+
});
|
|
2064
|
+
if (bulbs.length) lamps.push({ kind: "fog", inner: bulbs });
|
|
2065
|
+
const has = { head: lamps.some((l) => l.kind === "headlight"), tail: lamps.some((l) => l.kind === "taillight"), fog: lamps.some((l) => l.kind === "fog"), indicators: false };
|
|
2066
|
+
const originals = /* @__PURE__ */ new Map();
|
|
2067
|
+
const copies = /* @__PURE__ */ new Map();
|
|
2068
|
+
const base = /* @__PURE__ */ new Map();
|
|
2069
|
+
const own = (mesh) => {
|
|
2070
|
+
const mat = mesh.material;
|
|
2071
|
+
if (!mat || Array.isArray(mat)) return null;
|
|
2072
|
+
if (copies.has(mat) || base.has(mat)) return mat;
|
|
2073
|
+
const copy = mat.clone();
|
|
2074
|
+
originals.set(mesh, mat);
|
|
2075
|
+
copies.set(copy, mat);
|
|
2076
|
+
base.set(copy, mat.emissiveIntensity ?? 1);
|
|
2077
|
+
mesh.material = copy;
|
|
2078
|
+
return copy;
|
|
2079
|
+
};
|
|
2080
|
+
const haloBase = /* @__PURE__ */ new Map();
|
|
2081
|
+
for (const l of lamps) {
|
|
2082
|
+
for (const m of l.inner) own(m);
|
|
2083
|
+
for (const h of l.halo ?? []) haloBase.set(h, h.scale.x);
|
|
2084
|
+
}
|
|
2085
|
+
const amber = /* @__PURE__ */ new Set();
|
|
2086
|
+
const sideOf = /* @__PURE__ */ new Map();
|
|
2087
|
+
for (const l of lamps) {
|
|
2088
|
+
const x = l.group?.position?.x ?? 0;
|
|
2089
|
+
for (const m of l.inner) {
|
|
2090
|
+
const mat = Array.isArray(m.material) ? null : m.material;
|
|
2091
|
+
const c = (mat && (copies.get(mat) ?? mat))?.color;
|
|
2092
|
+
if (c && c.r > 0.5 && c.r - c.b > 0.35 && c.g < c.r * 0.8 && c.g > c.b) {
|
|
2093
|
+
amber.add(m);
|
|
2094
|
+
sideOf.set(m, x >= 0 ? "left" : "right");
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
has.indicators = amber.size > 0;
|
|
2099
|
+
const glowMesh = (m, share) => {
|
|
2100
|
+
const mat = m.material;
|
|
2101
|
+
if (mat && !Array.isArray(mat) && base.has(mat) && "emissiveIntensity" in mat) mat.emissiveIntensity = base.get(mat) * share;
|
|
2102
|
+
};
|
|
2103
|
+
const glow = (l, share) => {
|
|
2104
|
+
for (const m of l.inner) {
|
|
2105
|
+
if (amber.has(m)) continue;
|
|
2106
|
+
glowMesh(m, share);
|
|
2107
|
+
}
|
|
2108
|
+
for (const h of l.halo ?? []) {
|
|
2109
|
+
h.visible = share > 0.5;
|
|
2110
|
+
const s = (haloBase.get(h) ?? 1) * Math.min(1.4, share);
|
|
2111
|
+
h.scale.set(s, s, 1);
|
|
2112
|
+
}
|
|
2113
|
+
};
|
|
2114
|
+
const L = built.length;
|
|
2115
|
+
const W = built.width;
|
|
2116
|
+
const H = built.height;
|
|
2117
|
+
const anchors = [];
|
|
2118
|
+
const anchor = (x, y, z, yaw, drop) => {
|
|
2119
|
+
const a = new THREE.Object3D();
|
|
2120
|
+
a.position.set(x, y, z);
|
|
2121
|
+
a.rotation.set(-drop, yaw, 0, "YXZ");
|
|
2122
|
+
built.group.add(a);
|
|
2123
|
+
anchors.push(a);
|
|
2124
|
+
return a;
|
|
2125
|
+
};
|
|
2126
|
+
const front = [-1, 1].map((s) => anchor(s * W * 0.34, Math.min(0.9, H * 0.48), L / 2 - 0.05, Math.PI, 0));
|
|
2127
|
+
const fogAnchors = [-1, 1].map((s) => anchor(s * W * 0.38, Math.min(0.45, H * 0.25), L / 2 - 0.03, Math.PI, 0));
|
|
2128
|
+
const rear = anchor(0, Math.min(0.9, H * 0.45), -L / 2 - 0.3, 0, 0);
|
|
2129
|
+
const cabin = anchor(0, H * 0.85, 0, 0, 0);
|
|
2130
|
+
const sources = /* @__PURE__ */ new Map();
|
|
2131
|
+
const beam = (id, on, make) => {
|
|
2132
|
+
const now = sources.get(id);
|
|
2133
|
+
if (on && !now && budget) sources.set(id, make());
|
|
2134
|
+
if (!on && now) {
|
|
2135
|
+
for (const s of now) s.remove();
|
|
2136
|
+
sources.delete(id);
|
|
2137
|
+
}
|
|
2138
|
+
};
|
|
2139
|
+
const spots = (at, rule) => at.map((a) => {
|
|
2140
|
+
a.rotation.x = -rule.drop;
|
|
2141
|
+
return budget.addSource({ owner, kind: "spot", priority: LIGHT_PRIORITY.headlight, color: rule.color, intensity: rule.intensity, distance: rule.distance, decay: rule.decay, angle: rule.angle, penumbra: rule.penumbra, position: [0, 0, 0], follow: a });
|
|
2142
|
+
});
|
|
2143
|
+
const point = (a, rule) => [
|
|
2144
|
+
budget.addSource({ owner, kind: "point", priority: LIGHT_PRIORITY.lamp, color: rule.color, intensity: rule.intensity, distance: rule.distance, decay: rule.decay, position: [0, 0, 0], follow: a })
|
|
2145
|
+
];
|
|
2146
|
+
const state = { mode: "off", fog: false, interior: false, brake: false, reverse: false, left: false, right: false };
|
|
2147
|
+
const apply = () => {
|
|
2148
|
+
const g = LAMP_RULES.glow;
|
|
2149
|
+
for (const l of lamps) {
|
|
2150
|
+
if (l.kind === "headlight") glow(l, g.head[state.mode]);
|
|
2151
|
+
else if (l.kind === "taillight") glow(l, state.brake ? g.tail.brake : state.mode === "off" ? g.tail.off : g.tail.on);
|
|
2152
|
+
else if (l.kind === "fog") glow(l, state.fog ? g.fog.on : g.fog.off);
|
|
2153
|
+
}
|
|
2154
|
+
for (const m of amber) glowMesh(m, state[sideOf.get(m) ?? "left"] ? g.amber.on : g.amber.off);
|
|
2155
|
+
const beamId = state.mode === "low" || state.mode === "high" ? state.mode : null;
|
|
2156
|
+
for (const id of ["low", "high"]) beam(id, beamId === id, () => spots(front, LAMP_RULES[id]));
|
|
2157
|
+
beam("fog", state.fog && has.fog, () => spots(fogAnchors, LAMP_RULES.fog));
|
|
2158
|
+
beam("reverse", state.reverse, () => point(rear, LAMP_RULES.reverse));
|
|
2159
|
+
beam("interior", state.interior, () => point(cabin, LAMP_RULES.interior));
|
|
2160
|
+
};
|
|
2161
|
+
apply();
|
|
2162
|
+
return {
|
|
2163
|
+
has,
|
|
2164
|
+
get state() {
|
|
2165
|
+
return { ...state };
|
|
2166
|
+
},
|
|
2167
|
+
set(next) {
|
|
2168
|
+
if (next.mode === state.mode && next.fog === state.fog && next.interior === state.interior && next.brake === state.brake && next.reverse === state.reverse && next.left === state.left && next.right === state.right) return;
|
|
2169
|
+
Object.assign(state, next);
|
|
2170
|
+
apply();
|
|
2171
|
+
},
|
|
2172
|
+
dispose() {
|
|
2173
|
+
for (const list of sources.values()) for (const s of list) s.remove();
|
|
2174
|
+
sources.clear();
|
|
2175
|
+
for (const a of anchors) a.removeFromParent();
|
|
2176
|
+
anchors.length = 0;
|
|
2177
|
+
for (const l of lamps) {
|
|
2178
|
+
for (const m of l.inner) {
|
|
2179
|
+
const copy = m.material;
|
|
2180
|
+
const original = originals.get(m);
|
|
2181
|
+
if (original) {
|
|
2182
|
+
m.material = original;
|
|
2183
|
+
if (copy && !Array.isArray(copy)) copy.dispose?.();
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
for (const h of l.halo ?? []) {
|
|
2187
|
+
h.visible = true;
|
|
2188
|
+
const s = haloBase.get(h) ?? 1;
|
|
2189
|
+
h.scale.set(s, s, 1);
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
originals.clear();
|
|
2193
|
+
copies.clear();
|
|
2194
|
+
base.clear();
|
|
2195
|
+
}
|
|
2196
|
+
};
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
// 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" };
|
|
2201
|
+
function createCockpit(o) {
|
|
2202
|
+
const sounds = { ...DEFAULT_COCKPIT_SOUNDS, ...o.sounds };
|
|
2203
|
+
const spec = subjectSpec(o.subject);
|
|
2204
|
+
const driver = createSemiSimDriver3(spec);
|
|
2205
|
+
driver.place(o.place[0], o.place[1], o.place[2]);
|
|
2206
|
+
const lamps = createVehicleLamps(o.THREE, o.built, { lights: o.host.lights, owner: "proving-ground" });
|
|
2207
|
+
const audio = o.host.audio;
|
|
2208
|
+
const top = o.subject.stats.maxSpeedKmh;
|
|
2209
|
+
const dashboard = o.gauge ? createVehicleDashboard(o.THREE, o.built, { gauge: o.gauge, needle: o.needle, redlineRpm: spec.engine.redlineRpm, maxKmh: top ? Math.ceil((top + 10) / 20) * 20 : void 0 }) : null;
|
|
2210
|
+
const at = () => [o.built.group.position.x, 1, o.built.group.position.z];
|
|
2211
|
+
const play = (sound, volume = 1) => void audio?.play?.({ sound, position: at(), volume });
|
|
2212
|
+
let alarmAgain = 0;
|
|
2213
|
+
let hornAgain = 0;
|
|
2214
|
+
let wipeAgain = 0;
|
|
2215
|
+
let chimeAgain = 0;
|
|
2216
|
+
let radioAgain = 0;
|
|
2217
|
+
let exitAsked = false;
|
|
2218
|
+
let gas = 0;
|
|
2219
|
+
const controls = createVehicleControls({
|
|
2220
|
+
gears: spec.gears?.length ?? 5,
|
|
2221
|
+
fourWheelDrive: !!o.subject.body.fourWheelDrive,
|
|
2222
|
+
fogLamps: lamps.has.fog,
|
|
2223
|
+
onEvent: (event) => {
|
|
2224
|
+
if (event.type === "command") {
|
|
2225
|
+
if (event.command === "ignition" && event.state.ignition && sounds.starter) play(sounds.starter);
|
|
2226
|
+
if (event.command === "ignition" && !event.state.ignition) driver.state.engineOn = false;
|
|
2227
|
+
if (event.command === "alarm" && event.state.alarm === "armed") play(sounds.chirp);
|
|
2228
|
+
if (event.command === "alarm") alarmAgain = 0;
|
|
2229
|
+
if (event.command === "radio" || event.command === "radio_source") radioAgain = 0;
|
|
2230
|
+
if (event.command === "exit") exitAsked = true;
|
|
2231
|
+
}
|
|
2232
|
+
o.onEvent?.(event);
|
|
2233
|
+
}
|
|
2234
|
+
});
|
|
2235
|
+
driver.state.engineOn = false;
|
|
2236
|
+
const target = typeof window !== "undefined" ? window : null;
|
|
2237
|
+
const down = (e) => {
|
|
2238
|
+
if (!controls.key(e.code, true)) return;
|
|
2239
|
+
e.stopImmediatePropagation();
|
|
2240
|
+
if (e.code.startsWith("Arrow") || e.code === "Space") e.preventDefault();
|
|
2241
|
+
};
|
|
2242
|
+
const up = (e) => {
|
|
2243
|
+
if (controls.key(e.code, false)) e.stopImmediatePropagation();
|
|
2244
|
+
};
|
|
2245
|
+
const blur = () => controls.release();
|
|
2246
|
+
target?.addEventListener("keydown", down, true);
|
|
2247
|
+
target?.addEventListener("keyup", up, true);
|
|
2248
|
+
target?.addEventListener("blur", blur);
|
|
2249
|
+
return {
|
|
2250
|
+
driver,
|
|
2251
|
+
controls,
|
|
2252
|
+
lamps,
|
|
2253
|
+
dashboard,
|
|
2254
|
+
get throttle() {
|
|
2255
|
+
return gas;
|
|
2256
|
+
},
|
|
2257
|
+
step(dt) {
|
|
2258
|
+
const input = controls.input(dt, driver);
|
|
2259
|
+
gas = input.throttle;
|
|
2260
|
+
driver.step(input, dt);
|
|
2261
|
+
controls.after(driver, dt);
|
|
2262
|
+
const s = controls.state;
|
|
2263
|
+
lamps.set({
|
|
2264
|
+
mode: s.lamps.mode,
|
|
2265
|
+
fog: s.lamps.fog,
|
|
2266
|
+
interior: s.lamps.interior,
|
|
2267
|
+
brake: s.brakeLights,
|
|
2268
|
+
reverse: s.reverseLights,
|
|
2269
|
+
left: s.blinkOn && (s.indicator === "left" || s.indicator === "hazard"),
|
|
2270
|
+
right: s.blinkOn && (s.indicator === "right" || s.indicator === "hazard")
|
|
2271
|
+
});
|
|
2272
|
+
dashboard?.update(
|
|
2273
|
+
{
|
|
2274
|
+
kmh: Math.abs(driver.state.u) * 3.6,
|
|
2275
|
+
rpm: driver.state.engineOn ? driver.state.rpm : 0,
|
|
2276
|
+
gear: driver.state.gear,
|
|
2277
|
+
ignition: s.ignition,
|
|
2278
|
+
engineOn: driver.state.engineOn,
|
|
2279
|
+
left: s.blinkOn && (s.indicator === "left" || s.indicator === "hazard"),
|
|
2280
|
+
right: s.blinkOn && (s.indicator === "right" || s.indicator === "hazard"),
|
|
2281
|
+
highBeam: s.lamps.mode === "high",
|
|
2282
|
+
lowBeam: s.lamps.mode === "low" || s.lamps.mode === "position",
|
|
2283
|
+
fog: s.lamps.fog,
|
|
2284
|
+
parkingBrake: s.parkingBrake,
|
|
2285
|
+
seatbelt: s.seatbelt,
|
|
2286
|
+
fourWheel: s.fourWheelEngaged
|
|
2287
|
+
},
|
|
2288
|
+
dt
|
|
2289
|
+
);
|
|
2290
|
+
if (s.tick) play(sounds.blinker, 0.8);
|
|
2291
|
+
if (s.horn && (hornAgain -= dt) <= 0) {
|
|
2292
|
+
play(sounds.horn);
|
|
2293
|
+
hornAgain = 0.5;
|
|
2294
|
+
} else if (!s.horn) hornAgain = 0;
|
|
2295
|
+
if (s.alarm === "ringing" && (alarmAgain -= dt) <= 0) {
|
|
2296
|
+
play(sounds.alarm);
|
|
2297
|
+
alarmAgain = 2.9;
|
|
2298
|
+
}
|
|
2299
|
+
if (s.wipers !== "off" && (wipeAgain -= dt) <= 0) {
|
|
2300
|
+
play(sounds.wiper, 0.7);
|
|
2301
|
+
wipeAgain = s.wipers === "fast" ? 0.6 : 1.4;
|
|
2302
|
+
}
|
|
2303
|
+
const kmh = Math.abs(driver.state.u) * 3.6;
|
|
2304
|
+
if (!s.seatbelt && s.ignition && kmh > 10 && (chimeAgain -= dt) <= 0) {
|
|
2305
|
+
play(sounds.chime, 0.6);
|
|
2306
|
+
chimeAgain = 6;
|
|
2307
|
+
}
|
|
2308
|
+
if (s.radio.on && (radioAgain -= dt) <= 0) {
|
|
2309
|
+
play(sounds.radio, s.radio.source === "tape" ? 0.25 : 0.45);
|
|
2310
|
+
radioAgain = 0.55;
|
|
2311
|
+
}
|
|
2312
|
+
if (exitAsked) {
|
|
2313
|
+
exitAsked = false;
|
|
2314
|
+
o.onExit();
|
|
2315
|
+
}
|
|
2316
|
+
},
|
|
2317
|
+
stop() {
|
|
2318
|
+
target?.removeEventListener("keydown", down, true);
|
|
2319
|
+
target?.removeEventListener("keyup", up, true);
|
|
2320
|
+
target?.removeEventListener("blur", blur);
|
|
2321
|
+
controls.release();
|
|
2322
|
+
lamps.dispose();
|
|
2323
|
+
dashboard?.dispose();
|
|
2324
|
+
}
|
|
2325
|
+
};
|
|
2326
|
+
}
|
|
1810
2327
|
|
|
1811
2328
|
// src/test-area/proving/text.ts
|
|
1812
2329
|
var PROVING_TEXT = {
|
|
1813
2330
|
"pt-BR": {
|
|
1814
2331
|
name: "Banco de provas",
|
|
1815
|
-
controls: { vehicle: "Ve\xEDculo", test: "Prova", camera: "C\xE2mera", time_scale: "Velocidade da simula\xE7\xE3o", visual_check: "Checagem visual" },
|
|
2332
|
+
controls: { vehicle: "Ve\xEDculo", test: "Prova", camera: "C\xE2mera", time_scale: "Velocidade da simula\xE7\xE3o", visual_check: "Checagem visual", gearbox: "C\xE2mbio" },
|
|
2333
|
+
gearbox: { automatic: "Autom\xE1tico", manual: "Manual" },
|
|
1816
2334
|
hints: {
|
|
2335
|
+
gearbox: "Autom\xE1tico troca sozinho. Manual: E sobe e Q desce a marcha (abaixo da 1\xAA v\xEAm o neutro e a r\xE9). M alterna dirigindo",
|
|
1817
2336
|
vehicle: "Ve\xEDculo da frota que vai para a pista",
|
|
1818
2337
|
test: "Prova medida que o bot\xE3o Rodar prova executa",
|
|
1819
2338
|
camera: "Sala: a c\xE2mera do jogador. Persegui\xE7\xE3o, cabine e cap\xF4 seguem o ve\xEDculo",
|
|
1820
2339
|
time_scale: "Quantas vezes mais r\xE1pido que o tempo real a prova roda",
|
|
1821
2340
|
visual_check: "O que voc\xEA olhou e vai aprovar ou reprovar"
|
|
1822
2341
|
},
|
|
1823
|
-
actions: { run_test: "Rodar prova", run_all: "Rodar todas na pista", certify_now: "Homologar agora (sem anima\xE7\xE3o)", reverse_park: "Dar r\xE9 (c\xE2mera de r\xE9)", sign_ok: "Aprovar checagem visual", sign_fail: "Reprovar checagem visual", reset: "Limpar ficha" },
|
|
2342
|
+
actions: { drive: "Dirigir (entrar / sair)", run_test: "Rodar prova", run_all: "Rodar todas na pista", certify_now: "Homologar agora (sem anima\xE7\xE3o)", reverse_park: "Dar r\xE9 (c\xE2mera de r\xE9)", sign_ok: "Aprovar checagem visual", sign_fail: "Reprovar checagem visual", reset: "Limpar ficha" },
|
|
1824
2343
|
layers: { mirrors: "Retrovisores", reverse_camera: "C\xE2mera de r\xE9", scoreboard: "Painel da ficha" },
|
|
1825
|
-
metrics: { speed: "Velocidade", time: "Tempo da prova", value: "Medi\xE7\xE3o", passed: "Aprovadas", failed: "Reprovadas", pending: "Pendentes", status: "Situa\xE7\xE3o (0 pend., 1 aprov., -1 reprov.)" },
|
|
2344
|
+
metrics: { gear: "Marcha (-1 r\xE9, 0 neutro)", rpm: "Rota\xE7\xE3o", speed: "Velocidade", time: "Tempo da prova", value: "Medi\xE7\xE3o", passed: "Aprovadas", failed: "Reprovadas", pending: "Pendentes", status: "Situa\xE7\xE3o (0 pend., 1 aprov., -1 reprov.)" },
|
|
1826
2345
|
cameras: { room: "Sala", chase: "Persegui\xE7\xE3o (3\xAA pessoa)", cockpit: "Cabine (1\xAA pessoa)", hood: "Cap\xF4" },
|
|
1827
2346
|
tests: {
|
|
1828
2347
|
accel_0_100: "0 a 100 km/h",
|
|
@@ -1844,22 +2363,49 @@ var PROVING_TEXT = {
|
|
|
1844
2363
|
},
|
|
1845
2364
|
status: { approved: "APROVADO", rejected: "REPROVADO", pending: "PENDENTE" },
|
|
1846
2365
|
verdict: { pass: "ok", fail: "falhou", pending: "\u2026" },
|
|
1847
|
-
board: { title: "Ficha de homologa\xE7\xE3o", test: "Prova", value: "Medido", range: "Faixa", verdict: "Resultado", notRun: "n\xE3o rodada", running: "rodando" },
|
|
1848
|
-
log: { start: "Prova {0}: {1}", result: "{0} \xB7 {1}: {2} {3} (faixa {4}) \u2192 {5}", signed: "{0} \xB7 {1}: {2} na sala", certified: "{0}: {1}" }
|
|
2366
|
+
board: { title: "Ficha de homologa\xE7\xE3o", test: "Prova", value: "Medido", range: "Faixa", verdict: "Resultado", notRun: "n\xE3o rodada", running: "rodando", gear: "Marcha {0} \xB7 {1} rpm \xB7 {2} km/h" },
|
|
2367
|
+
log: { start: "Prova {0}: {1}", result: "{0} \xB7 {1}: {2} {3} (faixa {4}) \u2192 {5}", signed: "{0} \xB7 {1}: {2} na sala", certified: "{0}: {1}", drive: "Dirigindo {0}: I liga o motor, F sai do carro", leave: "Saiu do volante", shift: "Marcha {0} \u2192 {1} a {2} km/h" },
|
|
2368
|
+
cabin: {
|
|
2369
|
+
keys: "W ou \u2191 acelera \xB7 S ou \u2193 freia (parado, d\xE1 r\xE9) \xB7 A/D ou \u2190/\u2192 vira \xB7 Espa\xE7o freio de m\xE3o \xB7 P puxa o freio de m\xE3o \xB7 I liga e desliga \xB7 M c\xE2mbio manual/autom\xE1tico \xB7 E sobe e Q desce marcha \xB7 G tra\xE7\xE3o 4x2/4x4/4x4 autom\xE1tica \xB7 L luzes (posi\xE7\xE3o, baixa) \xB7 K farol alto \xB7 N neblina \xB7 U luz interna \xB7 v\xEDrgula e ponto setas \xB7 B pisca-alerta \xB7 O limpador \xB7 J cinto \xB7 H buzina \xB7 Y alarme \xB7 R r\xE1dio \xB7 T r\xE1dio/fita \xB7 F sai do carro",
|
|
2370
|
+
on: "ligado",
|
|
2371
|
+
off: "desligado",
|
|
2372
|
+
ignition: "Motor",
|
|
2373
|
+
gearbox: "C\xE2mbio",
|
|
2374
|
+
gear: "Marcha",
|
|
2375
|
+
parkingBrake: "Freio de m\xE3o",
|
|
2376
|
+
driveMode: "Tra\xE7\xE3o",
|
|
2377
|
+
modes: { "4x2": "4x2", "4x4": "4x4", auto: "4x4 autom\xE1tica" },
|
|
2378
|
+
lights: "Luzes",
|
|
2379
|
+
lampModes: { off: "apagadas", position: "posi\xE7\xE3o", low: "farol baixo", high: "farol alto" },
|
|
2380
|
+
fog: "Neblina",
|
|
2381
|
+
interior: "Luz interna",
|
|
2382
|
+
alarm: "Alarme",
|
|
2383
|
+
alarmStates: { off: "desligado", armed: "armado", ringing: "disparado" },
|
|
2384
|
+
radio: "Som",
|
|
2385
|
+
sources: { radio: "r\xE1dio", tape: "toca-fitas" },
|
|
2386
|
+
indicator: "Setas",
|
|
2387
|
+
indicators: { off: "desligadas", left: "esquerda", right: "direita", hazard: "pisca-alerta" },
|
|
2388
|
+
wipers: "Limpador",
|
|
2389
|
+
wiperModes: { off: "desligado", slow: "lento", fast: "r\xE1pido" },
|
|
2390
|
+
seatbelt: "Cinto",
|
|
2391
|
+
refused: { no_four_wheel_drive: "Este ve\xEDculo n\xE3o tem tra\xE7\xE3o 4x4", no_fog_lamps: "Este ve\xEDculo n\xE3o tem farol de neblina", ignition_off: "Ligue o motor primeiro" }
|
|
2392
|
+
}
|
|
1849
2393
|
},
|
|
1850
2394
|
en: {
|
|
1851
2395
|
name: "Proving ground",
|
|
1852
|
-
controls: { vehicle: "Vehicle", test: "Test", camera: "Camera", time_scale: "Simulation speed", visual_check: "Visual check" },
|
|
2396
|
+
controls: { vehicle: "Vehicle", test: "Test", camera: "Camera", time_scale: "Simulation speed", visual_check: "Visual check", gearbox: "Gearbox" },
|
|
2397
|
+
gearbox: { automatic: "Automatic", manual: "Manual" },
|
|
1853
2398
|
hints: {
|
|
2399
|
+
gearbox: "Automatic shifts by itself. Manual: E shifts up and Q down (below 1st come neutral and reverse). M switches while driving",
|
|
1854
2400
|
vehicle: "Fleet vehicle that goes on the track",
|
|
1855
2401
|
test: "Measured test the Run test button runs",
|
|
1856
2402
|
camera: "Room: the player camera. Chase, cockpit and hood follow the vehicle",
|
|
1857
2403
|
time_scale: "How many times faster than real time the test runs",
|
|
1858
2404
|
visual_check: "What you looked at and will approve or refuse"
|
|
1859
2405
|
},
|
|
1860
|
-
actions: { run_test: "Run test", run_all: "Run all on the track", certify_now: "Certify now (no animation)", reverse_park: "Reverse (reversing camera)", sign_ok: "Approve visual check", sign_fail: "Refuse visual check", reset: "Clear certificate" },
|
|
2406
|
+
actions: { drive: "Drive (get in / out)", run_test: "Run test", run_all: "Run all on the track", certify_now: "Certify now (no animation)", reverse_park: "Reverse (reversing camera)", sign_ok: "Approve visual check", sign_fail: "Refuse visual check", reset: "Clear certificate" },
|
|
1861
2407
|
layers: { mirrors: "Mirrors", reverse_camera: "Reversing camera", scoreboard: "Certificate board" },
|
|
1862
|
-
metrics: { speed: "Speed", time: "Test time", value: "Measured", passed: "Passed", failed: "Failed", pending: "Pending", status: "Status (0 pending, 1 approved, -1 rejected)" },
|
|
2408
|
+
metrics: { gear: "Gear (-1 reverse, 0 neutral)", rpm: "Engine speed", speed: "Speed", time: "Test time", value: "Measured", passed: "Passed", failed: "Failed", pending: "Pending", status: "Status (0 pending, 1 approved, -1 rejected)" },
|
|
1863
2409
|
cameras: { room: "Room", chase: "Chase (third person)", cockpit: "Cockpit (first person)", hood: "Hood" },
|
|
1864
2410
|
tests: {
|
|
1865
2411
|
accel_0_100: "0 to 100 km/h",
|
|
@@ -1881,8 +2427,33 @@ var PROVING_TEXT = {
|
|
|
1881
2427
|
},
|
|
1882
2428
|
status: { approved: "APPROVED", rejected: "REJECTED", pending: "PENDING" },
|
|
1883
2429
|
verdict: { pass: "ok", fail: "failed", pending: "\u2026" },
|
|
1884
|
-
board: { title: "Certificate", test: "Test", value: "Measured", range: "Range", verdict: "Result", notRun: "not run", running: "running" },
|
|
1885
|
-
log: { start: "Test {0}: {1}", result: "{0} \xB7 {1}: {2} {3} (range {4}) \u2192 {5}", signed: "{0} \xB7 {1}: {2} in the room", certified: "{0}: {1}" }
|
|
2430
|
+
board: { title: "Certificate", test: "Test", value: "Measured", range: "Range", verdict: "Result", notRun: "not run", running: "running", gear: "Gear {0} \xB7 {1} rpm \xB7 {2} km/h" },
|
|
2431
|
+
log: { start: "Test {0}: {1}", result: "{0} \xB7 {1}: {2} {3} (range {4}) \u2192 {5}", signed: "{0} \xB7 {1}: {2} in the room", certified: "{0}: {1}", drive: "Driving {0}: I starts the engine, F gets out", leave: "Left the wheel", shift: "Gear {0} \u2192 {1} at {2} km/h" },
|
|
2432
|
+
cabin: {
|
|
2433
|
+
keys: "W or \u2191 accelerates \xB7 S or \u2193 brakes (stopped, reverses) \xB7 A/D or \u2190/\u2192 steer \xB7 Space handbrake \xB7 P pulls the handbrake \xB7 I ignition \xB7 M manual/automatic gearbox \xB7 E shifts up, Q down \xB7 G drive 4x2/4x4/auto 4x4 \xB7 L lamps (position, low) \xB7 K high beam \xB7 N fog \xB7 U cabin light \xB7 comma and period indicators \xB7 B hazard \xB7 O wipers \xB7 J seat belt \xB7 H horn \xB7 Y alarm \xB7 R radio \xB7 T radio/tape \xB7 F gets out",
|
|
2434
|
+
on: "on",
|
|
2435
|
+
off: "off",
|
|
2436
|
+
ignition: "Engine",
|
|
2437
|
+
gearbox: "Gearbox",
|
|
2438
|
+
gear: "Gear",
|
|
2439
|
+
parkingBrake: "Handbrake",
|
|
2440
|
+
driveMode: "Drive",
|
|
2441
|
+
modes: { "4x2": "4x2", "4x4": "4x4", auto: "automatic 4x4" },
|
|
2442
|
+
lights: "Lamps",
|
|
2443
|
+
lampModes: { off: "off", position: "position", low: "low beam", high: "high beam" },
|
|
2444
|
+
fog: "Fog lamps",
|
|
2445
|
+
interior: "Cabin light",
|
|
2446
|
+
alarm: "Alarm",
|
|
2447
|
+
alarmStates: { off: "off", armed: "armed", ringing: "ringing" },
|
|
2448
|
+
radio: "Sound",
|
|
2449
|
+
sources: { radio: "radio", tape: "tape" },
|
|
2450
|
+
indicator: "Indicators",
|
|
2451
|
+
indicators: { off: "off", left: "left", right: "right", hazard: "hazard" },
|
|
2452
|
+
wipers: "Wipers",
|
|
2453
|
+
wiperModes: { off: "off", slow: "slow", fast: "fast" },
|
|
2454
|
+
seatbelt: "Seat belt",
|
|
2455
|
+
refused: { no_four_wheel_drive: "This vehicle has no 4x4", no_fog_lamps: "This vehicle has no fog lamps", ignition_off: "Start the engine first" }
|
|
2456
|
+
}
|
|
1886
2457
|
}
|
|
1887
2458
|
};
|
|
1888
2459
|
var fillText = (template, ...values) => template.replace(/\{(\d+)\}/g, (_, i) => String(values[Number(i)] ?? ""));
|
|
@@ -1916,6 +2487,10 @@ function createProvingModule(options) {
|
|
|
1916
2487
|
let car = null;
|
|
1917
2488
|
let run = null;
|
|
1918
2489
|
let parking = null;
|
|
2490
|
+
let driving = null;
|
|
2491
|
+
let gearbox = "automatic";
|
|
2492
|
+
let lastGear = null;
|
|
2493
|
+
let driveClock = 0;
|
|
1919
2494
|
let queue = [];
|
|
1920
2495
|
let open = false;
|
|
1921
2496
|
let lastWrap = 0;
|
|
@@ -2050,13 +2625,13 @@ function createProvingModule(options) {
|
|
|
2050
2625
|
[text.board.test, text.board.value, text.board.range, text.board.verdict].forEach((h, i) => g.fillText(h.toUpperCase(), cols[i], 150));
|
|
2051
2626
|
let y = 190;
|
|
2052
2627
|
for (const r of cert.results) {
|
|
2053
|
-
const
|
|
2628
|
+
const live2 = run && run.test.id === r.test && !run.done;
|
|
2054
2629
|
g.fillStyle = "#e7e9ec";
|
|
2055
2630
|
g.font = "26px sans-serif";
|
|
2056
2631
|
g.fillText(text.tests[r.test] ?? r.test, cols[0], y);
|
|
2057
2632
|
g.font = "26px monospace";
|
|
2058
2633
|
g.fillStyle = "#5fd4e0";
|
|
2059
|
-
const value =
|
|
2634
|
+
const value = live2 ? text.board.running : r.value == null ? r.kind === "measured" && r.note === "not run" ? text.board.notRun : "\u2014" : `${r.value} ${r.unit}`;
|
|
2060
2635
|
g.fillText(value, cols[1], y);
|
|
2061
2636
|
g.fillStyle = "#8d95a3";
|
|
2062
2637
|
g.fillText(r.kind === "measured" ? fmtRange(r.range) : r.kind === "visual" ? "visual" : "sala", cols[2], y);
|
|
@@ -2064,11 +2639,17 @@ function createProvingModule(options) {
|
|
|
2064
2639
|
g.fillText(r.verdict === "pass" ? "\u2713 " + text.verdict.pass : r.verdict === "fail" ? "\u2717 " + text.verdict.fail : "\u2026", cols[3], y);
|
|
2065
2640
|
y += 30;
|
|
2066
2641
|
}
|
|
2642
|
+
const live = run?.driver.state ?? driving?.driver.state;
|
|
2643
|
+
if (live) {
|
|
2644
|
+
g.font = "24px monospace";
|
|
2645
|
+
g.fillStyle = "#f2c230";
|
|
2646
|
+
g.fillText(fillText(text.board.gear, gearName(live.gear), Math.round(live.rpm), Math.round(Math.abs(live.u) * 3.6)), cols[0], y + 16);
|
|
2647
|
+
}
|
|
2067
2648
|
board.texture.needsUpdate = true;
|
|
2068
2649
|
};
|
|
2069
2650
|
const removeCar = () => {
|
|
2070
2651
|
if (!car) return;
|
|
2071
|
-
|
|
2652
|
+
discardObject3D3(car.built.group);
|
|
2072
2653
|
car = null;
|
|
2073
2654
|
};
|
|
2074
2655
|
const placeCar = () => {
|
|
@@ -2135,7 +2716,7 @@ function createProvingModule(options) {
|
|
|
2135
2716
|
const frameOf = (speed) => {
|
|
2136
2717
|
if (!car) return null;
|
|
2137
2718
|
const g = car.built.group;
|
|
2138
|
-
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, length: car.built.length, width: car.built.width, height: car.built.height };
|
|
2719
|
+
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, length: car.built.length, width: car.built.width, height: car.built.height, ...car.built.driverEye ? { eye: car.built.driverEye } : {} };
|
|
2139
2720
|
};
|
|
2140
2721
|
const applyCamera = () => {
|
|
2141
2722
|
if (!host?.setViewCamera) return;
|
|
@@ -2197,16 +2778,115 @@ function createProvingModule(options) {
|
|
|
2197
2778
|
run = null;
|
|
2198
2779
|
queue = [];
|
|
2199
2780
|
setSurface("dry_asphalt");
|
|
2200
|
-
const driver =
|
|
2781
|
+
const driver = createSemiSimDriver4(subjectSpec2(car.subject));
|
|
2201
2782
|
driver.place(40, 0, 0);
|
|
2202
2783
|
parking = { driver, time: 0 };
|
|
2203
2784
|
};
|
|
2785
|
+
const stopDriving = () => {
|
|
2786
|
+
if (!driving) return;
|
|
2787
|
+
driving.stop();
|
|
2788
|
+
driving = null;
|
|
2789
|
+
host?.holdPlayer?.(false);
|
|
2790
|
+
host?.bench?.sheet?.(null);
|
|
2791
|
+
stopEngine();
|
|
2792
|
+
host?.log(text.log.leave);
|
|
2793
|
+
};
|
|
2794
|
+
const startDriving = () => {
|
|
2795
|
+
if (!host || !three) return;
|
|
2796
|
+
if (!car || car.subject.id !== settings.vehicle) placeCar();
|
|
2797
|
+
if (!car) return;
|
|
2798
|
+
run = null;
|
|
2799
|
+
queue = [];
|
|
2800
|
+
parking = null;
|
|
2801
|
+
setSurface("dry_asphalt");
|
|
2802
|
+
driving = createCockpit({
|
|
2803
|
+
THREE: three,
|
|
2804
|
+
host,
|
|
2805
|
+
built: car.built,
|
|
2806
|
+
subject: car.subject,
|
|
2807
|
+
place: [0, 0, 0],
|
|
2808
|
+
gauge: kitFor(three).materials?.gauge,
|
|
2809
|
+
needle: kitFor(three).materials?.amber,
|
|
2810
|
+
// the engine's own sound turns the key (starter, catch, idle by RPM)
|
|
2811
|
+
sounds: { starter: "" },
|
|
2812
|
+
onExit: () => stopDriving(),
|
|
2813
|
+
onEvent: (event) => {
|
|
2814
|
+
if (event.type === "refused") return host?.log(text.cabin.refused[event.reason] ?? event.reason);
|
|
2815
|
+
const st = event.state;
|
|
2816
|
+
const line = cabinLine(event.command, st);
|
|
2817
|
+
if (line) host?.log(line);
|
|
2818
|
+
if (event.command === "ignition" && car) {
|
|
2819
|
+
const at = [car.built.group.position.x, 0.5, car.built.group.position.z];
|
|
2820
|
+
if (st.ignition) engine?.start(at);
|
|
2821
|
+
else engine?.stop(at);
|
|
2822
|
+
}
|
|
2823
|
+
if (event.command === "gearbox") {
|
|
2824
|
+
gearbox = st.gearbox;
|
|
2825
|
+
host?.bench?.patch("gearbox", { value: st.gearbox });
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
});
|
|
2829
|
+
if (gearbox !== driving.controls.state.gearbox) driving.controls.command("gearbox");
|
|
2830
|
+
host.holdPlayer?.(true);
|
|
2831
|
+
if (settings.camera === "room") {
|
|
2832
|
+
settings.camera = "cockpit";
|
|
2833
|
+
host.bench?.patch("camera", { value: "cockpit" });
|
|
2834
|
+
applyCamera();
|
|
2835
|
+
}
|
|
2836
|
+
rig.vehicle?.reset();
|
|
2837
|
+
stopEngine();
|
|
2838
|
+
engine = createVehicleSound(host.audio, { sounds });
|
|
2839
|
+
host.bench?.sheet?.({ kicker: text.name, title: fillText(text.log.drive, name(settings.vehicle)), body: text.cabin.keys });
|
|
2840
|
+
host.log(fillText(text.log.drive, name(settings.vehicle)));
|
|
2841
|
+
};
|
|
2842
|
+
const onOff = (on) => on ? text.cabin.on : text.cabin.off;
|
|
2843
|
+
const cabinLine = (command, st) => {
|
|
2844
|
+
const c = text.cabin;
|
|
2845
|
+
switch (command) {
|
|
2846
|
+
case "ignition":
|
|
2847
|
+
return `${c.ignition}: ${onOff(st.ignition)}`;
|
|
2848
|
+
case "gearbox":
|
|
2849
|
+
return `${c.gearbox}: ${text.gearbox[st.gearbox]}`;
|
|
2850
|
+
case "gear_up":
|
|
2851
|
+
case "gear_down":
|
|
2852
|
+
return `${c.gear}: ${gearName(st.gear)}`;
|
|
2853
|
+
case "parking_brake":
|
|
2854
|
+
return `${c.parkingBrake}: ${onOff(st.parkingBrake)}`;
|
|
2855
|
+
case "drive_mode":
|
|
2856
|
+
return `${c.driveMode}: ${c.modes[st.driveMode]}`;
|
|
2857
|
+
case "lights":
|
|
2858
|
+
case "high_beam":
|
|
2859
|
+
return `${c.lights}: ${c.lampModes[st.lamps.mode]}`;
|
|
2860
|
+
case "fog_lights":
|
|
2861
|
+
return `${c.fog}: ${onOff(st.lamps.fog)}`;
|
|
2862
|
+
case "interior_light":
|
|
2863
|
+
return `${c.interior}: ${onOff(st.lamps.interior)}`;
|
|
2864
|
+
case "alarm":
|
|
2865
|
+
return `${c.alarm}: ${c.alarmStates[st.alarm]}`;
|
|
2866
|
+
case "radio":
|
|
2867
|
+
case "radio_source":
|
|
2868
|
+
return `${c.radio}: ${st.radio.on ? c.sources[st.radio.source] : c.off}`;
|
|
2869
|
+
case "indicator_left":
|
|
2870
|
+
case "indicator_right":
|
|
2871
|
+
case "hazard":
|
|
2872
|
+
return `${c.indicator}: ${c.indicators[st.indicator]}`;
|
|
2873
|
+
case "wipers":
|
|
2874
|
+
return `${c.wipers}: ${c.wiperModes[st.wipers]}`;
|
|
2875
|
+
case "seatbelt":
|
|
2876
|
+
return `${c.seatbelt}: ${onOff(st.seatbelt)}`;
|
|
2877
|
+
default:
|
|
2878
|
+
return null;
|
|
2879
|
+
}
|
|
2880
|
+
};
|
|
2881
|
+
const gearName = (gear) => gear < 0 ? "R" : gear === 0 ? "N" : String(gear);
|
|
2204
2882
|
const onChange = (change) => {
|
|
2205
2883
|
if (change.kind === "layer") {
|
|
2206
2884
|
if (change.id === "scoreboard" && board) board.mesh.visible = !!host?.bench?.layer("scoreboard");
|
|
2207
2885
|
return refreshMirrors();
|
|
2208
2886
|
}
|
|
2209
2887
|
if (change.kind === "action") {
|
|
2888
|
+
if (change.id === "drive") return driving ? stopDriving() : startDriving();
|
|
2889
|
+
if (change.id === "run_test" || change.id === "run_all" || change.id === "reverse_park") stopDriving();
|
|
2210
2890
|
if (change.id === "run_test") {
|
|
2211
2891
|
queue = [];
|
|
2212
2892
|
startTest(settings.test);
|
|
@@ -2237,6 +2917,7 @@ function createProvingModule(options) {
|
|
|
2237
2917
|
switch (change.id) {
|
|
2238
2918
|
case "vehicle":
|
|
2239
2919
|
settings.vehicle = String(change.value);
|
|
2920
|
+
stopDriving();
|
|
2240
2921
|
run = null;
|
|
2241
2922
|
queue = [];
|
|
2242
2923
|
parking = null;
|
|
@@ -2254,6 +2935,10 @@ function createProvingModule(options) {
|
|
|
2254
2935
|
case "time_scale":
|
|
2255
2936
|
settings.timeScale = Number(change.value);
|
|
2256
2937
|
break;
|
|
2938
|
+
case "gearbox":
|
|
2939
|
+
gearbox = change.value === "manual" ? "manual" : "automatic";
|
|
2940
|
+
if (driving && driving.controls.state.gearbox !== gearbox) driving.controls.command("gearbox");
|
|
2941
|
+
break;
|
|
2257
2942
|
case "visual_check":
|
|
2258
2943
|
settings.visual = String(change.value);
|
|
2259
2944
|
break;
|
|
@@ -2277,6 +2962,13 @@ function createProvingModule(options) {
|
|
|
2277
2962
|
speed = s.u;
|
|
2278
2963
|
steer = s.steerAngle;
|
|
2279
2964
|
if (run.done) finishTest();
|
|
2965
|
+
} else if (driving) {
|
|
2966
|
+
const d = driving;
|
|
2967
|
+
d.step(dt);
|
|
2968
|
+
if (!driving) return;
|
|
2969
|
+
pose(d.driver.state, d.driver.state.steerAngle);
|
|
2970
|
+
speed = d.driver.state.u;
|
|
2971
|
+
steer = d.driver.state.steerAngle;
|
|
2280
2972
|
} else if (parking) {
|
|
2281
2973
|
parking.time += dt;
|
|
2282
2974
|
const p = parking;
|
|
@@ -2286,7 +2978,7 @@ function createProvingModule(options) {
|
|
|
2286
2978
|
steer = p.driver.state.steerAngle;
|
|
2287
2979
|
if (p.time > 9) parking = null;
|
|
2288
2980
|
}
|
|
2289
|
-
if (car) engine?.update({ dt, position: [car.built.group.position.x, 0.5, car.built.group.position.z], speed, throttle: run ? 1 : 0 });
|
|
2981
|
+
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 });
|
|
2290
2982
|
const f = frameOf(speed);
|
|
2291
2983
|
if (f && rig.vehicle && settings.camera !== "room") rig.vehicle.update(rig.camera, f, dt);
|
|
2292
2984
|
if (f && rig.vehicle) for (const m of rig.mirrors) rig.vehicle.placeMirror(m.camera, m.pose, f);
|
|
@@ -2296,17 +2988,25 @@ function createProvingModule(options) {
|
|
|
2296
2988
|
mirrorKey = key;
|
|
2297
2989
|
refreshMirrors();
|
|
2298
2990
|
}
|
|
2299
|
-
|
|
2991
|
+
const geared = run?.driver.state ?? driving?.driver.state;
|
|
2992
|
+
if (geared && geared.gear !== lastGear) {
|
|
2993
|
+
if (lastGear !== null && geared.gear > 0) host.log(fillText(text.log.shift, gearName(lastGear), gearName(geared.gear), Math.round(Math.abs(geared.u) * 3.6)), { from: lastGear, to: geared.gear, kmh: +(Math.abs(geared.u) * 3.6).toFixed(1), rpm: Math.round(geared.rpm) }, "proving_shift");
|
|
2994
|
+
lastGear = geared.gear;
|
|
2995
|
+
drawBoard();
|
|
2996
|
+
} else if (!geared) lastGear = null;
|
|
2997
|
+
if (run && Math.floor(run.time * 2) !== Math.floor((run.time - dt * settings.timeScale) * 2) || driving && Math.floor(driveClock * 2) !== Math.floor((driveClock - dt) * 2)) drawBoard();
|
|
2998
|
+
if (driving) driveClock += dt;
|
|
2300
2999
|
};
|
|
2301
|
-
const speedNow = () => Math.abs(run?.driver.state.u ?? parking?.driver.state.u ?? 0) * 3.6;
|
|
3000
|
+
const speedNow = () => Math.abs(run?.driver.state.u ?? driving?.driver.state.u ?? parking?.driver.state.u ?? 0) * 3.6;
|
|
2302
3001
|
const counts = (verdict) => certificateOf(settings.vehicle)?.results.filter((r) => r.verdict === verdict).length ?? 0;
|
|
2303
3002
|
const teardown = () => {
|
|
3003
|
+
stopDriving();
|
|
2304
3004
|
stopEngine();
|
|
2305
3005
|
for (const m of rig.mirrors) m.remove();
|
|
2306
3006
|
rig.mirrors = [];
|
|
2307
3007
|
removeCar();
|
|
2308
3008
|
mirrorKey = "";
|
|
2309
|
-
if (track)
|
|
3009
|
+
if (track) discardObject3D3(track);
|
|
2310
3010
|
track = null;
|
|
2311
3011
|
strip = null;
|
|
2312
3012
|
board = null;
|
|
@@ -2332,6 +3032,8 @@ function createProvingModule(options) {
|
|
|
2332
3032
|
{ 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 },
|
|
2333
3033
|
{ kind: "number", id: "time_scale", name: text.controls.time_scale, hint: text.hints.time_scale, min: 1, max: 20, step: 1, default: settings.timeScale },
|
|
2334
3034
|
{ 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
|
+
{ kind: "action", id: "drive", name: text.actions.drive },
|
|
3036
|
+
{ kind: "choice", id: "gearbox", name: text.controls.gearbox, hint: text.hints.gearbox, options: ["automatic", "manual"].map((id) => ({ id, name: text.gearbox[id] })), default: gearbox },
|
|
2335
3037
|
{ kind: "action", id: "run_test", name: text.actions.run_test },
|
|
2336
3038
|
{ kind: "action", id: "run_all", name: text.actions.run_all },
|
|
2337
3039
|
{ kind: "action", id: "certify_now", name: text.actions.certify_now },
|
|
@@ -2347,6 +3049,8 @@ function createProvingModule(options) {
|
|
|
2347
3049
|
],
|
|
2348
3050
|
metrics: () => [
|
|
2349
3051
|
{ id: "speed", name: text.metrics.speed, unit: "km/h", digits: 0, read: speedNow },
|
|
3052
|
+
{ id: "gear", name: text.metrics.gear, read: () => driving?.driver.state.gear ?? run?.driver.state.gear ?? 0 },
|
|
3053
|
+
{ id: "rpm", name: text.metrics.rpm, unit: "rpm", digits: 0, read: () => driving?.driver.state.rpm ?? run?.driver.state.rpm ?? 0 },
|
|
2350
3054
|
{ id: "time", name: text.metrics.time, unit: "s", digits: 1, read: () => run?.time ?? 0 },
|
|
2351
3055
|
{ id: "value", name: text.metrics.value, digits: 1, read: () => run ? run.trace[run.trace.length - 1]?.[1] ?? 0 : 0 },
|
|
2352
3056
|
{ id: "passed", name: text.metrics.passed, read: () => counts("pass") },
|
|
@@ -2361,6 +3065,7 @@ function createProvingModule(options) {
|
|
|
2361
3065
|
rig.camera = new three.PerspectiveCamera(60, 1.6, 0.05, 900);
|
|
2362
3066
|
rig.vehicle = createVehicleCamera(three);
|
|
2363
3067
|
lent.audio?.register?.(LIBRARY_BANK2);
|
|
3068
|
+
lent.audio?.register?.(CABIN_BANK);
|
|
2364
3069
|
const stopChange = lent.bench?.onChange(onChange);
|
|
2365
3070
|
const stopFrame = lent.onFrame(frame);
|
|
2366
3071
|
const stopClear = lent.onClear?.(() => {
|
|
@@ -2421,9 +3126,13 @@ export {
|
|
|
2421
3126
|
DEFAULT_TEST_VEHICLES,
|
|
2422
3127
|
createVehiclesTestModule,
|
|
2423
3128
|
loadVehiclesTestModule,
|
|
3129
|
+
DASHBOARD_LOOK,
|
|
3130
|
+
createVehicleDashboard,
|
|
3131
|
+
LAMP_RULES,
|
|
3132
|
+
createVehicleLamps,
|
|
2424
3133
|
PROVING_TEXT,
|
|
2425
3134
|
PROVING_AREA_ID,
|
|
2426
3135
|
createProvingModule,
|
|
2427
3136
|
loadProvingModule
|
|
2428
3137
|
};
|
|
2429
|
-
//# sourceMappingURL=chunk-
|
|
3138
|
+
//# sourceMappingURL=chunk-MRUPKE7J.js.map
|