@adatechnology/game-vehicles-three 0.7.0 → 0.8.1
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-L63R2JQA.js → chunk-NPS4X64L.js} +335 -44
- package/dist/chunk-NPS4X64L.js.map +1 -0
- package/dist/index.d.ts +122 -12
- package/dist/index.global.js +782 -205
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +9 -1
- package/dist/{testing-DI0_R8dH.d.ts → testing-Cb10i9fm.d.ts} +19 -4
- package/dist/testing.d.ts +3 -1
- package/dist/testing.js +1 -1
- package/package.json +8 -8
- package/dist/chunk-L63R2JQA.js.map +0 -1
|
@@ -76,7 +76,8 @@ function fuse(THREE, c, pivot, paintColor, primary, hidden) {
|
|
|
76
76
|
const tmp = new THREE.Box3();
|
|
77
77
|
let pruned = 0;
|
|
78
78
|
c.vehicle.updateMatrixWorld(true);
|
|
79
|
-
c.vehicle.traverse((
|
|
79
|
+
c.vehicle.traverse((node) => {
|
|
80
|
+
const o = node;
|
|
80
81
|
if (!o.isMesh || o.isInstancedMesh || o.isSkinnedMesh || !o.geometry || !o.geometry.attributes.position) return;
|
|
81
82
|
for (let p = o; p; p = p.parent) if (p.visible === false) return;
|
|
82
83
|
if (o.userData && (o.userData.ghost || o.userData.skipLite)) return;
|
|
@@ -179,7 +180,8 @@ function liteMeshes(THREE, fusion, materials) {
|
|
|
179
180
|
function count(root) {
|
|
180
181
|
let triangles = 0;
|
|
181
182
|
let drawCalls = 0;
|
|
182
|
-
root.traverse((
|
|
183
|
+
root.traverse((node) => {
|
|
184
|
+
const o = node;
|
|
183
185
|
if (!o.isMesh || !o.geometry) return;
|
|
184
186
|
for (let p = o; p; p = p.parent) if (p.visible === false) return;
|
|
185
187
|
const g = o.geometry;
|
|
@@ -194,7 +196,8 @@ function primaryPaint(c) {
|
|
|
194
196
|
let best = null;
|
|
195
197
|
let bestCount = -1;
|
|
196
198
|
const counts = /* @__PURE__ */ new Map();
|
|
197
|
-
c.vehicle.traverse((
|
|
199
|
+
c.vehicle.traverse((node) => {
|
|
200
|
+
const o = node;
|
|
198
201
|
if (!o.isMesh || !o.geometry) return;
|
|
199
202
|
const mats = Array.isArray(o.material) ? o.material : [o.material];
|
|
200
203
|
const g = o.geometry;
|
|
@@ -239,7 +242,8 @@ function createVehiclesThree(THREE, options) {
|
|
|
239
242
|
const bounds = new THREE.Box3();
|
|
240
243
|
const piece = new THREE.Box3();
|
|
241
244
|
const turned = new THREE.Matrix4();
|
|
242
|
-
c.vehicle.traverse((
|
|
245
|
+
c.vehicle.traverse((node) => {
|
|
246
|
+
const m = node;
|
|
243
247
|
if (!m.isMesh || !m.geometry || m.userData.ghost) return;
|
|
244
248
|
for (let p = m; p; p = p.parent) if (p.visible === false) return;
|
|
245
249
|
m.geometry.computeBoundingBox();
|
|
@@ -268,7 +272,8 @@ function createVehiclesThree(THREE, options) {
|
|
|
268
272
|
if (color && primary) {
|
|
269
273
|
const repainted = primary.clone();
|
|
270
274
|
repainted.color.set(color);
|
|
271
|
-
c.vehicle.traverse((
|
|
275
|
+
c.vehicle.traverse((node) => {
|
|
276
|
+
const m = node;
|
|
272
277
|
if (!m.isMesh) return;
|
|
273
278
|
if (Array.isArray(m.material)) m.material = m.material.map((x) => x === primary ? repainted : x);
|
|
274
279
|
else if (m.material === primary) m.material = repainted;
|
|
@@ -304,7 +309,8 @@ function createVehiclesThree(THREE, options) {
|
|
|
304
309
|
context: quality === "high" ? c : null,
|
|
305
310
|
driverEye,
|
|
306
311
|
dispose() {
|
|
307
|
-
group.traverse((
|
|
312
|
+
group.traverse((node) => {
|
|
313
|
+
const m = node;
|
|
308
314
|
if (m.geometry) m.geometry.dispose();
|
|
309
315
|
});
|
|
310
316
|
}
|
|
@@ -349,7 +355,8 @@ function createVehicleLod(kit, context, root, full, options = {}) {
|
|
|
349
355
|
culled.clear();
|
|
350
356
|
modelKey = keyOf();
|
|
351
357
|
isUploaded = false;
|
|
352
|
-
full.traverse((
|
|
358
|
+
full.traverse((node) => {
|
|
359
|
+
const object = node;
|
|
353
360
|
if (!object.isMesh || culled.has(object)) return;
|
|
354
361
|
culled.set(object, object.frustumCulled);
|
|
355
362
|
object.frustumCulled = false;
|
|
@@ -376,7 +383,8 @@ function createVehicleLod(kit, context, root, full, options = {}) {
|
|
|
376
383
|
const shadowProxyOf = (fused) => {
|
|
377
384
|
const positions = [];
|
|
378
385
|
const indices = [];
|
|
379
|
-
for (const
|
|
386
|
+
for (const child of fused.children) {
|
|
387
|
+
const mesh = child;
|
|
380
388
|
if (!mesh.isMesh || !mesh.castShadow) continue;
|
|
381
389
|
const geometry2 = mesh.geometry.clone().applyMatrix4(mesh.matrix);
|
|
382
390
|
const offset = positions.length / 3;
|
|
@@ -415,7 +423,8 @@ function createVehicleLod(kit, context, root, full, options = {}) {
|
|
|
415
423
|
lite.visible = isFused;
|
|
416
424
|
shadow ?? (shadow = shadowProxyOf(lite));
|
|
417
425
|
shadow.visible = !isFused;
|
|
418
|
-
full.traverse((
|
|
426
|
+
full.traverse((node) => {
|
|
427
|
+
const object = node;
|
|
419
428
|
if (!object.isMesh || !object.castShadow) return;
|
|
420
429
|
castsShadow.add(object);
|
|
421
430
|
object.castShadow = false;
|
|
@@ -437,7 +446,8 @@ function createVehicleLod(kit, context, root, full, options = {}) {
|
|
|
437
446
|
root.updateMatrixWorld(true);
|
|
438
447
|
const inverse = new THREE.Matrix4().copy(root.matrixWorld).invert();
|
|
439
448
|
const textured = [];
|
|
440
|
-
full.traverse((
|
|
449
|
+
full.traverse((node) => {
|
|
450
|
+
const object = node;
|
|
441
451
|
if (!object.isMesh || object.userData.skipLite || !isShown(object, full)) return;
|
|
442
452
|
const materials = Array.isArray(object.material) ? object.material : [object.material];
|
|
443
453
|
if (materials.some((material) => material?.map && !/plate/i.test(material.name ?? "")) && !/plate/i.test(object.name ?? "")) textured.push(object);
|
|
@@ -998,7 +1008,11 @@ var DEFAULT_VEHICLE_SOUND_KEYS = {
|
|
|
998
1008
|
crashHeavy: "crash_heavy",
|
|
999
1009
|
skidAsphalt: "tire_skid_asphalt",
|
|
1000
1010
|
skidDirt: "tire_skid_dirt",
|
|
1001
|
-
roll: "tire_roll"
|
|
1011
|
+
roll: "tire_roll",
|
|
1012
|
+
brakeSqueal: "brake_squeal",
|
|
1013
|
+
hitBody: "vehicle_hit_pedestrian",
|
|
1014
|
+
siren: "siren",
|
|
1015
|
+
rotor: "helicopter_rotor_loop"
|
|
1002
1016
|
};
|
|
1003
1017
|
var CRASH_SPEEDS = { medium: 6, heavy: 14 };
|
|
1004
1018
|
var layers = (id, cutoffs, gains) => [
|
|
@@ -1111,8 +1125,10 @@ function createVehicleSound(audio, options = {}) {
|
|
|
1111
1125
|
const crankSeconds = options.crankSeconds ?? 0.75;
|
|
1112
1126
|
const gears = createGearGuess(profile);
|
|
1113
1127
|
const loops = /* @__PURE__ */ new Map();
|
|
1128
|
+
const budget = options.budget;
|
|
1129
|
+
const self = {};
|
|
1114
1130
|
const levels = {};
|
|
1115
|
-
const state = { running: false, cranking: 0, catches: false, flare: 0, rpm: 0 };
|
|
1131
|
+
const state = { running: false, cranking: 0, catches: false, flare: 0, rpm: 0, sirenPhase: 0 };
|
|
1116
1132
|
const loopOf = (sound) => {
|
|
1117
1133
|
let handle = loops.get(sound);
|
|
1118
1134
|
if (!handle && audio?.loop) {
|
|
@@ -1146,6 +1162,11 @@ function createVehicleSound(audio, options = {}) {
|
|
|
1146
1162
|
play(kind === "heavy" ? keys.crashHeavy : kind === "medium" ? keys.crashMedium : keys.crashLight, position, Math.min(1, 0.35 + v / 20));
|
|
1147
1163
|
return kind;
|
|
1148
1164
|
},
|
|
1165
|
+
hit(speed, position, sound) {
|
|
1166
|
+
const v = Math.abs(speed);
|
|
1167
|
+
if (v < 0.5) return;
|
|
1168
|
+
play(sound ?? keys.hitBody, position, Math.min(1, 0.3 + v / 15));
|
|
1169
|
+
},
|
|
1149
1170
|
update(frame) {
|
|
1150
1171
|
if (state.cranking > 0) {
|
|
1151
1172
|
state.cranking -= frame.dt;
|
|
@@ -1159,20 +1180,33 @@ function createVehicleSound(audio, options = {}) {
|
|
|
1159
1180
|
const driven = frame.rpm ?? gears.rpm(frame.speed, throttle);
|
|
1160
1181
|
const rpm = state.running ? Math.max(driven, profile.idleRpm * (1 + 1.2 * state.flare * state.flare)) : 0;
|
|
1161
1182
|
state.rpm = rpm;
|
|
1183
|
+
const heard = budget ? budget.admit(self, frame.position) ? 1 : 0 : 1;
|
|
1162
1184
|
for (const layer of engineMix(profile, { rpm, throttle: Math.max(throttle, state.flare * 0.6) })) {
|
|
1163
|
-
setLoop(layer.sound, layer.volume, frame.position, { ...layer.frequency === void 0 ? {} : { frequency: layer.frequency }, cutoff: layer.cutoff });
|
|
1185
|
+
setLoop(layer.sound, heard * layer.volume, frame.position, { ...layer.frequency === void 0 ? {} : { frequency: layer.frequency }, cutoff: layer.cutoff });
|
|
1164
1186
|
}
|
|
1165
1187
|
const speed = Math.abs(frame.speed);
|
|
1166
1188
|
const slide = clamp012(((frame.slip ?? 0) - 0.15) / 0.5) * clamp012(speed / 4);
|
|
1167
1189
|
const dirt = frame.surface === "dirt";
|
|
1168
|
-
setLoop(keys.skidAsphalt, dirt ? 0 : 0.16 * slide, frame.position, { cutoff: 1700 + 600 * slide });
|
|
1169
|
-
setLoop(keys.skidDirt, dirt ? 0.2 * slide : 0, frame.position, { cutoff: 500 + 500 * slide });
|
|
1170
|
-
setLoop(keys.roll, 0.06 * clamp012(speed / 25), frame.position, { cutoff: 250 + 45 * Math.min(speed, 40) });
|
|
1190
|
+
setLoop(keys.skidAsphalt, heard * (dirt ? 0 : 0.16 * slide), frame.position, { cutoff: 1700 + 600 * slide });
|
|
1191
|
+
setLoop(keys.skidDirt, heard * (dirt ? 0.2 * slide : 0), frame.position, { cutoff: 500 + 500 * slide });
|
|
1192
|
+
setLoop(keys.roll, heard * 0.06 * clamp012(speed / 25), frame.position, { cutoff: 250 + 45 * Math.min(speed, 40) });
|
|
1193
|
+
const squeal = clamp012(((frame.brake ?? 0) - 0.3) / 0.4) * clamp012(speed / 0.8) * clamp012((5 - speed) / 2) * (slide > 0 ? 0 : 1);
|
|
1194
|
+
setLoop(keys.brakeSqueal, heard * 0.05 * squeal, frame.position, { cutoff: 3200 + 400 * squeal });
|
|
1195
|
+
const siren = frame.siren ?? "off";
|
|
1196
|
+
if (siren !== "off" || loops.has(keys.siren)) {
|
|
1197
|
+
const period = siren === "yelp" ? 0.25 : 4;
|
|
1198
|
+
state.sirenPhase = (state.sirenPhase + frame.dt / period) % 1;
|
|
1199
|
+
const tri = state.sirenPhase < 0.5 ? state.sirenPhase * 2 : 2 - state.sirenPhase * 2;
|
|
1200
|
+
setLoop(keys.siren, heard * (siren === "off" ? 0 : 0.07), frame.position, { frequency: 650 + 850 * tri, cutoff: 2600 });
|
|
1201
|
+
}
|
|
1202
|
+
const rotor = clamp012(frame.rotor ?? 0);
|
|
1203
|
+
setLoop(keys.rotor, heard * 0.12 * rotor, frame.position, { cutoff: 180 + 220 * rotor });
|
|
1171
1204
|
},
|
|
1172
1205
|
get reading() {
|
|
1173
1206
|
return { running: state.running, rpm: state.rpm, levels: { ...levels } };
|
|
1174
1207
|
},
|
|
1175
1208
|
dispose() {
|
|
1209
|
+
budget?.release(self);
|
|
1176
1210
|
for (const handle of loops.values()) handle.stop();
|
|
1177
1211
|
loops.clear();
|
|
1178
1212
|
}
|
|
@@ -1218,7 +1252,16 @@ var VEHICLE_LOOPS = [
|
|
|
1218
1252
|
...engineLoops("bike", 600, 1400, 2800, 1200),
|
|
1219
1253
|
noiseLoop("tire_skid_asphalt", "bandpass", 1900, 5, "rubber squealing on asphalt: a narrow band of noise near 2 kHz"),
|
|
1220
1254
|
noiseLoop("tire_skid_dirt", "lowpass", 700, 0.8, "tyres sliding on dirt and gravel: a dull, wide hiss"),
|
|
1221
|
-
noiseLoop("tire_roll", "lowpass", 400, 0.7, "road noise of the tyres rolling, brighter with the speed")
|
|
1255
|
+
noiseLoop("tire_roll", "lowpass", 400, 0.7, "road noise of the tyres rolling, brighter with the speed"),
|
|
1256
|
+
// a pad on a disc: a thin, nearly pure whistle near 3.4 kHz (a narrow band of noise, so it wavers as a real squeal does)
|
|
1257
|
+
// a siren: a square through a low-pass, its pitch swept by the vehicle every frame (wail: a slow rise and fall; yelp: a fast one)
|
|
1258
|
+
toneLoop("siren", "square", "lowpass", 2600, 0.8, "emergency siren, the sweep driven by the vehicle"),
|
|
1259
|
+
// the blades: a low rumble chopped at the blade-pass rate (≈ 18 Hz for a light helicopter's main rotor)
|
|
1260
|
+
{
|
|
1261
|
+
...noiseLoop("helicopter_rotor_loop", "lowpass", 320, 0.9, "helicopter rotor: low rumble chopped by the blades"),
|
|
1262
|
+
modulators: [{ target: "gain", wave: "sine", rate: 18, depth: 0.45, base: 0.55 }]
|
|
1263
|
+
},
|
|
1264
|
+
noiseLoop("brake_squeal", "bandpass", 3400, 28, "brake pads squealing on the discs as the car comes to a stop")
|
|
1222
1265
|
];
|
|
1223
1266
|
var recipe = (id, layers2, line) => ({ ...sceneRecipe(id, layers2, line), sources: [SDK, line] });
|
|
1224
1267
|
var crank = (count2, spacing) => [
|
|
@@ -1262,6 +1305,17 @@ var VEHICLE_SOUNDS = [
|
|
|
1262
1305
|
],
|
|
1263
1306
|
"crash above 50 km/h: metal folding, the glass going, debris falling"
|
|
1264
1307
|
),
|
|
1308
|
+
// a body hit by a car: the dull slap of the bumper on a person, the body folding over the bonnet, at speed a crack of the windscreen
|
|
1309
|
+
recipe(
|
|
1310
|
+
"vehicle_hit_pedestrian",
|
|
1311
|
+
[
|
|
1312
|
+
tone({ f: 95, f2: 50, d: 0.14, vol: 0.9 }),
|
|
1313
|
+
noise({ type: "lowpass", f: 600, d: 0.12, vol: 0.8 }),
|
|
1314
|
+
noise({ type: "bandpass", f: 280, q: 1.2, d: 0.18, vol: 0.55, delay: 0.09 }),
|
|
1315
|
+
noise({ type: "highpass", f: 2600, d: 0.05, vol: 0.18, delay: 0.1 })
|
|
1316
|
+
],
|
|
1317
|
+
"a person hit by a car: the bumper slap, the body on the bonnet"
|
|
1318
|
+
),
|
|
1265
1319
|
// the game's horn (two squares at 415 and 523 Hz, jogo L70272) heard from where the car is; a truck's air horn lower and longer
|
|
1266
1320
|
recipe("horn_car", [tone({ wave: "square", f: 415, d: 0.6, vol: 0.09 }), tone({ wave: "square", f: 523, d: 0.6, vol: 0.09 })], "car horn: the game chord, placed and panned"),
|
|
1267
1321
|
recipe("horn_truck", [tone({ wave: "sawtooth", f: 220, d: 1.1, vol: 0.1 }), tone({ wave: "sawtooth", f: 277, d: 1.1, vol: 0.08 }), noise({ type: "bandpass", f: 900, q: 2, d: 1.1, vol: 0.05 })], "truck air horn: low reedy chord"),
|
|
@@ -1273,6 +1327,8 @@ var VEHICLE_SOUNDS = [
|
|
|
1273
1327
|
];
|
|
1274
1328
|
var CAR_RECORDED_RPM = { idle: 1e3, high: 2520 };
|
|
1275
1329
|
var CAR_START_RUN_STOP = {
|
|
1330
|
+
/** The take the times below were measured on. */
|
|
1331
|
+
take: "_01.",
|
|
1276
1332
|
/** The starter and the engine catching (at 0.58 s) and flaring; the loops take over at the crank time of the vehicle. */
|
|
1277
1333
|
start: { offset: 0, duration: 1.3 },
|
|
1278
1334
|
/** The ignition off: the engine running down to the end of the file. */
|
|
@@ -1292,11 +1348,13 @@ var VEHICLE_SAMPLE_ALIASES = [
|
|
|
1292
1348
|
carLayer("engine_car_mid", "engine_loop_car_high", CAR_RECORDED_RPM.high, 1),
|
|
1293
1349
|
// the same file as the mid layer, read from two seconds in, so the two do not sum in phase where they cross
|
|
1294
1350
|
carLayer("engine_car_high", "engine_loop_car_high", CAR_RECORDED_RPM.high, 2, 2),
|
|
1295
|
-
|
|
1296
|
-
{ id: "
|
|
1351
|
+
// the cut points are of take _01 (10.6 s); _02 (21.9 s) starts already running and idles at 9.96 s, so it is left out of both
|
|
1352
|
+
{ id: "engine_start", file: "engine_start_run_stop", only: CAR_START_RUN_STOP.take, ...CAR_START_RUN_STOP.start, falloff: VEHICLE_LOOP_FALLOFF },
|
|
1353
|
+
{ id: "engine_stop", file: "engine_start_run_stop", only: CAR_START_RUN_STOP.take, ...CAR_START_RUN_STOP.stop, falloff: VEHICLE_LOOP_FALLOFF },
|
|
1297
1354
|
{ id: "car_door_close", file: "car_door_shut" },
|
|
1298
1355
|
{ id: "car_trunk_close", file: "car_trunk_shut" }
|
|
1299
1356
|
];
|
|
1357
|
+
var VEHICLE_PRELOAD_SOUNDS = [...VEHICLE_SAMPLE_ALIASES.map((alias) => alias.id), "car_door_open"];
|
|
1300
1358
|
var VEHICLE_SOUND_BANK = { id: "vehicles_three", sounds: VEHICLE_SOUNDS, loops: VEHICLE_LOOPS, sampleAliases: VEHICLE_SAMPLE_ALIASES };
|
|
1301
1359
|
|
|
1302
1360
|
// src/test-area/module.ts
|
|
@@ -1641,7 +1699,8 @@ function createVehiclesTestModule(options = {}) {
|
|
|
1641
1699
|
speed,
|
|
1642
1700
|
throttle: input.throttle,
|
|
1643
1701
|
...semiState ? { rpm: semiState.rpm, slip: Math.max(0, ...semiState.slip.map(Math.abs)) } : { slip: braking && speed > 3 ? 0.6 : 0 },
|
|
1644
|
-
surface: /dirt|gravel|sand|grass|mud/.test(settings.surface) ? "dirt" : "asphalt"
|
|
1702
|
+
surface: /dirt|gravel|sand|grass|mud/.test(settings.surface) ? "dirt" : "asphalt",
|
|
1703
|
+
brake: input.brake
|
|
1645
1704
|
});
|
|
1646
1705
|
}
|
|
1647
1706
|
state.exhaust -= dt;
|
|
@@ -1817,6 +1876,7 @@ function createVehiclesTestModule(options = {}) {
|
|
|
1817
1876
|
if (isRenderPools(lent.pools)) lent.pools.materials.register("vehicles.cracked-glass", () => crackedGlassMaterial(three, new three.DataTexture(new Uint8Array(4), 1, 1)));
|
|
1818
1877
|
lent.audio?.register?.(LIBRARY_BANK);
|
|
1819
1878
|
lent.audio?.register?.(VEHICLE_SOUND_BANK);
|
|
1879
|
+
lent.audio?.preload?.(VEHICLE_PRELOAD_SOUNDS);
|
|
1820
1880
|
return () => {
|
|
1821
1881
|
stopChange?.();
|
|
1822
1882
|
stopFrame();
|
|
@@ -1851,7 +1911,7 @@ import { discardObject3D as discardObject3D3 } from "@adatechnology/game-shapes-
|
|
|
1851
1911
|
|
|
1852
1912
|
// src/test-area/proving/cockpit.ts
|
|
1853
1913
|
import { RADIO_TRACKS } from "@adatechnology/game-audio";
|
|
1854
|
-
import { bumperDistance, createSemiSimDriver as createSemiSimDriver3, createVehicleControls, createVehicleSystems, parkingBeepInterval } from "@adatechnology/game-vehicles-driving";
|
|
1914
|
+
import { bumperDistance, createSemiSimDriver as createSemiSimDriver3, createVehicleControls, createVehicleStorage, createVehicleSystems, createWindshieldFog, GLOVEBOX, parkingBeepInterval, trunkFor } from "@adatechnology/game-vehicles-driving";
|
|
1855
1915
|
import { subjectSpec } from "@adatechnology/game-vehicles-proving";
|
|
1856
1916
|
import { createVehicleDestructible as createVehicleDestructible3 } from "@adatechnology/game-vehicles";
|
|
1857
1917
|
|
|
@@ -2200,7 +2260,7 @@ function createVehicleDashboard(THREE, built, options) {
|
|
|
2200
2260
|
// src/lamps.ts
|
|
2201
2261
|
import { isLightBudget, LIGHT_PRIORITY } from "@adatechnology/game-render-three";
|
|
2202
2262
|
var LAMP_RULES = {
|
|
2203
|
-
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 } },
|
|
2263
|
+
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 }, beacon: { off: 0.35, dark: 0.12, lit: 2.8 } },
|
|
2204
2264
|
low: { color: 16773584, intensity: 2.4, distance: 32, angle: 0.42, penumbra: 0.55, decay: 1.6, drop: 0.09 },
|
|
2205
2265
|
high: { color: 16774882, intensity: 3.6, distance: 70, angle: 0.3, penumbra: 0.45, decay: 1.4, drop: 0.015 },
|
|
2206
2266
|
fog: { color: 16773320, intensity: 1.6, distance: 16, angle: 0.75, penumbra: 0.7, decay: 1.8, drop: 0.12 },
|
|
@@ -2217,7 +2277,11 @@ function createVehicleLamps(THREE, built, options = {}) {
|
|
|
2217
2277
|
if (o.userData?.lamp === "fog" && o.material && !bulbs.includes(o)) bulbs.push(o);
|
|
2218
2278
|
});
|
|
2219
2279
|
if (bulbs.length) lamps.push({ kind: "fog", inner: bulbs });
|
|
2220
|
-
const
|
|
2280
|
+
const beacons = [];
|
|
2281
|
+
built.group.traverse((o) => {
|
|
2282
|
+
if (o.userData?.lamp === "beacon" && o.material) beacons.push({ mesh: o, side: o.userData.beaconSide === 1 ? 1 : 0 });
|
|
2283
|
+
});
|
|
2284
|
+
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, beacon: beacons.length > 0 };
|
|
2221
2285
|
const originals = /* @__PURE__ */ new Map();
|
|
2222
2286
|
const copies = /* @__PURE__ */ new Map();
|
|
2223
2287
|
const base = /* @__PURE__ */ new Map();
|
|
@@ -2232,6 +2296,7 @@ function createVehicleLamps(THREE, built, options = {}) {
|
|
|
2232
2296
|
mesh.material = copy;
|
|
2233
2297
|
return copy;
|
|
2234
2298
|
};
|
|
2299
|
+
for (const b of beacons) own(b.mesh);
|
|
2235
2300
|
const haloBase = /* @__PURE__ */ new Map();
|
|
2236
2301
|
for (const l of lamps) {
|
|
2237
2302
|
for (const m of l.inner) own(m);
|
|
@@ -2307,6 +2372,8 @@ function createVehicleLamps(THREE, built, options = {}) {
|
|
|
2307
2372
|
else if (l.kind === "fog") glow(l, state.fog ? g.fog.on : g.fog.off);
|
|
2308
2373
|
}
|
|
2309
2374
|
for (const m of amber) glowMesh(m, state[sideOf.get(m) ?? "left"] ? g.amber.on : g.amber.off);
|
|
2375
|
+
const lit = state.beacon ?? null;
|
|
2376
|
+
for (const b of beacons) glowMesh(b.mesh, lit === null ? g.beacon.off : lit === b.side ? g.beacon.lit : g.beacon.dark);
|
|
2310
2377
|
const beamId = state.mode === "low" || state.mode === "high" ? state.mode : null;
|
|
2311
2378
|
for (const id of ["low", "high"]) beam(id, beamId === id, () => spots(front, LAMP_RULES[id]));
|
|
2312
2379
|
beam("fog", state.fog && has.fog, () => spots(fogAnchors, LAMP_RULES.fog));
|
|
@@ -2320,7 +2387,7 @@ function createVehicleLamps(THREE, built, options = {}) {
|
|
|
2320
2387
|
return { ...state };
|
|
2321
2388
|
},
|
|
2322
2389
|
set(next) {
|
|
2323
|
-
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;
|
|
2390
|
+
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 && (next.beacon ?? null) === (state.beacon ?? null)) return;
|
|
2324
2391
|
Object.assign(state, next);
|
|
2325
2392
|
apply();
|
|
2326
2393
|
},
|
|
@@ -2329,15 +2396,17 @@ function createVehicleLamps(THREE, built, options = {}) {
|
|
|
2329
2396
|
sources.clear();
|
|
2330
2397
|
for (const a of anchors) a.removeFromParent();
|
|
2331
2398
|
anchors.length = 0;
|
|
2399
|
+
const giveBack = (m) => {
|
|
2400
|
+
const copy = m.material;
|
|
2401
|
+
const original = originals.get(m);
|
|
2402
|
+
if (!original) return;
|
|
2403
|
+
originals.delete(m);
|
|
2404
|
+
m.material = original;
|
|
2405
|
+
if (copy && !Array.isArray(copy)) copy.dispose?.();
|
|
2406
|
+
};
|
|
2407
|
+
for (const b of beacons) giveBack(b.mesh);
|
|
2332
2408
|
for (const l of lamps) {
|
|
2333
|
-
for (const m of l.inner)
|
|
2334
|
-
const copy = m.material;
|
|
2335
|
-
const original = originals.get(m);
|
|
2336
|
-
if (original) {
|
|
2337
|
-
m.material = original;
|
|
2338
|
-
if (copy && !Array.isArray(copy)) copy.dispose?.();
|
|
2339
|
-
}
|
|
2340
|
-
}
|
|
2409
|
+
for (const m of l.inner) giveBack(m);
|
|
2341
2410
|
for (const h of l.halo ?? []) {
|
|
2342
2411
|
h.visible = true;
|
|
2343
2412
|
const s = haloBase.get(h) ?? 1;
|
|
@@ -2351,8 +2420,66 @@ function createVehicleLamps(THREE, built, options = {}) {
|
|
|
2351
2420
|
};
|
|
2352
2421
|
}
|
|
2353
2422
|
|
|
2423
|
+
// src/windshield-mist.ts
|
|
2424
|
+
var MIST_RULES = { color: 15001836, opacity: 0.72, others: 0.8 };
|
|
2425
|
+
var DOUBLE_SIDE = 2;
|
|
2426
|
+
function createWindshieldMist(THREE, built) {
|
|
2427
|
+
const glass = (built.context?.glass ?? []).filter((p) => p?.geometry);
|
|
2428
|
+
const films = [];
|
|
2429
|
+
for (const pane of glass) {
|
|
2430
|
+
const side = !Array.isArray(pane.material) && pane.material?.side || DOUBLE_SIDE;
|
|
2431
|
+
const material = new THREE.MeshBasicMaterial({ color: MIST_RULES.color, transparent: true, opacity: 0, depthWrite: false, side, polygonOffset: true, polygonOffsetFactor: -1, polygonOffsetUnits: -1 });
|
|
2432
|
+
const mesh = new THREE.Mesh(pane.geometry, material);
|
|
2433
|
+
mesh.name = "windshield_mist";
|
|
2434
|
+
mesh.raycast = () => {
|
|
2435
|
+
};
|
|
2436
|
+
mesh.userData.mist = true;
|
|
2437
|
+
mesh.renderOrder = pane.renderOrder + 1;
|
|
2438
|
+
mesh.visible = false;
|
|
2439
|
+
pane.add(mesh);
|
|
2440
|
+
films.push({ mesh, material, share: pane.userData.glass?.key === "windshield" ? 1 : MIST_RULES.others });
|
|
2441
|
+
}
|
|
2442
|
+
let fog = 0;
|
|
2443
|
+
return {
|
|
2444
|
+
panes: films.length,
|
|
2445
|
+
get fog() {
|
|
2446
|
+
return fog;
|
|
2447
|
+
},
|
|
2448
|
+
set(next) {
|
|
2449
|
+
const share = Math.max(0, Math.min(1, next));
|
|
2450
|
+
if (Math.abs(share - fog) < 5e-3 && share > 0 === fog > 0) return;
|
|
2451
|
+
fog = share;
|
|
2452
|
+
for (const f of films) {
|
|
2453
|
+
f.material.opacity = MIST_RULES.opacity * fog * f.share;
|
|
2454
|
+
f.mesh.visible = fog > 0.01;
|
|
2455
|
+
}
|
|
2456
|
+
},
|
|
2457
|
+
dispose() {
|
|
2458
|
+
for (const f of films) {
|
|
2459
|
+
f.mesh.removeFromParent();
|
|
2460
|
+
f.material.dispose();
|
|
2461
|
+
}
|
|
2462
|
+
films.length = 0;
|
|
2463
|
+
}
|
|
2464
|
+
};
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2354
2467
|
// src/test-area/proving/cockpit.ts
|
|
2355
|
-
var DEFAULT_COCKPIT_SOUNDS = {
|
|
2468
|
+
var DEFAULT_COCKPIT_SOUNDS = {
|
|
2469
|
+
horn: "horn",
|
|
2470
|
+
blinker: "blinker_tick",
|
|
2471
|
+
starter: "starter_crank",
|
|
2472
|
+
alarm: "car_alarm",
|
|
2473
|
+
chirp: "alarm_chirp",
|
|
2474
|
+
wiper: "wiper_swish",
|
|
2475
|
+
chime: "seatbelt_chime",
|
|
2476
|
+
radio: "radio_static",
|
|
2477
|
+
beep: "parking_beep",
|
|
2478
|
+
wail: "siren_wail",
|
|
2479
|
+
yelp: "siren_yelp",
|
|
2480
|
+
spark: "hotwire_spark",
|
|
2481
|
+
glovebox: "glovebox_latch"
|
|
2482
|
+
};
|
|
2356
2483
|
var BODY_KEYS = {
|
|
2357
2484
|
Digit1: "door_front_left",
|
|
2358
2485
|
Digit2: "door_front_right",
|
|
@@ -2370,6 +2497,8 @@ function createCockpit(o) {
|
|
|
2370
2497
|
const driver = createSemiSimDriver3(spec);
|
|
2371
2498
|
driver.place(o.place[0], o.place[1], o.place[2]);
|
|
2372
2499
|
const lamps = createVehicleLamps(o.THREE, o.built, { lights: o.host.lights, owner: "proving-ground" });
|
|
2500
|
+
const mist = createWindshieldMist(o.THREE, o.built);
|
|
2501
|
+
const fog = createWindshieldFog();
|
|
2373
2502
|
const audio = o.host.audio;
|
|
2374
2503
|
const top = o.subject.stats.maxSpeedKmh;
|
|
2375
2504
|
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;
|
|
@@ -2383,11 +2512,25 @@ function createCockpit(o) {
|
|
|
2383
2512
|
let beepAgain = 0;
|
|
2384
2513
|
let bar = 0;
|
|
2385
2514
|
let exitAsked = false;
|
|
2515
|
+
let sirenAgain = 0;
|
|
2516
|
+
let sparkAgain = 0;
|
|
2517
|
+
let wiresBefore = 0;
|
|
2386
2518
|
let gas = 0;
|
|
2387
2519
|
const systems = createVehicleSystems();
|
|
2388
2520
|
const context = o.built.context;
|
|
2389
2521
|
const destructible = o.kit && context ? createVehicleDestructible3(o.kit, context, { id: "driven", frame: o.built.group }) : null;
|
|
2390
2522
|
const body = destructible ? createVehicleBody(o.built, { THREE: o.THREE, clips: destructible.animations() }) : null;
|
|
2523
|
+
const compartments = [];
|
|
2524
|
+
if (!body || body.parts.some((p) => p.startsWith("door"))) compartments.push(GLOVEBOX);
|
|
2525
|
+
if (body?.parts.includes("lid")) compartments.push(trunkFor(o.trunkLitres));
|
|
2526
|
+
const storage = createVehicleStorage({ compartments });
|
|
2527
|
+
for (const c of compartments) storage.stock(c.id, o.stock?.(c.id) ?? []);
|
|
2528
|
+
const access = () => ({
|
|
2529
|
+
seated: true,
|
|
2530
|
+
frontDoorOpen: (body?.openness("door_front_left") ?? 0) >= 0.5 || (body?.openness("door_front_right") ?? 0) >= 0.5,
|
|
2531
|
+
gloveboxOpen: controls.state.glovebox,
|
|
2532
|
+
lid: body?.openness("lid") ?? 0
|
|
2533
|
+
});
|
|
2391
2534
|
const bodyEvents = (events) => {
|
|
2392
2535
|
for (const event of events) {
|
|
2393
2536
|
if (event.type === "sound") play(event.key, 0.9);
|
|
@@ -2403,6 +2546,8 @@ function createCockpit(o) {
|
|
|
2403
2546
|
fogLamps: lamps.has.fog,
|
|
2404
2547
|
clutchPedal: true,
|
|
2405
2548
|
radioTracks: RADIO_TRACKS.length,
|
|
2549
|
+
hasKey: o.hasKey,
|
|
2550
|
+
emergency: lamps.has.beacon,
|
|
2406
2551
|
onEvent: (event) => {
|
|
2407
2552
|
if (event.type === "command") {
|
|
2408
2553
|
if (event.command === "ignition" && !event.state.ignition) driver.state.engineOn = false;
|
|
@@ -2413,6 +2558,8 @@ function createCockpit(o) {
|
|
|
2413
2558
|
bar = 0;
|
|
2414
2559
|
}
|
|
2415
2560
|
if (event.command === "exit") exitAsked = true;
|
|
2561
|
+
if (event.command === "siren") sirenAgain = 0;
|
|
2562
|
+
if (event.command === "glovebox") play(sounds.glovebox, 0.8);
|
|
2416
2563
|
}
|
|
2417
2564
|
o.onEvent?.(event);
|
|
2418
2565
|
}
|
|
@@ -2446,6 +2593,12 @@ function createCockpit(o) {
|
|
|
2446
2593
|
target?.addEventListener("blur", blur);
|
|
2447
2594
|
const cockpit = {
|
|
2448
2595
|
radiator: 1,
|
|
2596
|
+
storage,
|
|
2597
|
+
access,
|
|
2598
|
+
fog,
|
|
2599
|
+
mist,
|
|
2600
|
+
weather: { ...o.weather ?? { outsideC: 25, rain: 0 } },
|
|
2601
|
+
emergency: lamps.has.beacon,
|
|
2449
2602
|
driver,
|
|
2450
2603
|
controls,
|
|
2451
2604
|
systems,
|
|
@@ -2474,8 +2627,24 @@ function createCockpit(o) {
|
|
|
2474
2627
|
brake: s.brakeLights,
|
|
2475
2628
|
reverse: s.reverseLights,
|
|
2476
2629
|
left: s.blinkOn && (s.indicator === "left" || s.indicator === "hazard"),
|
|
2477
|
-
right: s.blinkOn && (s.indicator === "right" || s.indicator === "hazard")
|
|
2630
|
+
right: s.blinkOn && (s.indicator === "right" || s.indicator === "hazard"),
|
|
2631
|
+
beacon: s.beacon && powered ? s.beaconSide : null
|
|
2478
2632
|
});
|
|
2633
|
+
const w = cockpit.weather;
|
|
2634
|
+
const windows = body ? body.windows.reduce((sum, id) => sum + body.window(id), 0) / Math.max(1, body.windows.length) : 0;
|
|
2635
|
+
const warm = systems.state.temperature;
|
|
2636
|
+
const cabinC = s.climate.on ? 22 : w.outsideC + (driver.state.engineOn ? 3 + 5 * Math.min(1, warm * 2) : 2);
|
|
2637
|
+
mist.set(fog.step({ dt, occupants: 1, outsideC: w.outsideC, cabinC, rain: w.rain, speed: Math.abs(driver.state.u), windows, climate: s.climate, engineWarm: driver.state.engineOn ? Math.min(1, warm * 2) : 0 }).fog);
|
|
2638
|
+
if (s.siren !== "off" && powered && (sirenAgain -= dt) <= 0) {
|
|
2639
|
+
play(s.siren === "wail" ? sounds.wail : sounds.yelp);
|
|
2640
|
+
sirenAgain = s.siren === "wail" ? 2.95 : 1.95;
|
|
2641
|
+
}
|
|
2642
|
+
const working = s.hotwire.progress > wiresBefore;
|
|
2643
|
+
wiresBefore = s.hotwire.progress;
|
|
2644
|
+
if (working && (sparkAgain -= dt) <= 0) {
|
|
2645
|
+
play(sounds.spark, 0.7);
|
|
2646
|
+
sparkAgain = 0.45 + 0.4 * (s.hotwire.progress * 7 % 1);
|
|
2647
|
+
}
|
|
2479
2648
|
dashboard?.update(
|
|
2480
2649
|
{
|
|
2481
2650
|
kmh: Math.abs(driver.state.u) * 3.6,
|
|
@@ -2551,6 +2720,8 @@ function createCockpit(o) {
|
|
|
2551
2720
|
target?.removeEventListener("blur", blur);
|
|
2552
2721
|
controls.release();
|
|
2553
2722
|
lamps.dispose();
|
|
2723
|
+
mist.dispose();
|
|
2724
|
+
storage.clear();
|
|
2554
2725
|
dashboard?.dispose();
|
|
2555
2726
|
body?.dispose();
|
|
2556
2727
|
destructible?.dispose?.();
|
|
@@ -2559,6 +2730,33 @@ function createCockpit(o) {
|
|
|
2559
2730
|
return cockpit;
|
|
2560
2731
|
}
|
|
2561
2732
|
|
|
2733
|
+
// src/audio/engine-budget.ts
|
|
2734
|
+
var distanceSq = (a, b) => (a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2 + (a[2] - b[2]) ** 2;
|
|
2735
|
+
function createEngineBudget(options) {
|
|
2736
|
+
const max = Math.max(1, options.max ?? 6);
|
|
2737
|
+
const places = /* @__PURE__ */ new Map();
|
|
2738
|
+
return {
|
|
2739
|
+
admit(vehicle, position) {
|
|
2740
|
+
const own = distanceSq(position, options.listener());
|
|
2741
|
+
places.set(vehicle, own);
|
|
2742
|
+
if (places.size <= max) return true;
|
|
2743
|
+
let nearer = 0;
|
|
2744
|
+
for (const [other, d] of places) {
|
|
2745
|
+
if (other === vehicle) continue;
|
|
2746
|
+
if (d < own) nearer++;
|
|
2747
|
+
if (nearer >= max) return false;
|
|
2748
|
+
}
|
|
2749
|
+
return true;
|
|
2750
|
+
},
|
|
2751
|
+
release(vehicle) {
|
|
2752
|
+
places.delete(vehicle);
|
|
2753
|
+
},
|
|
2754
|
+
get size() {
|
|
2755
|
+
return places.size;
|
|
2756
|
+
}
|
|
2757
|
+
};
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2562
2760
|
// src/wheel-roll.ts
|
|
2563
2761
|
var DEFAULT_RADIUS = 0.32;
|
|
2564
2762
|
function createWheelRoll(THREE, built) {
|
|
@@ -2630,7 +2828,7 @@ function createProvingCrash(host, occupant) {
|
|
|
2630
2828
|
// src/test-area/proving/systems-text.ts
|
|
2631
2829
|
var SYSTEMS_TEXT = {
|
|
2632
2830
|
"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",
|
|
2831
|
+
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 \xB7 ; sirene \xB7 ' giroflex (ve\xEDculo de emerg\xEAncia) \xB7 / porta-luvas \xB7 sem a chave, segure I: liga\xE7\xE3o direta",
|
|
2634
2832
|
profile: "Modo de condu\xE7\xE3o",
|
|
2635
2833
|
profiles: { eco: "econ\xF4mico", normal: "normal", sport: "esporte" },
|
|
2636
2834
|
traction: "Controle de tra\xE7\xE3o",
|
|
@@ -2640,7 +2838,7 @@ var SYSTEMS_TEXT = {
|
|
|
2640
2838
|
climate: "Ar-condicionado",
|
|
2641
2839
|
demist: "Desemba\xE7ador",
|
|
2642
2840
|
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" },
|
|
2841
|
+
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", no_key: "Sem a chave: segure I para fazer a liga\xE7\xE3o direta", no_siren: "Este ve\xEDculo n\xE3o tem sirene nem giroflex" },
|
|
2644
2842
|
events: {
|
|
2645
2843
|
stall: "O motor morreu",
|
|
2646
2844
|
out_of_fuel: "Acabou o combust\xEDvel",
|
|
@@ -2651,9 +2849,20 @@ var SYSTEMS_TEXT = {
|
|
|
2651
2849
|
seized: "O motor fundiu de tanto esquentar",
|
|
2652
2850
|
spare_fitted: "Estepe colocado",
|
|
2653
2851
|
no_spare: "N\xE3o h\xE1 estepe",
|
|
2654
|
-
cruise_cancelled: "Piloto autom\xE1tico desligado"
|
|
2852
|
+
cruise_cancelled: "Piloto autom\xE1tico desligado",
|
|
2853
|
+
hotwire_started: "Fios descascados sob a coluna: continue segurando I",
|
|
2854
|
+
hotwire_joined: "Liga\xE7\xE3o direta feita: a igni\xE7\xE3o pegou e o motor de partida gira"
|
|
2655
2855
|
},
|
|
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)" },
|
|
2856
|
+
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)", take_key: "Tirar a chave (liga\xE7\xE3o direta) / devolver", cold_rain: "Tempo: frio e chuva / bom", glovebox_contents: "Ver o porta-luvas", trunk_contents: "Ver o porta-malas", take_from_trunk: "Tirar um item do porta-malas para o ch\xE3o" },
|
|
2857
|
+
siren: "Sirene",
|
|
2858
|
+
sirenModes: { off: "desligada", wail: "cont\xEDnua", yelp: "r\xE1pida (cruzamento)" },
|
|
2859
|
+
beacon: "Giroflex",
|
|
2860
|
+
glovebox: "Porta-luvas",
|
|
2861
|
+
lid: { open: "aberto", shut: "fechado" },
|
|
2862
|
+
storage: { names: { glovebox: "Porta-luvas", trunk: "Porta-malas" }, empty: "{0}: vazio", shut: "{0} fechado: abra antes (porta-luvas com / sentado, porta-malas com 6)", list: "{0} ({1} kg): {2}", none: "Este ve\xEDculo n\xE3o tem {0}", taken: "{0} tirado do porta-malas e posto no ch\xE3o", ground: "{0} tirado do porta-malas (sem cat\xE1logo de itens nesta sala para p\xF4-lo no ch\xE3o)" },
|
|
2863
|
+
fog: { misted: "O para-brisa emba\xE7ou por dentro: = desemba\xE7ador, ar-condicionado ou um vidro aberto", clear: "Para-brisa desemba\xE7ado", weather: "L\xE1 fora: 8 \xB0C e chuva", fair: "L\xE1 fora: 25 \xB0C, sem chuva", metric: "Vidro emba\xE7ado" },
|
|
2864
|
+
keyTaken: "Sem a chave no contato: segure I para a liga\xE7\xE3o direta",
|
|
2865
|
+
keyBack: "Chave de volta no contato",
|
|
2657
2866
|
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
2867
|
track: "faixa {0}",
|
|
2659
2868
|
adjust: { seat_forward: "Banco: frente/tr\xE1s (m)", seat_height: "Banco: altura (m)", mirror_left: "Retrovisor esquerdo: \xE2ngulo", mirror_right: "Retrovisor direito: \xE2ngulo" },
|
|
@@ -2663,7 +2872,7 @@ var SYSTEMS_TEXT = {
|
|
|
2663
2872
|
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
2873
|
},
|
|
2665
2874
|
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",
|
|
2875
|
+
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 \xB7 ; siren \xB7 ' beacon (emergency vehicle) \xB7 / glovebox \xB7 without the key, hold I: hotwire",
|
|
2667
2876
|
profile: "Drive profile",
|
|
2668
2877
|
profiles: { eco: "eco", normal: "normal", sport: "sport" },
|
|
2669
2878
|
traction: "Traction control",
|
|
@@ -2673,7 +2882,7 @@ var SYSTEMS_TEXT = {
|
|
|
2673
2882
|
climate: "Air conditioning",
|
|
2674
2883
|
demist: "Demister",
|
|
2675
2884
|
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" },
|
|
2885
|
+
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", no_key: "No key: hold I to hotwire it", no_siren: "This vehicle has no siren or beacon" },
|
|
2677
2886
|
events: {
|
|
2678
2887
|
stall: "The engine stalled",
|
|
2679
2888
|
out_of_fuel: "Out of fuel",
|
|
@@ -2684,9 +2893,20 @@ var SYSTEMS_TEXT = {
|
|
|
2684
2893
|
seized: "The engine seized from the heat",
|
|
2685
2894
|
spare_fitted: "Spare tyre fitted",
|
|
2686
2895
|
no_spare: "No spare tyre left",
|
|
2687
|
-
cruise_cancelled: "Cruise control off"
|
|
2896
|
+
cruise_cancelled: "Cruise control off",
|
|
2897
|
+
hotwire_started: "Wires stripped under the column: keep holding I",
|
|
2898
|
+
hotwire_joined: "Hotwired: the ignition is live and the starter turns"
|
|
2688
2899
|
},
|
|
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)" },
|
|
2900
|
+
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)", take_key: "Take the key out (hotwire) / give it back", cold_rain: "Weather: cold and rain / fair", glovebox_contents: "Look in the glovebox", trunk_contents: "Look in the trunk", take_from_trunk: "Take an item out of the trunk to the ground" },
|
|
2901
|
+
siren: "Siren",
|
|
2902
|
+
sirenModes: { off: "off", wail: "wail", yelp: "yelp (junction)" },
|
|
2903
|
+
beacon: "Beacon",
|
|
2904
|
+
glovebox: "Glovebox",
|
|
2905
|
+
lid: { open: "open", shut: "shut" },
|
|
2906
|
+
storage: { names: { glovebox: "Glovebox", trunk: "Trunk" }, empty: "{0}: empty", shut: "{0} shut: open it first (glovebox with / while seated, trunk with 6)", list: "{0} ({1} kg): {2}", none: "This vehicle has no {0}", taken: "{0} taken out of the trunk and put on the ground", ground: "{0} taken out of the trunk (no item catalogue in this room to put it on the ground)" },
|
|
2907
|
+
fog: { misted: "The windshield misted up inside: = demister, air conditioning or a window down", clear: "Windshield clear", weather: "Outside: 8 \xB0C and rain", fair: "Outside: 25 \xB0C, no rain", metric: "Glass misted" },
|
|
2908
|
+
keyTaken: "No key in the ignition: hold I to hotwire",
|
|
2909
|
+
keyBack: "Key back in the ignition",
|
|
2690
2910
|
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
2911
|
track: "track {0}",
|
|
2692
2912
|
adjust: { seat_forward: "Seat: forward/back (m)", seat_height: "Seat: height (m)", mirror_left: "Left mirror: angle", mirror_right: "Right mirror: angle" },
|
|
@@ -2872,6 +3092,9 @@ function createProvingModule(options) {
|
|
|
2872
3092
|
return turn ? { ...pose2, look: [pose2.look[0] + turn, pose2.look[1], pose2.look[2]] } : pose2;
|
|
2873
3093
|
};
|
|
2874
3094
|
let gearbox = "automatic";
|
|
3095
|
+
let hasKey = true;
|
|
3096
|
+
let weather = { outsideC: 25, rain: 0 };
|
|
3097
|
+
let misted = false;
|
|
2875
3098
|
let lastGear = null;
|
|
2876
3099
|
let driveClock = 0;
|
|
2877
3100
|
let queue = [];
|
|
@@ -3191,7 +3414,8 @@ function createProvingModule(options) {
|
|
|
3191
3414
|
run = null;
|
|
3192
3415
|
queue = [];
|
|
3193
3416
|
parking = null;
|
|
3194
|
-
setSurface("dry_asphalt");
|
|
3417
|
+
setSurface(weather.rain > 0 ? "wet_asphalt" : "dry_asphalt");
|
|
3418
|
+
misted = false;
|
|
3195
3419
|
driving = createCockpit({
|
|
3196
3420
|
THREE: three,
|
|
3197
3421
|
host,
|
|
@@ -3204,8 +3428,16 @@ function createProvingModule(options) {
|
|
|
3204
3428
|
needle: kitFor(three).materials?.amber,
|
|
3205
3429
|
// the engine's own sound turns the key (starter, catch, idle by RPM)
|
|
3206
3430
|
sounds: { starter: "" },
|
|
3431
|
+
hasKey,
|
|
3432
|
+
weather,
|
|
3433
|
+
stock: (compartment) => options.stock?.(compartment, settings.vehicle) ?? [],
|
|
3207
3434
|
onExit: () => stopDriving(),
|
|
3208
3435
|
onEvent: (event) => {
|
|
3436
|
+
if (event.type === "hotwire") {
|
|
3437
|
+
host?.log(sysText.events[event.stage === "joined" ? "hotwire_joined" : "hotwire_started"], { vehicle: settings.vehicle, stage: event.stage }, "proving_system");
|
|
3438
|
+
if (event.stage === "joined" && car && driving?.systems.canCrank() !== false) engine?.start([car.built.group.position.x, 0.5, car.built.group.position.z]);
|
|
3439
|
+
return;
|
|
3440
|
+
}
|
|
3209
3441
|
if (event.type === "refused") return host?.log(text.cabin.refused[event.reason] ?? sysText.refused[event.reason] ?? event.reason);
|
|
3210
3442
|
if (event.type === "sound") return;
|
|
3211
3443
|
if (event.type !== "command") {
|
|
@@ -3287,11 +3519,41 @@ function createProvingModule(options) {
|
|
|
3287
3519
|
return `${sysText.demist}: ${onOff(st.climate.demist)}`;
|
|
3288
3520
|
case "seatbelt":
|
|
3289
3521
|
return `${c.seatbelt}: ${onOff(st.seatbelt)}`;
|
|
3522
|
+
case "siren":
|
|
3523
|
+
return `${sysText.siren}: ${sysText.sirenModes[st.siren]}`;
|
|
3524
|
+
case "beacon":
|
|
3525
|
+
return `${sysText.beacon}: ${onOff(st.beacon)}`;
|
|
3526
|
+
case "glovebox":
|
|
3527
|
+
return `${sysText.glovebox}: ${sysText.lid[st.glovebox ? "open" : "shut"]}`;
|
|
3290
3528
|
default:
|
|
3291
3529
|
return null;
|
|
3292
3530
|
}
|
|
3293
3531
|
};
|
|
3532
|
+
const itemName = (id) => options.itemName?.(id) ?? id;
|
|
3533
|
+
const listCompartment = (cockpit, id) => {
|
|
3534
|
+
const label = sysText.storage.names[id];
|
|
3535
|
+
const data = { vehicle: settings.vehicle, compartment: id };
|
|
3536
|
+
if (!cockpit.storage.compartments.some((c) => c.id === id)) return host?.log(fillText(sysText.storage.none, label.toLowerCase()), data, "proving_storage");
|
|
3537
|
+
if (!cockpit.storage.reachable(id, cockpit.access())) return host?.log(fillText(sysText.storage.shut, label), data, "proving_storage");
|
|
3538
|
+
const stacks = cockpit.storage.contents(id).filter((x) => x !== null);
|
|
3539
|
+
if (!stacks.length) return host?.log(fillText(sysText.storage.empty, label), data, "proving_storage");
|
|
3540
|
+
host?.log(fillText(sysText.storage.list, label, cockpit.storage.loadKg(id).toFixed(1), stacks.map((x) => x.count > 1 ? `${x.count}\xD7 ${itemName(x.itemId)}` : itemName(x.itemId)).join(", ")), { ...data, stacks: stacks.map((x) => ({ item: x.itemId, count: x.count })) }, "proving_storage");
|
|
3541
|
+
};
|
|
3294
3542
|
const systemsAction = (id) => {
|
|
3543
|
+
if (id === "take_key") {
|
|
3544
|
+
hasKey = !hasKey;
|
|
3545
|
+
if (driving) stopDriving();
|
|
3546
|
+
startDriving();
|
|
3547
|
+
host?.log(hasKey ? sysText.keyBack : sysText.keyTaken, { vehicle: settings.vehicle, hasKey }, "proving_system");
|
|
3548
|
+
return;
|
|
3549
|
+
}
|
|
3550
|
+
if (id === "cold_rain") {
|
|
3551
|
+
weather = weather.rain > 0 ? { outsideC: 25, rain: 0 } : { outsideC: 8, rain: 1 };
|
|
3552
|
+
if (driving) driving.weather = { ...weather };
|
|
3553
|
+
setSurface(weather.rain > 0 ? "wet_asphalt" : "dry_asphalt");
|
|
3554
|
+
host?.log(weather.rain > 0 ? sysText.fog.weather : sysText.fog.fair, { vehicle: settings.vehicle, ...weather }, "proving_system");
|
|
3555
|
+
return;
|
|
3556
|
+
}
|
|
3295
3557
|
if (!driving) startDriving();
|
|
3296
3558
|
const cockpit = driving;
|
|
3297
3559
|
if (!cockpit) return;
|
|
@@ -3357,6 +3619,23 @@ function createProvingModule(options) {
|
|
|
3357
3619
|
host.log(placed ? sysText.parking.placed : sysText.parking.none, { vehicle: settings.vehicle }, "proving_system");
|
|
3358
3620
|
return;
|
|
3359
3621
|
}
|
|
3622
|
+
case "glovebox_contents":
|
|
3623
|
+
return listCompartment(cockpit, "glovebox");
|
|
3624
|
+
case "trunk_contents":
|
|
3625
|
+
return listCompartment(cockpit, "trunk");
|
|
3626
|
+
case "take_from_trunk": {
|
|
3627
|
+
const storage = cockpit.storage;
|
|
3628
|
+
if (!storage.compartments.some((c) => c.id === "trunk")) return listCompartment(cockpit, "trunk");
|
|
3629
|
+
const slot = storage.contents("trunk").findIndex((x) => x !== null);
|
|
3630
|
+
if (slot < 0 || !storage.reachable("trunk", cockpit.access())) return listCompartment(cockpit, "trunk");
|
|
3631
|
+
const stack = storage.take("trunk", slot, cockpit.access());
|
|
3632
|
+
if (!stack) return;
|
|
3633
|
+
const g = car?.built.group;
|
|
3634
|
+
const back = (car?.built.length ?? 4.5) / 2 + 0.8;
|
|
3635
|
+
const placed = g ? host?.addObject?.(stack.itemId, { position: [g.position.x - Math.sin(g.rotation.y) * back, 0, g.position.z - Math.cos(g.rotation.y) * back] }) : null;
|
|
3636
|
+
host?.log(fillText(placed ? sysText.storage.taken : sysText.storage.ground, itemName(stack.itemId)), { vehicle: settings.vehicle, item: stack.itemId, count: stack.count, placed: !!placed }, "proving_storage");
|
|
3637
|
+
return;
|
|
3638
|
+
}
|
|
3360
3639
|
case "force_door": {
|
|
3361
3640
|
const body = cockpit.body;
|
|
3362
3641
|
if (!body) return;
|
|
@@ -3463,6 +3742,13 @@ function createProvingModule(options) {
|
|
|
3463
3742
|
const d = driving;
|
|
3464
3743
|
d.step(dt);
|
|
3465
3744
|
if (!driving) return;
|
|
3745
|
+
if (!misted && d.fog.state.fog > 0.6) {
|
|
3746
|
+
misted = true;
|
|
3747
|
+
host.log(sysText.fog.misted, { vehicle: settings.vehicle, fog: +d.fog.state.fog.toFixed(2), humidity: +d.fog.state.humidity.toFixed(2) }, "proving_system");
|
|
3748
|
+
} else if (misted && d.fog.state.fog < 0.1) {
|
|
3749
|
+
misted = false;
|
|
3750
|
+
host.log(sysText.fog.clear, { vehicle: settings.vehicle }, "proving_system");
|
|
3751
|
+
}
|
|
3466
3752
|
pose(d.driver.state, d.driver.state.steerAngle);
|
|
3467
3753
|
speed = d.driver.state.u;
|
|
3468
3754
|
steer = d.driver.state.steerAngle;
|
|
@@ -3558,6 +3844,7 @@ function createProvingModule(options) {
|
|
|
3558
3844
|
{ id: "gear", name: text.metrics.gear, read: () => driving?.driver.state.gear ?? run?.driver.state.gear ?? 0 },
|
|
3559
3845
|
{ id: "rpm", name: text.metrics.rpm, unit: "rpm", digits: 0, read: () => driving?.driver.state.rpm ?? run?.driver.state.rpm ?? 0 },
|
|
3560
3846
|
{ id: "time", name: text.metrics.time, unit: "s", digits: 1, read: () => run?.time ?? 0 },
|
|
3847
|
+
{ id: "fog", name: sysText.fog.metric, unit: "%", digits: 0, read: () => (driving?.fog.state.fog ?? 0) * 100 },
|
|
3561
3848
|
{ id: "value", name: text.metrics.value, digits: 1, read: () => run ? run.trace[run.trace.length - 1]?.[1] ?? 0 : 0 },
|
|
3562
3849
|
{ id: "passed", name: text.metrics.passed, read: () => counts("pass") },
|
|
3563
3850
|
{ id: "failed", name: text.metrics.failed, read: () => counts("fail") },
|
|
@@ -3623,7 +3910,9 @@ export {
|
|
|
3623
3910
|
CAR_RECORDED_RPM,
|
|
3624
3911
|
CAR_START_RUN_STOP,
|
|
3625
3912
|
VEHICLE_SAMPLE_ALIASES,
|
|
3913
|
+
VEHICLE_PRELOAD_SOUNDS,
|
|
3626
3914
|
VEHICLE_SOUND_BANK,
|
|
3915
|
+
createEngineBudget,
|
|
3627
3916
|
createVehicleSound,
|
|
3628
3917
|
VEHICLE_LOD_DISTANCE,
|
|
3629
3918
|
createVehicleLod,
|
|
@@ -3642,10 +3931,12 @@ export {
|
|
|
3642
3931
|
createVehicleDashboard,
|
|
3643
3932
|
LAMP_RULES,
|
|
3644
3933
|
createVehicleLamps,
|
|
3934
|
+
MIST_RULES,
|
|
3935
|
+
createWindshieldMist,
|
|
3645
3936
|
createWheelRoll,
|
|
3646
3937
|
PROVING_TEXT,
|
|
3647
3938
|
PROVING_AREA_ID,
|
|
3648
3939
|
createProvingModule,
|
|
3649
3940
|
loadProvingModule
|
|
3650
3941
|
};
|
|
3651
|
-
//# sourceMappingURL=chunk-
|
|
3942
|
+
//# sourceMappingURL=chunk-NPS4X64L.js.map
|