@adatechnology/game-vehicles-three 0.3.7 → 0.5.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 +1456 -0
- package/dist/chunk-GAO7ZJP7.js.map +1 -0
- package/dist/index.d.ts +31 -181
- package/dist/index.global.js +1725 -115
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +26 -1180
- package/dist/index.js.map +1 -1
- package/dist/testing-DmGv7Gpb.d.ts +219 -0
- package/dist/testing.d.ts +3 -0
- package/dist/testing.js +25 -0
- package/dist/testing.js.map +1 -0
- package/package.json +15 -9
package/dist/index.js
CHANGED
|
@@ -1,1183 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
if (mat.isMeshBasicMaterial) return null;
|
|
26
|
-
const e = mat.emissive;
|
|
27
|
-
const ei = mat.emissiveIntensity == null ? 1 : mat.emissiveIntensity;
|
|
28
|
-
if (e && (e.r + e.g + e.b) / 3 * ei > 0.25 && !mat.metalness) return "light";
|
|
29
|
-
if (e && (e.r + e.g + e.b) / 3 * ei > 0.45) return "light";
|
|
30
|
-
if (mat.metalness >= 0.5) return "metal";
|
|
31
|
-
if (c.paints && c.paints.has(mat)) return "paint";
|
|
32
|
-
if (mat.opacity < 1 && mat.transparent) return null;
|
|
33
|
-
return "trim";
|
|
34
|
-
}
|
|
35
|
-
function classColor(mat, cls, primary, paintColor, out) {
|
|
36
|
-
if (cls === "paint" && paintColor && mat === primary) return out.copy(paintColor);
|
|
37
|
-
if (cls === "light") {
|
|
38
|
-
const e = mat.emissive;
|
|
39
|
-
const ei = mat.emissiveIntensity == null ? 1 : mat.emissiveIntensity;
|
|
40
|
-
if (e && e.r + e.g + e.b > 0.05) {
|
|
41
|
-
out.copy(e).multiplyScalar(Math.max(1, ei) * 1);
|
|
42
|
-
out.r = Math.min(out.r, 1);
|
|
43
|
-
out.g = Math.min(out.g, 1);
|
|
44
|
-
out.b = Math.min(out.b, 1);
|
|
45
|
-
return out;
|
|
46
|
-
}
|
|
47
|
-
return out.copy(mat.color);
|
|
48
|
-
}
|
|
49
|
-
if (mat.map) return out.setRGB(0.12, 0.13, 0.14);
|
|
50
|
-
return out.copy(mat.color);
|
|
51
|
-
}
|
|
52
|
-
function liteMaterials(THREE, environment) {
|
|
53
|
-
return {
|
|
54
|
-
paint: new THREE.MeshStandardMaterial({ vertexColors: true, roughness: 0.42, metalness: 0.3, side: THREE.DoubleSide, envMap: environment, envMapIntensity: 0.35 }),
|
|
55
|
-
trim: new THREE.MeshStandardMaterial({ vertexColors: true, roughness: 0.78, metalness: 0.08, side: THREE.DoubleSide }),
|
|
56
|
-
metal: new THREE.MeshStandardMaterial({ vertexColors: true, roughness: 0.32, metalness: 0.85, envMap: environment, envMapIntensity: 1.1, side: THREE.DoubleSide }),
|
|
57
|
-
glass: new THREE.MeshStandardMaterial({ vertexColors: true, transparent: true, depthWrite: false, roughness: 0.06, metalness: 0.3, side: THREE.DoubleSide, envMap: environment, envMapIntensity: 0.9 }),
|
|
58
|
-
light: new THREE.MeshBasicMaterial({ vertexColors: true, side: THREE.DoubleSide }),
|
|
59
|
-
plate: new THREE.MeshStandardMaterial({ roughness: 0.5, metalness: 0.1, side: THREE.DoubleSide })
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
function fuse(THREE, c, pivot, paintColor, primary, hidden) {
|
|
63
|
-
const acc = createAccumulators();
|
|
64
|
-
const mw = new THREE.Matrix4();
|
|
65
|
-
const nm = new THREE.Matrix3();
|
|
66
|
-
const v = new THREE.Vector3();
|
|
67
|
-
const col = new THREE.Color();
|
|
68
|
-
const tmp = new THREE.Box3();
|
|
69
|
-
let pruned = 0;
|
|
70
|
-
c.vehicle.updateMatrixWorld(true);
|
|
71
|
-
c.vehicle.traverse((o) => {
|
|
72
|
-
if (!o.isMesh || o.isInstancedMesh || o.isSkinnedMesh || !o.geometry || !o.geometry.attributes.position) return;
|
|
73
|
-
for (let p = o; p; p = p.parent) if (p.visible === false) return;
|
|
74
|
-
if (o.userData && (o.userData.ghost || o.userData.skipLite)) return;
|
|
75
|
-
const mats = Array.isArray(o.material) ? o.material : [o.material];
|
|
76
|
-
const geo = o.geometry;
|
|
77
|
-
mw.multiplyMatrices(pivot, o.matrixWorld);
|
|
78
|
-
nm.getNormalMatrix(mw);
|
|
79
|
-
const flip = mw.determinant() < 0;
|
|
80
|
-
geo.computeBoundingBox();
|
|
81
|
-
tmp.copy(geo.boundingBox).applyMatrix4(mw);
|
|
82
|
-
const ext = Math.max(tmp.max.x - tmp.min.x, tmp.max.y - tmp.min.y, tmp.max.z - tmp.min.z);
|
|
83
|
-
const pa = geo.attributes.position;
|
|
84
|
-
const na = geo.attributes.normal;
|
|
85
|
-
const ua = geo.attributes.uv;
|
|
86
|
-
const ca = geo.attributes.color;
|
|
87
|
-
const ix = geo.index;
|
|
88
|
-
const tot = ix ? ix.count : pa.count;
|
|
89
|
-
const groups = Array.isArray(o.material) && geo.groups.length ? geo.groups : [{ start: 0, count: tot, materialIndex: 0 }];
|
|
90
|
-
groups.forEach((gr) => {
|
|
91
|
-
const mat = mats[gr.materialIndex] || mats[0];
|
|
92
|
-
if (!mat || hidden.has(mat)) return;
|
|
93
|
-
const cls = classify(mat, o, c);
|
|
94
|
-
if (!cls) return;
|
|
95
|
-
if (ext < PRUNE_BELOW && cls !== "plate") {
|
|
96
|
-
pruned++;
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
const A = acc[cls];
|
|
100
|
-
const a0 = Math.min(gr.start, tot);
|
|
101
|
-
let n0 = Math.min(gr.count, tot - a0);
|
|
102
|
-
n0 -= n0 % 3;
|
|
103
|
-
if (n0 < 3) return;
|
|
104
|
-
const cc = classColor(mat, cls, primary, paintColor, col);
|
|
105
|
-
const alpha = cls === "glass" ? mat.transparent ? mat.opacity : 1 : 1;
|
|
106
|
-
if (cls === "plate" && A.map && mat.map !== A.map) return;
|
|
107
|
-
if (cls === "plate") A.map = mat.map;
|
|
108
|
-
const remap = /* @__PURE__ */ new Map();
|
|
109
|
-
for (let i = 0; i < n0; i++) {
|
|
110
|
-
const vi = ix ? ix.getX(a0 + i) : a0 + i;
|
|
111
|
-
if (remap.has(vi)) continue;
|
|
112
|
-
remap.set(vi, A.nv++);
|
|
113
|
-
v.fromBufferAttribute(pa, vi).applyMatrix4(mw);
|
|
114
|
-
A.P.push(v.x, v.y, v.z);
|
|
115
|
-
if (na) {
|
|
116
|
-
v.fromBufferAttribute(na, vi).applyMatrix3(nm).normalize();
|
|
117
|
-
A.N.push(v.x, v.y, v.z);
|
|
118
|
-
} else A.N.push(0, 1, 0);
|
|
119
|
-
let cr = cc.r;
|
|
120
|
-
let cg = cc.g;
|
|
121
|
-
let cb = cc.b;
|
|
122
|
-
if (ca && cls !== "plate") {
|
|
123
|
-
cr *= ca.getX(vi);
|
|
124
|
-
cg *= ca.getY(vi);
|
|
125
|
-
cb *= ca.getZ(vi);
|
|
126
|
-
}
|
|
127
|
-
if (A.alpha) A.C.push(cr, cg, cb, alpha);
|
|
128
|
-
else A.C.push(cr, cg, cb);
|
|
129
|
-
if (A.uv) A.U.push(ua ? ua.getX(vi) : 0, ua ? ua.getY(vi) : 0);
|
|
130
|
-
}
|
|
131
|
-
for (let t = 0; t < n0; t += 3) {
|
|
132
|
-
const i0 = remap.get(ix ? ix.getX(a0 + t) : a0 + t);
|
|
133
|
-
const i1 = remap.get(ix ? ix.getX(a0 + t + 1) : a0 + t + 1);
|
|
134
|
-
const i2 = remap.get(ix ? ix.getX(a0 + t + 2) : a0 + t + 2);
|
|
135
|
-
if (flip) A.I.push(i0, i2, i1);
|
|
136
|
-
else A.I.push(i0, i1, i2);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
return { acc, pruned };
|
|
141
|
-
}
|
|
142
|
-
function liteMeshes(THREE, fusion, materials) {
|
|
143
|
-
const meshes = [];
|
|
144
|
-
for (const name of LITE_CLASSES) {
|
|
145
|
-
const A = fusion.acc[name];
|
|
146
|
-
if (!A.I.length) continue;
|
|
147
|
-
const g = new THREE.BufferGeometry();
|
|
148
|
-
g.setAttribute("position", new THREE.Float32BufferAttribute(A.P, 3));
|
|
149
|
-
g.setAttribute("normal", new THREE.Float32BufferAttribute(A.N, 3));
|
|
150
|
-
g.setAttribute("color", new THREE.Float32BufferAttribute(A.C, A.alpha ? 4 : 3));
|
|
151
|
-
if (A.uv) g.setAttribute("uv", new THREE.Float32BufferAttribute(A.U, 2));
|
|
152
|
-
g.setIndex(A.nv > 65535 ? new THREE.Uint32BufferAttribute(A.I, 1) : new THREE.Uint16BufferAttribute(A.I, 1));
|
|
153
|
-
g.computeBoundingBox();
|
|
154
|
-
g.computeBoundingSphere();
|
|
155
|
-
let mt = materials[name];
|
|
156
|
-
if (name === "plate") {
|
|
157
|
-
mt = mt.clone();
|
|
158
|
-
mt.map = A.map;
|
|
159
|
-
if (mt.map) mt.map.needsUpdate = true;
|
|
160
|
-
}
|
|
161
|
-
const mesh = new THREE.Mesh(g, mt);
|
|
162
|
-
mesh.name = name;
|
|
163
|
-
mesh.castShadow = name !== "glass" && name !== "light";
|
|
164
|
-
mesh.receiveShadow = name !== "light";
|
|
165
|
-
meshes.push(mesh);
|
|
166
|
-
}
|
|
167
|
-
return meshes;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// src/build.ts
|
|
171
|
-
function count(root) {
|
|
172
|
-
let triangles = 0;
|
|
173
|
-
let drawCalls = 0;
|
|
174
|
-
root.traverse((o) => {
|
|
175
|
-
if (!o.isMesh || !o.geometry) return;
|
|
176
|
-
for (let p = o; p; p = p.parent) if (p.visible === false) return;
|
|
177
|
-
const g = o.geometry;
|
|
178
|
-
const n = g.index ? g.index.count : g.attributes.position ? g.attributes.position.count : 0;
|
|
179
|
-
if (Array.isArray(o.material) && g.groups.length) drawCalls += g.groups.length;
|
|
180
|
-
else drawCalls++;
|
|
181
|
-
triangles += n / 3;
|
|
182
|
-
});
|
|
183
|
-
return { triangles: Math.round(triangles), drawCalls };
|
|
184
|
-
}
|
|
185
|
-
function primaryPaint(c) {
|
|
186
|
-
let best = null;
|
|
187
|
-
let bestCount = -1;
|
|
188
|
-
const counts = /* @__PURE__ */ new Map();
|
|
189
|
-
c.vehicle.traverse((o) => {
|
|
190
|
-
if (!o.isMesh || !o.geometry) return;
|
|
191
|
-
const mats = Array.isArray(o.material) ? o.material : [o.material];
|
|
192
|
-
const g = o.geometry;
|
|
193
|
-
const n = (g.index ? g.index.count : g.attributes.position.count) / 3;
|
|
194
|
-
for (const m of mats) if (c.paints.has(m)) counts.set(m, (counts.get(m) || 0) + n);
|
|
195
|
-
});
|
|
196
|
-
counts.forEach((n, m) => {
|
|
197
|
-
if (n > bestCount) {
|
|
198
|
-
bestCount = n;
|
|
199
|
-
best = m;
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
return best;
|
|
203
|
-
}
|
|
204
|
-
function createVehiclesThree(THREE, options) {
|
|
205
|
-
const kit = options.kit;
|
|
206
|
-
const groupOf = options.groupOf || ((key) => kit.find(key)?.group === kit.config.defaultGroup ? "cars" : String(kit.find(key)?.group));
|
|
207
|
-
const militaryKeys = /* @__PURE__ */ new Set();
|
|
208
|
-
const isMilitary = (key) => militaryKeys.has(key) || paletteOf(groupOf(key)) === paintPalettes.military;
|
|
209
|
-
const build = (key, o = {}) => {
|
|
210
|
-
const quality = o.quality === "lite" ? "lite" : "high";
|
|
211
|
-
const spec = kit.find(key);
|
|
212
|
-
if (!spec) throw new Error(`createVehiclesThree: unknown vehicle "${key}"`);
|
|
213
|
-
const seed = o.seed == null ? null : o.seed;
|
|
214
|
-
const color = o.color || (seed != null ? colorFromSeed(seed, isMilitary(key) ? paintPalettes.military : paintPalettes.civil) : null);
|
|
215
|
-
const c = kit.build(key, o.state || "intact", void 0, { lod: quality === "lite" });
|
|
216
|
-
c.root.updateMatrixWorld(true);
|
|
217
|
-
const primary = primaryPaint(c);
|
|
218
|
-
const rotation = new THREE.Matrix4().makeRotationY(-Math.PI / 2);
|
|
219
|
-
const bounds = new THREE.Box3();
|
|
220
|
-
const piece = new THREE.Box3();
|
|
221
|
-
const turned = new THREE.Matrix4();
|
|
222
|
-
c.vehicle.traverse((m) => {
|
|
223
|
-
if (!m.isMesh || !m.geometry || m.userData.ghost) return;
|
|
224
|
-
for (let p = m; p; p = p.parent) if (p.visible === false) return;
|
|
225
|
-
m.geometry.computeBoundingBox();
|
|
226
|
-
piece.copy(m.geometry.boundingBox).applyMatrix4(turned.multiplyMatrices(rotation, m.matrixWorld));
|
|
227
|
-
bounds.union(piece);
|
|
228
|
-
});
|
|
229
|
-
const cx = (bounds.min.x + bounds.max.x) / 2;
|
|
230
|
-
const cz = (bounds.min.z + bounds.max.z) / 2;
|
|
231
|
-
const pivotMatrix = new THREE.Matrix4().makeTranslation(-cx, -bounds.min.y, -cz).multiply(rotation);
|
|
232
|
-
const group = new THREE.Group();
|
|
233
|
-
group.name = `vehicle:${key}`;
|
|
234
|
-
markKitOrigin(group, { owner: "game-vehicles-three", kind: "vehicle", id: key });
|
|
235
|
-
const damageables = kit.damageables(c, pivotMatrix);
|
|
236
|
-
let pruned = 0;
|
|
237
|
-
if (quality === "high") {
|
|
238
|
-
const pivot = new THREE.Group();
|
|
239
|
-
pivot.name = "vehicle";
|
|
240
|
-
pivot.matrixAutoUpdate = false;
|
|
241
|
-
pivot.matrix.copy(pivotMatrix);
|
|
242
|
-
pivot.matrixWorldNeedsUpdate = true;
|
|
243
|
-
c.vehicle.matrixAutoUpdate = true;
|
|
244
|
-
pivot.add(c.vehicle);
|
|
245
|
-
group.add(pivot);
|
|
246
|
-
if (color && primary) {
|
|
247
|
-
const repainted = primary.clone();
|
|
248
|
-
repainted.color.set(color);
|
|
249
|
-
c.vehicle.traverse((m) => {
|
|
250
|
-
if (!m.isMesh) return;
|
|
251
|
-
if (Array.isArray(m.material)) m.material = m.material.map((x) => x === primary ? repainted : x);
|
|
252
|
-
else if (m.material === primary) m.material = repainted;
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
markDamageables(c, damageables);
|
|
256
|
-
group.updateMatrixWorld(true);
|
|
257
|
-
} else {
|
|
258
|
-
const fusion = fuse(THREE, c, pivotMatrix, color ? new THREE.Color(color) : null, primary, kit.lodHiddenMaterials());
|
|
259
|
-
for (const mesh of liteMeshes(THREE, fusion, liteMaterials(THREE, kit.studioEnvironment()))) group.add(mesh);
|
|
260
|
-
pruned = fusion.pruned;
|
|
261
|
-
}
|
|
262
|
-
const size = new THREE.Box3().setFromObject(group).getSize(new THREE.Vector3());
|
|
263
|
-
const counted = count(group);
|
|
264
|
-
group.userData.damageables = damageables;
|
|
265
|
-
group.userData.key = key;
|
|
266
|
-
group.userData.quality = quality;
|
|
267
|
-
return {
|
|
268
|
-
key,
|
|
269
|
-
quality,
|
|
270
|
-
color,
|
|
271
|
-
seed,
|
|
272
|
-
group,
|
|
273
|
-
damageables,
|
|
274
|
-
triangles: counted.triangles,
|
|
275
|
-
drawCalls: counted.drawCalls,
|
|
276
|
-
length: +size.z.toFixed(3),
|
|
277
|
-
width: +size.x.toFixed(3),
|
|
278
|
-
height: +size.y.toFixed(3),
|
|
279
|
-
pruned,
|
|
280
|
-
expected: fleetDims(key),
|
|
281
|
-
context: quality === "high" ? c : null,
|
|
282
|
-
dispose() {
|
|
283
|
-
group.traverse((m) => {
|
|
284
|
-
if (m.geometry) m.geometry.dispose();
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
};
|
|
288
|
-
};
|
|
289
|
-
return { THREE, kit, build };
|
|
290
|
-
}
|
|
291
|
-
function markDamageables(c, damageables) {
|
|
292
|
-
const byZone = {};
|
|
293
|
-
for (const d of damageables) byZone[d.zone] = d;
|
|
294
|
-
for (const z of c.zones || []) {
|
|
295
|
-
const d = byZone[z.id];
|
|
296
|
-
if (!d) continue;
|
|
297
|
-
for (const o of z.objs || z.meshes || []) if (o && o.userData) o.userData.damageable = { material: d.material, zone: d.zone };
|
|
298
|
-
}
|
|
299
|
-
for (const w of c.wheels || []) {
|
|
300
|
-
const z = (c.zones || []).find((zz) => zz.type === "wheel" && zz.objs && zz.objs.indexOf(w) >= 0);
|
|
301
|
-
w.userData.damageable = { material: "rubber", zone: z ? z.id : "wheel" };
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// src/test-area/module.ts
|
|
306
|
-
import { discardObject3D } from "@adatechnology/game-shapes-three";
|
|
307
|
-
import { footprintBlocker } from "@adatechnology/game-contracts";
|
|
308
|
-
import { buildVehicleItem, createVehicleDestructible, createVehicleKit } from "@adatechnology/game-vehicles";
|
|
309
|
-
import { createArcadeDriver as createArcadeDriver2, createSemiSimDriver as createSemiSimDriver2, createSurfaces as createSurfaces2, vehicleCorners as vehicleCorners2 } from "@adatechnology/game-vehicles-driving";
|
|
310
|
-
import { colorFromSeed as colorFromSeed2, fleetDims as fleetDims2, installFleet, paintPalettes as paintPalettes2, paletteOf as paletteOf2 } from "@adatechnology/game-vehicles-fleet";
|
|
311
|
-
|
|
312
|
-
// src/test-area/scenarios.ts
|
|
313
|
-
import { createArcadeDriver, createSemiSimDriver, createSurfaces, vehicleCorners } from "@adatechnology/game-vehicles-driving";
|
|
314
|
-
|
|
315
|
-
// src/test-area/text.ts
|
|
316
|
-
var scenarioIds = [
|
|
317
|
-
"vehicles_9mm_windshield",
|
|
318
|
-
"vehicles_shotgun_door",
|
|
319
|
-
"vehicles_ram_front",
|
|
320
|
-
"vehicles_engine_fire",
|
|
321
|
-
"vehicles_fuel_tank",
|
|
322
|
-
"vehicles_unbolt_wheel",
|
|
323
|
-
"vehicles_tear_bumper",
|
|
324
|
-
"vehicles_break_mirror",
|
|
325
|
-
"vehicles_kill_battery",
|
|
326
|
-
"vehicles_rollover",
|
|
327
|
-
"vehicles_melt",
|
|
328
|
-
"vehicles_burnt_wreck",
|
|
329
|
-
"vehicles_drive_arcade",
|
|
330
|
-
"vehicles_drive_semi"
|
|
331
|
-
];
|
|
332
|
-
var VEHICLE_SCENARIO_IDS = scenarioIds;
|
|
333
|
-
var PAGE_SCENARIO_ALIASES = {
|
|
334
|
-
veiculos_9mm_parabrisa: "vehicles_9mm_windshield",
|
|
335
|
-
veiculos_espingarda_porta: "vehicles_shotgun_door",
|
|
336
|
-
veiculos_bater_frente: "vehicles_ram_front",
|
|
337
|
-
veiculos_fogo_motor: "vehicles_engine_fire",
|
|
338
|
-
veiculos_tanque: "vehicles_fuel_tank",
|
|
339
|
-
veiculos_soltar_roda: "vehicles_unbolt_wheel",
|
|
340
|
-
veiculos_arrancar_parachoque: "vehicles_tear_bumper",
|
|
341
|
-
veiculos_quebrar_retrovisor: "vehicles_break_mirror",
|
|
342
|
-
veiculos_matar_bateria: "vehicles_kill_battery",
|
|
343
|
-
veiculos_capotar: "vehicles_rollover",
|
|
344
|
-
veiculos_derreter: "vehicles_melt"
|
|
345
|
-
};
|
|
346
|
-
var PAGE_ITEM_ALIASES = {
|
|
347
|
-
hatch: "hatch",
|
|
348
|
-
sedan: "sedan",
|
|
349
|
-
picape: "pickup",
|
|
350
|
-
moto: "sport_bike",
|
|
351
|
-
caminhao: "box_truck",
|
|
352
|
-
onibus: "city_bus",
|
|
353
|
-
tanque: "battle_tank",
|
|
354
|
-
helicoptero: "attack_helicopter"
|
|
355
|
-
};
|
|
356
|
-
var TEXT = {
|
|
357
|
-
en: {
|
|
358
|
-
group: "Vehicles",
|
|
359
|
-
name: "Vehicles area",
|
|
360
|
-
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" },
|
|
361
|
-
controls: { quality: "Quality", paint_by_seed: "Paint by seed", color_seed: "Colour seed", palette: "Palette", driving: "Driving", surface: "Surface" },
|
|
362
|
-
hints: {
|
|
363
|
-
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",
|
|
364
|
-
paint_by_seed: "Off: every vehicle keeps its own paint. On: the body colour comes from the seed and the palette",
|
|
365
|
-
palette: "Auto: the palette of the vehicle group (civil, or military for the army vehicles)",
|
|
366
|
-
driving: "Arcade: the game car, simple and forgiving. Semi: a rigid body with springs, tyre friction, gears and surfaces",
|
|
367
|
-
surface: "The ground under the wheels in the semi mode (grip and aquaplaning)"
|
|
368
|
-
},
|
|
369
|
-
options: {
|
|
370
|
-
quality: { high: "High", lite: "Light" },
|
|
371
|
-
palette: { auto: "Auto", civil: "Civil", military: "Military" },
|
|
372
|
-
driving: { arcade: "Arcade", semi: "Semi-simulation" },
|
|
373
|
-
surface: { dry_asphalt: "Dry asphalt", wet_asphalt: "Wet asphalt", dirt: "Dirt", grass: "Grass", mud: "Mud", blood: "Blood" }
|
|
374
|
-
},
|
|
375
|
-
actions: { repaint: "Repaint with the seed", drive_lane: "Drive the lane", clear_lane: "Remove the lane car", honk: "Honk the horn" },
|
|
376
|
-
layers: { damageables: "Damageable boxes by material", zones: "Zone boxes by type", axes: "Output frame axes" },
|
|
377
|
-
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" },
|
|
378
|
-
words: { yes: "true", no: "false", open: "open", tight: "tight", on: "on", off: "off" },
|
|
379
|
-
lane: { crash: "Lane: the car hit the wall at {0} m/s", end: "Lane: the car stopped {0} m from the wall" },
|
|
380
|
-
scenarios: {
|
|
381
|
-
vehicles_9mm_windshield: "Vehicles: 9 mm through the windshield",
|
|
382
|
-
vehicles_shotgun_door: "Vehicles: shotgun on the door",
|
|
383
|
-
vehicles_ram_front: "Vehicles: ram the front",
|
|
384
|
-
vehicles_engine_fire: "Vehicles: fire on the engine",
|
|
385
|
-
vehicles_fuel_tank: "Vehicles: shoot the fuel tank (leaks, ignites, explodes)",
|
|
386
|
-
vehicles_unbolt_wheel: "Vehicles: unbolt the wheel with the wrench",
|
|
387
|
-
vehicles_tear_bumper: "Vehicles: tear off the bumper",
|
|
388
|
-
vehicles_break_mirror: "Vehicles: break the mirror",
|
|
389
|
-
vehicles_kill_battery: "Vehicles: kill the battery (lights go out)",
|
|
390
|
-
vehicles_rollover: "Vehicles: roll over",
|
|
391
|
-
vehicles_melt: "Vehicles: melt (long fire burns the tyres)",
|
|
392
|
-
vehicles_drive_arcade: "Vehicles: arcade driving down a lane that ends in a wall",
|
|
393
|
-
vehicles_drive_semi: "Vehicles: semi-simulation braking on dry, wet and mud",
|
|
394
|
-
vehicles_burnt_wreck: "Vehicles: the burnt sedan comes out of the kit burnt, with the glass gone"
|
|
395
|
-
},
|
|
396
|
-
checks: {
|
|
397
|
-
windshield_holes: "Windshield: {0} hole(s), went through {1:b} (expected: 1, true)",
|
|
398
|
-
windshield_cracks: "Windshield cracked: {0} crack(s), state {1} (expected: damaged)",
|
|
399
|
-
door_marks: "Front left door: {0} hole(s), {1} dent(s), hp {2}/{3} (expected: 2 marks or more)",
|
|
400
|
-
front_crush: "Front crushed: bumper {0} dent(s), hood {1} (expected: 1 or more each)",
|
|
401
|
-
bumper_off: "The bumper came off: {0} (expected: bumper_front)",
|
|
402
|
-
engine_burning: "Engine: burning {0:b}, {1} \xB0C (expected: true, above 250)",
|
|
403
|
-
ignite_lines: "Ignite lines in the log: {0} (expected: 1 or more)",
|
|
404
|
-
tank_leaking: "Tank leaking: {0} leak(s), {1} L left (expected: 1 or more, below 55)",
|
|
405
|
-
ignited: "Ignited: {0} ignite line(s) (expected: 1 or more)",
|
|
406
|
-
exploded: "Exploded: {0} explosion(s) (expected: 1)",
|
|
407
|
-
wheel_wrench: "Wrench on the wheel: bolts {0:open}, {1} group(s), {2} detach line(s) (expected: open, 2 or more, 1)",
|
|
408
|
-
loose_parts: "Loose parts: {0} (expected: bumper_front)",
|
|
409
|
-
mirror_broken: "Left mirror: {0} (expected: broken)",
|
|
410
|
-
battery_before: "Before: lights on and it starts (expected: yes)",
|
|
411
|
-
battery_after: "No battery: lights {0:on}, start {1:b} (expected: off, false)",
|
|
412
|
-
rolled_over: "Rolled over: {0:b} (expected: true)",
|
|
413
|
-
roof: "Roof: hp {0}/{1}, {2} dent(s) (expected: below max, 1 or more)",
|
|
414
|
-
tyre_melt: "Tyre: {0}, melted {1}%, {2} melt line(s) (expected: melted or above 50%)",
|
|
415
|
-
arcade_crash: "Arcade: the wall is hit at {0} m/s (expected: the crash above 4 m/s, at most {1})",
|
|
416
|
-
arcade_bounce: "Arcade: the car bounces back at {0} m/s (expected: below 0)",
|
|
417
|
-
arcade_noise: "Arcade: {0} engine noise(s) and {1} crash noise(s) (expected: 1 or more each)",
|
|
418
|
-
semi_order: "Semi: braking from 80 km/h takes dry {0} m, wet {1} m, mud {2} m (expected: dry shorter than wet shorter than mud)",
|
|
419
|
-
semi_mud: "Semi: a 2WD car on mud {0:on} (expected: bogged on, a bog event)",
|
|
420
|
-
wreck_burnt: "Burnt sedan: burnt flag {0:b} (expected: yes)",
|
|
421
|
-
wreck_glass: "Burnt sedan: {0} glass zone(s), {1} still above 0 (expected: 1 or more, none above 0)"
|
|
422
|
-
}
|
|
423
|
-
},
|
|
424
|
-
"pt-BR": {
|
|
425
|
-
group: "Ve\xEDculos",
|
|
426
|
-
name: "\xC1rea de ve\xEDculos",
|
|
427
|
-
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" },
|
|
428
|
-
controls: { quality: "Qualidade", paint_by_seed: "Pintar pela semente", color_seed: "Semente da cor", palette: "Paleta", driving: "Dire\xE7\xE3o", surface: "Piso" },
|
|
429
|
-
hints: {
|
|
430
|
-
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",
|
|
431
|
-
paint_by_seed: "Desligado: cada ve\xEDculo mant\xE9m a pr\xF3pria pintura. Ligado: a cor da carroceria vem da semente e da paleta",
|
|
432
|
-
palette: "Auto: a paleta do grupo do ve\xEDculo (civil, ou militar para os do ex\xE9rcito)",
|
|
433
|
-
driving: "Arcade: o carro do jogo, simples e tolerante. Semi: corpo r\xEDgido com molas, atrito do pneu, marchas e pisos",
|
|
434
|
-
surface: "O ch\xE3o sob as rodas no modo semi (ader\xEAncia e aquaplanagem)"
|
|
435
|
-
},
|
|
436
|
-
options: {
|
|
437
|
-
quality: { high: "Alta", lite: "Leve" },
|
|
438
|
-
palette: { auto: "Auto", civil: "Civil", military: "Militar" },
|
|
439
|
-
driving: { arcade: "Arcade", semi: "Semissimula\xE7\xE3o" },
|
|
440
|
-
surface: { dry_asphalt: "Asfalto seco", wet_asphalt: "Asfalto molhado", dirt: "Terra", grass: "Grama", mud: "Lama", blood: "Sangue" }
|
|
441
|
-
},
|
|
442
|
-
actions: { repaint: "Repintar com a semente", drive_lane: "Percorrer a pista", clear_lane: "Tirar o carro da pista", honk: "Buzinar" },
|
|
443
|
-
layers: { damageables: "Caixas danific\xE1veis por material", zones: "Caixas das zonas por tipo", axes: "Eixos do quadro de sa\xEDda" },
|
|
444
|
-
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" },
|
|
445
|
-
words: { yes: "sim", no: "n\xE3o", open: "soltos", tight: "presos", on: "acesos", off: "apagados" },
|
|
446
|
-
lane: { crash: "Pista: o carro bateu no muro a {0} m/s", end: "Pista: o carro parou a {0} m do muro" },
|
|
447
|
-
scenarios: {
|
|
448
|
-
vehicles_9mm_windshield: "Ve\xEDculos: 9 mm atravessa o para-brisa",
|
|
449
|
-
vehicles_shotgun_door: "Ve\xEDculos: espingarda na porta",
|
|
450
|
-
vehicles_ram_front: "Ve\xEDculos: bater de frente",
|
|
451
|
-
vehicles_engine_fire: "Ve\xEDculos: fogo no motor",
|
|
452
|
-
vehicles_fuel_tank: "Ve\xEDculos: atirar no tanque (vaza, pega fogo, explode)",
|
|
453
|
-
vehicles_unbolt_wheel: "Ve\xEDculos: soltar a roda com a chave inglesa",
|
|
454
|
-
vehicles_tear_bumper: "Ve\xEDculos: arrancar o para-choque",
|
|
455
|
-
vehicles_break_mirror: "Ve\xEDculos: quebrar o retrovisor",
|
|
456
|
-
vehicles_kill_battery: "Ve\xEDculos: matar a bateria (far\xF3is apagam)",
|
|
457
|
-
vehicles_rollover: "Ve\xEDculos: capotar",
|
|
458
|
-
vehicles_melt: "Ve\xEDculos: derreter (fogo longo queima os pneus)",
|
|
459
|
-
vehicles_drive_arcade: "Ve\xEDculos: dire\xE7\xE3o arcade numa pista que acaba num muro",
|
|
460
|
-
vehicles_drive_semi: "Ve\xEDculos: frenagem da semissimula\xE7\xE3o no seco, no molhado e na lama",
|
|
461
|
-
vehicles_burnt_wreck: "Ve\xEDculos: o sed\xE3 queimado sai do kit queimado, sem os vidros"
|
|
462
|
-
},
|
|
463
|
-
checks: {
|
|
464
|
-
windshield_holes: "Para-brisa: {0} furo(s), atravessou {1:b} (esperado: 1, sim)",
|
|
465
|
-
windshield_cracks: "Para-brisa trincou: {0} trinca(s), estado {1} (esperado: damaged)",
|
|
466
|
-
door_marks: "Porta dianteira esquerda: {0} furo(s), {1} amassado(s), vida {2}/{3} (esperado: 2 marcas ou mais)",
|
|
467
|
-
front_crush: "Frente amassada: para-choque {0} amassado(s), cap\xF4 {1} (esperado: 1 ou mais cada)",
|
|
468
|
-
bumper_off: "O para-choque soltou: {0} (esperado: bumper_front)",
|
|
469
|
-
engine_burning: "Motor: pegando fogo {0:b}, {1} \xB0C (esperado: sim, acima de 250)",
|
|
470
|
-
ignite_lines: "Linhas de igni\xE7\xE3o no log: {0} (esperado: 1 ou mais)",
|
|
471
|
-
tank_leaking: "Tanque vazando: {0} vazamento(s), {1} L restantes (esperado: 1 ou mais, menos de 55)",
|
|
472
|
-
ignited: "Pegou fogo: {0} linha(s) de igni\xE7\xE3o (esperado: 1 ou mais)",
|
|
473
|
-
exploded: "Explodiu: {0} explos\xE3o (esperado: 1)",
|
|
474
|
-
wheel_wrench: "Chave na roda: parafusos {0:open}, {1} grupo(s), {2} linha(s) de soltura (esperado: soltos, 2 ou mais, 1)",
|
|
475
|
-
loose_parts: "Pe\xE7as soltas: {0} (esperado: bumper_front)",
|
|
476
|
-
mirror_broken: "Retrovisor esquerdo: {0} (esperado: broken)",
|
|
477
|
-
battery_before: "Antes: far\xF3is acesos e d\xE1 partida (esperado: sim)",
|
|
478
|
-
battery_after: "Sem bateria: far\xF3is {0:on}, partida {1:b} (esperado: apagados, n\xE3o)",
|
|
479
|
-
rolled_over: "Capotou: {0:b} (esperado: sim)",
|
|
480
|
-
roof: "Teto: vida {0}/{1}, {2} amassado(s) (esperado: menos que o m\xE1ximo, 1 ou mais)",
|
|
481
|
-
tyre_melt: "Pneu: {0}, derretido {1}%, {2} linha(s) de derretimento (esperado: melted ou mais de 50%)",
|
|
482
|
-
arcade_crash: "Arcade: bate no muro a {0} m/s (esperado: a batida acima de 4 m/s, no m\xE1ximo {1})",
|
|
483
|
-
arcade_bounce: "Arcade: o carro volta a {0} m/s (esperado: abaixo de 0)",
|
|
484
|
-
arcade_noise: "Arcade: {0} ru\xEDdo(s) de motor e {1} de batida (esperado: 1 ou mais de cada)",
|
|
485
|
-
semi_order: "Semi: frear de 80 km/h leva no seco {0} m, no molhado {1} m, na lama {2} m (esperado: seco menor que molhado menor que lama)",
|
|
486
|
-
semi_mud: "Semi: um carro 2WD na lama {0:on} (esperado: atolado, com evento de atolamento)",
|
|
487
|
-
wreck_burnt: "Sed\xE3 queimado: marca de queimado {0:b} (esperado: sim)",
|
|
488
|
-
wreck_glass: "Sed\xE3 queimado: {0} zona(s) de vidro, {1} ainda acima de 0 (esperado: 1 ou mais, nenhuma acima de 0)"
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
};
|
|
492
|
-
function fill(text, locale, template, ...values) {
|
|
493
|
-
return template.replace(/\{(\d+)(?::(b|open|on))?\}/g, (_match, index, kind) => {
|
|
494
|
-
const value = values[Number(index)];
|
|
495
|
-
if (kind === "b") return locale === "en" ? String(value) : value ? text.words.yes : text.words.no;
|
|
496
|
-
if (kind === "open") return value ? text.words.open : text.words.tight;
|
|
497
|
-
if (kind === "on") return value ? text.words.on : text.words.off;
|
|
498
|
-
return String(value);
|
|
499
|
-
});
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
// src/test-area/scenarios.ts
|
|
503
|
-
var add = (p, dx, dy, dz) => [p[0] + dx, p[1] + dy, p[2] + dz];
|
|
504
|
-
var createScenarios = (deps) => {
|
|
505
|
-
const { text, locale } = deps;
|
|
506
|
-
const say = (id, ...values) => fill(text, locale, text.checks[id], ...values);
|
|
507
|
-
const scenario = (id, run) => ({ id, name: text.scenarios[id] ?? id, run });
|
|
508
|
-
const put = (room, item, z = -7) => {
|
|
509
|
-
const spawned = room.spawn(item, [0, 0, z], 0);
|
|
510
|
-
return { id: spawned.objectId, d: spawned.root.userData.destructible };
|
|
511
|
-
};
|
|
512
|
-
const part = (room, v, id) => room.world.part(v.id, id);
|
|
513
|
-
const events = (room, type, v) => room.log.entries({ type, objectId: v.id }).length;
|
|
514
|
-
return [
|
|
515
|
-
scenario("vehicles_9mm_windshield", ({ room, step, expect }) => {
|
|
516
|
-
const v = put(room, "hatch");
|
|
517
|
-
const c = v.d.partWorldCenter("windshield");
|
|
518
|
-
room.setTool("pistol_9mm");
|
|
519
|
-
room.waitUntilReady();
|
|
520
|
-
room.look(add(c, 0, 0.5, 3), c);
|
|
521
|
-
step(0.1);
|
|
522
|
-
room.use();
|
|
523
|
-
step(0.4);
|
|
524
|
-
const w = part(room, v, "windshield");
|
|
525
|
-
expect(w.holes.length >= 1 && !!w.holes[0]?.through, say("windshield_holes", w.holes.length, w.holes[0]?.through));
|
|
526
|
-
expect(w.cracks.length >= 1 && w.status === "damaged", say("windshield_cracks", w.cracks.length, w.status));
|
|
527
|
-
}),
|
|
528
|
-
scenario("vehicles_shotgun_door", ({ room, step, expect }) => {
|
|
529
|
-
const v = put(room, "sedan");
|
|
530
|
-
const c = v.d.partWorldCenter("door_front_left");
|
|
531
|
-
room.setTool("shotgun_pump_12");
|
|
532
|
-
room.waitUntilReady();
|
|
533
|
-
room.look(add(c, 2.5, 0, 0), c);
|
|
534
|
-
step(0.1);
|
|
535
|
-
room.use();
|
|
536
|
-
step(0.4);
|
|
537
|
-
const p = part(room, v, "door_front_left");
|
|
538
|
-
const marks = p.holes.length + p.dents.length;
|
|
539
|
-
expect(marks >= 2 && p.hp < p.maxHp, say("door_marks", p.holes.length, p.dents.length, Math.round(p.hp), p.maxHp));
|
|
540
|
-
}),
|
|
541
|
-
scenario("vehicles_ram_front", ({ room, step, expect }) => {
|
|
542
|
-
const v = put(room, "hatch");
|
|
543
|
-
const c = v.d.partWorldCenter("bumper_front");
|
|
544
|
-
room.world.hit(v.id, { type: "blunt", point: c, direction: [0, 0, -1], energy: 45e3, radius: 0.6, source: "teste" });
|
|
545
|
-
step(2.5);
|
|
546
|
-
const b = part(room, v, "bumper_front");
|
|
547
|
-
const h = part(room, v, "hood");
|
|
548
|
-
expect(b.dents.length >= 1 && h.dents.length >= 1, say("front_crush", b.dents.length, h.dents.length));
|
|
549
|
-
const det = v.d.detachedParts();
|
|
550
|
-
expect(det.indexOf("bumper_front") >= 0, say("bumper_off", det.join(", ")));
|
|
551
|
-
}),
|
|
552
|
-
scenario("vehicles_engine_fire", ({ room, step, expect }) => {
|
|
553
|
-
const v = put(room, "pickup");
|
|
554
|
-
const c = v.d.partWorldCenter("engine");
|
|
555
|
-
for (let i = 0; i < 80; i++) {
|
|
556
|
-
room.world.hit(v.id, { type: "fire", point: c, temperature: 900, radius: 0.5, duration: 0.1, source: "teste" });
|
|
557
|
-
step(0.1);
|
|
558
|
-
}
|
|
559
|
-
const e = part(room, v, "engine");
|
|
560
|
-
expect(e.burning && e.temperature > 250, say("engine_burning", e.burning, Math.round(e.temperature)));
|
|
561
|
-
expect(events(room, "ignite", v) >= 1, say("ignite_lines", events(room, "ignite", v)));
|
|
562
|
-
}),
|
|
563
|
-
scenario("vehicles_fuel_tank", ({ room, step, expect }) => {
|
|
564
|
-
const v = put(room, "hatch");
|
|
565
|
-
const spec = v.d.describe().parts.filter((p) => p.id === "fuel_tank")[0];
|
|
566
|
-
const c = v.d.partWorldCenter("fuel_tank");
|
|
567
|
-
const low = [c[0], c[1] - spec.shape.size[1] / 2 + 0.06, c[2]];
|
|
568
|
-
room.setTool("pistol_9mm");
|
|
569
|
-
room.waitUntilReady();
|
|
570
|
-
room.look(add(low, 0, 0, -2.5), low);
|
|
571
|
-
step(0.1);
|
|
572
|
-
room.use();
|
|
573
|
-
step(1);
|
|
574
|
-
const t = part(room, v, "fuel_tank");
|
|
575
|
-
const nl = v.d.status().leaking.length;
|
|
576
|
-
expect(nl >= 1 && t.liters < 55, say("tank_leaking", nl, t.liters.toFixed(1)));
|
|
577
|
-
for (let i = 0; i < 160 && !events(room, "explode", v); i++) {
|
|
578
|
-
room.world.hit(v.id, { type: "fire", point: c, temperature: 900, radius: 0.6, duration: 0.1, source: "teste" });
|
|
579
|
-
step(0.1);
|
|
580
|
-
}
|
|
581
|
-
expect(events(room, "ignite", v) >= 1, say("ignited", events(room, "ignite", v)));
|
|
582
|
-
step(2);
|
|
583
|
-
expect(events(room, "explode", v) === 1 && v.d.status().exploded, say("exploded", events(room, "explode", v)));
|
|
584
|
-
const vc = v.d.partWorldCenter("engine");
|
|
585
|
-
room.look(add(vc, 4, 0, -3), vc);
|
|
586
|
-
}),
|
|
587
|
-
scenario("vehicles_unbolt_wheel", ({ room, step, expect }) => {
|
|
588
|
-
const v = put(room, "sedan");
|
|
589
|
-
const joint = "wheel_front_left~underbody";
|
|
590
|
-
const c = v.d.partWorldCenter("wheel_front_left");
|
|
591
|
-
room.setTool("wrench");
|
|
592
|
-
for (let k = 0; k < 120 && room.world.disassemble(v.id, joint, "wrench", 0.1) < 1; k++) step(0.1);
|
|
593
|
-
step(1);
|
|
594
|
-
const st = room.world.state(v.id);
|
|
595
|
-
const open = !!st.joints.filter((j) => j.id === joint)[0]?.open;
|
|
596
|
-
expect(open && st.groups.length >= 2 && events(room, "detach", v) >= 1, say("wheel_wrench", open, st.groups.length, events(room, "detach", v)));
|
|
597
|
-
step(2);
|
|
598
|
-
room.look(add(c, 2.2, 0, 0.2), add(c, 0, -0.1, 0));
|
|
599
|
-
}),
|
|
600
|
-
scenario("vehicles_tear_bumper", ({ room, step, expect }) => {
|
|
601
|
-
const v = put(room, "hatch");
|
|
602
|
-
const c = v.d.partWorldCenter("bumper_front");
|
|
603
|
-
room.world.hit(v.id, { type: "blunt", point: c, direction: [0, 0, -1], energy: 3e4, radius: 0.6, source: "teste" });
|
|
604
|
-
step(2.5);
|
|
605
|
-
const det = v.d.detachedParts();
|
|
606
|
-
expect(det.indexOf("bumper_front") >= 0, say("loose_parts", det.join(", ")));
|
|
607
|
-
room.look(add(c, 2, 0, 3.5), add(c, 0, 0, -0.5));
|
|
608
|
-
}),
|
|
609
|
-
scenario("vehicles_break_mirror", ({ room, step, expect }) => {
|
|
610
|
-
const v = put(room, "sedan");
|
|
611
|
-
const c = v.d.partWorldCenter("mirror_left");
|
|
612
|
-
room.world.hit(v.id, { type: "blunt", point: c, direction: [-1, 0, 0], energy: 2500, radius: 0.1, source: "teste" });
|
|
613
|
-
step(2);
|
|
614
|
-
const m = part(room, v, "mirror_left");
|
|
615
|
-
expect(m.status === "broken" && v.d.state().groups.length >= 1, say("mirror_broken", m.status));
|
|
616
|
-
}),
|
|
617
|
-
scenario("vehicles_kill_battery", ({ room, step, expect }) => {
|
|
618
|
-
const v = put(room, "pickup");
|
|
619
|
-
expect(v.d.status().lightsOn && v.d.status().canStart, say("battery_before"));
|
|
620
|
-
room.playAnimation("open_hood", v.id);
|
|
621
|
-
step(1.5);
|
|
622
|
-
for (let i = 0; i < 80 && room.world.disassemble(v.id, "battery~underbody", "wrench", 0.1) < 1; i++) step(0.1);
|
|
623
|
-
step(0.5);
|
|
624
|
-
const s = v.d.status();
|
|
625
|
-
expect(!s.lightsOn && !s.canStart && !s.canDrive, say("battery_after", s.lightsOn, s.canStart));
|
|
626
|
-
}),
|
|
627
|
-
scenario("vehicles_rollover", ({ room, step, expect }) => {
|
|
628
|
-
const v = put(room, "sedan");
|
|
629
|
-
const maxRoof = v.d.describe().parts.filter((p) => p.id === "roof")[0].hp;
|
|
630
|
-
v.d.rollover();
|
|
631
|
-
step(8);
|
|
632
|
-
const roof = part(room, v, "roof");
|
|
633
|
-
expect(!!v.d.vehicle.flipped, say("rolled_over", !!v.d.vehicle.flipped));
|
|
634
|
-
expect(roof.hp < maxRoof && roof.dents.length >= 1, say("roof", Math.round(roof.hp), maxRoof, roof.dents.length));
|
|
635
|
-
}),
|
|
636
|
-
scenario("vehicles_melt", ({ room, step, expect }) => {
|
|
637
|
-
const v = put(room, "hatch");
|
|
638
|
-
const c = v.d.partWorldCenter("tyre_front_left");
|
|
639
|
-
for (let i = 0; i < 60; i++) {
|
|
640
|
-
room.world.hit(v.id, { type: "fire", point: c, temperature: 900, radius: 0.7, duration: 0.1, source: "teste" });
|
|
641
|
-
step(0.1);
|
|
642
|
-
}
|
|
643
|
-
step(30);
|
|
644
|
-
const t = part(room, v, "tyre_front_left");
|
|
645
|
-
expect(events(room, "melt", v) >= 1 && (t.status === "melted" || t.melt > 0.5), say("tyre_melt", t.status, Math.round(t.melt * 100), events(room, "melt", v)));
|
|
646
|
-
}),
|
|
647
|
-
// new in the kit: the burnt wreck comes ready from the kit (the fleet's `burnt` scenario built into the vehicle), no damage applied by the area
|
|
648
|
-
scenario("vehicles_burnt_wreck", ({ room, expect }) => {
|
|
649
|
-
const c = room.spawn("sedan_burnt", [0, 0, -7], 0).root.userData.vehicle;
|
|
650
|
-
expect(c.zoneState.flags.burnt === true, say("wreck_burnt", c.zoneState.flags.burnt === true));
|
|
651
|
-
const glass = c.zones.filter((z) => z.type === "glass");
|
|
652
|
-
const up = glass.filter((z) => (c.zoneState.health[z.id] ?? 100) > 0);
|
|
653
|
-
expect(glass.length >= 1 && up.length === 0, say("wreck_glass", glass.length, up.length));
|
|
654
|
-
}),
|
|
655
|
-
// new in the kit: the drivers, headless (no vehicle is spawned)
|
|
656
|
-
scenario("vehicles_drive_arcade", ({ expect }) => {
|
|
657
|
-
const driver = createArcadeDriver();
|
|
658
|
-
const wallX = 60;
|
|
659
|
-
driver.place(0, 0, 0);
|
|
660
|
-
const world = { blocked: (x, z, yaw) => vehicleCorners(driver.spec, x, z, yaw).some(([px]) => px >= wallX) };
|
|
661
|
-
let crashSpeed = 0;
|
|
662
|
-
let engine = 0;
|
|
663
|
-
let crashNoise = 0;
|
|
664
|
-
let after = 0;
|
|
665
|
-
for (let t = 0; t < 12; t += 1 / 60) {
|
|
666
|
-
const { events: out } = driver.step({ throttle: 1, brake: 0, steer: 0, handbrake: false }, 1 / 60, world);
|
|
667
|
-
for (const e of out) {
|
|
668
|
-
if (e.type === "crash" && !crashSpeed) {
|
|
669
|
-
crashSpeed = e.speed;
|
|
670
|
-
after = driver.state.v;
|
|
671
|
-
}
|
|
672
|
-
if (e.type === "noise" && e.source === "engine") engine++;
|
|
673
|
-
if (e.type === "noise" && e.source === "crash") crashNoise++;
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
expect(crashSpeed > driver.spec.crash.damageSpeed && crashSpeed <= driver.spec.maxForward + 1e-6, say("arcade_crash", crashSpeed.toFixed(1), driver.spec.maxForward));
|
|
677
|
-
expect(after < 0, say("arcade_bounce", after.toFixed(1)));
|
|
678
|
-
expect(engine >= 1 && crashNoise >= 1, say("arcade_noise", engine, crashNoise));
|
|
679
|
-
}),
|
|
680
|
-
scenario("vehicles_drive_semi", ({ expect }) => {
|
|
681
|
-
const surfaces = createSurfaces();
|
|
682
|
-
const coast = { throttle: 0, brake: 0, steer: 0, handbrake: false };
|
|
683
|
-
const dt = 1 / 60;
|
|
684
|
-
const world = (surface) => ({ ground: () => ({ surface }) });
|
|
685
|
-
const braking = (surface) => {
|
|
686
|
-
const driver = createSemiSimDriver({}, { surfaces });
|
|
687
|
-
driver.state.u = 80 / 3.6;
|
|
688
|
-
for (let t = 0; t < 1; t += dt) driver.step({ ...coast, throttle: 0.3 }, dt, world(surface));
|
|
689
|
-
driver.state.u = 80 / 3.6;
|
|
690
|
-
const start = driver.state.x;
|
|
691
|
-
for (let t = 0; t < 30 && driver.state.u > 0.1; t += dt) driver.step({ ...coast, brake: 1 }, dt, world(surface));
|
|
692
|
-
return driver.state.x - start;
|
|
693
|
-
};
|
|
694
|
-
const dry = braking("dry_asphalt");
|
|
695
|
-
const wet = braking("wet_asphalt");
|
|
696
|
-
const mud = braking("mud");
|
|
697
|
-
expect(dry < wet && wet < mud, say("semi_order", dry.toFixed(1), wet.toFixed(1), mud.toFixed(1)));
|
|
698
|
-
const bog = createSemiSimDriver({}, { surfaces });
|
|
699
|
-
let bogged = 0;
|
|
700
|
-
for (let t = 0; t < 8; t += dt) bogged += bog.step({ ...coast, throttle: 1 }, dt, world("mud")).events.filter((e) => e.type === "bogged").length;
|
|
701
|
-
expect(bog.state.bogged && bogged >= 1, say("semi_mud", bog.state.bogged));
|
|
702
|
-
})
|
|
703
|
-
];
|
|
704
|
-
};
|
|
705
|
-
|
|
706
|
-
// src/test-area/module.ts
|
|
707
|
-
import { LIBRARY_BANK } from "@adatechnology/game-audio";
|
|
708
|
-
|
|
709
|
-
// src/test-area/types.ts
|
|
710
|
-
var defaultEffectIds = {
|
|
711
|
-
ignite: "vehicle_fire",
|
|
712
|
-
explode: "car_fuel_tank",
|
|
713
|
-
glass: "glass_burst",
|
|
714
|
-
sparks: "impact_metal",
|
|
715
|
-
leak: "vehicle_leak",
|
|
716
|
-
leakIgnited: "vehicle_leak_ignited",
|
|
717
|
-
exhaust: "vehicle_exhaust"
|
|
718
|
-
};
|
|
719
|
-
var defaultSoundIds = { engine: "engine_saw", horn: "horn", crash: "sfx_impact" };
|
|
720
|
-
var DEFAULT_TEST_WRECKS = [
|
|
721
|
-
{ key: "sedan_burnt", vehicle: "midsize_sedan", scenario: "burnt" },
|
|
722
|
-
{ key: "hatch_burnt", vehicle: "compact_hatch", scenario: "burnt" }
|
|
723
|
-
];
|
|
724
|
-
|
|
725
|
-
// src/test-area/module.ts
|
|
726
|
-
var VEHICLES_AREA_ID = "vehicles_area";
|
|
727
|
-
var DEFAULT_TEST_VEHICLES = ["compact_hatch", "midsize_sedan", "midsize_pickup", "sport_bike", "box_truck", "city_bus", "battle_tank", "attack_helicopter"];
|
|
728
|
-
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" };
|
|
729
|
-
var itemId = (key) => ITEM_OF[key] ?? key;
|
|
730
|
-
var LANE = { z: 11, from: -12, wall: 13 };
|
|
731
|
-
var MATERIAL_COLOURS = { metal: 13421772, glass: 4500223, rubber: 16750848, fuel: 16724736 };
|
|
732
|
-
var ZONE_COLOURS = { door: 5627221, hood: 14540083, lid: 14522675, bumper: 14505437, glass: 4500223, light: 16777215, wheel: 16742195 };
|
|
733
|
-
var BODY_HEIGHT = 1.7;
|
|
734
|
-
function createVehiclesTestModule(options = {}) {
|
|
735
|
-
const locale = options.locale ?? "pt-BR";
|
|
736
|
-
const text = TEXT[locale] ?? TEXT["pt-BR"];
|
|
737
|
-
const keys = options.vehicles ?? DEFAULT_TEST_VEHICLES;
|
|
738
|
-
const wrecks = options.wrecks ?? DEFAULT_TEST_WRECKS;
|
|
739
|
-
const entries = [...keys.map((key) => ({ key, id: itemId(key) })), ...wrecks.map((w) => ({ key: w.vehicle, id: w.key, scenario: w.scenario }))];
|
|
740
|
-
const effectIds = { ...defaultEffectIds, ...options.effects };
|
|
741
|
-
const soundIds = { ...defaultSoundIds, ...options.sounds };
|
|
742
|
-
const settings = { quality: "high", paint: false, seed: 1, palette: "auto", driving: "arcade", surface: "dry_asphalt" };
|
|
743
|
-
const kits = /* @__PURE__ */ new WeakMap();
|
|
744
|
-
const placed = /* @__PURE__ */ new Map();
|
|
745
|
-
let host = null;
|
|
746
|
-
let three = null;
|
|
747
|
-
let counter = 0;
|
|
748
|
-
let helpersShown = "";
|
|
749
|
-
let axes = [];
|
|
750
|
-
let lane = null;
|
|
751
|
-
const effects = () => host?.effects;
|
|
752
|
-
const audio = () => host?.audio;
|
|
753
|
-
let engineLoop;
|
|
754
|
-
const stopEngine = () => {
|
|
755
|
-
engineLoop?.stop();
|
|
756
|
-
engineLoop = void 0;
|
|
757
|
-
};
|
|
758
|
-
const kitFor = (THREE) => {
|
|
759
|
-
three = THREE;
|
|
760
|
-
let kit = kits.get(THREE);
|
|
761
|
-
if (!kit) {
|
|
762
|
-
if (options.createKit) kit = options.createKit(THREE);
|
|
763
|
-
else {
|
|
764
|
-
kit = createVehicleKit({ THREE, locale, ...options.document ? { document: options.document } : {} });
|
|
765
|
-
installFleet(kit);
|
|
766
|
-
}
|
|
767
|
-
kits.set(THREE, kit);
|
|
768
|
-
}
|
|
769
|
-
return kit;
|
|
770
|
-
};
|
|
771
|
-
const paletteFor = (key) => settings.palette === "civil" ? paintPalettes2.civil : settings.palette === "military" ? paintPalettes2.military : paletteOf2(key);
|
|
772
|
-
const paintOf = (entry) => {
|
|
773
|
-
if (!settings.paint) return;
|
|
774
|
-
const color = colorFromSeed2(settings.seed + entry.index, paletteFor(entry.key));
|
|
775
|
-
if (!entry.paint) {
|
|
776
|
-
const vehicle = entry.item.vehicle;
|
|
777
|
-
const primary = primaryPaint(entry.item.vehicle);
|
|
778
|
-
if (!primary) return;
|
|
779
|
-
entry.paint = primary.clone();
|
|
780
|
-
vehicle.vehicle.traverse((mesh) => {
|
|
781
|
-
if (!mesh.isMesh) return;
|
|
782
|
-
if (Array.isArray(mesh.material)) mesh.material = mesh.material.map((m) => m === primary ? entry.paint : m);
|
|
783
|
-
else if (mesh.material === primary) mesh.material = entry.paint;
|
|
784
|
-
});
|
|
785
|
-
}
|
|
786
|
-
entry.paint.color.set(color);
|
|
787
|
-
};
|
|
788
|
-
const item = ({ key, id: itemKey, scenario }) => ({
|
|
789
|
-
id: itemKey,
|
|
790
|
-
name: text.items[itemKey] ?? key,
|
|
791
|
-
movable: false,
|
|
792
|
-
// the first build of a vehicle takes most of a second
|
|
793
|
-
slow: true,
|
|
794
|
-
build: (THREE) => {
|
|
795
|
-
const kit = kitFor(THREE);
|
|
796
|
-
const built = buildVehicleItem(kit, key, { lod: settings.quality === "lite", ...scenario ? { scenario } : {} });
|
|
797
|
-
built.root.userData.kit = kit;
|
|
798
|
-
const entry = { root: built.root, key, ...scenario ? { scenario } : {}, item: built, index: counter++, helpers: [] };
|
|
799
|
-
placed.set(built.root.uuid, entry);
|
|
800
|
-
measure(entry);
|
|
801
|
-
paintOf(entry);
|
|
802
|
-
return built.root;
|
|
803
|
-
},
|
|
804
|
-
impl: (root, id) => {
|
|
805
|
-
const r = root;
|
|
806
|
-
const kit = r.userData.kit;
|
|
807
|
-
const d = createVehicleDestructible(kit, r.userData.vehicle, { id, frame: root, name: text.items[itemKey] ?? key });
|
|
808
|
-
tapEffects(d);
|
|
809
|
-
r.userData.destructible = d;
|
|
810
|
-
return d;
|
|
811
|
-
},
|
|
812
|
-
...clipsOf(key) ? { animations: clipsOf(key) } : {}
|
|
813
|
-
});
|
|
814
|
-
const clipCache = /* @__PURE__ */ new Map();
|
|
815
|
-
const clipsOf = (key) => {
|
|
816
|
-
const THREE = options.three ?? globalThis.THREE;
|
|
817
|
-
if (!THREE) return void 0;
|
|
818
|
-
if (!clipCache.has(key)) {
|
|
819
|
-
const kit = kitFor(THREE);
|
|
820
|
-
const built = buildVehicleItem(kit, key);
|
|
821
|
-
const d = createVehicleDestructible(kit, built.vehicle, { id: `${itemId(key)}_clips`, frame: built.root, headless: true });
|
|
822
|
-
clipCache.set(key, d.animations());
|
|
823
|
-
kit.dispose(built.vehicle);
|
|
824
|
-
}
|
|
825
|
-
return clipCache.get(key);
|
|
826
|
-
};
|
|
827
|
-
const tapEffects = (d) => {
|
|
828
|
-
const request = (events) => {
|
|
829
|
-
const fx = effects();
|
|
830
|
-
if (!fx) return;
|
|
831
|
-
for (const e of events) {
|
|
832
|
-
const position = e.point ?? e.worldPoint;
|
|
833
|
-
if (!position) continue;
|
|
834
|
-
if (e.type === "ignite") fx.play({ effect: /fuel_tank|tank/.test(e.part ?? "") ? effectIds.leakIgnited : effectIds.ignite, point: position });
|
|
835
|
-
else if (e.type === "explode") fx.play({ effect: effectIds.explode, point: position });
|
|
836
|
-
else if (e.type === "leak") fx.play({ effect: effectIds.leak, point: position });
|
|
837
|
-
else if (e.type === "fracture" && /glass|window|windshield/.test(e.part ?? "")) fx.play({ effect: effectIds.glass, point: position });
|
|
838
|
-
else if (e.type === "hole") fx.play({ effect: /glass|window|windshield/.test(e.part ?? "") ? effectIds.glass : effectIds.sparks, point: position });
|
|
839
|
-
}
|
|
840
|
-
};
|
|
841
|
-
const hit = d.hit.bind(d);
|
|
842
|
-
d.hit = (h) => {
|
|
843
|
-
const events = hit(h);
|
|
844
|
-
request(events);
|
|
845
|
-
return events;
|
|
846
|
-
};
|
|
847
|
-
if (d.update) {
|
|
848
|
-
const update = d.update.bind(d);
|
|
849
|
-
d.update = (dt) => {
|
|
850
|
-
const events = update(dt);
|
|
851
|
-
request(events);
|
|
852
|
-
return events;
|
|
853
|
-
};
|
|
854
|
-
}
|
|
855
|
-
};
|
|
856
|
-
const rootMatrix = (entry) => {
|
|
857
|
-
const m = new three.Matrix4().copy(entry.root.matrixWorld).invert();
|
|
858
|
-
return m.multiply(entry.item.vehicle.vehicle.matrixWorld);
|
|
859
|
-
};
|
|
860
|
-
const measure = (entry) => {
|
|
861
|
-
if (entry.size) return entry.size;
|
|
862
|
-
entry.root.updateMatrixWorld(true);
|
|
863
|
-
const inverse = new three.Matrix4().copy(entry.root.matrixWorld).invert();
|
|
864
|
-
const bounds = new three.Box3();
|
|
865
|
-
const low = new three.Box3();
|
|
866
|
-
const piece = new three.Box3();
|
|
867
|
-
const matrix = new three.Matrix4();
|
|
868
|
-
entry.root.traverse((mesh) => {
|
|
869
|
-
if (!mesh.isMesh || !mesh.geometry || mesh.userData.ghost) return;
|
|
870
|
-
for (let p = mesh; p; p = p.parent) if (p.visible === false) return;
|
|
871
|
-
if (mesh.userData.helper) return;
|
|
872
|
-
mesh.geometry.computeBoundingBox();
|
|
873
|
-
piece.copy(mesh.geometry.boundingBox).applyMatrix4(matrix.multiplyMatrices(inverse, mesh.matrixWorld));
|
|
874
|
-
bounds.union(piece);
|
|
875
|
-
if (piece.min.y < BODY_HEIGHT) low.union(piece);
|
|
876
|
-
});
|
|
877
|
-
const size = bounds.getSize(new three.Vector3());
|
|
878
|
-
entry.size = { length: size.z, width: size.x, height: size.y };
|
|
879
|
-
if (!low.isEmpty()) entry.footprint = { minX: low.min.x, maxX: low.max.x, minZ: low.min.z, maxZ: low.max.z };
|
|
880
|
-
return entry.size;
|
|
881
|
-
};
|
|
882
|
-
const clearHelpers = () => {
|
|
883
|
-
for (const entry of placed.values()) {
|
|
884
|
-
discardObject3D(entry.helpers);
|
|
885
|
-
entry.helpers = [];
|
|
886
|
-
}
|
|
887
|
-
discardObject3D(axes);
|
|
888
|
-
axes = [];
|
|
889
|
-
helpersShown = "";
|
|
890
|
-
};
|
|
891
|
-
const boxKey = () => `${host?.bench?.layer("damageables")}|${host?.bench?.layer("zones")}|${host?.bench?.layer("axes")}|${placed.size}`;
|
|
892
|
-
const refreshLayers = () => {
|
|
893
|
-
if (!host?.bench || !three) return;
|
|
894
|
-
clearHelpers();
|
|
895
|
-
const kit = [...placed.values()][0] && ([...placed.values()][0].root.userData.kit ?? void 0);
|
|
896
|
-
const mark = (entry, box, color) => {
|
|
897
|
-
const helper = new three.Box3Helper(box, color);
|
|
898
|
-
helper.userData.helper = true;
|
|
899
|
-
entry.root.add(helper);
|
|
900
|
-
entry.helpers.push(helper);
|
|
901
|
-
};
|
|
902
|
-
if (kit) {
|
|
903
|
-
for (const entry of placed.values()) {
|
|
904
|
-
if (!entry.root.parent) continue;
|
|
905
|
-
entry.root.updateMatrixWorld(true);
|
|
906
|
-
const matrix = rootMatrix(entry);
|
|
907
|
-
if (host.bench.layer("damageables")) for (const d of kit.damageables(entry.item.vehicle, matrix)) mark(entry, d.box, MATERIAL_COLOURS[d.material] ?? 16777215);
|
|
908
|
-
if (host.bench.layer("zones")) {
|
|
909
|
-
for (const zone of entry.item.vehicle.zones ?? []) {
|
|
910
|
-
if (!zone.box || zone.type === "internal") continue;
|
|
911
|
-
const box = new three.Box3(new three.Vector3(zone.box.x0, zone.box.y0, zone.box.z0), new three.Vector3(zone.box.x1, zone.box.y1, zone.box.z1)).applyMatrix4(matrix);
|
|
912
|
-
mark(entry, box, ZONE_COLOURS[zone.type] ?? 16777215);
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
if (host.bench.layer("axes")) {
|
|
918
|
-
const front = new three.ArrowHelper(new three.Vector3(0, 0, 1), new three.Vector3(0, 0.05, 0), 3, 3368703);
|
|
919
|
-
const right = new three.ArrowHelper(new three.Vector3(-1, 0, 0), new three.Vector3(0, 0.05, 0), 3, 16724787);
|
|
920
|
-
const up = new three.ArrowHelper(new three.Vector3(0, 1, 0), new three.Vector3(0, 0.05, 0), 3, 3394611);
|
|
921
|
-
for (const arrow of [front, right, up]) {
|
|
922
|
-
;
|
|
923
|
-
host.scene.add(arrow);
|
|
924
|
-
axes.push(arrow);
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
helpersShown = boxKey();
|
|
928
|
-
};
|
|
929
|
-
const laneBlocked = footprintBlocker(
|
|
930
|
-
() => lane?.group,
|
|
931
|
-
() => lane ? { minX: -lane.car.width / 2, maxX: lane.car.width / 2, minZ: -lane.car.length / 2, maxZ: lane.car.length / 2 } : null
|
|
932
|
-
);
|
|
933
|
-
const blocked = (x, z, radius) => {
|
|
934
|
-
for (const entry of placed.values()) if (entry.footprint && footprintBlocker(entry.root, entry.footprint)(x, z, radius)) return true;
|
|
935
|
-
return laneBlocked(x, z, radius);
|
|
936
|
-
};
|
|
937
|
-
const clearLane = () => {
|
|
938
|
-
stopEngine();
|
|
939
|
-
if (!lane) return;
|
|
940
|
-
lane.group.parent?.remove(lane.group);
|
|
941
|
-
lane.wall.parent?.remove(lane.wall);
|
|
942
|
-
lane = null;
|
|
943
|
-
};
|
|
944
|
-
const startLane = () => {
|
|
945
|
-
if (!host || !three) return;
|
|
946
|
-
clearLane();
|
|
947
|
-
const kit = kitFor(three);
|
|
948
|
-
const vehicles = createVehiclesThree(three, { kit });
|
|
949
|
-
const built = vehicles.build("compact_hatch", { quality: "lite", seed: settings.seed });
|
|
950
|
-
const wall = new three.Mesh(new three.BoxGeometry(0.6, 2, 6), new three.MeshStandardMaterial({ color: 7829367 }));
|
|
951
|
-
wall.position.set(LANE.wall + 0.3, 1, LANE.z);
|
|
952
|
-
const scene = host.scene;
|
|
953
|
-
scene.add(built.group, wall);
|
|
954
|
-
const mode = settings.driving === "semi" ? "semi" : "arcade";
|
|
955
|
-
const surfaces = createSurfaces2();
|
|
956
|
-
const idle = { throttle: 0, brake: 0, steer: 0, handbrake: false };
|
|
957
|
-
const state = { time: 0, exhaust: 0, ended: false, wasBlocked: false };
|
|
958
|
-
const arcade = createArcadeDriver2({ length: built.length, width: built.width });
|
|
959
|
-
const semi = createSemiSimDriver2({}, { surfaces });
|
|
960
|
-
const driver = mode === "semi" ? semi : arcade;
|
|
961
|
-
driver.place(LANE.from, LANE.z, 0);
|
|
962
|
-
const gap = () => LANE.wall - (driver.state.x + built.length / 2);
|
|
963
|
-
const decel = () => mode === "semi" ? 9 * surfaces.get(settings.surface).grip : arcade.spec.brakeAgainstMotion;
|
|
964
|
-
const brakeAt = () => lane ? lane.speed() * lane.speed() / (2 * decel()) + 3 : 3;
|
|
965
|
-
const pose = () => {
|
|
966
|
-
built.group.position.set(driver.state.x, 0, driver.state.z);
|
|
967
|
-
built.group.rotation.y = driver.state.yaw + Math.PI / 2;
|
|
968
|
-
};
|
|
969
|
-
pose();
|
|
970
|
-
engineLoop = audio()?.loop?.(soundIds.engine, { position: [driver.state.x, 0.5, driver.state.z] });
|
|
971
|
-
const log = (key, value) => host?.log(fill(text, locale, text.lane[key], value.toFixed(1)));
|
|
972
|
-
lane = {
|
|
973
|
-
car: built,
|
|
974
|
-
group: built.group,
|
|
975
|
-
wall,
|
|
976
|
-
mode,
|
|
977
|
-
done: false,
|
|
978
|
-
stopped: 0,
|
|
979
|
-
speed: () => "u" in driver.state ? driver.state.u : driver.state.v,
|
|
980
|
-
distance: () => driver.state.x - LANE.from,
|
|
981
|
-
step(dt) {
|
|
982
|
-
if (state.ended) return;
|
|
983
|
-
state.time += dt;
|
|
984
|
-
const braking = gap() < brakeAt() && state.time > 0.5;
|
|
985
|
-
const input = { ...idle, throttle: braking ? 0 : 1, brake: braking ? 1 : 0, steer: Math.sin(state.time * 1.4) * 0.15 };
|
|
986
|
-
if (mode === "arcade") {
|
|
987
|
-
const { events } = arcade.step(input, dt, { blocked: (x, z, yaw) => vehicleCorners2(arcade.spec, x, z, yaw).some(([px]) => px >= LANE.wall) });
|
|
988
|
-
for (const e of events) {
|
|
989
|
-
if (e.type !== "crash") continue;
|
|
990
|
-
log("crash", e.speed);
|
|
991
|
-
audio()?.play({ sound: soundIds.crash, position: [driver.state.x, 0.5, driver.state.z], volume: Math.min(1, e.speed / 15) });
|
|
992
|
-
}
|
|
993
|
-
} else {
|
|
994
|
-
semi.step(input, dt, { ground: () => ({ surface: settings.surface }) });
|
|
995
|
-
if (gap() <= 0) {
|
|
996
|
-
state.ended = true;
|
|
997
|
-
log("crash", Math.abs(semi.state.u));
|
|
998
|
-
audio()?.play({ sound: soundIds.crash, position: [driver.state.x, 0.5, driver.state.z], volume: Math.min(1, Math.abs(semi.state.u) / 15) });
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
pose();
|
|
1002
|
-
{
|
|
1003
|
-
const speed = Math.abs(lane?.speed() ?? 0);
|
|
1004
|
-
engineLoop?.set({ volume: state.ended ? 0 : 0.035 + speed * 3e-3, frequency: 38 + speed * 7, position: [driver.state.x, 0.5, driver.state.z] });
|
|
1005
|
-
}
|
|
1006
|
-
state.exhaust -= dt;
|
|
1007
|
-
const fx = effects();
|
|
1008
|
-
if (fx && state.exhaust <= 0 && Math.abs(lane?.speed() ?? 0) > 0.5) {
|
|
1009
|
-
state.exhaust = 0.4;
|
|
1010
|
-
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) });
|
|
1011
|
-
}
|
|
1012
|
-
if (!state.ended && state.time > 1 && Math.abs(lane?.speed() ?? 0) < 0.05) {
|
|
1013
|
-
state.ended = true;
|
|
1014
|
-
log("end", Math.max(0, gap()));
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
};
|
|
1018
|
-
};
|
|
1019
|
-
const onChange = (change) => {
|
|
1020
|
-
if (change.kind === "layer") return refreshLayers();
|
|
1021
|
-
if (change.kind === "action") {
|
|
1022
|
-
if (change.id === "repaint") for (const entry of placed.values()) paintOf(entry);
|
|
1023
|
-
else if (change.id === "drive_lane") startLane();
|
|
1024
|
-
else if (change.id === "clear_lane") clearLane();
|
|
1025
|
-
else if (change.id === "honk") audio()?.play({ sound: soundIds.horn, position: lane ? [lane.group.position.x, 1, lane.group.position.z] : void 0 });
|
|
1026
|
-
return;
|
|
1027
|
-
}
|
|
1028
|
-
switch (change.id) {
|
|
1029
|
-
case "quality":
|
|
1030
|
-
settings.quality = String(change.value);
|
|
1031
|
-
break;
|
|
1032
|
-
case "paint_by_seed":
|
|
1033
|
-
settings.paint = Boolean(change.value);
|
|
1034
|
-
for (const entry of placed.values()) paintOf(entry);
|
|
1035
|
-
break;
|
|
1036
|
-
case "color_seed":
|
|
1037
|
-
settings.seed = Number(change.value);
|
|
1038
|
-
for (const entry of placed.values()) paintOf(entry);
|
|
1039
|
-
break;
|
|
1040
|
-
case "palette":
|
|
1041
|
-
settings.palette = String(change.value);
|
|
1042
|
-
for (const entry of placed.values()) paintOf(entry);
|
|
1043
|
-
break;
|
|
1044
|
-
case "driving":
|
|
1045
|
-
settings.driving = String(change.value);
|
|
1046
|
-
break;
|
|
1047
|
-
case "surface":
|
|
1048
|
-
settings.surface = String(change.value);
|
|
1049
|
-
break;
|
|
1050
|
-
default:
|
|
1051
|
-
break;
|
|
1052
|
-
}
|
|
1053
|
-
};
|
|
1054
|
-
const frame = (dt) => {
|
|
1055
|
-
for (const [id, entry] of placed) {
|
|
1056
|
-
if (!entry.root.parent) {
|
|
1057
|
-
placed.delete(id);
|
|
1058
|
-
continue;
|
|
1059
|
-
}
|
|
1060
|
-
measure(entry);
|
|
1061
|
-
}
|
|
1062
|
-
if (host?.bench && boxKey() !== helpersShown && (host.bench.layer("damageables") || host.bench.layer("zones") || helpersShown !== "")) refreshLayers();
|
|
1063
|
-
lane?.step(dt);
|
|
1064
|
-
};
|
|
1065
|
-
const count2 = () => {
|
|
1066
|
-
let drawCalls = 0;
|
|
1067
|
-
let triangles = 0;
|
|
1068
|
-
for (const entry of placed.values()) {
|
|
1069
|
-
entry.root.traverse((mesh) => {
|
|
1070
|
-
if (!mesh.isMesh || mesh.userData.helper) return;
|
|
1071
|
-
for (let p = mesh; p; p = p.parent) if (p.visible === false) return;
|
|
1072
|
-
drawCalls += 1;
|
|
1073
|
-
const g = mesh.geometry;
|
|
1074
|
-
triangles += (g.index ? g.index.count : g.attributes.position.count) / 3;
|
|
1075
|
-
});
|
|
1076
|
-
}
|
|
1077
|
-
return { drawCalls, triangles: Math.round(triangles) };
|
|
1078
|
-
};
|
|
1079
|
-
const boxesOf = (material) => {
|
|
1080
|
-
let total = 0;
|
|
1081
|
-
for (const entry of placed.values()) {
|
|
1082
|
-
const kit = entry.root.userData.kit;
|
|
1083
|
-
if (kit) total += kit.damageables(entry.item.vehicle).filter((d) => d.material === material).length;
|
|
1084
|
-
}
|
|
1085
|
-
return total;
|
|
1086
|
-
};
|
|
1087
|
-
const sizeError = () => {
|
|
1088
|
-
let worst = 0;
|
|
1089
|
-
for (const entry of placed.values()) {
|
|
1090
|
-
const want = fleetDims2(entry.key);
|
|
1091
|
-
const got = entry.size;
|
|
1092
|
-
if (!want || !got || entry.scenario) continue;
|
|
1093
|
-
worst = Math.max(worst, Math.abs(got.length - want.length), Math.abs(got.width - want.width), Math.abs(got.height - want.height));
|
|
1094
|
-
}
|
|
1095
|
-
return Math.round(worst * 1e3);
|
|
1096
|
-
};
|
|
1097
|
-
return {
|
|
1098
|
-
id: VEHICLES_AREA_ID,
|
|
1099
|
-
name: text.name,
|
|
1100
|
-
ground: { surface: "road" },
|
|
1101
|
-
group: text.group,
|
|
1102
|
-
get placed() {
|
|
1103
|
-
return placed;
|
|
1104
|
-
},
|
|
1105
|
-
items: () => entries.map(item),
|
|
1106
|
-
area: () => {
|
|
1107
|
-
const gap = 1.6;
|
|
1108
|
-
const WIDE = 6;
|
|
1109
|
-
const rowOf = (list, z) => {
|
|
1110
|
-
let cursor = 0;
|
|
1111
|
-
const spots = list.map((entry) => {
|
|
1112
|
-
const width = fleetDims2(entry.key)?.width ?? 2;
|
|
1113
|
-
const x = cursor + width / 2;
|
|
1114
|
-
cursor += width + gap;
|
|
1115
|
-
return { id: entry.id, x };
|
|
1116
|
-
});
|
|
1117
|
-
return { spots, z, width: Math.max(0, cursor - gap) };
|
|
1118
|
-
};
|
|
1119
|
-
const front = rowOf(entries.filter((e) => (fleetDims2(e.key)?.width ?? 2) <= WIDE), 0);
|
|
1120
|
-
const back = rowOf(entries.filter((e) => (fleetDims2(e.key)?.width ?? 2) > WIDE), -16);
|
|
1121
|
-
const placements = [front, back].flatMap((row) => row.spots.map(({ id, x }) => ({ item: id, position: [x - row.width / 2, 0, row.z], rotationY: 0 })));
|
|
1122
|
-
return { placements, view: { eye: [0, 5, Math.max(14, front.width * 0.6)], target: [0, 1, -2] } };
|
|
1123
|
-
},
|
|
1124
|
-
controls: () => {
|
|
1125
|
-
const surfaceIds = createSurfaces2().surfaces.map((s) => s.id);
|
|
1126
|
-
return [
|
|
1127
|
-
{ kind: "choice", id: "quality", name: text.controls.quality, hint: text.hints.quality, options: ["high", "lite"].map((id) => ({ id, name: text.options.quality[id] })), default: "high" },
|
|
1128
|
-
{ kind: "boolean", id: "paint_by_seed", name: text.controls.paint_by_seed, hint: text.hints.paint_by_seed, default: false },
|
|
1129
|
-
{ kind: "number", id: "color_seed", name: text.controls.color_seed, min: 0, max: 999, step: 1, default: 1 },
|
|
1130
|
-
{ 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" },
|
|
1131
|
-
{ 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" },
|
|
1132
|
-
{ 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" },
|
|
1133
|
-
{ kind: "action", id: "repaint", name: text.actions.repaint },
|
|
1134
|
-
{ kind: "action", id: "drive_lane", name: text.actions.drive_lane },
|
|
1135
|
-
{ kind: "action", id: "clear_lane", name: text.actions.clear_lane },
|
|
1136
|
-
{ kind: "action", id: "honk", name: text.actions.honk }
|
|
1137
|
-
];
|
|
1138
|
-
},
|
|
1139
|
-
layers: () => ["damageables", "zones", "axes"].map((id) => ({ id, name: text.layers[id], default: false })),
|
|
1140
|
-
metrics: () => [
|
|
1141
|
-
{ id: "vehicles", name: text.metrics.vehicles, read: () => placed.size },
|
|
1142
|
-
{ id: "triangles", name: text.metrics.triangles, read: () => count2().triangles },
|
|
1143
|
-
{ id: "draw_calls", name: text.metrics.draw_calls, read: () => count2().drawCalls },
|
|
1144
|
-
{ id: "size_error_mm", name: text.metrics.size_error_mm, unit: "mm", read: sizeError },
|
|
1145
|
-
{ id: "metal", name: text.metrics.metal, read: () => boxesOf("metal") },
|
|
1146
|
-
{ id: "glass", name: text.metrics.glass, read: () => boxesOf("glass") },
|
|
1147
|
-
{ id: "rubber", name: text.metrics.rubber, read: () => boxesOf("rubber") },
|
|
1148
|
-
{ id: "fuel", name: text.metrics.fuel, read: () => boxesOf("fuel") },
|
|
1149
|
-
{ id: "speed", name: text.metrics.speed, unit: "km/h", digits: 0, read: () => Math.round(Math.abs(lane?.speed() ?? 0) * 3.6) },
|
|
1150
|
-
{ id: "lane", name: text.metrics.lane, unit: "m", digits: 1, read: () => lane?.distance() ?? 0 }
|
|
1151
|
-
],
|
|
1152
|
-
scenarios: () => createScenarios({ text, locale }),
|
|
1153
|
-
attach(lent) {
|
|
1154
|
-
host = lent;
|
|
1155
|
-
three = lent.THREE;
|
|
1156
|
-
const stopChange = lent.bench?.onChange(onChange);
|
|
1157
|
-
const stopFrame = lent.onFrame(frame);
|
|
1158
|
-
const stopBlocker = lent.addBlocker?.(blocked);
|
|
1159
|
-
lent.audio?.register?.(LIBRARY_BANK);
|
|
1160
|
-
return () => {
|
|
1161
|
-
stopChange?.();
|
|
1162
|
-
stopFrame();
|
|
1163
|
-
stopBlocker?.();
|
|
1164
|
-
clearHelpers();
|
|
1165
|
-
clearLane();
|
|
1166
|
-
host = null;
|
|
1167
|
-
};
|
|
1168
|
-
}
|
|
1169
|
-
};
|
|
1170
|
-
}
|
|
1171
|
-
function loadVehiclesTestModule(room, options = {}) {
|
|
1172
|
-
let three = options.three;
|
|
1173
|
-
if (!three) {
|
|
1174
|
-
room.loadModule({ id: "vehicles_probe", name: "Vehicles probe", group: "Vehicles", items: () => [], attach: (lent) => {
|
|
1175
|
-
three = lent.THREE;
|
|
1176
|
-
return () => void 0;
|
|
1177
|
-
} });
|
|
1178
|
-
}
|
|
1179
|
-
return room.loadModule(createVehiclesTestModule({ ...options, three }));
|
|
1180
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_TEST_VEHICLES,
|
|
3
|
+
DEFAULT_TEST_WRECKS,
|
|
4
|
+
LITE_CLASSES,
|
|
5
|
+
PAGE_ITEM_ALIASES,
|
|
6
|
+
PAGE_SCENARIO_ALIASES,
|
|
7
|
+
PRUNE_BELOW,
|
|
8
|
+
VEHICLES_AREA_ID,
|
|
9
|
+
VEHICLE_LOD_DISTANCE,
|
|
10
|
+
VEHICLE_SCENARIO_IDS,
|
|
11
|
+
classColor,
|
|
12
|
+
classify,
|
|
13
|
+
createCabinSmoke,
|
|
14
|
+
createVehicleLod,
|
|
15
|
+
createVehiclesTestModule,
|
|
16
|
+
createVehiclesThree,
|
|
17
|
+
defaultEffectIds,
|
|
18
|
+
defaultSoundIds,
|
|
19
|
+
fuse,
|
|
20
|
+
liteMaterials,
|
|
21
|
+
liteMeshes,
|
|
22
|
+
loadVehiclesTestModule
|
|
23
|
+
} from "./chunk-GAO7ZJP7.js";
|
|
1181
24
|
export {
|
|
1182
25
|
DEFAULT_TEST_VEHICLES,
|
|
1183
26
|
DEFAULT_TEST_WRECKS,
|
|
@@ -1186,9 +29,12 @@ export {
|
|
|
1186
29
|
PAGE_SCENARIO_ALIASES,
|
|
1187
30
|
PRUNE_BELOW,
|
|
1188
31
|
VEHICLES_AREA_ID,
|
|
32
|
+
VEHICLE_LOD_DISTANCE,
|
|
1189
33
|
VEHICLE_SCENARIO_IDS,
|
|
1190
34
|
classColor,
|
|
1191
35
|
classify,
|
|
36
|
+
createCabinSmoke,
|
|
37
|
+
createVehicleLod,
|
|
1192
38
|
createVehiclesTestModule,
|
|
1193
39
|
createVehiclesThree,
|
|
1194
40
|
defaultEffectIds,
|