@adatechnology/game-vehicles-three 0.5.0 → 0.6.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-GAO7ZJP7.js → chunk-4SUQMRS3.js} +1006 -33
- package/dist/chunk-4SUQMRS3.js.map +1 -0
- package/dist/index.d.ts +97 -4
- package/dist/index.global.js +4040 -1019
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +43 -3
- package/dist/{testing-DmGv7Gpb.d.ts → testing-gEnSqEF6.d.ts} +136 -6
- package/dist/testing.d.ts +2 -1
- package/dist/testing.js +9 -1
- package/package.json +10 -8
- package/dist/chunk-GAO7ZJP7.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/test-area/module.ts
|
|
2
2
|
import { DEFERRED_PRIORITY, DEFERRED_QUEUE_ID, footprintBlocker } from "@adatechnology/game-contracts";
|
|
3
3
|
import { discardObject3D } from "@adatechnology/game-shapes-three";
|
|
4
|
-
import { buildVehicleItem, crackedGlassMaterial, createCreaseQueue, createVehicleDestructible, createVehicleKit } from "@adatechnology/game-vehicles";
|
|
4
|
+
import { buildVehicleItem, crackedGlassMaterial, createCreaseQueue, createVehicleDestructible as createVehicleDestructible2, createVehicleKit } from "@adatechnology/game-vehicles";
|
|
5
5
|
import { isRenderPools } from "@adatechnology/game-render-three";
|
|
6
6
|
import { createArcadeDriver as createArcadeDriver2, createSemiSimDriver as createSemiSimDriver2, createSurfaces as createSurfaces2, vehicleCorners as vehicleCorners2 } from "@adatechnology/game-vehicles-driving";
|
|
7
7
|
import { colorFromSeed, fleetDims, installFleet, paintPalettes, paletteOf } from "@adatechnology/game-vehicles-fleet";
|
|
@@ -253,6 +253,7 @@ function createVehiclesThree(THREE, options) {
|
|
|
253
253
|
c.vehicle.matrixAutoUpdate = true;
|
|
254
254
|
pivot.add(c.vehicle);
|
|
255
255
|
group.add(pivot);
|
|
256
|
+
c.root = group;
|
|
256
257
|
if (color && primary) {
|
|
257
258
|
const repainted = primary.clone();
|
|
258
259
|
repainted.color.set(color);
|
|
@@ -317,12 +318,15 @@ import { markKitOrigin as markKitOrigin2 } from "@adatechnology/game-contracts";
|
|
|
317
318
|
import { createStaticBatcher } from "@adatechnology/game-render-three";
|
|
318
319
|
var VEHICLE_LOD_DISTANCE = 14;
|
|
319
320
|
var BATCH_BUDGET_MS = 2;
|
|
321
|
+
var VEHICLE_REBATCH_AFTER_MS = 1500;
|
|
320
322
|
var isShown = (object, top) => {
|
|
321
323
|
for (let node = object; node && node !== top; node = node.parent) if (node.visible === false) return false;
|
|
322
324
|
return true;
|
|
323
325
|
};
|
|
324
|
-
function createVehicleLod(kit, context, root, full) {
|
|
326
|
+
function createVehicleLod(kit, context, root, full, options = {}) {
|
|
325
327
|
const THREE = kit.THREE;
|
|
328
|
+
const now = options.now ?? (() => performance.now());
|
|
329
|
+
let changedAt = Number.NEGATIVE_INFINITY;
|
|
326
330
|
let isUploaded = false;
|
|
327
331
|
let modelKey = "";
|
|
328
332
|
const culled = /* @__PURE__ */ new Map();
|
|
@@ -382,8 +386,17 @@ function createVehicleLod(kit, context, root, full) {
|
|
|
382
386
|
root.add(proxy);
|
|
383
387
|
return proxy;
|
|
384
388
|
};
|
|
389
|
+
const batchLocked = () => {
|
|
390
|
+
batcher = createStaticBatcher(THREE, full, { budgetMs: BATCH_BUDGET_MS });
|
|
391
|
+
batcher.group.traverse((object) => object.userData.skipLite = true);
|
|
392
|
+
batcher.group.userData.noSurfaceRay = true;
|
|
393
|
+
markKitOrigin2(batcher.group, { owner: "game-vehicles-three", kind: "vehicle", id: root.name });
|
|
394
|
+
};
|
|
385
395
|
const drawFull = () => {
|
|
386
|
-
if (!batcher && isLocked)
|
|
396
|
+
if (!batcher && isLocked) {
|
|
397
|
+
if (now() - changedAt >= VEHICLE_REBATCH_AFTER_MS) batchLocked();
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
387
400
|
if (!batcher) {
|
|
388
401
|
lite ?? (lite = build());
|
|
389
402
|
lite.visible = isFused;
|
|
@@ -396,11 +409,13 @@ function createVehicleLod(kit, context, root, full) {
|
|
|
396
409
|
});
|
|
397
410
|
batcher = createStaticBatcher(THREE, full, { budgetMs: BATCH_BUDGET_MS });
|
|
398
411
|
batcher.group.traverse((object) => object.userData.skipLite = true);
|
|
412
|
+
batcher.group.userData.noSurfaceRay = true;
|
|
399
413
|
markKitOrigin2(batcher.group, { owner: "game-vehicles-three", kind: "vehicle", id: root.name });
|
|
400
414
|
return;
|
|
401
415
|
}
|
|
402
|
-
batcher
|
|
403
|
-
if (
|
|
416
|
+
if (!batcher) return;
|
|
417
|
+
if (batcher.update() > 0) changedAt = now();
|
|
418
|
+
if (batcher.stats().batched === 0 && batcher.stats().released > 0) {
|
|
404
419
|
batcher.dispose();
|
|
405
420
|
batcher = null;
|
|
406
421
|
}
|
|
@@ -441,8 +456,10 @@ function createVehicleLod(kit, context, root, full) {
|
|
|
441
456
|
};
|
|
442
457
|
const api = {
|
|
443
458
|
update(distance, touched, posed = false) {
|
|
459
|
+
if (touched && !isLocked) changedAt = now();
|
|
444
460
|
isLocked || (isLocked = touched);
|
|
445
461
|
if (keyOf() !== modelKey) {
|
|
462
|
+
changedAt = now();
|
|
446
463
|
api.dispose();
|
|
447
464
|
armUpload();
|
|
448
465
|
}
|
|
@@ -513,6 +530,50 @@ function createCabinSmoke() {
|
|
|
513
530
|
};
|
|
514
531
|
}
|
|
515
532
|
|
|
533
|
+
// src/test-area/lane-damage.ts
|
|
534
|
+
import { crashVehicle, createVehicleDestructible } from "@adatechnology/game-vehicles";
|
|
535
|
+
import { conditionFromDamage } from "@adatechnology/game-vehicles-driving";
|
|
536
|
+
var LANE_DAMAGE = ["none", "into_wall", "flat_tyre", "torn_tyre", "run_flat", "no_brake_fluid"];
|
|
537
|
+
var RIFLE = { energy: 3500, caliber: 7.8, penetration: { steel: 18 } };
|
|
538
|
+
var TEAR_OFF_M = 5e3;
|
|
539
|
+
var LANE_CAR_KG = 1150;
|
|
540
|
+
var LANE_WALL_CONTACT = 1.6;
|
|
541
|
+
var WALL_REACH = 0.15;
|
|
542
|
+
var DRAIN_S = 600;
|
|
543
|
+
function createLaneDamage(params) {
|
|
544
|
+
const d = createVehicleDestructible(params.kit, params.context, { id: "lane", frame: params.frame, armour: params.damage === "run_flat" ? "IIIA" : null });
|
|
545
|
+
const part = (prefix) => d.describe().parts.find((p) => p.id.startsWith(prefix))?.id;
|
|
546
|
+
const shoot = (id) => {
|
|
547
|
+
if (id) d.hit({ type: "ballistic", point: d.partWorldCenter(id), part: id, direction: [0, 0, 1], ...RIFLE });
|
|
548
|
+
};
|
|
549
|
+
if (params.damage === "into_wall") {
|
|
550
|
+
} else if (params.damage === "no_brake_fluid") {
|
|
551
|
+
shoot(part("brakes"));
|
|
552
|
+
for (let t = 0; t < DRAIN_S; t++) d.update?.(1);
|
|
553
|
+
} else {
|
|
554
|
+
shoot(part("tyre_front_left"));
|
|
555
|
+
if (params.damage === "torn_tyre") d.rollFlat(TEAR_OFF_M, 8);
|
|
556
|
+
}
|
|
557
|
+
return {
|
|
558
|
+
step({ dt, speed, throttle, driver }) {
|
|
559
|
+
d.rollFlat(speed * dt, speed);
|
|
560
|
+
d.runEngine(dt, throttle);
|
|
561
|
+
const events = d.update?.(dt) ?? [];
|
|
562
|
+
driver.condition = conditionFromDamage(driver.spec, d.driveReport());
|
|
563
|
+
return events.flatMap((e) => e.type === "debris" && e.kind === "sparks" ? [e.point] : []);
|
|
564
|
+
},
|
|
565
|
+
settle(dt) {
|
|
566
|
+
d.update?.(dt);
|
|
567
|
+
},
|
|
568
|
+
crash({ speed, direction }) {
|
|
569
|
+
const front = part("bumper_front");
|
|
570
|
+
if (!front) return [];
|
|
571
|
+
const at = d.partWorldCenter(front);
|
|
572
|
+
return crashVehicle(d, { point: [at[0] + direction[0] * WALL_REACH, at[1], at[2] + direction[2] * WALL_REACH], direction, speed, massKg: LANE_CAR_KG, otherMassKg: Infinity, contactWidth: LANE_WALL_CONTACT }).events;
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
516
577
|
// src/test-area/scenarios.ts
|
|
517
578
|
import { createArcadeDriver, createSemiSimDriver, createSurfaces, vehicleCorners } from "@adatechnology/game-vehicles-driving";
|
|
518
579
|
|
|
@@ -562,25 +623,27 @@ var TEXT = {
|
|
|
562
623
|
group: "Vehicles",
|
|
563
624
|
name: "Vehicles area",
|
|
564
625
|
items: { hatch: "Compact hatch", sedan: "Midsize sedan", pickup: "Midsize pickup", sport_bike: "Sport bike", box_truck: "Box truck", city_bus: "City bus", battle_tank: "Battle tank", attack_helicopter: "Attack helicopter", sedan_burnt: "Burnt sedan", hatch_burnt: "Burnt hatch" },
|
|
565
|
-
controls: { quality: "Quality", paint_by_seed: "Paint by seed", color_seed: "Colour seed", palette: "Palette", driving: "Driving", surface: "Surface" },
|
|
626
|
+
controls: { quality: "Quality", paint_by_seed: "Paint by seed", color_seed: "Colour seed", palette: "Palette", driving: "Driving", surface: "Surface", lane_damage: "Lane damage" },
|
|
566
627
|
hints: {
|
|
567
628
|
quality: "High: the kit at full detail. Light: fewer segments and no interior (the light mode of the fleet); applies to the vehicles placed from now on",
|
|
568
629
|
paint_by_seed: "Off: every vehicle keeps its own paint. On: the body colour comes from the seed and the palette",
|
|
569
630
|
palette: "Auto: the palette of the vehicle group (civil, or military for the army vehicles)",
|
|
570
631
|
driving: "Arcade: the game car, simple and forgiving. Semi: a rigid body with springs, tyre friction, gears and surfaces",
|
|
571
|
-
surface: "The ground under the wheels in the semi mode (grip and aquaplaning)"
|
|
632
|
+
surface: "The ground under the wheels in the semi mode (grip and aquaplaning)",
|
|
633
|
+
lane_damage: "What the lane car starts with; it drives in the semi mode and the damage model sets its tyres, brakes and engine"
|
|
572
634
|
},
|
|
573
635
|
options: {
|
|
574
636
|
quality: { high: "High", lite: "Light" },
|
|
575
637
|
palette: { auto: "Auto", civil: "Civil", military: "Military" },
|
|
576
638
|
driving: { arcade: "Arcade", semi: "Semi-simulation" },
|
|
577
|
-
surface: { dry_asphalt: "Dry asphalt", wet_asphalt: "Wet asphalt", dirt: "Dirt", grass: "Grass", mud: "Mud", blood: "Blood" }
|
|
639
|
+
surface: { dry_asphalt: "Dry asphalt", wet_asphalt: "Wet asphalt", dirt: "Dirt", grass: "Grass", mud: "Mud", blood: "Blood" },
|
|
640
|
+
lane_damage: { none: "None", into_wall: "Into the wall, no braking", flat_tyre: "Flat tyre (rifle round)", torn_tyre: "Tyre torn off: rim on the road", run_flat: "Armoured, flat tyre (run-flat)", no_brake_fluid: "No brake fluid" }
|
|
578
641
|
},
|
|
579
642
|
actions: { repaint: "Repaint with the seed", drive_lane: "Drive the lane", clear_lane: "Remove the lane car", honk: "Honk the horn" },
|
|
580
643
|
layers: { damageables: "Damageable boxes by material", zones: "Zone boxes by type", axes: "Output frame axes" },
|
|
581
644
|
metrics: { vehicles: "Vehicles", triangles: "Triangles", draw_calls: "Draw calls", size_error_mm: "Size against DIMS", metal: "Metal boxes", glass: "Glass boxes", rubber: "Rubber boxes", fuel: "Fuel boxes", speed: "Lane speed", lane: "Lane distance" },
|
|
582
645
|
words: { yes: "true", no: "false", open: "open", tight: "tight", on: "on", off: "off" },
|
|
583
|
-
lane: { crash: "Lane: the car hit the wall at {0} m/s", end: "Lane: the car stopped {0} m from the wall" },
|
|
646
|
+
lane: { crash: "Lane: the car hit the wall at {0} m/s", end: "Lane: the car stopped {0} m from the wall", damage: "Lane: the crash dented {0} and broke {1}" },
|
|
584
647
|
scenarios: {
|
|
585
648
|
vehicles_9mm_windshield: "Vehicles: 9 mm through the windshield",
|
|
586
649
|
vehicles_shotgun_door: "Vehicles: shotgun on the door",
|
|
@@ -629,25 +692,27 @@ var TEXT = {
|
|
|
629
692
|
group: "Ve\xEDculos",
|
|
630
693
|
name: "\xC1rea de ve\xEDculos",
|
|
631
694
|
items: { hatch: "Hatch compacto", sedan: "Sed\xE3 m\xE9dio", pickup: "Picape m\xE9dia", sport_bike: "Moto esportiva", box_truck: "Caminh\xE3o ba\xFA", city_bus: "\xD4nibus urbano", battle_tank: "Tanque de guerra", attack_helicopter: "Helic\xF3ptero de ataque", sedan_burnt: "Sed\xE3 queimado", hatch_burnt: "Hatch queimado" },
|
|
632
|
-
controls: { quality: "Qualidade", paint_by_seed: "Pintar pela semente", color_seed: "Semente da cor", palette: "Paleta", driving: "Dire\xE7\xE3o", surface: "Piso" },
|
|
695
|
+
controls: { quality: "Qualidade", paint_by_seed: "Pintar pela semente", color_seed: "Semente da cor", palette: "Paleta", driving: "Dire\xE7\xE3o", surface: "Piso", lane_damage: "Dano na pista" },
|
|
633
696
|
hints: {
|
|
634
697
|
quality: "Alta: o kit com todo o detalhe. Leve: menos segmentos e sem interior (o modo leve da frota); vale para os ve\xEDculos postos daqui em diante",
|
|
635
698
|
paint_by_seed: "Desligado: cada ve\xEDculo mant\xE9m a pr\xF3pria pintura. Ligado: a cor da carroceria vem da semente e da paleta",
|
|
636
699
|
palette: "Auto: a paleta do grupo do ve\xEDculo (civil, ou militar para os do ex\xE9rcito)",
|
|
637
700
|
driving: "Arcade: o carro do jogo, simples e tolerante. Semi: corpo r\xEDgido com molas, atrito do pneu, marchas e pisos",
|
|
638
|
-
surface: "O ch\xE3o sob as rodas no modo semi (ader\xEAncia e aquaplanagem)"
|
|
701
|
+
surface: "O ch\xE3o sob as rodas no modo semi (ader\xEAncia e aquaplanagem)",
|
|
702
|
+
lane_damage: "Com o que o carro da pista sai; ele anda no modo semi e o modelo de dano define pneus, freio e motor"
|
|
639
703
|
},
|
|
640
704
|
options: {
|
|
641
705
|
quality: { high: "Alta", lite: "Leve" },
|
|
642
706
|
palette: { auto: "Auto", civil: "Civil", military: "Militar" },
|
|
643
707
|
driving: { arcade: "Arcade", semi: "Semissimula\xE7\xE3o" },
|
|
644
|
-
surface: { dry_asphalt: "Asfalto seco", wet_asphalt: "Asfalto molhado", dirt: "Terra", grass: "Grama", mud: "Lama", blood: "Sangue" }
|
|
708
|
+
surface: { dry_asphalt: "Asfalto seco", wet_asphalt: "Asfalto molhado", dirt: "Terra", grass: "Grama", mud: "Lama", blood: "Sangue" },
|
|
709
|
+
lane_damage: { none: "Nenhum", into_wall: "Contra o muro, sem frear", flat_tyre: "Pneu furado (tiro de fuzil)", torn_tyre: "Pneu desfeito: aro no ch\xE3o", run_flat: "Blindado, pneu furado (run-flat)", no_brake_fluid: "Sem fluido de freio" }
|
|
645
710
|
},
|
|
646
711
|
actions: { repaint: "Repintar com a semente", drive_lane: "Percorrer a pista", clear_lane: "Tirar o carro da pista", honk: "Buzinar" },
|
|
647
712
|
layers: { damageables: "Caixas danific\xE1veis por material", zones: "Caixas das zonas por tipo", axes: "Eixos do quadro de sa\xEDda" },
|
|
648
713
|
metrics: { vehicles: "Ve\xEDculos", triangles: "Tri\xE2ngulos", draw_calls: "Chamadas de desenho", size_error_mm: "Medida contra DIMS", metal: "Caixas de metal", glass: "Caixas de vidro", rubber: "Caixas de borracha", fuel: "Caixas de combust\xEDvel", speed: "Velocidade na pista", lane: "Dist\xE2ncia na pista" },
|
|
649
714
|
words: { yes: "sim", no: "n\xE3o", open: "soltos", tight: "presos", on: "acesos", off: "apagados" },
|
|
650
|
-
lane: { crash: "Pista: o carro bateu no muro a {0} m/s", end: "Pista: o carro parou a {0} m do muro" },
|
|
715
|
+
lane: { crash: "Pista: o carro bateu no muro a {0} m/s", end: "Pista: o carro parou a {0} m do muro", damage: "Pista: a batida amassou {0} e quebrou {1}" },
|
|
651
716
|
scenarios: {
|
|
652
717
|
vehicles_9mm_windshield: "Ve\xEDculos: 9 mm atravessa o para-brisa",
|
|
653
718
|
vehicles_shotgun_door: "Ve\xEDculos: espingarda na porta",
|
|
@@ -910,6 +975,57 @@ var createScenarios = (deps) => {
|
|
|
910
975
|
// src/test-area/module.ts
|
|
911
976
|
import { LIBRARY_BANK } from "@adatechnology/game-audio";
|
|
912
977
|
|
|
978
|
+
// src/audio/profiles.ts
|
|
979
|
+
var DEFAULT_VEHICLE_SOUND_KEYS = {
|
|
980
|
+
start: "engine_start",
|
|
981
|
+
stop: "engine_stop",
|
|
982
|
+
fail: "engine_fail",
|
|
983
|
+
crashLight: "crash_light",
|
|
984
|
+
crashMedium: "crash_medium",
|
|
985
|
+
crashHeavy: "crash_heavy",
|
|
986
|
+
skidAsphalt: "tire_skid_asphalt",
|
|
987
|
+
skidDirt: "tire_skid_dirt",
|
|
988
|
+
roll: "tire_roll"
|
|
989
|
+
};
|
|
990
|
+
var CRASH_SPEEDS = { medium: 6, heavy: 14 };
|
|
991
|
+
var layers = (id, cutoffs, gains) => [
|
|
992
|
+
{ sound: `engine_${id}_low`, centre: 0, width: 0.5, harmonic: 1, gain: gains[0], cutoff: cutoffs[0] },
|
|
993
|
+
{ sound: `engine_${id}_mid`, centre: 0.45, width: 0.5, harmonic: 1, gain: gains[1], cutoff: cutoffs[1] },
|
|
994
|
+
{ sound: `engine_${id}_high`, centre: 1, width: 0.6, harmonic: 2, gain: gains[2], cutoff: cutoffs[2] }
|
|
995
|
+
];
|
|
996
|
+
var CAR_ENGINE = {
|
|
997
|
+
id: "car",
|
|
998
|
+
cylinders: 4,
|
|
999
|
+
idleRpm: 800,
|
|
1000
|
+
redlineRpm: 6500,
|
|
1001
|
+
// a 5-speed hatch: 1st tops at ~13 m/s, 5th at ~50 m/s
|
|
1002
|
+
rpmPerMps: [500, 300, 205, 155, 125],
|
|
1003
|
+
layers: layers("car", [[220, 520], [420, 1300], [900, 2600]], [0.11, 0.075, 0.04]),
|
|
1004
|
+
load: { sound: "engine_car_load", gain: 0.05, cutoff: [350, 1600] },
|
|
1005
|
+
closedThrottle: 0.55
|
|
1006
|
+
};
|
|
1007
|
+
var TRUCK_ENGINE = {
|
|
1008
|
+
id: "truck",
|
|
1009
|
+
cylinders: 6,
|
|
1010
|
+
idleRpm: 650,
|
|
1011
|
+
redlineRpm: 2600,
|
|
1012
|
+
rpmPerMps: [420, 260, 170, 120, 90, 72],
|
|
1013
|
+
layers: layers("truck", [[160, 380], [300, 800], [600, 1500]], [0.13, 0.08, 0.035]),
|
|
1014
|
+
load: { sound: "engine_truck_load", gain: 0.06, cutoff: [250, 900] },
|
|
1015
|
+
closedThrottle: 0.6
|
|
1016
|
+
};
|
|
1017
|
+
var BIKE_ENGINE = {
|
|
1018
|
+
id: "bike",
|
|
1019
|
+
cylinders: 4,
|
|
1020
|
+
idleRpm: 1300,
|
|
1021
|
+
redlineRpm: 12e3,
|
|
1022
|
+
rpmPerMps: [900, 640, 500, 410, 350, 310],
|
|
1023
|
+
layers: layers("bike", [[350, 900], [700, 2200], [1500, 4200]], [0.08, 0.07, 0.05]),
|
|
1024
|
+
load: { sound: "engine_bike_load", gain: 0.045, cutoff: [600, 2600] },
|
|
1025
|
+
closedThrottle: 0.5
|
|
1026
|
+
};
|
|
1027
|
+
var ENGINE_PROFILES = { car: CAR_ENGINE, truck: TRUCK_ENGINE, bike: BIKE_ENGINE };
|
|
1028
|
+
|
|
913
1029
|
// src/test-area/types.ts
|
|
914
1030
|
var defaultEffectIds = {
|
|
915
1031
|
ignite: "vehicle_fire",
|
|
@@ -920,20 +1036,240 @@ var defaultEffectIds = {
|
|
|
920
1036
|
leakIgnited: "vehicle_leak_ignited",
|
|
921
1037
|
exhaust: "vehicle_exhaust"
|
|
922
1038
|
};
|
|
923
|
-
var defaultSoundIds = {
|
|
1039
|
+
var defaultSoundIds = { horn: "horn_car", ...DEFAULT_VEHICLE_SOUND_KEYS };
|
|
924
1040
|
var DEFAULT_TEST_WRECKS = [
|
|
925
1041
|
{ key: "sedan_burnt", vehicle: "midsize_sedan", scenario: "burnt" },
|
|
926
1042
|
{ key: "hatch_burnt", vehicle: "compact_hatch", scenario: "burnt" }
|
|
927
1043
|
];
|
|
928
1044
|
|
|
1045
|
+
// src/audio/engine-mix.ts
|
|
1046
|
+
var clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
|
|
1047
|
+
var firingHz = (profile, rpm) => rpm / 60 * (profile.cylinders / 2);
|
|
1048
|
+
var rpmShare = (profile, rpm) => clamp01((rpm - profile.idleRpm) / (profile.redlineRpm - profile.idleRpm));
|
|
1049
|
+
var layerWeight = (centre, width, n) => {
|
|
1050
|
+
const d = Math.abs(n - centre) / width;
|
|
1051
|
+
return d >= 1 ? 0 : Math.cos(d * Math.PI / 2);
|
|
1052
|
+
};
|
|
1053
|
+
function engineMix(profile, input) {
|
|
1054
|
+
const throttle = clamp01(input.throttle);
|
|
1055
|
+
const on = input.rpm > 0;
|
|
1056
|
+
const n = rpmShare(profile, input.rpm);
|
|
1057
|
+
const level = on ? profile.closedThrottle + (1 - profile.closedThrottle) * throttle : 0;
|
|
1058
|
+
const open = 0.5 * n + 0.5 * throttle;
|
|
1059
|
+
const hz = firingHz(profile, input.rpm);
|
|
1060
|
+
const tones = profile.layers.map((layer) => ({
|
|
1061
|
+
sound: layer.sound,
|
|
1062
|
+
volume: layer.gain * level * layerWeight(layer.centre, layer.width, n),
|
|
1063
|
+
frequency: hz * layer.harmonic,
|
|
1064
|
+
cutoff: layer.cutoff[0] + (layer.cutoff[1] - layer.cutoff[0]) * open
|
|
1065
|
+
}));
|
|
1066
|
+
const load = profile.load;
|
|
1067
|
+
return [
|
|
1068
|
+
...tones,
|
|
1069
|
+
{ sound: load.sound, volume: on ? load.gain * (0.15 + 0.85 * throttle) * (0.35 + 0.65 * n) : 0, cutoff: load.cutoff[0] + (load.cutoff[1] - load.cutoff[0]) * open }
|
|
1070
|
+
];
|
|
1071
|
+
}
|
|
1072
|
+
function createGearGuess(profile) {
|
|
1073
|
+
let gear = 0;
|
|
1074
|
+
return {
|
|
1075
|
+
rpm(speed, throttle) {
|
|
1076
|
+
const v = Math.abs(speed);
|
|
1077
|
+
const ratios = profile.rpmPerMps;
|
|
1078
|
+
if (v < 0.3) {
|
|
1079
|
+
gear = 0;
|
|
1080
|
+
return profile.idleRpm + (profile.redlineRpm - profile.idleRpm) * 0.12 * clamp01(throttle);
|
|
1081
|
+
}
|
|
1082
|
+
const at = (g) => v * ratios[g];
|
|
1083
|
+
while (gear < ratios.length - 1 && at(gear) > profile.redlineRpm * 0.85) gear++;
|
|
1084
|
+
while (gear > 0 && at(gear) < profile.redlineRpm * 0.4) gear--;
|
|
1085
|
+
return Math.max(profile.idleRpm, Math.min(profile.redlineRpm, at(gear)));
|
|
1086
|
+
},
|
|
1087
|
+
get gear() {
|
|
1088
|
+
return gear;
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// src/audio/vehicle-sound.ts
|
|
1094
|
+
var clamp012 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
|
|
1095
|
+
function createVehicleSound(audio, options = {}) {
|
|
1096
|
+
const profile = options.profile ?? CAR_ENGINE;
|
|
1097
|
+
const keys = { ...DEFAULT_VEHICLE_SOUND_KEYS, ...options.sounds };
|
|
1098
|
+
const crankSeconds = options.crankSeconds ?? 0.75;
|
|
1099
|
+
const gears = createGearGuess(profile);
|
|
1100
|
+
const loops = /* @__PURE__ */ new Map();
|
|
1101
|
+
const levels = {};
|
|
1102
|
+
const state = { running: false, cranking: 0, catches: false, flare: 0, rpm: 0 };
|
|
1103
|
+
const loopOf = (sound) => {
|
|
1104
|
+
let handle = loops.get(sound);
|
|
1105
|
+
if (!handle && audio?.loop) {
|
|
1106
|
+
handle = audio.loop(sound);
|
|
1107
|
+
loops.set(sound, handle);
|
|
1108
|
+
}
|
|
1109
|
+
return handle;
|
|
1110
|
+
};
|
|
1111
|
+
const setLoop = (sound, volume, position, extra = {}) => {
|
|
1112
|
+
levels[sound] = volume;
|
|
1113
|
+
if (volume <= 0 && !loops.has(sound)) return;
|
|
1114
|
+
loopOf(sound)?.set({ volume, position, ...extra.frequency === void 0 ? {} : { frequency: extra.frequency }, ...extra.cutoff === void 0 ? {} : { params: { cutoff: extra.cutoff } } });
|
|
1115
|
+
};
|
|
1116
|
+
const play = (sound, position, volume = 1) => void audio?.play({ sound, position, volume });
|
|
1117
|
+
return {
|
|
1118
|
+
start(position, startOptions = {}) {
|
|
1119
|
+
if (state.running || state.cranking > 0) return;
|
|
1120
|
+
state.catches = !startOptions.fail;
|
|
1121
|
+
state.cranking = state.catches ? crankSeconds : 1.7;
|
|
1122
|
+
play(state.catches ? keys.start : keys.fail, position);
|
|
1123
|
+
},
|
|
1124
|
+
stop(position) {
|
|
1125
|
+
if (!state.running && state.cranking <= 0) return;
|
|
1126
|
+
state.running = false;
|
|
1127
|
+
state.cranking = 0;
|
|
1128
|
+
play(keys.stop, position);
|
|
1129
|
+
},
|
|
1130
|
+
crash(speed, position) {
|
|
1131
|
+
const v = Math.abs(speed);
|
|
1132
|
+
const kind = v >= CRASH_SPEEDS.heavy ? "heavy" : v >= CRASH_SPEEDS.medium ? "medium" : "light";
|
|
1133
|
+
play(kind === "heavy" ? keys.crashHeavy : kind === "medium" ? keys.crashMedium : keys.crashLight, position, Math.min(1, 0.35 + v / 20));
|
|
1134
|
+
return kind;
|
|
1135
|
+
},
|
|
1136
|
+
update(frame) {
|
|
1137
|
+
if (state.cranking > 0) {
|
|
1138
|
+
state.cranking -= frame.dt;
|
|
1139
|
+
if (state.cranking <= 0 && state.catches) {
|
|
1140
|
+
state.running = true;
|
|
1141
|
+
state.flare = 1;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
state.flare = Math.max(0, state.flare - frame.dt / 0.9);
|
|
1145
|
+
const throttle = clamp012(frame.throttle);
|
|
1146
|
+
const driven = frame.rpm ?? gears.rpm(frame.speed, throttle);
|
|
1147
|
+
const rpm = state.running ? Math.max(driven, profile.idleRpm * (1 + 1.2 * state.flare * state.flare)) : 0;
|
|
1148
|
+
state.rpm = rpm;
|
|
1149
|
+
for (const layer of engineMix(profile, { rpm, throttle: Math.max(throttle, state.flare * 0.6) })) {
|
|
1150
|
+
setLoop(layer.sound, layer.volume, frame.position, { ...layer.frequency === void 0 ? {} : { frequency: layer.frequency }, cutoff: layer.cutoff });
|
|
1151
|
+
}
|
|
1152
|
+
const speed = Math.abs(frame.speed);
|
|
1153
|
+
const slide = clamp012(((frame.slip ?? 0) - 0.15) / 0.5) * clamp012(speed / 4);
|
|
1154
|
+
const dirt = frame.surface === "dirt";
|
|
1155
|
+
setLoop(keys.skidAsphalt, dirt ? 0 : 0.16 * slide, frame.position, { cutoff: 1700 + 600 * slide });
|
|
1156
|
+
setLoop(keys.skidDirt, dirt ? 0.2 * slide : 0, frame.position, { cutoff: 500 + 500 * slide });
|
|
1157
|
+
setLoop(keys.roll, 0.06 * clamp012(speed / 25), frame.position, { cutoff: 250 + 45 * Math.min(speed, 40) });
|
|
1158
|
+
},
|
|
1159
|
+
get reading() {
|
|
1160
|
+
return { running: state.running, rpm: state.rpm, levels: { ...levels } };
|
|
1161
|
+
},
|
|
1162
|
+
dispose() {
|
|
1163
|
+
for (const handle of loops.values()) handle.stop();
|
|
1164
|
+
loops.clear();
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// src/audio/bank.ts
|
|
1170
|
+
import { sceneMany as many, sceneNoise as noise, sceneRecipe, sceneTone as tone } from "@adatechnology/game-audio";
|
|
1171
|
+
var VEHICLE_LOOP_FALLOFF = { model: "inverse_square", k: 4e-3, cutoffK: 0.01 };
|
|
1172
|
+
var SDK = "sdk: vehicle sounds (sons/checklist-sons.md 5.6)";
|
|
1173
|
+
var SMOOTHING = { volume: 0.06, frequency: 0.04 };
|
|
1174
|
+
var toneLoop = (id, wave, filter, cutoff, q, line) => ({
|
|
1175
|
+
id,
|
|
1176
|
+
bus: "world",
|
|
1177
|
+
order: "game_loop",
|
|
1178
|
+
source: { kind: "osc", wave, loop: true },
|
|
1179
|
+
filters: { mode: "serial", bank: [{ type: filter, frequency: { set: cutoff }, q }] },
|
|
1180
|
+
smoothing: SMOOTHING,
|
|
1181
|
+
falloff: VEHICLE_LOOP_FALLOFF,
|
|
1182
|
+
pan: true,
|
|
1183
|
+
sources: [SDK, line]
|
|
1184
|
+
});
|
|
1185
|
+
var noiseLoop = (id, filter, cutoff, q, line) => ({
|
|
1186
|
+
id,
|
|
1187
|
+
bus: "world",
|
|
1188
|
+
order: "game_loop",
|
|
1189
|
+
source: { kind: "noise", offset: "random", loop: true },
|
|
1190
|
+
filters: { mode: "serial", bank: [{ type: filter, frequency: { set: cutoff }, q }] },
|
|
1191
|
+
smoothing: SMOOTHING,
|
|
1192
|
+
falloff: VEHICLE_LOOP_FALLOFF,
|
|
1193
|
+
pan: true,
|
|
1194
|
+
sources: [SDK, line]
|
|
1195
|
+
});
|
|
1196
|
+
var engineLoops = (id, low, mid, high, load) => [
|
|
1197
|
+
toneLoop(`engine_${id}_low`, "sawtooth", "lowpass", low, 1.4, "idle and low RPM: the firing frequency, a saw through a low-pass"),
|
|
1198
|
+
toneLoop(`engine_${id}_mid`, "square", "lowpass", mid, 0.9, "mid RPM: odd harmonics, the bark"),
|
|
1199
|
+
toneLoop(`engine_${id}_high`, "sawtooth", "bandpass", high, 1.2, "high RPM: the second harmonic through a band-pass, the scream near the redline"),
|
|
1200
|
+
noiseLoop(`engine_${id}_load`, "bandpass", load, 0.8, "intake and exhaust roar, opened by the throttle")
|
|
1201
|
+
];
|
|
1202
|
+
var VEHICLE_LOOPS = [
|
|
1203
|
+
...engineLoops("car", 400, 900, 1600, 700),
|
|
1204
|
+
...engineLoops("truck", 280, 600, 1e3, 500),
|
|
1205
|
+
...engineLoops("bike", 600, 1400, 2800, 1200),
|
|
1206
|
+
noiseLoop("tire_skid_asphalt", "bandpass", 1900, 5, "rubber squealing on asphalt: a narrow band of noise near 2 kHz"),
|
|
1207
|
+
noiseLoop("tire_skid_dirt", "lowpass", 700, 0.8, "tyres sliding on dirt and gravel: a dull, wide hiss"),
|
|
1208
|
+
noiseLoop("tire_roll", "lowpass", 400, 0.7, "road noise of the tyres rolling, brighter with the speed")
|
|
1209
|
+
];
|
|
1210
|
+
var recipe = (id, layers2, line) => ({ ...sceneRecipe(id, layers2, line), sources: [SDK, line] });
|
|
1211
|
+
var crank = (count2, spacing) => [
|
|
1212
|
+
{ ...noise({ type: "bandpass", f: [300, 380], q: 1.5, d: 0.07, vol: 0.45 }), repeat: { count: count2, spacing } },
|
|
1213
|
+
tone({ wave: "sawtooth", f: 95, f2: count2 > 8 ? 70 : 110, d: count2 * spacing, vol: 0.035 })
|
|
1214
|
+
];
|
|
1215
|
+
var latch = (delay, vol) => [
|
|
1216
|
+
noise({ type: "bandpass", f: [2600, 3200], q: 3, d: 0.018, vol, delay }),
|
|
1217
|
+
tone({ f: 1300, f2: 1180, d: 0.025, vol: vol * 0.15, delay: delay + 4e-3 })
|
|
1218
|
+
];
|
|
1219
|
+
var VEHICLE_SOUNDS = [
|
|
1220
|
+
// the engine catches at 0.75 s: the loops take over from there (the vehicle flares the RPM)
|
|
1221
|
+
recipe("engine_start", [...crank(7, 0.105), tone({ wave: "sawtooth", f: 45, f2: 27, d: 0.35, vol: 0.3, delay: 0.72 }), noise({ f: 260, d: 0.3, vol: 0.35, delay: 0.72 })], "starter cranking, then the engine catches"),
|
|
1222
|
+
recipe("engine_fail", [...crank(13, 0.125), ...latch(1.65, 0.2)], "starter cranking slower and slower, the engine never catches, the key released"),
|
|
1223
|
+
recipe("engine_stop", [tone({ wave: "sawtooth", f: 27, f2: 8, d: 0.45, vol: 0.3 }), noise({ f: 200, f2: 60, d: 0.4, vol: 0.32 }), noise({ type: "bandpass", f: 120, q: 2, d: 0.12, vol: 0.3, delay: 0.32 })], "ignition off: the engine runs down and shudders to a stop"),
|
|
1224
|
+
// light: a dent and plastic giving; medium: the body folding and a pane cracking; heavy: metal folding, glass going, debris falling
|
|
1225
|
+
recipe(
|
|
1226
|
+
"crash_light",
|
|
1227
|
+
[tone({ f: 110, f2: 55, d: 0.16, vol: 0.7 }), noise({ type: "bandpass", f: 1100, q: 1, d: 0.18, vol: 0.85, wet: 0.2 }), noise({ type: "highpass", f: 2500, d: 0.05, vol: 0.45 }), ...many(3, () => noise({ type: "bandpass", f: [1800, 3200], q: 3, d: 0.03, vol: 0.2, delay: [0.03, 0.2] }))],
|
|
1228
|
+
"crash below 22 km/h: bumper and plastic, a dull dent"
|
|
1229
|
+
),
|
|
1230
|
+
recipe(
|
|
1231
|
+
"crash_medium",
|
|
1232
|
+
[
|
|
1233
|
+
tone({ f: 85, f2: 38, d: 0.3, vol: 1.1 }),
|
|
1234
|
+
noise({ type: "bandpass", f: 700, q: 0.9, d: 0.5, vol: 0.8, wet: 0.3 }),
|
|
1235
|
+
...many(5, () => noise({ type: "bandpass", f: [500, 1300], q: 2, d: [0.08, 0.18], vol: 0.55, delay: [0, 0.35] })),
|
|
1236
|
+
...many(6, () => tone({ f: [3500, 7e3], d: 0.06, vol: 0.08, delay: [0.05, 0.5] }))
|
|
1237
|
+
],
|
|
1238
|
+
"crash at 22\u201350 km/h: the body folding, a pane cracking"
|
|
1239
|
+
),
|
|
1240
|
+
recipe(
|
|
1241
|
+
"crash_heavy",
|
|
1242
|
+
[
|
|
1243
|
+
tone({ f: 65, f2: 28, d: 0.6, vol: 1.5 }),
|
|
1244
|
+
noise({ f: 900, f2: 150, d: 0.9, vol: 1.3, wet: 0.5 }),
|
|
1245
|
+
...many(9, () => noise({ type: "bandpass", f: [300, 1200], q: 2, d: [0.1, 0.25], vol: 0.6, delay: [0, 0.7] })),
|
|
1246
|
+
noise({ type: "highpass", f: 3200, d: 0.5, vol: 0.7, delay: 0.03, wet: 0.4 }),
|
|
1247
|
+
...many(14, () => tone({ f: [3e3, 7500], d: [0.05, 0.12], vol: 0.12, delay: [0.05, 1.2] })),
|
|
1248
|
+
...many(6, () => noise({ type: "bandpass", f: [1500, 3e3], q: 2, d: 0.04, vol: 0.25, delay: [0.5, 1.5] }))
|
|
1249
|
+
],
|
|
1250
|
+
"crash above 50 km/h: metal folding, the glass going, debris falling"
|
|
1251
|
+
),
|
|
1252
|
+
// 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
|
|
1253
|
+
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"),
|
|
1254
|
+
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"),
|
|
1255
|
+
// a car door is a padded "tum" with a latch, not a hollow metal box (the fridge's shut_metal)
|
|
1256
|
+
recipe("car_door_open", [...latch(0, 0.32), noise({ f: 400, d: 0.06, vol: 0.14, delay: 0.035 })], "car door: the handle pulled, the latch released, the seal letting go"),
|
|
1257
|
+
recipe("car_door_close", [tone({ f: 75, f2: 48, d: 0.16, vol: 0.75 }), noise({ f: 320, d: 0.1, vol: 0.6 }), ...latch(0.012, 0.3)], "car door shut: a dull padded thump and the latch catching"),
|
|
1258
|
+
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"),
|
|
1259
|
+
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")
|
|
1260
|
+
];
|
|
1261
|
+
var VEHICLE_SOUND_BANK = { id: "vehicles_three", sounds: VEHICLE_SOUNDS, loops: VEHICLE_LOOPS };
|
|
1262
|
+
|
|
929
1263
|
// src/test-area/module.ts
|
|
930
1264
|
var VEHICLES_AREA_ID = "vehicles_area";
|
|
931
1265
|
var DEFAULT_TEST_VEHICLES = ["compact_hatch", "midsize_sedan", "midsize_pickup", "sport_bike", "box_truck", "city_bus", "battle_tank", "attack_helicopter"];
|
|
932
1266
|
var ITEM_OF = { compact_hatch: "hatch", midsize_sedan: "sedan", midsize_pickup: "pickup", sport_bike: "sport_bike", box_truck: "box_truck", city_bus: "city_bus", battle_tank: "battle_tank", attack_helicopter: "attack_helicopter" };
|
|
933
1267
|
var itemId = (key) => ITEM_OF[key] ?? key;
|
|
934
1268
|
var LANE = { z: 11, from: -12, wall: 13 };
|
|
1269
|
+
var SPARK_EVERY_S = 0.08;
|
|
935
1270
|
var LEAF_REST_RADIANS = 0.02;
|
|
936
1271
|
var MATERIAL_COLOURS = { metal: 13421772, glass: 4500223, rubber: 16750848, fuel: 16724736 };
|
|
1272
|
+
var LANE_START_S = 1.2;
|
|
937
1273
|
var ZONE_COLOURS = { door: 5627221, hood: 14540083, lid: 14522675, bumper: 14505437, glass: 4500223, light: 16777215, wheel: 16742195 };
|
|
938
1274
|
var BODY_HEIGHT = 1.7;
|
|
939
1275
|
function createVehiclesTestModule(options = {}) {
|
|
@@ -945,7 +1281,7 @@ function createVehiclesTestModule(options = {}) {
|
|
|
945
1281
|
const entries = [...keys.map((key) => ({ key, id: itemId(key) })), ...wrecks.map((w) => ({ key: w.vehicle, id: w.key, scenario: w.scenario }))];
|
|
946
1282
|
const effectIds = { ...defaultEffectIds, ...options.effects };
|
|
947
1283
|
const soundIds = { ...defaultSoundIds, ...options.sounds };
|
|
948
|
-
const settings = { quality: "high", paint: false, seed: 1, palette: "auto", driving: "arcade", surface: "dry_asphalt" };
|
|
1284
|
+
const settings = { quality: "high", paint: false, seed: 1, palette: "auto", driving: "arcade", surface: "dry_asphalt", damage: "none" };
|
|
949
1285
|
const kits = /* @__PURE__ */ new WeakMap();
|
|
950
1286
|
const placed = /* @__PURE__ */ new Map();
|
|
951
1287
|
const cabins = createCabinSmoke();
|
|
@@ -957,10 +1293,10 @@ function createVehiclesTestModule(options = {}) {
|
|
|
957
1293
|
let lane = null;
|
|
958
1294
|
const effects = () => host?.effects;
|
|
959
1295
|
const audio = () => host?.audio;
|
|
960
|
-
let
|
|
1296
|
+
let carSound;
|
|
961
1297
|
const stopEngine = () => {
|
|
962
|
-
|
|
963
|
-
|
|
1298
|
+
carSound?.dispose();
|
|
1299
|
+
carSound = void 0;
|
|
964
1300
|
};
|
|
965
1301
|
const particleMaterials = {
|
|
966
1302
|
own: (build) => isRenderPools(host?.pools) ? host.pools.materials.own(build) : build(),
|
|
@@ -1017,7 +1353,7 @@ function createVehiclesTestModule(options = {}) {
|
|
|
1017
1353
|
impl: (root, id) => {
|
|
1018
1354
|
const r = root;
|
|
1019
1355
|
const kit = r.userData.kit;
|
|
1020
|
-
const d =
|
|
1356
|
+
const d = createVehicleDestructible2(kit, r.userData.vehicle, { id, frame: root, name: text.items[itemKey] ?? key, creaseQueue });
|
|
1021
1357
|
tapEffects(d);
|
|
1022
1358
|
const setPartPose = d.setPartPose?.bind(d);
|
|
1023
1359
|
{
|
|
@@ -1053,7 +1389,7 @@ function createVehiclesTestModule(options = {}) {
|
|
|
1053
1389
|
if (!clipCache.has(key)) {
|
|
1054
1390
|
const kit = kitFor(THREE);
|
|
1055
1391
|
const built = buildVehicleItem(kit, key);
|
|
1056
|
-
const d =
|
|
1392
|
+
const d = createVehicleDestructible2(kit, built.vehicle, { id: `${itemId(key)}_clips`, frame: built.root, headless: true });
|
|
1057
1393
|
clipCache.set(key, d.animations());
|
|
1058
1394
|
kit.dispose(built.vehicle);
|
|
1059
1395
|
}
|
|
@@ -1181,15 +1517,16 @@ function createVehiclesTestModule(options = {}) {
|
|
|
1181
1517
|
clearLane();
|
|
1182
1518
|
const kit = kitFor(three);
|
|
1183
1519
|
const vehicles = createVehiclesThree(three, { kit, fleet });
|
|
1184
|
-
const built = vehicles.build("compact_hatch", { quality: "lite", seed: settings.seed });
|
|
1520
|
+
const built = vehicles.build("compact_hatch", { quality: settings.damage === "none" ? "lite" : "high", seed: settings.seed });
|
|
1185
1521
|
const wall = new three.Mesh(new three.BoxGeometry(0.6, 2, 6), new three.MeshStandardMaterial({ color: 7829367 }));
|
|
1186
1522
|
wall.position.set(LANE.wall + 0.3, 1, LANE.z);
|
|
1187
1523
|
const scene = host.scene;
|
|
1188
1524
|
scene.add(built.group, wall);
|
|
1189
|
-
const mode = settings.driving === "semi" ? "semi" : "arcade";
|
|
1525
|
+
const mode = settings.driving === "semi" || settings.damage !== "none" ? "semi" : "arcade";
|
|
1526
|
+
const damage = settings.damage === "none" || !built.context ? null : createLaneDamage({ kit, context: built.context, frame: built.group, damage: settings.damage });
|
|
1190
1527
|
const surfaces = createSurfaces2();
|
|
1191
1528
|
const idle = { throttle: 0, brake: 0, steer: 0, handbrake: false };
|
|
1192
|
-
const state = { time: 0, exhaust: 0, ended: false, wasBlocked: false };
|
|
1529
|
+
const state = { time: 0, exhaust: 0, sparks: 0, ended: false, wasBlocked: false };
|
|
1193
1530
|
const arcade = createArcadeDriver2({ length: built.length, width: built.width });
|
|
1194
1531
|
const semi = createSemiSimDriver2({}, { surfaces });
|
|
1195
1532
|
const driver = mode === "semi" ? semi : arcade;
|
|
@@ -1202,7 +1539,8 @@ function createVehiclesTestModule(options = {}) {
|
|
|
1202
1539
|
built.group.rotation.y = driver.state.yaw + Math.PI / 2;
|
|
1203
1540
|
};
|
|
1204
1541
|
pose();
|
|
1205
|
-
|
|
1542
|
+
carSound = createVehicleSound(audio(), { sounds: soundIds });
|
|
1543
|
+
carSound.start([driver.state.x, 0.5, driver.state.z]);
|
|
1206
1544
|
const log = (key, value) => host?.log(fill(text, locale, text.lane[key], value.toFixed(1)));
|
|
1207
1545
|
lane = {
|
|
1208
1546
|
car: built,
|
|
@@ -1214,29 +1552,56 @@ function createVehiclesTestModule(options = {}) {
|
|
|
1214
1552
|
speed: () => "u" in driver.state ? driver.state.u : driver.state.v,
|
|
1215
1553
|
distance: () => driver.state.x - LANE.from,
|
|
1216
1554
|
step(dt) {
|
|
1217
|
-
if (state.ended)
|
|
1555
|
+
if (state.ended) {
|
|
1556
|
+
damage?.settle(dt);
|
|
1557
|
+
const at = [driver.state.x, 0.5, driver.state.z];
|
|
1558
|
+
if (carSound?.reading.running) carSound.stop(at);
|
|
1559
|
+
carSound?.update({ dt, position: at, speed: 0, throttle: 0 });
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1218
1562
|
state.time += dt;
|
|
1219
|
-
const braking = gap() < brakeAt() && state.time > 0.5;
|
|
1220
|
-
const
|
|
1563
|
+
const braking = settings.damage !== "into_wall" && gap() < brakeAt() && state.time > 0.5;
|
|
1564
|
+
const moving = state.time > LANE_START_S;
|
|
1565
|
+
const input = { ...idle, throttle: braking || !moving ? 0 : 1, brake: braking ? 1 : 0, steer: settings.damage === "into_wall" || !moving ? 0 : Math.sin(state.time * 1.4) * 0.15 };
|
|
1221
1566
|
if (mode === "arcade") {
|
|
1222
1567
|
const { events } = arcade.step(input, dt, { blocked: (x, z, yaw) => vehicleCorners2(arcade.spec, x, z, yaw).some(([px]) => px >= LANE.wall) });
|
|
1223
1568
|
for (const e of events) {
|
|
1224
1569
|
if (e.type !== "crash") continue;
|
|
1225
1570
|
log("crash", e.speed);
|
|
1226
|
-
|
|
1571
|
+
carSound?.crash(e.speed, [driver.state.x + built.length / 2, 0.5, driver.state.z]);
|
|
1227
1572
|
}
|
|
1228
1573
|
} else {
|
|
1229
1574
|
semi.step(input, dt, { ground: () => ({ surface: settings.surface }) });
|
|
1575
|
+
const grinding = damage?.step({ dt, speed: Math.abs(lane?.speed() ?? 0), throttle: input.throttle, driver: semi }) ?? [];
|
|
1576
|
+
state.sparks -= dt;
|
|
1577
|
+
const fx2 = effects();
|
|
1578
|
+
if (fx2 && state.sparks <= 0 && Math.abs(lane?.speed() ?? 0) > 0.5) {
|
|
1579
|
+
state.sparks = grinding.length ? SPARK_EVERY_S : 0;
|
|
1580
|
+
for (const point of grinding) fx2.play({ effect: effectIds.sparks, point });
|
|
1581
|
+
}
|
|
1230
1582
|
if (gap() <= 0) {
|
|
1231
1583
|
state.ended = true;
|
|
1584
|
+
const crashed = damage?.crash({ speed: Math.abs(semi.state.u), direction: [Math.cos(driver.state.yaw), 0, -Math.sin(driver.state.yaw)] });
|
|
1585
|
+
if (crashed) {
|
|
1586
|
+
const parts = (type) => [...new Set(crashed.flatMap((e) => e.type === type && "part" in e ? [e.part] : []))].join(", ") || "-";
|
|
1587
|
+
host?.log(fill(text, locale, text.lane.damage, parts("dent"), parts("fracture")));
|
|
1588
|
+
}
|
|
1232
1589
|
log("crash", Math.abs(semi.state.u));
|
|
1233
|
-
|
|
1590
|
+
carSound?.crash(semi.state.u, [driver.state.x + built.length / 2, 0.5, driver.state.z]);
|
|
1234
1591
|
}
|
|
1235
1592
|
}
|
|
1236
1593
|
pose();
|
|
1237
1594
|
{
|
|
1238
1595
|
const speed = Math.abs(lane?.speed() ?? 0);
|
|
1239
|
-
|
|
1596
|
+
const semiState = mode === "semi" ? semi.state : null;
|
|
1597
|
+
carSound?.update({
|
|
1598
|
+
dt,
|
|
1599
|
+
position: [driver.state.x, 0.5, driver.state.z],
|
|
1600
|
+
speed,
|
|
1601
|
+
throttle: input.throttle,
|
|
1602
|
+
...semiState ? { rpm: semiState.rpm, slip: Math.max(0, ...semiState.slip.map(Math.abs)) } : { slip: braking && speed > 3 ? 0.6 : 0 },
|
|
1603
|
+
surface: /dirt|gravel|sand|grass|mud/.test(settings.surface) ? "dirt" : "asphalt"
|
|
1604
|
+
});
|
|
1240
1605
|
}
|
|
1241
1606
|
state.exhaust -= dt;
|
|
1242
1607
|
const fx = effects();
|
|
@@ -1244,7 +1609,7 @@ function createVehiclesTestModule(options = {}) {
|
|
|
1244
1609
|
state.exhaust = 0.4;
|
|
1245
1610
|
fx.play({ effect: effectIds.exhaust, point: [driver.state.x - Math.cos(driver.state.yaw) * (built.length / 2), 0.3, driver.state.z + Math.sin(driver.state.yaw) * (built.length / 2)], scale: Math.min(1, Math.abs(lane?.speed() ?? 0) / 17) });
|
|
1246
1611
|
}
|
|
1247
|
-
if (!state.ended && state.time > 1 && Math.abs(lane?.speed() ?? 0) < 0.05) {
|
|
1612
|
+
if (!state.ended && state.time > LANE_START_S + 1 && Math.abs(lane?.speed() ?? 0) < 0.05) {
|
|
1248
1613
|
state.ended = true;
|
|
1249
1614
|
log("end", Math.max(0, gap()));
|
|
1250
1615
|
}
|
|
@@ -1282,6 +1647,9 @@ function createVehiclesTestModule(options = {}) {
|
|
|
1282
1647
|
case "surface":
|
|
1283
1648
|
settings.surface = String(change.value);
|
|
1284
1649
|
break;
|
|
1650
|
+
case "lane_damage":
|
|
1651
|
+
settings.damage = String(change.value);
|
|
1652
|
+
break;
|
|
1285
1653
|
default:
|
|
1286
1654
|
break;
|
|
1287
1655
|
}
|
|
@@ -1377,6 +1745,7 @@ function createVehiclesTestModule(options = {}) {
|
|
|
1377
1745
|
{ kind: "choice", id: "palette", name: text.controls.palette, hint: text.hints.palette, options: ["auto", "civil", "military"].map((id) => ({ id, name: text.options.palette[id] })), default: "auto" },
|
|
1378
1746
|
{ kind: "choice", id: "driving", name: text.controls.driving, hint: text.hints.driving, options: ["arcade", "semi"].map((id) => ({ id, name: text.options.driving[id] })), default: "arcade" },
|
|
1379
1747
|
{ kind: "choice", id: "surface", name: text.controls.surface, hint: text.hints.surface, options: surfaceIds.map((id) => ({ id, name: text.options.surface[id] ?? id })), default: "dry_asphalt" },
|
|
1748
|
+
{ kind: "choice", id: "lane_damage", name: text.controls.lane_damage, hint: text.hints.lane_damage, options: LANE_DAMAGE.map((id) => ({ id, name: text.options.lane_damage[id] ?? id })), default: "none" },
|
|
1380
1749
|
{ kind: "action", id: "repaint", name: text.actions.repaint },
|
|
1381
1750
|
{ kind: "action", id: "drive_lane", name: text.actions.drive_lane },
|
|
1382
1751
|
{ kind: "action", id: "clear_lane", name: text.actions.clear_lane },
|
|
@@ -1406,6 +1775,7 @@ function createVehiclesTestModule(options = {}) {
|
|
|
1406
1775
|
const stopCrease = lent.defer?.(creaseQueue.deferredQueue(), DEFERRED_PRIORITY[DEFERRED_QUEUE_ID.VEHICLE_CREASE]);
|
|
1407
1776
|
if (isRenderPools(lent.pools)) lent.pools.materials.register("vehicles.cracked-glass", () => crackedGlassMaterial(three, new three.DataTexture(new Uint8Array(4), 1, 1)));
|
|
1408
1777
|
lent.audio?.register?.(LIBRARY_BANK);
|
|
1778
|
+
lent.audio?.register?.(VEHICLE_SOUND_BANK);
|
|
1409
1779
|
return () => {
|
|
1410
1780
|
stopChange?.();
|
|
1411
1781
|
stopFrame();
|
|
@@ -1430,6 +1800,589 @@ function loadVehiclesTestModule(room, options = {}) {
|
|
|
1430
1800
|
return room.loadModule(createVehiclesTestModule({ ...options, three }));
|
|
1431
1801
|
}
|
|
1432
1802
|
|
|
1803
|
+
// src/test-area/proving/module.ts
|
|
1804
|
+
import { createVehicleCamera, mirrorPoses } from "@adatechnology/game-camera";
|
|
1805
|
+
import { markKitOrigin as markKitOrigin3 } from "@adatechnology/game-contracts";
|
|
1806
|
+
import { LIBRARY_BANK as LIBRARY_BANK2 } from "@adatechnology/game-audio";
|
|
1807
|
+
import { createSemiSimDriver as createSemiSimDriver3 } from "@adatechnology/game-vehicles-driving";
|
|
1808
|
+
import { certify, createProvingTests, startRun, statusOf, subjectSpec, withResult } from "@adatechnology/game-vehicles-proving";
|
|
1809
|
+
import { discardObject3D as discardObject3D2 } from "@adatechnology/game-shapes-three";
|
|
1810
|
+
|
|
1811
|
+
// src/test-area/proving/text.ts
|
|
1812
|
+
var PROVING_TEXT = {
|
|
1813
|
+
"pt-BR": {
|
|
1814
|
+
name: "Banco de provas",
|
|
1815
|
+
controls: { vehicle: "Ve\xEDculo", test: "Prova", camera: "C\xE2mera", time_scale: "Velocidade da simula\xE7\xE3o", visual_check: "Checagem visual" },
|
|
1816
|
+
hints: {
|
|
1817
|
+
vehicle: "Ve\xEDculo da frota que vai para a pista",
|
|
1818
|
+
test: "Prova medida que o bot\xE3o Rodar prova executa",
|
|
1819
|
+
camera: "Sala: a c\xE2mera do jogador. Persegui\xE7\xE3o, cabine e cap\xF4 seguem o ve\xEDculo",
|
|
1820
|
+
time_scale: "Quantas vezes mais r\xE1pido que o tempo real a prova roda",
|
|
1821
|
+
visual_check: "O que voc\xEA olhou e vai aprovar ou reprovar"
|
|
1822
|
+
},
|
|
1823
|
+
actions: { run_test: "Rodar prova", run_all: "Rodar todas na pista", certify_now: "Homologar agora (sem anima\xE7\xE3o)", reverse_park: "Dar r\xE9 (c\xE2mera de r\xE9)", sign_ok: "Aprovar checagem visual", sign_fail: "Reprovar checagem visual", reset: "Limpar ficha" },
|
|
1824
|
+
layers: { mirrors: "Retrovisores", reverse_camera: "C\xE2mera de r\xE9", scoreboard: "Painel da ficha" },
|
|
1825
|
+
metrics: { speed: "Velocidade", time: "Tempo da prova", value: "Medi\xE7\xE3o", passed: "Aprovadas", failed: "Reprovadas", pending: "Pendentes", status: "Situa\xE7\xE3o (0 pend., 1 aprov., -1 reprov.)" },
|
|
1826
|
+
cameras: { room: "Sala", chase: "Persegui\xE7\xE3o (3\xAA pessoa)", cockpit: "Cabine (1\xAA pessoa)", hood: "Cap\xF4" },
|
|
1827
|
+
tests: {
|
|
1828
|
+
accel_0_100: "0 a 100 km/h",
|
|
1829
|
+
top_speed: "Velocidade m\xE1xima",
|
|
1830
|
+
brake_100_0: "Frenagem 100 a 0",
|
|
1831
|
+
turn_circle: "Di\xE2metro de giro",
|
|
1832
|
+
wet_brake_80_0: "Frenagem molhada 80 a 0",
|
|
1833
|
+
mud_traction: "Tra\xE7\xE3o na lama",
|
|
1834
|
+
cabin: "Cabine e personagem",
|
|
1835
|
+
cameras: "C\xE2meras",
|
|
1836
|
+
mirrors: "Retrovisores e r\xE9",
|
|
1837
|
+
terrain: "Terrenos",
|
|
1838
|
+
wind_tunnel: "T\xFAnel de vento",
|
|
1839
|
+
crash_test: "Crash test",
|
|
1840
|
+
pedestrian: "Atropelamento",
|
|
1841
|
+
performance: "Desempenho",
|
|
1842
|
+
buoyancy: "Flutua\xE7\xE3o",
|
|
1843
|
+
flight: "Voo"
|
|
1844
|
+
},
|
|
1845
|
+
status: { approved: "APROVADO", rejected: "REPROVADO", pending: "PENDENTE" },
|
|
1846
|
+
verdict: { pass: "ok", fail: "falhou", pending: "\u2026" },
|
|
1847
|
+
board: { title: "Ficha de homologa\xE7\xE3o", test: "Prova", value: "Medido", range: "Faixa", verdict: "Resultado", notRun: "n\xE3o rodada", running: "rodando" },
|
|
1848
|
+
log: { start: "Prova {0}: {1}", result: "{0} \xB7 {1}: {2} {3} (faixa {4}) \u2192 {5}", signed: "{0} \xB7 {1}: {2} na sala", certified: "{0}: {1}" }
|
|
1849
|
+
},
|
|
1850
|
+
en: {
|
|
1851
|
+
name: "Proving ground",
|
|
1852
|
+
controls: { vehicle: "Vehicle", test: "Test", camera: "Camera", time_scale: "Simulation speed", visual_check: "Visual check" },
|
|
1853
|
+
hints: {
|
|
1854
|
+
vehicle: "Fleet vehicle that goes on the track",
|
|
1855
|
+
test: "Measured test the Run test button runs",
|
|
1856
|
+
camera: "Room: the player camera. Chase, cockpit and hood follow the vehicle",
|
|
1857
|
+
time_scale: "How many times faster than real time the test runs",
|
|
1858
|
+
visual_check: "What you looked at and will approve or refuse"
|
|
1859
|
+
},
|
|
1860
|
+
actions: { run_test: "Run test", run_all: "Run all on the track", certify_now: "Certify now (no animation)", reverse_park: "Reverse (reversing camera)", sign_ok: "Approve visual check", sign_fail: "Refuse visual check", reset: "Clear certificate" },
|
|
1861
|
+
layers: { mirrors: "Mirrors", reverse_camera: "Reversing camera", scoreboard: "Certificate board" },
|
|
1862
|
+
metrics: { speed: "Speed", time: "Test time", value: "Measured", passed: "Passed", failed: "Failed", pending: "Pending", status: "Status (0 pending, 1 approved, -1 rejected)" },
|
|
1863
|
+
cameras: { room: "Room", chase: "Chase (third person)", cockpit: "Cockpit (first person)", hood: "Hood" },
|
|
1864
|
+
tests: {
|
|
1865
|
+
accel_0_100: "0 to 100 km/h",
|
|
1866
|
+
top_speed: "Top speed",
|
|
1867
|
+
brake_100_0: "Braking 100 to 0",
|
|
1868
|
+
turn_circle: "Turning circle",
|
|
1869
|
+
wet_brake_80_0: "Wet braking 80 to 0",
|
|
1870
|
+
mud_traction: "Mud traction",
|
|
1871
|
+
cabin: "Cabin and character",
|
|
1872
|
+
cameras: "Cameras",
|
|
1873
|
+
mirrors: "Mirrors and reversing",
|
|
1874
|
+
terrain: "Terrain",
|
|
1875
|
+
wind_tunnel: "Wind tunnel",
|
|
1876
|
+
crash_test: "Crash test",
|
|
1877
|
+
pedestrian: "Pedestrian impact",
|
|
1878
|
+
performance: "Performance",
|
|
1879
|
+
buoyancy: "Buoyancy",
|
|
1880
|
+
flight: "Flight"
|
|
1881
|
+
},
|
|
1882
|
+
status: { approved: "APPROVED", rejected: "REJECTED", pending: "PENDING" },
|
|
1883
|
+
verdict: { pass: "ok", fail: "failed", pending: "\u2026" },
|
|
1884
|
+
board: { title: "Certificate", test: "Test", value: "Measured", range: "Range", verdict: "Result", notRun: "not run", running: "running" },
|
|
1885
|
+
log: { start: "Test {0}: {1}", result: "{0} \xB7 {1}: {2} {3} (range {4}) \u2192 {5}", signed: "{0} \xB7 {1}: {2} in the room", certified: "{0}: {1}" }
|
|
1886
|
+
}
|
|
1887
|
+
};
|
|
1888
|
+
var fillText = (template, ...values) => template.replace(/\{(\d+)\}/g, (_, i) => String(values[Number(i)] ?? ""));
|
|
1889
|
+
|
|
1890
|
+
// src/test-area/proving/module.ts
|
|
1891
|
+
var PROVING_AREA_ID = "vehicles_proving";
|
|
1892
|
+
var TRACK = { x0: -30, z: 22, tile: 260, width: 14, start: 4 };
|
|
1893
|
+
var SURFACE_LOOK = { dry_asphalt: { color: 2829617, roughness: 0.95 }, wet_asphalt: { color: 1842980, roughness: 0.18 }, mud: { color: 4863268, roughness: 1 } };
|
|
1894
|
+
var VISUAL_CHECKS = ["cabin", "cameras", "mirrors"];
|
|
1895
|
+
var MIRROR_RECTS = {
|
|
1896
|
+
rear_view: { x: 0.38, y: 0.02, width: 0.24, height: 0.09, rounded: 0.35 },
|
|
1897
|
+
left: { x: 0.02, y: 0.72, width: 0.19, height: 0.13, rounded: 0.3 },
|
|
1898
|
+
right: { x: 0.79, y: 0.72, width: 0.19, height: 0.13, rounded: 0.3 },
|
|
1899
|
+
reverse: { x: 0.36, y: 0.13, width: 0.28, height: 0.18, rounded: 0.06 }
|
|
1900
|
+
};
|
|
1901
|
+
function createProvingModule(options) {
|
|
1902
|
+
const locale = options.locale ?? "pt-BR";
|
|
1903
|
+
const text = PROVING_TEXT[locale] ?? PROVING_TEXT["pt-BR"];
|
|
1904
|
+
const tests = options.tests ?? createProvingTests();
|
|
1905
|
+
const vehicles = options.vehicles;
|
|
1906
|
+
const sounds = { ...defaultSoundIds, ...options.sounds };
|
|
1907
|
+
const measured = tests.tests.filter((t) => t.kind === "measured").map((t) => t.id);
|
|
1908
|
+
const settings = { vehicle: vehicles.includes("compact_hatch") ? "compact_hatch" : vehicles[0] ?? "compact_hatch", test: measured[0] ?? "accel_0_100", camera: "chase", timeScale: 4, visual: "cameras" };
|
|
1909
|
+
const certificates = /* @__PURE__ */ new Map();
|
|
1910
|
+
const kits = /* @__PURE__ */ new WeakMap();
|
|
1911
|
+
let host = null;
|
|
1912
|
+
let three = null;
|
|
1913
|
+
let track = null;
|
|
1914
|
+
let strip = null;
|
|
1915
|
+
let board = null;
|
|
1916
|
+
let car = null;
|
|
1917
|
+
let run = null;
|
|
1918
|
+
let parking = null;
|
|
1919
|
+
let queue = [];
|
|
1920
|
+
let open = false;
|
|
1921
|
+
let lastWrap = 0;
|
|
1922
|
+
let engine;
|
|
1923
|
+
const rig = { camera: null, vehicle: null, mirrors: [] };
|
|
1924
|
+
const name = (id) => options.nameOf?.(id) ?? id.replace(/_/g, " ");
|
|
1925
|
+
const kitFor = (THREE) => {
|
|
1926
|
+
let kit = kits.get(THREE);
|
|
1927
|
+
if (!kit) {
|
|
1928
|
+
kit = options.createKit(THREE);
|
|
1929
|
+
kits.set(THREE, kit);
|
|
1930
|
+
}
|
|
1931
|
+
return kit;
|
|
1932
|
+
};
|
|
1933
|
+
const subjectOf = (id) => {
|
|
1934
|
+
const mass = three ? kitFor(three).find(id)?.mass : void 0;
|
|
1935
|
+
return options.subject(id, mass);
|
|
1936
|
+
};
|
|
1937
|
+
const blank = (subject) => {
|
|
1938
|
+
const results = tests.forDomain(subject.domain).map((t) => ({ test: t.id, kind: t.kind, unit: t.unit, range: t.range?.(subject) ?? {}, value: null, verdict: "pending", note: t.kind === "measured" ? "not run" : t.kind === "visual" ? "awaiting sign-off" : "room not built yet" }));
|
|
1939
|
+
return { vehicle: subject.id, domain: subject.domain, category: subject.category, status: statusOf(results), results };
|
|
1940
|
+
};
|
|
1941
|
+
const certificateOf = (id) => {
|
|
1942
|
+
if (!certificates.has(id)) {
|
|
1943
|
+
const subject = subjectOf(id);
|
|
1944
|
+
if (subject) certificates.set(id, blank(subject));
|
|
1945
|
+
}
|
|
1946
|
+
return certificates.get(id);
|
|
1947
|
+
};
|
|
1948
|
+
const store = (certificate) => {
|
|
1949
|
+
certificates.set(certificate.vehicle, certificate);
|
|
1950
|
+
options.onCertificate?.(certificate);
|
|
1951
|
+
drawBoard();
|
|
1952
|
+
};
|
|
1953
|
+
const buildTrack = () => {
|
|
1954
|
+
if (!host || track) return;
|
|
1955
|
+
const THREE = three;
|
|
1956
|
+
track = markKitOrigin3(new THREE.Group(), { owner: "@adatechnology/game-vehicles-three", kind: "room", id: "proving_track" });
|
|
1957
|
+
track.name = "proving_track";
|
|
1958
|
+
const look = SURFACE_LOOK.dry_asphalt;
|
|
1959
|
+
strip = new THREE.Mesh(new THREE.PlaneGeometry(TRACK.tile + 80, TRACK.width), new THREE.MeshStandardMaterial({ color: look.color, roughness: look.roughness, metalness: 0.05 }));
|
|
1960
|
+
strip.rotation.x = -Math.PI / 2;
|
|
1961
|
+
strip.position.set(TRACK.x0 + TRACK.tile / 2, 0.012, TRACK.z);
|
|
1962
|
+
strip.receiveShadow = true;
|
|
1963
|
+
track.add(strip);
|
|
1964
|
+
const paint = new THREE.MeshBasicMaterial({ color: 15263970 });
|
|
1965
|
+
const yellow = new THREE.MeshBasicMaterial({ color: 15909424 });
|
|
1966
|
+
const dash = new THREE.PlaneGeometry(3, 0.14);
|
|
1967
|
+
const edge = new THREE.PlaneGeometry(TRACK.tile + 80, 0.16);
|
|
1968
|
+
for (const side of [-1, 1]) {
|
|
1969
|
+
const line = new THREE.Mesh(edge, paint);
|
|
1970
|
+
line.rotation.x = -Math.PI / 2;
|
|
1971
|
+
line.position.set(TRACK.x0 + TRACK.tile / 2, 0.016, TRACK.z + side * (TRACK.width / 2 - 0.4));
|
|
1972
|
+
track.add(line);
|
|
1973
|
+
}
|
|
1974
|
+
for (let x = TRACK.x0 - 40; x < TRACK.x0 + TRACK.tile + 40; x += 9) {
|
|
1975
|
+
const d = new THREE.Mesh(dash, paint);
|
|
1976
|
+
d.rotation.x = -Math.PI / 2;
|
|
1977
|
+
d.position.set(x, 0.016, TRACK.z - 1.5);
|
|
1978
|
+
track.add(d);
|
|
1979
|
+
}
|
|
1980
|
+
const post = new THREE.BoxGeometry(0.12, 1.4, 0.12);
|
|
1981
|
+
const plate = new THREE.BoxGeometry(0.06, 0.5, 0.9);
|
|
1982
|
+
for (let m = 0; m <= TRACK.tile; m += 50) {
|
|
1983
|
+
const p = new THREE.Mesh(post, yellow);
|
|
1984
|
+
p.position.set(TRACK.x0 + m, 0.7, TRACK.z - TRACK.width / 2 - 0.6);
|
|
1985
|
+
const q = new THREE.Mesh(plate, m === 0 ? yellow : paint);
|
|
1986
|
+
q.position.set(TRACK.x0 + m, 1.45, TRACK.z - TRACK.width / 2 - 0.6);
|
|
1987
|
+
track.add(p, q);
|
|
1988
|
+
}
|
|
1989
|
+
const startLine = new THREE.Mesh(new THREE.PlaneGeometry(0.5, TRACK.width - 1), yellow);
|
|
1990
|
+
startLine.rotation.x = -Math.PI / 2;
|
|
1991
|
+
startLine.position.set(TRACK.x0, 0.017, TRACK.z);
|
|
1992
|
+
track.add(startLine);
|
|
1993
|
+
host.scene.add(track);
|
|
1994
|
+
buildBoard();
|
|
1995
|
+
};
|
|
1996
|
+
const setSurface = (surface) => {
|
|
1997
|
+
if (!strip) return;
|
|
1998
|
+
const look = SURFACE_LOOK[surface] ?? SURFACE_LOOK.dry_asphalt;
|
|
1999
|
+
strip.material.color.setHex(look.color);
|
|
2000
|
+
strip.material.roughness = look.roughness;
|
|
2001
|
+
};
|
|
2002
|
+
const buildBoard = () => {
|
|
2003
|
+
if (!host || board || typeof document === "undefined") return;
|
|
2004
|
+
const THREE = three;
|
|
2005
|
+
const canvas = document.createElement("canvas");
|
|
2006
|
+
canvas.width = 1024;
|
|
2007
|
+
canvas.height = 640;
|
|
2008
|
+
const texture = new THREE.CanvasTexture(canvas);
|
|
2009
|
+
texture.colorSpace = THREE.SRGBColorSpace;
|
|
2010
|
+
const mesh = markKitOrigin3(new THREE.Mesh(new THREE.PlaneGeometry(9.6, 6), new THREE.MeshBasicMaterial({ map: texture, toneMapped: false })), { owner: "@adatechnology/game-vehicles-three", kind: "room", id: "proving_board" });
|
|
2011
|
+
mesh.position.set(TRACK.x0 + 6, 3.6, TRACK.z - TRACK.width / 2 - 3);
|
|
2012
|
+
const legs = new THREE.Mesh(new THREE.BoxGeometry(9.8, 0.2, 0.2), new THREE.MeshStandardMaterial({ color: 1842981 }));
|
|
2013
|
+
legs.position.set(0, -3.1, -0.05);
|
|
2014
|
+
const stand = new THREE.Mesh(new THREE.BoxGeometry(0.25, 3.4, 0.25), legs.material);
|
|
2015
|
+
stand.position.set(0, -4.6, -0.1);
|
|
2016
|
+
mesh.add(legs, stand);
|
|
2017
|
+
track.add(mesh);
|
|
2018
|
+
board = { mesh, canvas, texture };
|
|
2019
|
+
drawBoard();
|
|
2020
|
+
};
|
|
2021
|
+
const fmtRange = (r) => r.min == null && r.max == null ? "\u2014" : `${r.min ?? "\u2026"}\u2013${r.max ?? "\u2026"}`;
|
|
2022
|
+
const drawBoard = () => {
|
|
2023
|
+
if (!board) return;
|
|
2024
|
+
const cert = certificateOf(settings.vehicle);
|
|
2025
|
+
const g = board.canvas.getContext("2d");
|
|
2026
|
+
if (!g || !cert) return;
|
|
2027
|
+
const W = board.canvas.width;
|
|
2028
|
+
g.fillStyle = "#16181c";
|
|
2029
|
+
g.fillRect(0, 0, W, board.canvas.height);
|
|
2030
|
+
g.fillStyle = "#f2c230";
|
|
2031
|
+
g.fillRect(0, 0, W, 10);
|
|
2032
|
+
g.font = "600 26px monospace";
|
|
2033
|
+
g.fillStyle = "#8d95a3";
|
|
2034
|
+
g.fillText(text.board.title.toUpperCase(), 32, 50);
|
|
2035
|
+
g.font = "700 46px sans-serif";
|
|
2036
|
+
g.fillStyle = "#e7e9ec";
|
|
2037
|
+
g.fillText(name(cert.vehicle), 32, 102);
|
|
2038
|
+
const colour = { approved: "#5cc98a", rejected: "#ef6a5b", pending: "#a7a0f0" }[cert.status];
|
|
2039
|
+
g.font = "700 34px sans-serif";
|
|
2040
|
+
const status = text.status[cert.status];
|
|
2041
|
+
const sw = g.measureText(status).width + 32;
|
|
2042
|
+
g.strokeStyle = colour;
|
|
2043
|
+
g.lineWidth = 3;
|
|
2044
|
+
g.strokeRect(W - sw - 32, 66, sw, 48);
|
|
2045
|
+
g.fillStyle = colour;
|
|
2046
|
+
g.fillText(status, W - sw - 16, 102);
|
|
2047
|
+
g.font = "22px monospace";
|
|
2048
|
+
g.fillStyle = "#8d95a3";
|
|
2049
|
+
const cols = [32, 430, 640, 860];
|
|
2050
|
+
[text.board.test, text.board.value, text.board.range, text.board.verdict].forEach((h, i) => g.fillText(h.toUpperCase(), cols[i], 150));
|
|
2051
|
+
let y = 190;
|
|
2052
|
+
for (const r of cert.results) {
|
|
2053
|
+
const live = run && run.test.id === r.test && !run.done;
|
|
2054
|
+
g.fillStyle = "#e7e9ec";
|
|
2055
|
+
g.font = "26px sans-serif";
|
|
2056
|
+
g.fillText(text.tests[r.test] ?? r.test, cols[0], y);
|
|
2057
|
+
g.font = "26px monospace";
|
|
2058
|
+
g.fillStyle = "#5fd4e0";
|
|
2059
|
+
const value = live ? text.board.running : r.value == null ? r.kind === "measured" && r.note === "not run" ? text.board.notRun : "\u2014" : `${r.value} ${r.unit}`;
|
|
2060
|
+
g.fillText(value, cols[1], y);
|
|
2061
|
+
g.fillStyle = "#8d95a3";
|
|
2062
|
+
g.fillText(r.kind === "measured" ? fmtRange(r.range) : r.kind === "visual" ? "visual" : "sala", cols[2], y);
|
|
2063
|
+
g.fillStyle = { pass: "#5cc98a", fail: "#ef6a5b", pending: "#a7a0f0" }[r.verdict];
|
|
2064
|
+
g.fillText(r.verdict === "pass" ? "\u2713 " + text.verdict.pass : r.verdict === "fail" ? "\u2717 " + text.verdict.fail : "\u2026", cols[3], y);
|
|
2065
|
+
y += 30;
|
|
2066
|
+
}
|
|
2067
|
+
board.texture.needsUpdate = true;
|
|
2068
|
+
};
|
|
2069
|
+
const removeCar = () => {
|
|
2070
|
+
if (!car) return;
|
|
2071
|
+
discardObject3D2(car.built.group);
|
|
2072
|
+
car = null;
|
|
2073
|
+
};
|
|
2074
|
+
const placeCar = () => {
|
|
2075
|
+
if (!host || !three) return;
|
|
2076
|
+
const subject = subjectOf(settings.vehicle);
|
|
2077
|
+
if (!subject) return;
|
|
2078
|
+
removeCar();
|
|
2079
|
+
const built = createVehiclesThree(three, { kit: kitFor(three) }).build(settings.vehicle, { quality: "high" });
|
|
2080
|
+
const guides = reverseGuides(built);
|
|
2081
|
+
built.group.add(guides);
|
|
2082
|
+
host.scene.add(built.group);
|
|
2083
|
+
car = { built, guides, subject };
|
|
2084
|
+
pose({ x: 0, z: TRACK.start, yaw: 0, pitch: 0, roll: 0, height: 0 }, 0);
|
|
2085
|
+
rig.vehicle?.reset();
|
|
2086
|
+
drawBoard();
|
|
2087
|
+
};
|
|
2088
|
+
const reverseGuides = (built) => {
|
|
2089
|
+
const THREE = three;
|
|
2090
|
+
const group = new THREE.Group();
|
|
2091
|
+
group.name = "reverse_guides";
|
|
2092
|
+
const half = built.width / 2 - 0.05;
|
|
2093
|
+
const tail = -built.length / 2;
|
|
2094
|
+
const bands = [
|
|
2095
|
+
[0.2, 1, 15682107],
|
|
2096
|
+
[1, 2, 15909424],
|
|
2097
|
+
[2, 3.2, 6080906]
|
|
2098
|
+
];
|
|
2099
|
+
for (const [a, b, colour] of bands) {
|
|
2100
|
+
const material = new THREE.MeshBasicMaterial({ color: colour, transparent: true, opacity: 0.85, depthTest: false });
|
|
2101
|
+
for (const side of [-1, 1]) {
|
|
2102
|
+
const bar = new THREE.Mesh(new THREE.PlaneGeometry(0.06, b - a), material);
|
|
2103
|
+
bar.rotation.x = -Math.PI / 2;
|
|
2104
|
+
bar.position.set(side * half, 0.03, tail - (a + b) / 2);
|
|
2105
|
+
bar.userData.side = side;
|
|
2106
|
+
bar.userData.mid = (a + b) / 2;
|
|
2107
|
+
group.add(bar);
|
|
2108
|
+
}
|
|
2109
|
+
const cross = new THREE.Mesh(new THREE.PlaneGeometry(half * 2, 0.05), material);
|
|
2110
|
+
cross.rotation.x = -Math.PI / 2;
|
|
2111
|
+
cross.position.set(0, 0.03, tail - a);
|
|
2112
|
+
cross.userData.mid = a;
|
|
2113
|
+
group.add(cross);
|
|
2114
|
+
}
|
|
2115
|
+
group.traverse((o) => o.layers.set(1));
|
|
2116
|
+
return group;
|
|
2117
|
+
};
|
|
2118
|
+
const bendGuides = (steer) => {
|
|
2119
|
+
if (!car) return;
|
|
2120
|
+
for (const bar of car.guides.children) {
|
|
2121
|
+
const d = bar.userData.mid;
|
|
2122
|
+
const offset = -steer * 0.18 * d * d;
|
|
2123
|
+
bar.position.x = (bar.userData.side ?? 0) * (car.built.width / 2 - 0.05) + offset;
|
|
2124
|
+
}
|
|
2125
|
+
};
|
|
2126
|
+
const pose = (s, steer) => {
|
|
2127
|
+
if (!car) return;
|
|
2128
|
+
const wrapped = (s.x % TRACK.tile + TRACK.tile) % TRACK.tile;
|
|
2129
|
+
if (Math.abs(wrapped - lastWrap) > TRACK.tile / 2) rig.vehicle?.reset();
|
|
2130
|
+
lastWrap = wrapped;
|
|
2131
|
+
car.built.group.position.set(TRACK.x0 + wrapped, 0, TRACK.z + s.z);
|
|
2132
|
+
car.built.group.rotation.set(-s.pitch, s.yaw + Math.PI / 2, s.roll, "YXZ");
|
|
2133
|
+
bendGuides(steer);
|
|
2134
|
+
};
|
|
2135
|
+
const frameOf = (speed) => {
|
|
2136
|
+
if (!car) return null;
|
|
2137
|
+
const g = car.built.group;
|
|
2138
|
+
return { x: g.position.x, y: g.position.y, z: g.position.z, yaw: g.rotation.y, pitch: -g.rotation.x, roll: g.rotation.z, speed, length: car.built.length, width: car.built.width, height: car.built.height };
|
|
2139
|
+
};
|
|
2140
|
+
const applyCamera = () => {
|
|
2141
|
+
if (!host?.setViewCamera) return;
|
|
2142
|
+
host.setViewCamera(settings.camera === "room" ? null : rig.camera);
|
|
2143
|
+
if (settings.camera !== "room") rig.vehicle?.setMode(settings.camera);
|
|
2144
|
+
};
|
|
2145
|
+
const refreshMirrors = () => {
|
|
2146
|
+
for (const m of rig.mirrors) m.remove();
|
|
2147
|
+
rig.mirrors = [];
|
|
2148
|
+
if (!host?.addView || !car) return;
|
|
2149
|
+
const poses = mirrorPoses(car.built);
|
|
2150
|
+
const want = (id) => id === "reverse" ? !!host?.bench?.layer("reverse_camera") : !!host?.bench?.layer("mirrors");
|
|
2151
|
+
for (const pose2 of poses) {
|
|
2152
|
+
if (!want(pose2.id)) continue;
|
|
2153
|
+
const camera = new three.PerspectiveCamera(pose2.fov, 2, 0.05, 400);
|
|
2154
|
+
if (pose2.id === "reverse") camera.layers.enable(1);
|
|
2155
|
+
const rect = MIRROR_RECTS[pose2.id];
|
|
2156
|
+
const remove = host.addView({ camera, rect, mirror: pose2.mirror, rounded: rect.rounded, label: pose2.id, resolution: pose2.id === "reverse" ? 0.6 : 0.45 });
|
|
2157
|
+
rig.mirrors.push({ pose: pose2, camera, remove });
|
|
2158
|
+
}
|
|
2159
|
+
};
|
|
2160
|
+
let mirrorKey = "";
|
|
2161
|
+
const stopEngine = () => {
|
|
2162
|
+
engine?.dispose();
|
|
2163
|
+
engine = void 0;
|
|
2164
|
+
};
|
|
2165
|
+
const startTest = (id) => {
|
|
2166
|
+
const test = tests.get(id);
|
|
2167
|
+
if (!host || !test || test.kind !== "measured") return;
|
|
2168
|
+
if (!car || car.subject.id !== settings.vehicle) placeCar();
|
|
2169
|
+
if (!car) return;
|
|
2170
|
+
parking = null;
|
|
2171
|
+
run = startRun(test, car.subject);
|
|
2172
|
+
setSurface(run.surface);
|
|
2173
|
+
if (id === "turn_circle") run.driver.place(0, TRACK.start, 0);
|
|
2174
|
+
rig.vehicle?.reset();
|
|
2175
|
+
stopEngine();
|
|
2176
|
+
engine = createVehicleSound(host.audio, { sounds });
|
|
2177
|
+
engine.start([car.built.group.position.x, 0.5, car.built.group.position.z]);
|
|
2178
|
+
host.log(fillText(text.log.start, name(settings.vehicle), text.tests[id] ?? id));
|
|
2179
|
+
drawBoard();
|
|
2180
|
+
};
|
|
2181
|
+
const finishTest = () => {
|
|
2182
|
+
if (!run || !car) return;
|
|
2183
|
+
const result = run.result();
|
|
2184
|
+
const cert = certificateOf(car.subject.id);
|
|
2185
|
+
if (cert) store(withResult(cert, result));
|
|
2186
|
+
host?.log(fillText(text.log.result, name(car.subject.id), text.tests[result.test] ?? result.test, result.value ?? "\u2014", result.unit, fmtRange(result.range), result.verdict), { vehicle: car.subject.id, ...result, trace: void 0 }, "proving_result");
|
|
2187
|
+
run = null;
|
|
2188
|
+
stopEngine();
|
|
2189
|
+
const next = queue.shift();
|
|
2190
|
+
if (next) startTest(next);
|
|
2191
|
+
else setSurface("dry_asphalt");
|
|
2192
|
+
};
|
|
2193
|
+
const startParking = () => {
|
|
2194
|
+
if (!host) return;
|
|
2195
|
+
if (!car || car.subject.id !== settings.vehicle) placeCar();
|
|
2196
|
+
if (!car) return;
|
|
2197
|
+
run = null;
|
|
2198
|
+
queue = [];
|
|
2199
|
+
setSurface("dry_asphalt");
|
|
2200
|
+
const driver = createSemiSimDriver3(subjectSpec(car.subject));
|
|
2201
|
+
driver.place(40, 0, 0);
|
|
2202
|
+
parking = { driver, time: 0 };
|
|
2203
|
+
};
|
|
2204
|
+
const onChange = (change) => {
|
|
2205
|
+
if (change.kind === "layer") {
|
|
2206
|
+
if (change.id === "scoreboard" && board) board.mesh.visible = !!host?.bench?.layer("scoreboard");
|
|
2207
|
+
return refreshMirrors();
|
|
2208
|
+
}
|
|
2209
|
+
if (change.kind === "action") {
|
|
2210
|
+
if (change.id === "run_test") {
|
|
2211
|
+
queue = [];
|
|
2212
|
+
startTest(settings.test);
|
|
2213
|
+
} else if (change.id === "run_all") {
|
|
2214
|
+
queue = measured.slice(1);
|
|
2215
|
+
startTest(measured[0]);
|
|
2216
|
+
} else if (change.id === "certify_now") {
|
|
2217
|
+
const subject = subjectOf(settings.vehicle);
|
|
2218
|
+
if (!subject) return;
|
|
2219
|
+
const prior = certificateOf(subject.id);
|
|
2220
|
+
const signOffs = (prior?.results ?? []).filter((r) => r.kind !== "measured" && r.verdict !== "pending").map((r) => ({ vehicle: subject.id, test: r.test, ok: r.verdict === "pass", by: r.signedBy ?? "test room" }));
|
|
2221
|
+
const cert = certify(subject, { tests, signOffs });
|
|
2222
|
+
store(cert);
|
|
2223
|
+
host?.log(fillText(text.log.certified, name(subject.id), text.status[cert.status]), { vehicle: subject.id, status: cert.status }, "proving_certificate");
|
|
2224
|
+
} else if (change.id === "reverse_park") startParking();
|
|
2225
|
+
else if (change.id === "sign_ok" || change.id === "sign_fail") {
|
|
2226
|
+
const cert = certificateOf(settings.vehicle);
|
|
2227
|
+
if (!cert) return;
|
|
2228
|
+
const ok = change.id === "sign_ok";
|
|
2229
|
+
store(withResult(cert, { test: settings.visual, kind: "visual", unit: "", range: {}, value: null, verdict: ok ? "pass" : "fail", signedBy: "test room" }));
|
|
2230
|
+
host?.log(fillText(text.log.signed, name(settings.vehicle), text.tests[settings.visual] ?? settings.visual, ok ? text.verdict.pass : text.verdict.fail));
|
|
2231
|
+
} else if (change.id === "reset") {
|
|
2232
|
+
const subject = subjectOf(settings.vehicle);
|
|
2233
|
+
if (subject) store(blank(subject));
|
|
2234
|
+
}
|
|
2235
|
+
return;
|
|
2236
|
+
}
|
|
2237
|
+
switch (change.id) {
|
|
2238
|
+
case "vehicle":
|
|
2239
|
+
settings.vehicle = String(change.value);
|
|
2240
|
+
run = null;
|
|
2241
|
+
queue = [];
|
|
2242
|
+
parking = null;
|
|
2243
|
+
stopEngine();
|
|
2244
|
+
placeCar();
|
|
2245
|
+
refreshMirrors();
|
|
2246
|
+
break;
|
|
2247
|
+
case "test":
|
|
2248
|
+
settings.test = String(change.value);
|
|
2249
|
+
break;
|
|
2250
|
+
case "camera":
|
|
2251
|
+
settings.camera = String(change.value);
|
|
2252
|
+
applyCamera();
|
|
2253
|
+
break;
|
|
2254
|
+
case "time_scale":
|
|
2255
|
+
settings.timeScale = Number(change.value);
|
|
2256
|
+
break;
|
|
2257
|
+
case "visual_check":
|
|
2258
|
+
settings.visual = String(change.value);
|
|
2259
|
+
break;
|
|
2260
|
+
default:
|
|
2261
|
+
break;
|
|
2262
|
+
}
|
|
2263
|
+
};
|
|
2264
|
+
const frame = (dt) => {
|
|
2265
|
+
if (!host || !open) return;
|
|
2266
|
+
if (!track) buildTrack();
|
|
2267
|
+
if (!car) {
|
|
2268
|
+
placeCar();
|
|
2269
|
+
refreshMirrors();
|
|
2270
|
+
}
|
|
2271
|
+
let speed = 0;
|
|
2272
|
+
let steer = 0;
|
|
2273
|
+
if (run) {
|
|
2274
|
+
run.step(dt * settings.timeScale);
|
|
2275
|
+
const s = run.driver.state;
|
|
2276
|
+
pose(s, s.steerAngle);
|
|
2277
|
+
speed = s.u;
|
|
2278
|
+
steer = s.steerAngle;
|
|
2279
|
+
if (run.done) finishTest();
|
|
2280
|
+
} else if (parking) {
|
|
2281
|
+
parking.time += dt;
|
|
2282
|
+
const p = parking;
|
|
2283
|
+
p.driver.step({ throttle: 0, brake: p.time < 7 ? 0.35 : 1, steer: p.time > 2 ? -0.6 : 0, handbrake: false }, dt);
|
|
2284
|
+
pose(p.driver.state, p.driver.state.steerAngle);
|
|
2285
|
+
speed = p.driver.state.u;
|
|
2286
|
+
steer = p.driver.state.steerAngle;
|
|
2287
|
+
if (p.time > 9) parking = null;
|
|
2288
|
+
}
|
|
2289
|
+
if (car) engine?.update({ dt, position: [car.built.group.position.x, 0.5, car.built.group.position.z], speed, throttle: run ? 1 : 0 });
|
|
2290
|
+
const f = frameOf(speed);
|
|
2291
|
+
if (f && rig.vehicle && settings.camera !== "room") rig.vehicle.update(rig.camera, f, dt);
|
|
2292
|
+
if (f && rig.vehicle) for (const m of rig.mirrors) rig.vehicle.placeMirror(m.camera, m.pose, f);
|
|
2293
|
+
void steer;
|
|
2294
|
+
const key = `${host.bench?.layer("mirrors")}|${host.bench?.layer("reverse_camera")}`;
|
|
2295
|
+
if (key !== mirrorKey) {
|
|
2296
|
+
mirrorKey = key;
|
|
2297
|
+
refreshMirrors();
|
|
2298
|
+
}
|
|
2299
|
+
if (run && Math.floor(run.time * 2) !== Math.floor((run.time - dt * settings.timeScale) * 2)) drawBoard();
|
|
2300
|
+
};
|
|
2301
|
+
const speedNow = () => Math.abs(run?.driver.state.u ?? parking?.driver.state.u ?? 0) * 3.6;
|
|
2302
|
+
const counts = (verdict) => certificateOf(settings.vehicle)?.results.filter((r) => r.verdict === verdict).length ?? 0;
|
|
2303
|
+
const teardown = () => {
|
|
2304
|
+
stopEngine();
|
|
2305
|
+
for (const m of rig.mirrors) m.remove();
|
|
2306
|
+
rig.mirrors = [];
|
|
2307
|
+
removeCar();
|
|
2308
|
+
mirrorKey = "";
|
|
2309
|
+
if (track) discardObject3D2(track);
|
|
2310
|
+
track = null;
|
|
2311
|
+
strip = null;
|
|
2312
|
+
board = null;
|
|
2313
|
+
run = null;
|
|
2314
|
+
parking = null;
|
|
2315
|
+
};
|
|
2316
|
+
return {
|
|
2317
|
+
id: PROVING_AREA_ID,
|
|
2318
|
+
name: text.name,
|
|
2319
|
+
group: text.name,
|
|
2320
|
+
ground: { surface: "road" },
|
|
2321
|
+
certificates: () => [...certificates.values()],
|
|
2322
|
+
certificate: (id) => certificateOf(id),
|
|
2323
|
+
items: () => [],
|
|
2324
|
+
area: () => {
|
|
2325
|
+
open = true;
|
|
2326
|
+
applyCamera();
|
|
2327
|
+
return { placements: [], view: { eye: [TRACK.x0 - 4, 3.2, TRACK.z + 9], target: [TRACK.x0 + 6, 2.2, TRACK.z - TRACK.width / 2 - 3] } };
|
|
2328
|
+
},
|
|
2329
|
+
controls: () => [
|
|
2330
|
+
{ kind: "choice", id: "vehicle", name: text.controls.vehicle, hint: text.hints.vehicle, options: vehicles.map((id) => ({ id, name: name(id) })), default: settings.vehicle },
|
|
2331
|
+
{ 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 },
|
|
2332
|
+
{ kind: "choice", id: "camera", name: text.controls.camera, hint: text.hints.camera, options: ["room", "chase", "cockpit", "hood"].map((id) => ({ id, name: text.cameras[id] })), default: settings.camera },
|
|
2333
|
+
{ kind: "number", id: "time_scale", name: text.controls.time_scale, hint: text.hints.time_scale, min: 1, max: 20, step: 1, default: settings.timeScale },
|
|
2334
|
+
{ 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 },
|
|
2335
|
+
{ kind: "action", id: "run_test", name: text.actions.run_test },
|
|
2336
|
+
{ kind: "action", id: "run_all", name: text.actions.run_all },
|
|
2337
|
+
{ kind: "action", id: "certify_now", name: text.actions.certify_now },
|
|
2338
|
+
{ kind: "action", id: "reverse_park", name: text.actions.reverse_park },
|
|
2339
|
+
{ kind: "action", id: "sign_ok", name: text.actions.sign_ok },
|
|
2340
|
+
{ kind: "action", id: "sign_fail", name: text.actions.sign_fail },
|
|
2341
|
+
{ kind: "action", id: "reset", name: text.actions.reset }
|
|
2342
|
+
],
|
|
2343
|
+
layers: () => [
|
|
2344
|
+
{ id: "mirrors", name: text.layers.mirrors, default: false },
|
|
2345
|
+
{ id: "reverse_camera", name: text.layers.reverse_camera, default: false },
|
|
2346
|
+
{ id: "scoreboard", name: text.layers.scoreboard, default: true }
|
|
2347
|
+
],
|
|
2348
|
+
metrics: () => [
|
|
2349
|
+
{ id: "speed", name: text.metrics.speed, unit: "km/h", digits: 0, read: speedNow },
|
|
2350
|
+
{ id: "time", name: text.metrics.time, unit: "s", digits: 1, read: () => run?.time ?? 0 },
|
|
2351
|
+
{ id: "value", name: text.metrics.value, digits: 1, read: () => run ? run.trace[run.trace.length - 1]?.[1] ?? 0 : 0 },
|
|
2352
|
+
{ id: "passed", name: text.metrics.passed, read: () => counts("pass") },
|
|
2353
|
+
{ id: "failed", name: text.metrics.failed, read: () => counts("fail") },
|
|
2354
|
+
{ id: "pending", name: text.metrics.pending, read: () => counts("pending") },
|
|
2355
|
+
{ id: "status", name: text.metrics.status, read: () => ({ approved: 1, rejected: -1, pending: 0 })[certificateOf(settings.vehicle)?.status ?? "pending"] }
|
|
2356
|
+
],
|
|
2357
|
+
scenarios: () => [],
|
|
2358
|
+
attach(lent) {
|
|
2359
|
+
host = lent;
|
|
2360
|
+
three = lent.THREE;
|
|
2361
|
+
rig.camera = new three.PerspectiveCamera(60, 1.6, 0.05, 900);
|
|
2362
|
+
rig.vehicle = createVehicleCamera(three);
|
|
2363
|
+
lent.audio?.register?.(LIBRARY_BANK2);
|
|
2364
|
+
const stopChange = lent.bench?.onChange(onChange);
|
|
2365
|
+
const stopFrame = lent.onFrame(frame);
|
|
2366
|
+
const stopClear = lent.onClear?.(() => {
|
|
2367
|
+
open = false;
|
|
2368
|
+
teardown();
|
|
2369
|
+
lent.setViewCamera?.(null);
|
|
2370
|
+
});
|
|
2371
|
+
return () => {
|
|
2372
|
+
stopChange?.();
|
|
2373
|
+
stopFrame();
|
|
2374
|
+
stopClear?.();
|
|
2375
|
+
teardown();
|
|
2376
|
+
lent.setViewCamera?.(null);
|
|
2377
|
+
host = null;
|
|
2378
|
+
};
|
|
2379
|
+
}
|
|
2380
|
+
};
|
|
2381
|
+
}
|
|
2382
|
+
function loadProvingModule(room, options) {
|
|
2383
|
+
return room.loadModule(createProvingModule(options));
|
|
2384
|
+
}
|
|
2385
|
+
|
|
1433
2386
|
export {
|
|
1434
2387
|
LITE_CLASSES,
|
|
1435
2388
|
PRUNE_BELOW,
|
|
@@ -1440,6 +2393,22 @@ export {
|
|
|
1440
2393
|
liteMeshes,
|
|
1441
2394
|
createVehiclesThree,
|
|
1442
2395
|
createCabinSmoke,
|
|
2396
|
+
VEHICLE_LOOP_FALLOFF,
|
|
2397
|
+
VEHICLE_LOOPS,
|
|
2398
|
+
VEHICLE_SOUNDS,
|
|
2399
|
+
VEHICLE_SOUND_BANK,
|
|
2400
|
+
firingHz,
|
|
2401
|
+
rpmShare,
|
|
2402
|
+
layerWeight,
|
|
2403
|
+
engineMix,
|
|
2404
|
+
createGearGuess,
|
|
2405
|
+
DEFAULT_VEHICLE_SOUND_KEYS,
|
|
2406
|
+
CRASH_SPEEDS,
|
|
2407
|
+
CAR_ENGINE,
|
|
2408
|
+
TRUCK_ENGINE,
|
|
2409
|
+
BIKE_ENGINE,
|
|
2410
|
+
ENGINE_PROFILES,
|
|
2411
|
+
createVehicleSound,
|
|
1443
2412
|
VEHICLE_LOD_DISTANCE,
|
|
1444
2413
|
createVehicleLod,
|
|
1445
2414
|
VEHICLE_SCENARIO_IDS,
|
|
@@ -1451,6 +2420,10 @@ export {
|
|
|
1451
2420
|
VEHICLES_AREA_ID,
|
|
1452
2421
|
DEFAULT_TEST_VEHICLES,
|
|
1453
2422
|
createVehiclesTestModule,
|
|
1454
|
-
loadVehiclesTestModule
|
|
2423
|
+
loadVehiclesTestModule,
|
|
2424
|
+
PROVING_TEXT,
|
|
2425
|
+
PROVING_AREA_ID,
|
|
2426
|
+
createProvingModule,
|
|
2427
|
+
loadProvingModule
|
|
1455
2428
|
};
|
|
1456
|
-
//# sourceMappingURL=chunk-
|
|
2429
|
+
//# sourceMappingURL=chunk-4SUQMRS3.js.map
|