@adatechnology/game-vehicles-three 0.6.4 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-MRUPKE7J.js → chunk-NPS4X64L.js} +864 -60
- package/dist/chunk-NPS4X64L.js.map +1 -0
- package/dist/index.d.ts +267 -14
- package/dist/index.global.js +2057 -401
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +19 -1
- package/dist/{testing-C-BgV7gj.d.ts → testing-Cb10i9fm.d.ts} +23 -9
- package/dist/testing.d.ts +3 -1
- package/dist/testing.js +1 -1
- package/package.json +8 -8
- package/dist/chunk-MRUPKE7J.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"),
|
|
@@ -1271,7 +1325,37 @@ var VEHICLE_SOUNDS = [
|
|
|
1271
1325
|
recipe("car_trunk_close", [tone({ f: 90, f2: 55, d: 0.15, vol: 0.65 }), noise({ type: "bandpass", f: 500, q: 1, d: 0.12, vol: 0.5 }), ...latch(0.01, 0.28), noise({ type: "bandpass", f: 1800, q: 3, d: 0.04, vol: 0.1, delay: 0.06 })], "trunk or hatch shut: a lighter thump, the latch and a small rattle"),
|
|
1272
1326
|
recipe("car_hood_close", [tone({ f: 70, f2: 40, d: 0.22, vol: 0.8 }), noise({ type: "bandpass", f: 850, q: 1.4, d: 0.2, vol: 0.6, wet: 0.3 }), tone({ f: 900, f2: 860, d: 0.18, vol: 0.05 }), ...latch(0.015, 0.3)], "hood slammed: a big sheet of metal ringing a little, the latch")
|
|
1273
1327
|
];
|
|
1274
|
-
var
|
|
1328
|
+
var CAR_RECORDED_RPM = { idle: 1e3, high: 2520 };
|
|
1329
|
+
var CAR_START_RUN_STOP = {
|
|
1330
|
+
/** The take the times below were measured on. */
|
|
1331
|
+
take: "_01.",
|
|
1332
|
+
/** The starter and the engine catching (at 0.58 s) and flaring; the loops take over at the crank time of the vehicle. */
|
|
1333
|
+
start: { offset: 0, duration: 1.3 },
|
|
1334
|
+
/** The ignition off: the engine running down to the end of the file. */
|
|
1335
|
+
stop: { offset: 9.96, duration: 0.64 }
|
|
1336
|
+
};
|
|
1337
|
+
var carLayer = (id, file, rpm, harmonic, offset) => ({
|
|
1338
|
+
id,
|
|
1339
|
+
file,
|
|
1340
|
+
loop: true,
|
|
1341
|
+
// the layer's oscillator plays the firing frequency times its harmonic: the file sounds at that frequency for the RPM it was recorded at
|
|
1342
|
+
baseFrequency: firingHz(CAR_ENGINE, rpm) * harmonic,
|
|
1343
|
+
falloff: VEHICLE_LOOP_FALLOFF,
|
|
1344
|
+
...offset === void 0 ? {} : { offset }
|
|
1345
|
+
});
|
|
1346
|
+
var VEHICLE_SAMPLE_ALIASES = [
|
|
1347
|
+
carLayer("engine_car_low", "engine_loop_car_idle", CAR_RECORDED_RPM.idle, 1),
|
|
1348
|
+
carLayer("engine_car_mid", "engine_loop_car_high", CAR_RECORDED_RPM.high, 1),
|
|
1349
|
+
// the same file as the mid layer, read from two seconds in, so the two do not sum in phase where they cross
|
|
1350
|
+
carLayer("engine_car_high", "engine_loop_car_high", CAR_RECORDED_RPM.high, 2, 2),
|
|
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 },
|
|
1354
|
+
{ id: "car_door_close", file: "car_door_shut" },
|
|
1355
|
+
{ id: "car_trunk_close", file: "car_trunk_shut" }
|
|
1356
|
+
];
|
|
1357
|
+
var VEHICLE_PRELOAD_SOUNDS = [...VEHICLE_SAMPLE_ALIASES.map((alias) => alias.id), "car_door_open"];
|
|
1358
|
+
var VEHICLE_SOUND_BANK = { id: "vehicles_three", sounds: VEHICLE_SOUNDS, loops: VEHICLE_LOOPS, sampleAliases: VEHICLE_SAMPLE_ALIASES };
|
|
1275
1359
|
|
|
1276
1360
|
// src/test-area/module.ts
|
|
1277
1361
|
var VEHICLES_AREA_ID = "vehicles_area";
|
|
@@ -1280,6 +1364,7 @@ var ITEM_OF = { compact_hatch: "hatch", midsize_sedan: "sedan", midsize_pickup:
|
|
|
1280
1364
|
var itemId = (key) => ITEM_OF[key] ?? key;
|
|
1281
1365
|
var LANE = { z: 11, from: -12, wall: 13 };
|
|
1282
1366
|
var SPARK_EVERY_S = 0.08;
|
|
1367
|
+
var TYRE_DAMAGE = /* @__PURE__ */ new Set(["flat_tyre", "torn_tyre", "run_flat"]);
|
|
1283
1368
|
var LEAF_REST_RADIANS = 0.02;
|
|
1284
1369
|
var MATERIAL_COLOURS = { metal: 13421772, glass: 4500223, rubber: 16750848, fuel: 16724736 };
|
|
1285
1370
|
var LANE_START_S = 1.2;
|
|
@@ -1575,7 +1660,8 @@ function createVehiclesTestModule(options = {}) {
|
|
|
1575
1660
|
state.time += dt;
|
|
1576
1661
|
const braking = settings.damage !== "into_wall" && gap() < brakeAt() && state.time > 0.5;
|
|
1577
1662
|
const moving = state.time > LANE_START_S;
|
|
1578
|
-
const
|
|
1663
|
+
const straight = settings.damage === "into_wall" || TYRE_DAMAGE.has(settings.damage) || !moving;
|
|
1664
|
+
const input = { ...idle, throttle: braking || !moving ? 0 : 1, brake: braking ? 1 : 0, steer: straight ? 0 : Math.sin(state.time * 1.4) * 0.15 };
|
|
1579
1665
|
if (mode === "arcade") {
|
|
1580
1666
|
const { events } = arcade.step(input, dt, { blocked: (x, z, yaw) => vehicleCorners2(arcade.spec, x, z, yaw).some(([px]) => px >= LANE.wall) });
|
|
1581
1667
|
for (const e of events) {
|
|
@@ -1613,7 +1699,8 @@ function createVehiclesTestModule(options = {}) {
|
|
|
1613
1699
|
speed,
|
|
1614
1700
|
throttle: input.throttle,
|
|
1615
1701
|
...semiState ? { rpm: semiState.rpm, slip: Math.max(0, ...semiState.slip.map(Math.abs)) } : { slip: braking && speed > 3 ? 0.6 : 0 },
|
|
1616
|
-
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
|
|
1617
1704
|
});
|
|
1618
1705
|
}
|
|
1619
1706
|
state.exhaust -= dt;
|
|
@@ -1789,6 +1876,7 @@ function createVehiclesTestModule(options = {}) {
|
|
|
1789
1876
|
if (isRenderPools(lent.pools)) lent.pools.materials.register("vehicles.cracked-glass", () => crackedGlassMaterial(three, new three.DataTexture(new Uint8Array(4), 1, 1)));
|
|
1790
1877
|
lent.audio?.register?.(LIBRARY_BANK);
|
|
1791
1878
|
lent.audio?.register?.(VEHICLE_SOUND_BANK);
|
|
1879
|
+
lent.audio?.preload?.(VEHICLE_PRELOAD_SOUNDS);
|
|
1792
1880
|
return () => {
|
|
1793
1881
|
stopChange?.();
|
|
1794
1882
|
stopFrame();
|
|
@@ -1814,16 +1902,139 @@ function loadVehiclesTestModule(room, options = {}) {
|
|
|
1814
1902
|
}
|
|
1815
1903
|
|
|
1816
1904
|
// src/test-area/proving/module.ts
|
|
1817
|
-
import { createVehicleCamera, mirrorPoses } from "@adatechnology/game-camera";
|
|
1905
|
+
import { createVehicleCamera, defaultEye, mirrorPoses } from "@adatechnology/game-camera";
|
|
1818
1906
|
import { markKitOrigin as markKitOrigin3 } from "@adatechnology/game-contracts";
|
|
1819
|
-
import { CABIN_BANK, LIBRARY_BANK as LIBRARY_BANK2 } from "@adatechnology/game-audio";
|
|
1907
|
+
import { CABIN_BANK, LIBRARY_BANK as LIBRARY_BANK2, RADIO_BANK } from "@adatechnology/game-audio";
|
|
1820
1908
|
import { createSemiSimDriver as createSemiSimDriver4 } from "@adatechnology/game-vehicles-driving";
|
|
1821
1909
|
import { certify, createProvingTests, startRun, statusOf, subjectSpec as subjectSpec2, withResult } from "@adatechnology/game-vehicles-proving";
|
|
1822
1910
|
import { discardObject3D as discardObject3D3 } from "@adatechnology/game-shapes-three";
|
|
1823
1911
|
|
|
1824
1912
|
// src/test-area/proving/cockpit.ts
|
|
1825
|
-
import {
|
|
1913
|
+
import { RADIO_TRACKS } from "@adatechnology/game-audio";
|
|
1914
|
+
import { bumperDistance, createSemiSimDriver as createSemiSimDriver3, createVehicleControls, createVehicleStorage, createVehicleSystems, createWindshieldFog, GLOVEBOX, parkingBeepInterval, trunkFor } from "@adatechnology/game-vehicles-driving";
|
|
1826
1915
|
import { subjectSpec } from "@adatechnology/game-vehicles-proving";
|
|
1916
|
+
import { createVehicleDestructible as createVehicleDestructible3 } from "@adatechnology/game-vehicles";
|
|
1917
|
+
|
|
1918
|
+
// src/body.ts
|
|
1919
|
+
var OPEN_SOUND = "car_door_open";
|
|
1920
|
+
var SHUT_SOUND = { door: "car_door_close", hood: "car_hood_close", lid: "car_trunk_close" };
|
|
1921
|
+
var clamp = (x, a, b) => Math.max(a, Math.min(b, x));
|
|
1922
|
+
function createVehicleBody(built, options) {
|
|
1923
|
+
const swing = options.swingSeconds ?? 1.2;
|
|
1924
|
+
const windowSeconds = options.windowSeconds ?? 3.5;
|
|
1925
|
+
const travel = options.windowTravel ?? 0.92;
|
|
1926
|
+
const root = built.context?.vehicle ?? built.group;
|
|
1927
|
+
const poses = /* @__PURE__ */ new Map();
|
|
1928
|
+
for (const clip of options.clips) {
|
|
1929
|
+
if (!clip.id.startsWith("open_")) continue;
|
|
1930
|
+
const last = clip.keys[clip.keys.length - 1];
|
|
1931
|
+
for (const [id, pose] of Object.entries(last?.parts ?? {})) if (pose.rotation) poses.set(id, pose.rotation);
|
|
1932
|
+
}
|
|
1933
|
+
const hinges = /* @__PURE__ */ new Map();
|
|
1934
|
+
for (const [id, open] of poses) {
|
|
1935
|
+
const pivot = root.getObjectByName(id);
|
|
1936
|
+
if (pivot) hinges.set(id, { pivot, open, now: 0, target: 0 });
|
|
1937
|
+
}
|
|
1938
|
+
const kind = (part) => part.startsWith("door") ? "door" : part === "hood" ? "hood" : "lid";
|
|
1939
|
+
const panes = /* @__PURE__ */ new Map();
|
|
1940
|
+
for (const glass of built.context?.glass ?? []) {
|
|
1941
|
+
const g = glass.userData.glass;
|
|
1942
|
+
if (!g || g.key !== "window_front" && g.key !== "window_rear") continue;
|
|
1943
|
+
const id = `${g.key === "window_front" ? "front" : "rear"}_${(g.side ?? 1) < 0 ? "left" : "right"}`;
|
|
1944
|
+
if (panes.has(id) || !glass.geometry) continue;
|
|
1945
|
+
glass.geometry.computeBoundingBox();
|
|
1946
|
+
const box = glass.geometry.boundingBox;
|
|
1947
|
+
if (box) panes.set(id, { pane: glass, drop: (box.max.y - box.min.y) * travel, now: 0, target: 0, applied: 0 });
|
|
1948
|
+
}
|
|
1949
|
+
const sunroofs = [];
|
|
1950
|
+
const wipers = [];
|
|
1951
|
+
const T = options.THREE;
|
|
1952
|
+
const turn = new T.Quaternion();
|
|
1953
|
+
root.traverse((o) => {
|
|
1954
|
+
const sun = o.userData.sunroof;
|
|
1955
|
+
if (sun) sunroofs.push({ obj: o, slide: sun.slide, base: o.position.x });
|
|
1956
|
+
});
|
|
1957
|
+
for (const { part, tag } of built.context?.wipers ?? [])
|
|
1958
|
+
wipers.push({ obj: part, arm: tag.arm, sweep: tag.sweep, spindle: new T.Vector3(...tag.spindle), axis: new T.Vector3(...tag.axis), rest: part.position.clone(), restQ: part.quaternion.clone() });
|
|
1959
|
+
let sunNow = 0;
|
|
1960
|
+
let sunTarget = 0;
|
|
1961
|
+
let wipePhase = 0;
|
|
1962
|
+
let locked = false;
|
|
1963
|
+
const placeWiper = (w, angle) => {
|
|
1964
|
+
if (angle === 0) {
|
|
1965
|
+
w.obj.position.copy(w.rest);
|
|
1966
|
+
w.obj.quaternion.copy(w.restQ);
|
|
1967
|
+
return;
|
|
1968
|
+
}
|
|
1969
|
+
turn.setFromAxisAngle(w.axis, angle);
|
|
1970
|
+
w.obj.position.copy(w.rest).sub(w.spindle).applyQuaternion(turn).add(w.spindle);
|
|
1971
|
+
w.obj.quaternion.copy(turn).multiply(w.restQ);
|
|
1972
|
+
};
|
|
1973
|
+
const ease = (now, target, seconds, dt) => now + clamp(target - now, -dt / seconds, dt / seconds);
|
|
1974
|
+
return {
|
|
1975
|
+
parts: [...hinges.keys()],
|
|
1976
|
+
windows: [...panes.keys()],
|
|
1977
|
+
hasSunroof: sunroofs.length > 0,
|
|
1978
|
+
wipers: new Set(wipers.map((w) => w.arm)).size,
|
|
1979
|
+
get locked() {
|
|
1980
|
+
return locked;
|
|
1981
|
+
},
|
|
1982
|
+
openness: (part) => hinges.get(part)?.now ?? 0,
|
|
1983
|
+
toggle(part) {
|
|
1984
|
+
const h = hinges.get(part);
|
|
1985
|
+
if (!h) return [{ type: "refused", part, reason: "missing" }];
|
|
1986
|
+
if (locked && kind(part) !== "hood" && h.target === 0) return [{ type: "refused", part, reason: "locked" }];
|
|
1987
|
+
h.target = h.target > 0 ? 0 : 1;
|
|
1988
|
+
return h.target > 0 ? [{ type: "sound", key: OPEN_SOUND, part }] : [];
|
|
1989
|
+
},
|
|
1990
|
+
window: (id) => panes.get(id)?.now ?? 0,
|
|
1991
|
+
toggleWindow(id) {
|
|
1992
|
+
const w = panes.get(id);
|
|
1993
|
+
if (w) w.target = w.target > 0.5 ? 0 : 1;
|
|
1994
|
+
},
|
|
1995
|
+
setWindow(id, open) {
|
|
1996
|
+
const w = panes.get(id);
|
|
1997
|
+
if (w) w.target = clamp(open, 0, 1);
|
|
1998
|
+
},
|
|
1999
|
+
toggleSunroof() {
|
|
2000
|
+
sunTarget = sunTarget > 0.5 ? 0 : 1;
|
|
2001
|
+
},
|
|
2002
|
+
lock(on) {
|
|
2003
|
+
locked = on;
|
|
2004
|
+
return [{ type: "sound", key: "blinker_tick", part: "doors" }];
|
|
2005
|
+
},
|
|
2006
|
+
update(dt, wipe = 0) {
|
|
2007
|
+
const events = [];
|
|
2008
|
+
for (const [id, h] of hinges) {
|
|
2009
|
+
if (h.now === h.target) continue;
|
|
2010
|
+
h.now = ease(h.now, h.target, swing, dt);
|
|
2011
|
+
h.pivot.rotation.set(h.open[0] * h.now, h.open[1] * h.now, h.open[2] * h.now);
|
|
2012
|
+
if (h.now === 0) events.push({ type: "sound", key: SHUT_SOUND[kind(id)] ?? "car_door_close", part: id });
|
|
2013
|
+
}
|
|
2014
|
+
for (const w of panes.values()) {
|
|
2015
|
+
if (w.now === w.target) continue;
|
|
2016
|
+
w.now = ease(w.now, w.target, windowSeconds, dt);
|
|
2017
|
+
const offset = -w.drop * w.now;
|
|
2018
|
+
w.pane.position.y += offset - w.applied;
|
|
2019
|
+
w.applied = offset;
|
|
2020
|
+
}
|
|
2021
|
+
if (sunNow !== sunTarget) {
|
|
2022
|
+
sunNow = ease(sunNow, sunTarget, 2.5, dt);
|
|
2023
|
+
for (const s of sunroofs) s.obj.position.x = s.base - s.slide * sunNow;
|
|
2024
|
+
}
|
|
2025
|
+
wipePhase = wipe > 0 ? (wipePhase + dt * wipe) % 1 : wipePhase > 0 ? Math.min(1, wipePhase + dt * 1.5) % 1 : 0;
|
|
2026
|
+
const sweep = Math.sin(Math.PI * wipePhase);
|
|
2027
|
+
for (const w of wipers) placeWiper(w, w.sweep * sweep);
|
|
2028
|
+
return events;
|
|
2029
|
+
},
|
|
2030
|
+
dispose() {
|
|
2031
|
+
for (const h of hinges.values()) h.pivot.rotation.set(0, 0, 0);
|
|
2032
|
+
for (const w of panes.values()) w.pane.position.y -= w.applied;
|
|
2033
|
+
for (const s of sunroofs) s.obj.position.x = s.base;
|
|
2034
|
+
for (const w of wipers) placeWiper(w, 0);
|
|
2035
|
+
}
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
1827
2038
|
|
|
1828
2039
|
// src/dashboard.ts
|
|
1829
2040
|
import { discardObject3D as discardObject3D2 } from "@adatechnology/game-shapes-three";
|
|
@@ -2000,6 +2211,8 @@ function createVehicleDashboard(THREE, built, options) {
|
|
|
2000
2211
|
lamp(g, 104, 214, r.highBeam, DASHBOARD_LOOK.blue, beam);
|
|
2001
2212
|
lamp(g, 152, 214, r.lowBeam || r.fog, r.fog ? DASHBOARD_LOOK.amber : DASHBOARD_LOOK.green, beam);
|
|
2002
2213
|
bar(g, 40, r.fuel, false, "F");
|
|
2214
|
+
lamp(g, 104, 244, !!r.cruise, DASHBOARD_LOOK.green, letter("CRZ"));
|
|
2215
|
+
if (r.profile && r.profile !== "normal") lamp(g, 152, 244, true, r.profile === "eco" ? DASHBOARD_LOOK.green : DASHBOARD_LOOK.amber, letter(r.profile === "eco" ? "E" : "S"));
|
|
2003
2216
|
} else {
|
|
2004
2217
|
dial(g, r.rpm / 1e3, Math.ceil(redline / 1e3) + 1, 1, redline / 1e3, (v) => String(Math.round(v)));
|
|
2005
2218
|
g.fillStyle = DASHBOARD_LOOK.marks;
|
|
@@ -2008,7 +2221,9 @@ function createVehicleDashboard(THREE, built, options) {
|
|
|
2008
2221
|
g.fillText(r.gear < 0 ? "R" : r.gear === 0 ? "N" : String(r.gear), SIZE / 2, SIZE / 2 + 54);
|
|
2009
2222
|
lamp(g, 86, 178, r.parkingBrake, DASHBOARD_LOOK.red, ring("P"));
|
|
2010
2223
|
lamp(g, 128, 214, r.ignition && !r.seatbelt, DASHBOARD_LOOK.red, belt);
|
|
2011
|
-
lamp(g, 170, 178, warn, DASHBOARD_LOOK.red, letter("+\u2212"));
|
|
2224
|
+
lamp(g, 170, 178, warn || r.engineOn && r.charging === false, DASHBOARD_LOOK.red, letter("+\u2212"));
|
|
2225
|
+
lamp(g, 104, 244, r.ignition && !!r.tractionOff, DASHBOARD_LOOK.amber, letter("TC"));
|
|
2226
|
+
lamp(g, 152, 244, r.ignition && !!r.absOff, DASHBOARD_LOOK.amber, letter("ABS"));
|
|
2012
2227
|
lamp(g, 86, 214, warn, DASHBOARD_LOOK.red, letter("OIL"));
|
|
2013
2228
|
lamp(g, 170, 214, r.fourWheel, DASHBOARD_LOOK.green, letter("4x4"));
|
|
2014
2229
|
bar(g, SIZE - 40, r.temperature, true, "T");
|
|
@@ -2045,7 +2260,7 @@ function createVehicleDashboard(THREE, built, options) {
|
|
|
2045
2260
|
// src/lamps.ts
|
|
2046
2261
|
import { isLightBudget, LIGHT_PRIORITY } from "@adatechnology/game-render-three";
|
|
2047
2262
|
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 } },
|
|
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 } },
|
|
2049
2264
|
low: { color: 16773584, intensity: 2.4, distance: 32, angle: 0.42, penumbra: 0.55, decay: 1.6, drop: 0.09 },
|
|
2050
2265
|
high: { color: 16774882, intensity: 3.6, distance: 70, angle: 0.3, penumbra: 0.45, decay: 1.4, drop: 0.015 },
|
|
2051
2266
|
fog: { color: 16773320, intensity: 1.6, distance: 16, angle: 0.75, penumbra: 0.7, decay: 1.8, drop: 0.12 },
|
|
@@ -2062,7 +2277,11 @@ function createVehicleLamps(THREE, built, options = {}) {
|
|
|
2062
2277
|
if (o.userData?.lamp === "fog" && o.material && !bulbs.includes(o)) bulbs.push(o);
|
|
2063
2278
|
});
|
|
2064
2279
|
if (bulbs.length) lamps.push({ kind: "fog", inner: bulbs });
|
|
2065
|
-
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 };
|
|
2066
2285
|
const originals = /* @__PURE__ */ new Map();
|
|
2067
2286
|
const copies = /* @__PURE__ */ new Map();
|
|
2068
2287
|
const base = /* @__PURE__ */ new Map();
|
|
@@ -2077,6 +2296,7 @@ function createVehicleLamps(THREE, built, options = {}) {
|
|
|
2077
2296
|
mesh.material = copy;
|
|
2078
2297
|
return copy;
|
|
2079
2298
|
};
|
|
2299
|
+
for (const b of beacons) own(b.mesh);
|
|
2080
2300
|
const haloBase = /* @__PURE__ */ new Map();
|
|
2081
2301
|
for (const l of lamps) {
|
|
2082
2302
|
for (const m of l.inner) own(m);
|
|
@@ -2152,6 +2372,8 @@ function createVehicleLamps(THREE, built, options = {}) {
|
|
|
2152
2372
|
else if (l.kind === "fog") glow(l, state.fog ? g.fog.on : g.fog.off);
|
|
2153
2373
|
}
|
|
2154
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);
|
|
2155
2377
|
const beamId = state.mode === "low" || state.mode === "high" ? state.mode : null;
|
|
2156
2378
|
for (const id of ["low", "high"]) beam(id, beamId === id, () => spots(front, LAMP_RULES[id]));
|
|
2157
2379
|
beam("fog", state.fog && has.fog, () => spots(fogAnchors, LAMP_RULES.fog));
|
|
@@ -2165,7 +2387,7 @@ function createVehicleLamps(THREE, built, options = {}) {
|
|
|
2165
2387
|
return { ...state };
|
|
2166
2388
|
},
|
|
2167
2389
|
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;
|
|
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;
|
|
2169
2391
|
Object.assign(state, next);
|
|
2170
2392
|
apply();
|
|
2171
2393
|
},
|
|
@@ -2174,15 +2396,17 @@ function createVehicleLamps(THREE, built, options = {}) {
|
|
|
2174
2396
|
sources.clear();
|
|
2175
2397
|
for (const a of anchors) a.removeFromParent();
|
|
2176
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);
|
|
2177
2408
|
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
|
-
}
|
|
2409
|
+
for (const m of l.inner) giveBack(m);
|
|
2186
2410
|
for (const h of l.halo ?? []) {
|
|
2187
2411
|
h.visible = true;
|
|
2188
2412
|
const s = haloBase.get(h) ?? 1;
|
|
@@ -2196,14 +2420,85 @@ function createVehicleLamps(THREE, built, options = {}) {
|
|
|
2196
2420
|
};
|
|
2197
2421
|
}
|
|
2198
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
|
+
|
|
2199
2467
|
// src/test-area/proving/cockpit.ts
|
|
2200
|
-
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
|
+
};
|
|
2483
|
+
var BODY_KEYS = {
|
|
2484
|
+
Digit1: "door_front_left",
|
|
2485
|
+
Digit2: "door_front_right",
|
|
2486
|
+
Digit3: "door_rear_left",
|
|
2487
|
+
Digit4: "door_rear_right",
|
|
2488
|
+
Digit5: "hood",
|
|
2489
|
+
Digit6: "lid"
|
|
2490
|
+
};
|
|
2491
|
+
var WINDOW_KEYS = { Digit1: "front_left", Digit2: "front_right", Digit3: "rear_left", Digit4: "rear_right" };
|
|
2492
|
+
var LOCK_KEY = "KeyV";
|
|
2493
|
+
var WIPE_RATE = { off: 0, intermittent: 0.25, slow: 0.7, fast: 1.4 };
|
|
2201
2494
|
function createCockpit(o) {
|
|
2202
2495
|
const sounds = { ...DEFAULT_COCKPIT_SOUNDS, ...o.sounds };
|
|
2203
2496
|
const spec = subjectSpec(o.subject);
|
|
2204
2497
|
const driver = createSemiSimDriver3(spec);
|
|
2205
2498
|
driver.place(o.place[0], o.place[1], o.place[2]);
|
|
2206
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();
|
|
2207
2502
|
const audio = o.host.audio;
|
|
2208
2503
|
const top = o.subject.stats.maxSpeedKmh;
|
|
2209
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;
|
|
@@ -2214,27 +2509,77 @@ function createCockpit(o) {
|
|
|
2214
2509
|
let wipeAgain = 0;
|
|
2215
2510
|
let chimeAgain = 0;
|
|
2216
2511
|
let radioAgain = 0;
|
|
2512
|
+
let beepAgain = 0;
|
|
2513
|
+
let bar = 0;
|
|
2217
2514
|
let exitAsked = false;
|
|
2515
|
+
let sirenAgain = 0;
|
|
2516
|
+
let sparkAgain = 0;
|
|
2517
|
+
let wiresBefore = 0;
|
|
2218
2518
|
let gas = 0;
|
|
2519
|
+
const systems = createVehicleSystems();
|
|
2520
|
+
const context = o.built.context;
|
|
2521
|
+
const destructible = o.kit && context ? createVehicleDestructible3(o.kit, context, { id: "driven", frame: o.built.group }) : null;
|
|
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
|
+
});
|
|
2534
|
+
const bodyEvents = (events) => {
|
|
2535
|
+
for (const event of events) {
|
|
2536
|
+
if (event.type === "sound") play(event.key, 0.9);
|
|
2537
|
+
o.onEvent?.(event);
|
|
2538
|
+
}
|
|
2539
|
+
};
|
|
2219
2540
|
const controls = createVehicleControls({
|
|
2541
|
+
canCrank: systems.canCrank,
|
|
2542
|
+
onCrank: systems.crank,
|
|
2543
|
+
powerLimit: systems.powerLimit,
|
|
2220
2544
|
gears: spec.gears?.length ?? 5,
|
|
2221
2545
|
fourWheelDrive: !!o.subject.body.fourWheelDrive,
|
|
2222
2546
|
fogLamps: lamps.has.fog,
|
|
2547
|
+
clutchPedal: true,
|
|
2548
|
+
radioTracks: RADIO_TRACKS.length,
|
|
2549
|
+
hasKey: o.hasKey,
|
|
2550
|
+
emergency: lamps.has.beacon,
|
|
2223
2551
|
onEvent: (event) => {
|
|
2224
2552
|
if (event.type === "command") {
|
|
2225
|
-
if (event.command === "ignition" && event.state.ignition && sounds.starter) play(sounds.starter);
|
|
2226
2553
|
if (event.command === "ignition" && !event.state.ignition) driver.state.engineOn = false;
|
|
2227
2554
|
if (event.command === "alarm" && event.state.alarm === "armed") play(sounds.chirp);
|
|
2228
2555
|
if (event.command === "alarm") alarmAgain = 0;
|
|
2229
|
-
if (event.command === "radio" || event.command === "radio_source"
|
|
2556
|
+
if (event.command === "radio" || event.command === "radio_source" || event.command === "radio_track") {
|
|
2557
|
+
radioAgain = 0;
|
|
2558
|
+
bar = 0;
|
|
2559
|
+
}
|
|
2230
2560
|
if (event.command === "exit") exitAsked = true;
|
|
2561
|
+
if (event.command === "siren") sirenAgain = 0;
|
|
2562
|
+
if (event.command === "glovebox") play(sounds.glovebox, 0.8);
|
|
2231
2563
|
}
|
|
2232
2564
|
o.onEvent?.(event);
|
|
2233
2565
|
}
|
|
2234
2566
|
});
|
|
2235
2567
|
driver.state.engineOn = false;
|
|
2236
2568
|
const target = typeof window !== "undefined" ? window : null;
|
|
2569
|
+
const bodyKey = (e) => {
|
|
2570
|
+
if (!body) return false;
|
|
2571
|
+
if (e.code === LOCK_KEY) bodyEvents(body.lock(!body.locked));
|
|
2572
|
+
else if (e.shiftKey && e.code === "Digit5") body.toggleSunroof();
|
|
2573
|
+
else if (e.shiftKey && WINDOW_KEYS[e.code]) body.toggleWindow(WINDOW_KEYS[e.code]);
|
|
2574
|
+
else if (!e.shiftKey && BODY_KEYS[e.code]) bodyEvents(body.toggle(BODY_KEYS[e.code]));
|
|
2575
|
+
else return false;
|
|
2576
|
+
return true;
|
|
2577
|
+
};
|
|
2237
2578
|
const down = (e) => {
|
|
2579
|
+
if (!e.repeat && bodyKey(e)) {
|
|
2580
|
+
e.stopImmediatePropagation();
|
|
2581
|
+
return;
|
|
2582
|
+
}
|
|
2238
2583
|
if (!controls.key(e.code, true)) return;
|
|
2239
2584
|
e.stopImmediatePropagation();
|
|
2240
2585
|
if (e.code.startsWith("Arrow") || e.code === "Space") e.preventDefault();
|
|
@@ -2246,9 +2591,19 @@ function createCockpit(o) {
|
|
|
2246
2591
|
target?.addEventListener("keydown", down, true);
|
|
2247
2592
|
target?.addEventListener("keyup", up, true);
|
|
2248
2593
|
target?.addEventListener("blur", blur);
|
|
2249
|
-
|
|
2594
|
+
const cockpit = {
|
|
2595
|
+
radiator: 1,
|
|
2596
|
+
storage,
|
|
2597
|
+
access,
|
|
2598
|
+
fog,
|
|
2599
|
+
mist,
|
|
2600
|
+
weather: { ...o.weather ?? { outsideC: 25, rain: 0 } },
|
|
2601
|
+
emergency: lamps.has.beacon,
|
|
2250
2602
|
driver,
|
|
2251
2603
|
controls,
|
|
2604
|
+
systems,
|
|
2605
|
+
body,
|
|
2606
|
+
destructible,
|
|
2252
2607
|
lamps,
|
|
2253
2608
|
dashboard,
|
|
2254
2609
|
get throttle() {
|
|
@@ -2257,18 +2612,39 @@ function createCockpit(o) {
|
|
|
2257
2612
|
step(dt) {
|
|
2258
2613
|
const input = controls.input(dt, driver);
|
|
2259
2614
|
gas = input.throttle;
|
|
2260
|
-
|
|
2615
|
+
if (input.start && sounds.starter) play(sounds.starter);
|
|
2616
|
+
const engine = driver.step(input, dt).events;
|
|
2261
2617
|
controls.after(driver, dt);
|
|
2262
2618
|
const s = controls.state;
|
|
2619
|
+
const report = [...engine.filter((e) => e.type !== "shift"), ...systems.step({ driver, controls: s, throttle: input.throttle, dt, radiator: cockpit.radiator })];
|
|
2620
|
+
for (const event of report) o.onEvent?.(event);
|
|
2621
|
+
const powered = driver.state.engineOn || systems.state.charge > 0;
|
|
2622
|
+
const high = s.lamps.mode === "high" || s.lamps.flash;
|
|
2263
2623
|
lamps.set({
|
|
2264
|
-
mode: s.lamps.mode,
|
|
2624
|
+
mode: !powered ? "off" : s.lamps.flash ? "high" : s.lamps.mode,
|
|
2265
2625
|
fog: s.lamps.fog,
|
|
2266
2626
|
interior: s.lamps.interior,
|
|
2267
2627
|
brake: s.brakeLights,
|
|
2268
2628
|
reverse: s.reverseLights,
|
|
2269
2629
|
left: s.blinkOn && (s.indicator === "left" || s.indicator === "hazard"),
|
|
2270
|
-
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
|
|
2271
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
|
+
}
|
|
2272
2648
|
dashboard?.update(
|
|
2273
2649
|
{
|
|
2274
2650
|
kmh: Math.abs(driver.state.u) * 3.6,
|
|
@@ -2278,7 +2654,14 @@ function createCockpit(o) {
|
|
|
2278
2654
|
engineOn: driver.state.engineOn,
|
|
2279
2655
|
left: s.blinkOn && (s.indicator === "left" || s.indicator === "hazard"),
|
|
2280
2656
|
right: s.blinkOn && (s.indicator === "right" || s.indicator === "hazard"),
|
|
2281
|
-
highBeam:
|
|
2657
|
+
highBeam: high,
|
|
2658
|
+
fuel: driver.state.fuel / spec.tank.capacity,
|
|
2659
|
+
temperature: systems.state.temperature,
|
|
2660
|
+
cruise: s.cruise.on,
|
|
2661
|
+
profile: s.profile,
|
|
2662
|
+
tractionOff: !s.tractionControl,
|
|
2663
|
+
absOff: !s.abs,
|
|
2664
|
+
charging: systems.state.netW >= 0,
|
|
2282
2665
|
lowBeam: s.lamps.mode === "low" || s.lamps.mode === "position",
|
|
2283
2666
|
fog: s.lamps.fog,
|
|
2284
2667
|
parkingBrake: s.parkingBrake,
|
|
@@ -2287,7 +2670,19 @@ function createCockpit(o) {
|
|
|
2287
2670
|
},
|
|
2288
2671
|
dt
|
|
2289
2672
|
);
|
|
2673
|
+
if (body) bodyEvents(body.update(dt, s.washer ? 1.2 : WIPE_RATE[s.wipers] ?? 0));
|
|
2290
2674
|
if (s.tick) play(sounds.blinker, 0.8);
|
|
2675
|
+
const near = o.obstacles?.() ?? [];
|
|
2676
|
+
if (s.ignition && near.length && Math.abs(driver.state.u) < 3) {
|
|
2677
|
+
const g = o.built.group;
|
|
2678
|
+
const reversing = driver.state.gear < 0 || driver.state.u < -0.05;
|
|
2679
|
+
const distance = bumperDistance({ x: g.position.x, z: g.position.z, heading: [Math.sin(g.rotation.y), Math.cos(g.rotation.y)], length: o.built.length, width: o.built.width, forward: !reversing, obstacles: near });
|
|
2680
|
+
const every = parkingBeepInterval(distance);
|
|
2681
|
+
if (every != null && (beepAgain -= dt) <= 0) {
|
|
2682
|
+
play(sounds.beep, 0.7);
|
|
2683
|
+
beepAgain = every === 0 ? 0.09 : every;
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2291
2686
|
if (s.horn && (hornAgain -= dt) <= 0) {
|
|
2292
2687
|
play(sounds.horn);
|
|
2293
2688
|
hornAgain = 0.5;
|
|
@@ -2296,9 +2691,9 @@ function createCockpit(o) {
|
|
|
2296
2691
|
play(sounds.alarm);
|
|
2297
2692
|
alarmAgain = 2.9;
|
|
2298
2693
|
}
|
|
2299
|
-
if (s.wipers !== "off" && (wipeAgain -= dt) <= 0) {
|
|
2694
|
+
if ((s.wipers !== "off" || s.washer) && (wipeAgain -= dt) <= 0) {
|
|
2300
2695
|
play(sounds.wiper, 0.7);
|
|
2301
|
-
wipeAgain = s.wipers === "fast" ? 0.6 : 1.4;
|
|
2696
|
+
wipeAgain = s.washer || s.wipers === "fast" ? 0.6 : s.wipers === "slow" ? 1.4 : 4;
|
|
2302
2697
|
}
|
|
2303
2698
|
const kmh = Math.abs(driver.state.u) * 3.6;
|
|
2304
2699
|
if (!s.seatbelt && s.ignition && kmh > 10 && (chimeAgain -= dt) <= 0) {
|
|
@@ -2306,8 +2701,13 @@ function createCockpit(o) {
|
|
|
2306
2701
|
chimeAgain = 6;
|
|
2307
2702
|
}
|
|
2308
2703
|
if (s.radio.on && (radioAgain -= dt) <= 0) {
|
|
2309
|
-
|
|
2310
|
-
|
|
2704
|
+
const song = RADIO_TRACKS[s.radio.track % RADIO_TRACKS.length];
|
|
2705
|
+
if (song) {
|
|
2706
|
+
play(song.bars[bar % song.bars.length], s.radio.source === "tape" ? 0.8 : 0.7);
|
|
2707
|
+
bar++;
|
|
2708
|
+
}
|
|
2709
|
+
if (s.radio.source === "radio") play(sounds.radio, 0.12);
|
|
2710
|
+
radioAgain = song?.barSeconds ?? 2;
|
|
2311
2711
|
}
|
|
2312
2712
|
if (exitAsked) {
|
|
2313
2713
|
exitAsked = false;
|
|
@@ -2320,11 +2720,203 @@ function createCockpit(o) {
|
|
|
2320
2720
|
target?.removeEventListener("blur", blur);
|
|
2321
2721
|
controls.release();
|
|
2322
2722
|
lamps.dispose();
|
|
2723
|
+
mist.dispose();
|
|
2724
|
+
storage.clear();
|
|
2323
2725
|
dashboard?.dispose();
|
|
2726
|
+
body?.dispose();
|
|
2727
|
+
destructible?.dispose?.();
|
|
2728
|
+
}
|
|
2729
|
+
};
|
|
2730
|
+
return cockpit;
|
|
2731
|
+
}
|
|
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
|
+
|
|
2760
|
+
// src/wheel-roll.ts
|
|
2761
|
+
var DEFAULT_RADIUS = 0.32;
|
|
2762
|
+
function createWheelRoll(THREE, built) {
|
|
2763
|
+
const wheels = (built.context?.wheels ?? []).map((wheel) => ({
|
|
2764
|
+
wheel,
|
|
2765
|
+
rest: wheel.quaternion.clone(),
|
|
2766
|
+
radius: Number(wheel.userData.r) || DEFAULT_RADIUS,
|
|
2767
|
+
// in the kit's frame the front is +x: the wheels ahead of the middle steer
|
|
2768
|
+
front: wheel.position.x > 0,
|
|
2769
|
+
angle: 0
|
|
2770
|
+
}));
|
|
2771
|
+
const axle = new THREE.Vector3(0, 0, 1);
|
|
2772
|
+
const up = new THREE.Vector3(0, 1, 0);
|
|
2773
|
+
const spin = new THREE.Quaternion();
|
|
2774
|
+
const steering = new THREE.Quaternion();
|
|
2775
|
+
return {
|
|
2776
|
+
count: wheels.length,
|
|
2777
|
+
roll(distance, steer) {
|
|
2778
|
+
for (const w of wheels) {
|
|
2779
|
+
w.angle = (w.angle - distance / w.radius) % (Math.PI * 2);
|
|
2780
|
+
spin.setFromAxisAngle(axle, w.angle);
|
|
2781
|
+
steering.setFromAxisAngle(up, w.front ? steer : 0);
|
|
2782
|
+
w.wheel.quaternion.copy(w.rest).premultiply(spin).premultiply(steering);
|
|
2783
|
+
}
|
|
2784
|
+
},
|
|
2785
|
+
dispose() {
|
|
2786
|
+
for (const w of wheels) w.wheel.quaternion.copy(w.rest);
|
|
2324
2787
|
}
|
|
2325
2788
|
};
|
|
2326
2789
|
}
|
|
2327
2790
|
|
|
2791
|
+
// src/test-area/proving/crash.ts
|
|
2792
|
+
import { launchBody } from "@adatechnology/game-contracts";
|
|
2793
|
+
import { ejectionOf } from "@adatechnology/game-vehicles-driving";
|
|
2794
|
+
var GLASS_BLOW = 6e3;
|
|
2795
|
+
function createProvingCrash(host, occupant) {
|
|
2796
|
+
const thrown = [];
|
|
2797
|
+
const breakGlass = (destructible, exit, forward) => {
|
|
2798
|
+
if (!destructible) return;
|
|
2799
|
+
const want = exit === "windshield" ? "windshield" : exit === "rear_window" ? "rear" : exit.startsWith("left") ? "left" : "right";
|
|
2800
|
+
const pane = destructible.describe().parts.find((p) => p.id.includes(exit === "windshield" || exit === "rear_window" ? want : "window") && (exit === "windshield" || exit === "rear_window" || p.id.includes(want)));
|
|
2801
|
+
if (!pane) return;
|
|
2802
|
+
destructible.hit({ type: "blunt", partId: pane.id, point: destructible.partWorldCenter(pane.id), direction: forward, energy: GLASS_BLOW, radius: 0.35, source: "occupant" });
|
|
2803
|
+
};
|
|
2804
|
+
return {
|
|
2805
|
+
crash({ car, u, w, belted, destructible }) {
|
|
2806
|
+
const ejection = ejectionOf({ before: { u, w }, after: { u: 0, w: 0 } }, belted);
|
|
2807
|
+
if (!ejection) return null;
|
|
2808
|
+
const s = Math.sin(car.rotationY);
|
|
2809
|
+
const c = Math.cos(car.rotationY);
|
|
2810
|
+
const forward = [s, 0, c];
|
|
2811
|
+
const right = [-c, 0, s];
|
|
2812
|
+
breakGlass(destructible, ejection.exit, forward);
|
|
2813
|
+
const [vf, vr, vu] = ejection.velocity;
|
|
2814
|
+
const start = [car.position.x + forward[0] * 1.2, 1.1, car.position.z + forward[2] * 1.2];
|
|
2815
|
+
const placed = occupant ? host.addObject?.(occupant, { position: start, rotationY: car.rotationY }) : null;
|
|
2816
|
+
if (placed) {
|
|
2817
|
+
launchBody(placed.root, [forward[0] * vf + right[0] * vr, vu, forward[2] * vf + right[2] * vr]);
|
|
2818
|
+
thrown.push(placed.remove);
|
|
2819
|
+
}
|
|
2820
|
+
return ejection;
|
|
2821
|
+
},
|
|
2822
|
+
dispose() {
|
|
2823
|
+
for (const remove of thrown.splice(0)) remove();
|
|
2824
|
+
}
|
|
2825
|
+
};
|
|
2826
|
+
}
|
|
2827
|
+
|
|
2828
|
+
// src/test-area/proving/systems-text.ts
|
|
2829
|
+
var SYSTEMS_TEXT = {
|
|
2830
|
+
"pt-BR": {
|
|
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",
|
|
2832
|
+
profile: "Modo de condu\xE7\xE3o",
|
|
2833
|
+
profiles: { eco: "econ\xF4mico", normal: "normal", sport: "esporte" },
|
|
2834
|
+
traction: "Controle de tra\xE7\xE3o",
|
|
2835
|
+
abs: "ABS",
|
|
2836
|
+
cruise: "Piloto autom\xE1tico",
|
|
2837
|
+
cruiseSet: "Piloto autom\xE1tico: segurando {0} km/h",
|
|
2838
|
+
climate: "Ar-condicionado",
|
|
2839
|
+
demist: "Desemba\xE7ador",
|
|
2840
|
+
wiperModes: { off: "desligado", intermittent: "intermitente", slow: "lento", fast: "r\xE1pido" },
|
|
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" },
|
|
2842
|
+
events: {
|
|
2843
|
+
stall: "O motor morreu",
|
|
2844
|
+
out_of_fuel: "Acabou o combust\xEDvel",
|
|
2845
|
+
bogged: "Atolou",
|
|
2846
|
+
aquaplaning: "Aquaplanagem",
|
|
2847
|
+
battery_flat: "A bateria descarregou",
|
|
2848
|
+
overheating: "Motor superaquecendo: perdendo pot\xEAncia",
|
|
2849
|
+
seized: "O motor fundiu de tanto esquentar",
|
|
2850
|
+
spare_fitted: "Estepe colocado",
|
|
2851
|
+
no_spare: "N\xE3o h\xE1 estepe",
|
|
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"
|
|
2855
|
+
},
|
|
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",
|
|
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" },
|
|
2867
|
+
track: "faixa {0}",
|
|
2868
|
+
adjust: { seat_forward: "Banco: frente/tr\xE1s (m)", seat_height: "Banco: altura (m)", mirror_left: "Retrovisor esquerdo: \xE2ngulo", mirror_right: "Retrovisor direito: \xE2ngulo" },
|
|
2869
|
+
crash: { mild: "Batida a {0} km/h: fraca demais para lan\xE7ar quem est\xE1 sem cinto", stayed: "Batida a {0} km/h: o cinto segurou o motorista", ejected: "Batida a {0} km/h sem cinto: o motorista saiu pelo {1}", exits: { windshield: "para-brisa", left_window: "vidro esquerdo", right_window: "vidro direito", rear_window: "vidro traseiro" } },
|
|
2870
|
+
bodyKeys: "1 a 4 portas \xB7 5 cap\xF4 \xB7 6 porta-malas \xB7 Shift+1 a 4 vidros \xB7 Shift+5 teto solar \xB7 V trava as portas",
|
|
2871
|
+
remote: { locked: "Travado pelo controle: alarme armado", unlocked: "Destravado pelo controle", forced: "Porta for\xE7ada" },
|
|
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" }
|
|
2873
|
+
},
|
|
2874
|
+
en: {
|
|
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",
|
|
2876
|
+
profile: "Drive profile",
|
|
2877
|
+
profiles: { eco: "eco", normal: "normal", sport: "sport" },
|
|
2878
|
+
traction: "Traction control",
|
|
2879
|
+
abs: "ABS",
|
|
2880
|
+
cruise: "Cruise control",
|
|
2881
|
+
cruiseSet: "Cruise control: holding {0} km/h",
|
|
2882
|
+
climate: "Air conditioning",
|
|
2883
|
+
demist: "Demister",
|
|
2884
|
+
wiperModes: { off: "off", intermittent: "intermittent", slow: "slow", fast: "fast" },
|
|
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" },
|
|
2886
|
+
events: {
|
|
2887
|
+
stall: "The engine stalled",
|
|
2888
|
+
out_of_fuel: "Out of fuel",
|
|
2889
|
+
bogged: "Bogged down",
|
|
2890
|
+
aquaplaning: "Aquaplaning",
|
|
2891
|
+
battery_flat: "The battery went flat",
|
|
2892
|
+
overheating: "Engine overheating: losing power",
|
|
2893
|
+
seized: "The engine seized from the heat",
|
|
2894
|
+
spare_fitted: "Spare tyre fitted",
|
|
2895
|
+
no_spare: "No spare tyre left",
|
|
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"
|
|
2899
|
+
},
|
|
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",
|
|
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" },
|
|
2911
|
+
track: "track {0}",
|
|
2912
|
+
adjust: { seat_forward: "Seat: forward/back (m)", seat_height: "Seat: height (m)", mirror_left: "Left mirror: angle", mirror_right: "Right mirror: angle" },
|
|
2913
|
+
crash: { mild: "Crash at {0} km/h: too mild to throw an unbelted occupant out", stayed: "Crash at {0} km/h: the belt held the driver", ejected: "Crash at {0} km/h without a belt: the driver went out through the {1}", exits: { windshield: "windshield", left_window: "left window", right_window: "right window", rear_window: "rear window" } },
|
|
2914
|
+
bodyKeys: "1 to 4 doors \xB7 5 hood \xB7 6 trunk \xB7 Shift+1 to 4 windows \xB7 Shift+5 sunroof \xB7 V locks the doors",
|
|
2915
|
+
remote: { locked: "Locked by the remote: alarm armed", unlocked: "Unlocked by the remote", forced: "Door forced" },
|
|
2916
|
+
done: { puncture: "Front left tyre punctured", refuel: "Tank full", drain_battery: "Battery almost flat", jump_start: "Battery charged", break_radiator: "Radiator punctured: the engine will heat up", empty_tank: "Tank on reserve" }
|
|
2917
|
+
}
|
|
2918
|
+
};
|
|
2919
|
+
|
|
2328
2920
|
// src/test-area/proving/text.ts
|
|
2329
2921
|
var PROVING_TEXT = {
|
|
2330
2922
|
"pt-BR": {
|
|
@@ -2469,9 +3061,11 @@ var MIRROR_RECTS = {
|
|
|
2469
3061
|
right: { x: 0.79, y: 0.72, width: 0.19, height: 0.13, rounded: 0.3 },
|
|
2470
3062
|
reverse: { x: 0.36, y: 0.13, width: 0.28, height: 0.18, rounded: 0.06 }
|
|
2471
3063
|
};
|
|
3064
|
+
var ADJUST_KEYS = { seat_forward: "seatForward", seat_height: "seatHeight", mirror_left: "mirrorLeft", mirror_right: "mirrorRight" };
|
|
2472
3065
|
function createProvingModule(options) {
|
|
2473
3066
|
const locale = options.locale ?? "pt-BR";
|
|
2474
3067
|
const text = PROVING_TEXT[locale] ?? PROVING_TEXT["pt-BR"];
|
|
3068
|
+
const sysText = SYSTEMS_TEXT[locale] ?? SYSTEMS_TEXT["pt-BR"];
|
|
2475
3069
|
const tests = options.tests ?? createProvingTests();
|
|
2476
3070
|
const vehicles = options.vehicles;
|
|
2477
3071
|
const sounds = { ...defaultSoundIds, ...options.sounds };
|
|
@@ -2488,7 +3082,19 @@ function createProvingModule(options) {
|
|
|
2488
3082
|
let run = null;
|
|
2489
3083
|
let parking = null;
|
|
2490
3084
|
let driving = null;
|
|
3085
|
+
let crashes = null;
|
|
3086
|
+
let wheels = null;
|
|
3087
|
+
let lastAt = null;
|
|
3088
|
+
let parked = null;
|
|
3089
|
+
const adjust = { seatForward: 0, seatHeight: 0, mirrorLeft: 0, mirrorRight: 0 };
|
|
3090
|
+
const aimed = (pose2) => {
|
|
3091
|
+
const turn = pose2.id === "left" ? adjust.mirrorLeft : pose2.id === "right" ? -adjust.mirrorRight : 0;
|
|
3092
|
+
return turn ? { ...pose2, look: [pose2.look[0] + turn, pose2.look[1], pose2.look[2]] } : pose2;
|
|
3093
|
+
};
|
|
2491
3094
|
let gearbox = "automatic";
|
|
3095
|
+
let hasKey = true;
|
|
3096
|
+
let weather = { outsideC: 25, rain: 0 };
|
|
3097
|
+
let misted = false;
|
|
2492
3098
|
let lastGear = null;
|
|
2493
3099
|
let driveClock = 0;
|
|
2494
3100
|
let queue = [];
|
|
@@ -2649,6 +3255,8 @@ function createProvingModule(options) {
|
|
|
2649
3255
|
};
|
|
2650
3256
|
const removeCar = () => {
|
|
2651
3257
|
if (!car) return;
|
|
3258
|
+
wheels = null;
|
|
3259
|
+
lastAt = null;
|
|
2652
3260
|
discardObject3D3(car.built.group);
|
|
2653
3261
|
car = null;
|
|
2654
3262
|
};
|
|
@@ -2662,6 +3270,7 @@ function createProvingModule(options) {
|
|
|
2662
3270
|
built.group.add(guides);
|
|
2663
3271
|
host.scene.add(built.group);
|
|
2664
3272
|
car = { built, guides, subject };
|
|
3273
|
+
wheels = createWheelRoll(three, built);
|
|
2665
3274
|
pose({ x: 0, z: TRACK.start, yaw: 0, pitch: 0, roll: 0, height: 0 }, 0);
|
|
2666
3275
|
rig.vehicle?.reset();
|
|
2667
3276
|
drawBoard();
|
|
@@ -2712,11 +3321,18 @@ function createProvingModule(options) {
|
|
|
2712
3321
|
car.built.group.position.set(TRACK.x0 + wrapped, 0, TRACK.z + s.z);
|
|
2713
3322
|
car.built.group.rotation.set(-s.pitch, s.yaw + Math.PI / 2, s.roll, "YXZ");
|
|
2714
3323
|
bendGuides(steer);
|
|
3324
|
+
if (lastAt) {
|
|
3325
|
+
const along = (s.x - lastAt.x) * Math.cos(s.yaw) - (s.z - lastAt.z) * Math.sin(s.yaw);
|
|
3326
|
+
if (Math.abs(along) < 5) wheels?.roll(along, steer);
|
|
3327
|
+
}
|
|
3328
|
+
lastAt = { x: s.x, z: s.z };
|
|
2715
3329
|
};
|
|
2716
3330
|
const frameOf = (speed) => {
|
|
2717
3331
|
if (!car) return null;
|
|
2718
3332
|
const g = car.built.group;
|
|
2719
|
-
|
|
3333
|
+
const size = { length: car.built.length, width: car.built.width, height: car.built.height };
|
|
3334
|
+
const [ex, ey, ez] = car.built.driverEye ?? defaultEye(size);
|
|
3335
|
+
return { x: g.position.x, y: g.position.y, z: g.position.z, yaw: g.rotation.y, pitch: -g.rotation.x, roll: g.rotation.z, speed, ...size, eye: [ex, ey + adjust.seatHeight, ez + adjust.seatForward] };
|
|
2720
3336
|
};
|
|
2721
3337
|
const applyCamera = () => {
|
|
2722
3338
|
if (!host?.setViewCamera) return;
|
|
@@ -2798,26 +3414,42 @@ function createProvingModule(options) {
|
|
|
2798
3414
|
run = null;
|
|
2799
3415
|
queue = [];
|
|
2800
3416
|
parking = null;
|
|
2801
|
-
setSurface("dry_asphalt");
|
|
3417
|
+
setSurface(weather.rain > 0 ? "wet_asphalt" : "dry_asphalt");
|
|
3418
|
+
misted = false;
|
|
2802
3419
|
driving = createCockpit({
|
|
2803
3420
|
THREE: three,
|
|
2804
3421
|
host,
|
|
2805
3422
|
built: car.built,
|
|
2806
3423
|
subject: car.subject,
|
|
2807
3424
|
place: [0, 0, 0],
|
|
3425
|
+
kit: kitFor(three),
|
|
3426
|
+
obstacles: () => parked ? [[parked.root.position.x, parked.root.position.z]] : [],
|
|
2808
3427
|
gauge: kitFor(three).materials?.gauge,
|
|
2809
3428
|
needle: kitFor(three).materials?.amber,
|
|
2810
3429
|
// the engine's own sound turns the key (starter, catch, idle by RPM)
|
|
2811
3430
|
sounds: { starter: "" },
|
|
3431
|
+
hasKey,
|
|
3432
|
+
weather,
|
|
3433
|
+
stock: (compartment) => options.stock?.(compartment, settings.vehicle) ?? [],
|
|
2812
3434
|
onExit: () => stopDriving(),
|
|
2813
3435
|
onEvent: (event) => {
|
|
2814
|
-
if (event.type === "
|
|
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
|
+
}
|
|
3441
|
+
if (event.type === "refused") return host?.log(text.cabin.refused[event.reason] ?? sysText.refused[event.reason] ?? event.reason);
|
|
3442
|
+
if (event.type === "sound") return;
|
|
3443
|
+
if (event.type !== "command") {
|
|
3444
|
+
if (event.type === "out_of_fuel" || event.type === "stall" || event.type === "seized") engine?.stop([car?.built.group.position.x ?? 0, 0.5, car?.built.group.position.z ?? 0]);
|
|
3445
|
+
return host?.log(sysText.events[event.type] ?? event.type, { vehicle: settings.vehicle, ...event }, "proving_system");
|
|
3446
|
+
}
|
|
2815
3447
|
const st = event.state;
|
|
2816
3448
|
const line = cabinLine(event.command, st);
|
|
2817
3449
|
if (line) host?.log(line);
|
|
2818
3450
|
if (event.command === "ignition" && car) {
|
|
2819
3451
|
const at = [car.built.group.position.x, 0.5, car.built.group.position.z];
|
|
2820
|
-
if (st.ignition) engine?.start(at);
|
|
3452
|
+
if (st.ignition && driving?.systems.canCrank() !== false) engine?.start(at);
|
|
2821
3453
|
else engine?.stop(at);
|
|
2822
3454
|
}
|
|
2823
3455
|
if (event.command === "gearbox") {
|
|
@@ -2836,7 +3468,7 @@ function createProvingModule(options) {
|
|
|
2836
3468
|
rig.vehicle?.reset();
|
|
2837
3469
|
stopEngine();
|
|
2838
3470
|
engine = createVehicleSound(host.audio, { sounds });
|
|
2839
|
-
host.bench?.sheet?.({ kicker: text.name, title: fillText(text.log.drive, name(settings.vehicle)), body: text.cabin.keys });
|
|
3471
|
+
host.bench?.sheet?.({ kicker: text.name, title: fillText(text.log.drive, name(settings.vehicle)), body: `${text.cabin.keys} \xB7 ${sysText.keys} \xB7 ${sysText.bodyKeys}` });
|
|
2840
3472
|
host.log(fillText(text.log.drive, name(settings.vehicle)));
|
|
2841
3473
|
};
|
|
2842
3474
|
const onOff = (on) => on ? text.cabin.on : text.cabin.off;
|
|
@@ -2865,19 +3497,156 @@ function createProvingModule(options) {
|
|
|
2865
3497
|
return `${c.alarm}: ${c.alarmStates[st.alarm]}`;
|
|
2866
3498
|
case "radio":
|
|
2867
3499
|
case "radio_source":
|
|
2868
|
-
|
|
3500
|
+
case "radio_track":
|
|
3501
|
+
return `${c.radio}: ${st.radio.on ? `${c.sources[st.radio.source]} \xB7 ${fillText(sysText.track, st.radio.track + 1)}` : c.off}`;
|
|
2869
3502
|
case "indicator_left":
|
|
2870
3503
|
case "indicator_right":
|
|
2871
3504
|
case "hazard":
|
|
2872
3505
|
return `${c.indicator}: ${c.indicators[st.indicator]}`;
|
|
2873
3506
|
case "wipers":
|
|
2874
|
-
return `${c.wipers}: ${
|
|
3507
|
+
return `${c.wipers}: ${sysText.wiperModes[st.wipers]}`;
|
|
3508
|
+
case "drive_profile":
|
|
3509
|
+
return `${sysText.profile}: ${sysText.profiles[st.profile]}`;
|
|
3510
|
+
case "traction_control":
|
|
3511
|
+
return `${sysText.traction}: ${onOff(st.tractionControl)}`;
|
|
3512
|
+
case "abs":
|
|
3513
|
+
return `${sysText.abs}: ${onOff(st.abs)}`;
|
|
3514
|
+
case "cruise":
|
|
3515
|
+
return st.cruise.on ? fillText(sysText.cruiseSet, Math.round(st.cruise.speed * 3.6)) : `${sysText.cruise}: ${c.off}`;
|
|
3516
|
+
case "climate":
|
|
3517
|
+
return `${sysText.climate}: ${onOff(st.climate.on)}`;
|
|
3518
|
+
case "demist":
|
|
3519
|
+
return `${sysText.demist}: ${onOff(st.climate.demist)}`;
|
|
2875
3520
|
case "seatbelt":
|
|
2876
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"]}`;
|
|
2877
3528
|
default:
|
|
2878
3529
|
return null;
|
|
2879
3530
|
}
|
|
2880
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
|
+
};
|
|
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
|
+
}
|
|
3557
|
+
if (!driving) startDriving();
|
|
3558
|
+
const cockpit = driving;
|
|
3559
|
+
if (!cockpit) return;
|
|
3560
|
+
const d = cockpit.driver;
|
|
3561
|
+
const done = (key) => host?.log(sysText.done[key], { vehicle: settings.vehicle, action: key }, "proving_system");
|
|
3562
|
+
switch (id) {
|
|
3563
|
+
case "puncture": {
|
|
3564
|
+
const tyres = d.spec.wheels.map((_, i) => d.condition.tyres?.[i] ?? 1);
|
|
3565
|
+
tyres[0] = 0;
|
|
3566
|
+
d.condition = { ...d.condition, tyres };
|
|
3567
|
+
return done("puncture");
|
|
3568
|
+
}
|
|
3569
|
+
case "fit_spare": {
|
|
3570
|
+
const flat = d.spec.wheels.findIndex((_, i) => (d.condition.tyres?.[i] ?? 1) < 0.2);
|
|
3571
|
+
const event = cockpit.systems.fitSpare(d, Math.max(0, flat));
|
|
3572
|
+
host?.log(sysText.events[event.type], { vehicle: settings.vehicle, ...event }, "proving_system");
|
|
3573
|
+
return;
|
|
3574
|
+
}
|
|
3575
|
+
case "refuel":
|
|
3576
|
+
d.refuel(d.spec.tank.capacity);
|
|
3577
|
+
return done("refuel");
|
|
3578
|
+
case "empty_tank":
|
|
3579
|
+
d.state.fuel = Math.min(d.state.fuel, d.spec.tank.capacity * 0.04);
|
|
3580
|
+
return done("empty_tank");
|
|
3581
|
+
case "drain_battery":
|
|
3582
|
+
cockpit.systems.charge(-(cockpit.systems.state.charge - 0.15));
|
|
3583
|
+
return done("drain_battery");
|
|
3584
|
+
case "jump_start":
|
|
3585
|
+
cockpit.systems.charge(1);
|
|
3586
|
+
return done("jump_start");
|
|
3587
|
+
case "break_radiator":
|
|
3588
|
+
cockpit.radiator = 0.05;
|
|
3589
|
+
return done("break_radiator");
|
|
3590
|
+
case "remote_lock":
|
|
3591
|
+
case "remote_unlock": {
|
|
3592
|
+
const lock = id === "remote_lock";
|
|
3593
|
+
cockpit.body?.lock(lock);
|
|
3594
|
+
cockpit.controls.state.alarm = lock ? "armed" : "off";
|
|
3595
|
+
host?.audio?.play?.({ sound: "alarm_chirp", position: [car?.built.group.position.x ?? 0, 1, car?.built.group.position.z ?? 0] });
|
|
3596
|
+
host?.log(sysText.remote[lock ? "locked" : "unlocked"], { vehicle: settings.vehicle, locked: lock }, "proving_system");
|
|
3597
|
+
return;
|
|
3598
|
+
}
|
|
3599
|
+
case "crash_wall": {
|
|
3600
|
+
const g = car?.built.group;
|
|
3601
|
+
if (!g || !host) return;
|
|
3602
|
+
crashes ?? (crashes = createProvingCrash(host, options.occupant));
|
|
3603
|
+
const speed = d.state.u;
|
|
3604
|
+
const ejection = crashes.crash({ car: { position: g.position, rotationY: g.rotation.y }, u: d.state.u, w: d.state.w, belted: cockpit.controls.state.seatbelt, destructible: cockpit.destructible });
|
|
3605
|
+
d.state.u = d.state.w = d.state.yawRate = 0;
|
|
3606
|
+
const kmh = Math.round(Math.abs(speed) * 3.6);
|
|
3607
|
+
const belted = cockpit.controls.state.seatbelt;
|
|
3608
|
+
host.log(ejection ? fillText(sysText.crash.ejected, kmh, sysText.crash.exits[ejection.exit]) : fillText(belted ? sysText.crash.stayed : sysText.crash.mild, kmh), { vehicle: settings.vehicle, kmh, belted: cockpit.controls.state.seatbelt, ejection }, "proving_crash");
|
|
3609
|
+
return;
|
|
3610
|
+
}
|
|
3611
|
+
case "park_behind": {
|
|
3612
|
+
const g = car?.built.group;
|
|
3613
|
+
if (!g || !host) return;
|
|
3614
|
+
parked?.remove();
|
|
3615
|
+
const back = (car?.built.length ?? 4.5) / 2 + 3 + 2.3;
|
|
3616
|
+
const at = [g.position.x - Math.sin(g.rotation.y) * back, 0, g.position.z - Math.cos(g.rotation.y) * back];
|
|
3617
|
+
const placed = host.addObject?.(options.parkedCar ?? "sedan", { position: at, rotationY: g.rotation.y });
|
|
3618
|
+
parked = placed ? { root: placed.root, remove: placed.remove } : null;
|
|
3619
|
+
host.log(placed ? sysText.parking.placed : sysText.parking.none, { vehicle: settings.vehicle }, "proving_system");
|
|
3620
|
+
return;
|
|
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
|
+
}
|
|
3639
|
+
case "force_door": {
|
|
3640
|
+
const body = cockpit.body;
|
|
3641
|
+
if (!body) return;
|
|
3642
|
+
if (body.locked) body.lock(false);
|
|
3643
|
+
body.toggle("door_front_left");
|
|
3644
|
+
if (cockpit.controls.state.alarm === "armed") cockpit.controls.state.alarm = "ringing";
|
|
3645
|
+
host?.log(sysText.remote.forced, { vehicle: settings.vehicle, alarm: cockpit.controls.state.alarm }, "proving_system");
|
|
3646
|
+
return;
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
};
|
|
2881
3650
|
const gearName = (gear) => gear < 0 ? "R" : gear === 0 ? "N" : String(gear);
|
|
2882
3651
|
const onChange = (change) => {
|
|
2883
3652
|
if (change.kind === "layer") {
|
|
@@ -2886,6 +3655,7 @@ function createProvingModule(options) {
|
|
|
2886
3655
|
}
|
|
2887
3656
|
if (change.kind === "action") {
|
|
2888
3657
|
if (change.id === "drive") return driving ? stopDriving() : startDriving();
|
|
3658
|
+
if (change.id in sysText.actions) return systemsAction(change.id);
|
|
2889
3659
|
if (change.id === "run_test" || change.id === "run_all" || change.id === "reverse_park") stopDriving();
|
|
2890
3660
|
if (change.id === "run_test") {
|
|
2891
3661
|
queue = [];
|
|
@@ -2932,6 +3702,12 @@ function createProvingModule(options) {
|
|
|
2932
3702
|
settings.camera = String(change.value);
|
|
2933
3703
|
applyCamera();
|
|
2934
3704
|
break;
|
|
3705
|
+
case "seat_forward":
|
|
3706
|
+
case "seat_height":
|
|
3707
|
+
case "mirror_left":
|
|
3708
|
+
case "mirror_right":
|
|
3709
|
+
adjust[ADJUST_KEYS[change.id]] = Number(change.value);
|
|
3710
|
+
break;
|
|
2935
3711
|
case "time_scale":
|
|
2936
3712
|
settings.timeScale = Number(change.value);
|
|
2937
3713
|
break;
|
|
@@ -2966,6 +3742,13 @@ function createProvingModule(options) {
|
|
|
2966
3742
|
const d = driving;
|
|
2967
3743
|
d.step(dt);
|
|
2968
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
|
+
}
|
|
2969
3752
|
pose(d.driver.state, d.driver.state.steerAngle);
|
|
2970
3753
|
speed = d.driver.state.u;
|
|
2971
3754
|
steer = d.driver.state.steerAngle;
|
|
@@ -2981,7 +3764,7 @@ function createProvingModule(options) {
|
|
|
2981
3764
|
if (car) engine?.update({ dt, position: [car.built.group.position.x, 0.5, car.built.group.position.z], speed, throttle: run ? 1 : driving?.throttle ?? 0, rpm: driving ? driving.driver.state.rpm : void 0 });
|
|
2982
3765
|
const f = frameOf(speed);
|
|
2983
3766
|
if (f && rig.vehicle && settings.camera !== "room") rig.vehicle.update(rig.camera, f, dt);
|
|
2984
|
-
if (f && rig.vehicle) for (const m of rig.mirrors) rig.vehicle.placeMirror(m.camera, m.pose, f);
|
|
3767
|
+
if (f && rig.vehicle) for (const m of rig.mirrors) rig.vehicle.placeMirror(m.camera, aimed(m.pose), f);
|
|
2985
3768
|
void steer;
|
|
2986
3769
|
const key = `${host.bench?.layer("mirrors")}|${host.bench?.layer("reverse_camera")}`;
|
|
2987
3770
|
if (key !== mirrorKey) {
|
|
@@ -3000,6 +3783,10 @@ function createProvingModule(options) {
|
|
|
3000
3783
|
const speedNow = () => Math.abs(run?.driver.state.u ?? driving?.driver.state.u ?? parking?.driver.state.u ?? 0) * 3.6;
|
|
3001
3784
|
const counts = (verdict) => certificateOf(settings.vehicle)?.results.filter((r) => r.verdict === verdict).length ?? 0;
|
|
3002
3785
|
const teardown = () => {
|
|
3786
|
+
crashes?.dispose();
|
|
3787
|
+
crashes = null;
|
|
3788
|
+
parked?.remove();
|
|
3789
|
+
parked = null;
|
|
3003
3790
|
stopDriving();
|
|
3004
3791
|
stopEngine();
|
|
3005
3792
|
for (const m of rig.mirrors) m.remove();
|
|
@@ -3030,9 +3817,14 @@ function createProvingModule(options) {
|
|
|
3030
3817
|
{ kind: "choice", id: "vehicle", name: text.controls.vehicle, hint: text.hints.vehicle, options: vehicles.map((id) => ({ id, name: name(id) })), default: settings.vehicle },
|
|
3031
3818
|
{ kind: "choice", id: "test", name: text.controls.test, hint: text.hints.test, options: measured.map((id) => ({ id, name: text.tests[id] ?? id })), default: settings.test },
|
|
3032
3819
|
{ 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 },
|
|
3820
|
+
{ kind: "number", id: "seat_forward", name: sysText.adjust.seat_forward, min: -0.2, max: 0.2, step: 0.02, default: 0 },
|
|
3821
|
+
{ kind: "number", id: "seat_height", name: sysText.adjust.seat_height, min: -0.1, max: 0.1, step: 0.01, default: 0 },
|
|
3822
|
+
{ kind: "number", id: "mirror_left", name: sysText.adjust.mirror_left, min: -0.3, max: 0.3, step: 0.02, default: 0 },
|
|
3823
|
+
{ kind: "number", id: "mirror_right", name: sysText.adjust.mirror_right, min: -0.3, max: 0.3, step: 0.02, default: 0 },
|
|
3033
3824
|
{ kind: "number", id: "time_scale", name: text.controls.time_scale, hint: text.hints.time_scale, min: 1, max: 20, step: 1, default: settings.timeScale },
|
|
3034
3825
|
{ 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
3826
|
{ kind: "action", id: "drive", name: text.actions.drive },
|
|
3827
|
+
...Object.keys(sysText.actions).map((id) => ({ kind: "action", id, name: sysText.actions[id] })),
|
|
3036
3828
|
{ kind: "choice", id: "gearbox", name: text.controls.gearbox, hint: text.hints.gearbox, options: ["automatic", "manual"].map((id) => ({ id, name: text.gearbox[id] })), default: gearbox },
|
|
3037
3829
|
{ kind: "action", id: "run_test", name: text.actions.run_test },
|
|
3038
3830
|
{ kind: "action", id: "run_all", name: text.actions.run_all },
|
|
@@ -3052,6 +3844,7 @@ function createProvingModule(options) {
|
|
|
3052
3844
|
{ id: "gear", name: text.metrics.gear, read: () => driving?.driver.state.gear ?? run?.driver.state.gear ?? 0 },
|
|
3053
3845
|
{ id: "rpm", name: text.metrics.rpm, unit: "rpm", digits: 0, read: () => driving?.driver.state.rpm ?? run?.driver.state.rpm ?? 0 },
|
|
3054
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 },
|
|
3055
3848
|
{ id: "value", name: text.metrics.value, digits: 1, read: () => run ? run.trace[run.trace.length - 1]?.[1] ?? 0 : 0 },
|
|
3056
3849
|
{ id: "passed", name: text.metrics.passed, read: () => counts("pass") },
|
|
3057
3850
|
{ id: "failed", name: text.metrics.failed, read: () => counts("fail") },
|
|
@@ -3066,6 +3859,8 @@ function createProvingModule(options) {
|
|
|
3066
3859
|
rig.vehicle = createVehicleCamera(three);
|
|
3067
3860
|
lent.audio?.register?.(LIBRARY_BANK2);
|
|
3068
3861
|
lent.audio?.register?.(CABIN_BANK);
|
|
3862
|
+
lent.audio?.register?.(VEHICLE_SOUND_BANK);
|
|
3863
|
+
lent.audio?.register?.(RADIO_BANK);
|
|
3069
3864
|
const stopChange = lent.bench?.onChange(onChange);
|
|
3070
3865
|
const stopFrame = lent.onFrame(frame);
|
|
3071
3866
|
const stopClear = lent.onClear?.(() => {
|
|
@@ -3098,10 +3893,6 @@ export {
|
|
|
3098
3893
|
liteMeshes,
|
|
3099
3894
|
createVehiclesThree,
|
|
3100
3895
|
createCabinSmoke,
|
|
3101
|
-
VEHICLE_LOOP_FALLOFF,
|
|
3102
|
-
VEHICLE_LOOPS,
|
|
3103
|
-
VEHICLE_SOUNDS,
|
|
3104
|
-
VEHICLE_SOUND_BANK,
|
|
3105
3896
|
firingHz,
|
|
3106
3897
|
rpmShare,
|
|
3107
3898
|
layerWeight,
|
|
@@ -3113,6 +3904,15 @@ export {
|
|
|
3113
3904
|
TRUCK_ENGINE,
|
|
3114
3905
|
BIKE_ENGINE,
|
|
3115
3906
|
ENGINE_PROFILES,
|
|
3907
|
+
VEHICLE_LOOP_FALLOFF,
|
|
3908
|
+
VEHICLE_LOOPS,
|
|
3909
|
+
VEHICLE_SOUNDS,
|
|
3910
|
+
CAR_RECORDED_RPM,
|
|
3911
|
+
CAR_START_RUN_STOP,
|
|
3912
|
+
VEHICLE_SAMPLE_ALIASES,
|
|
3913
|
+
VEHICLE_PRELOAD_SOUNDS,
|
|
3914
|
+
VEHICLE_SOUND_BANK,
|
|
3915
|
+
createEngineBudget,
|
|
3116
3916
|
createVehicleSound,
|
|
3117
3917
|
VEHICLE_LOD_DISTANCE,
|
|
3118
3918
|
createVehicleLod,
|
|
@@ -3126,13 +3926,17 @@ export {
|
|
|
3126
3926
|
DEFAULT_TEST_VEHICLES,
|
|
3127
3927
|
createVehiclesTestModule,
|
|
3128
3928
|
loadVehiclesTestModule,
|
|
3929
|
+
createVehicleBody,
|
|
3129
3930
|
DASHBOARD_LOOK,
|
|
3130
3931
|
createVehicleDashboard,
|
|
3131
3932
|
LAMP_RULES,
|
|
3132
3933
|
createVehicleLamps,
|
|
3934
|
+
MIST_RULES,
|
|
3935
|
+
createWindshieldMist,
|
|
3936
|
+
createWheelRoll,
|
|
3133
3937
|
PROVING_TEXT,
|
|
3134
3938
|
PROVING_AREA_ID,
|
|
3135
3939
|
createProvingModule,
|
|
3136
3940
|
loadProvingModule
|
|
3137
3941
|
};
|
|
3138
|
-
//# sourceMappingURL=chunk-
|
|
3942
|
+
//# sourceMappingURL=chunk-NPS4X64L.js.map
|