@adatechnology/game-test-room 0.1.1 → 0.1.3
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/index.global.js +443 -48
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/index.global.js
CHANGED
|
@@ -1641,9 +1641,12 @@ var GameTestRoom = (() => {
|
|
|
1641
1641
|
var DMG_PER_J = 0.02;
|
|
1642
1642
|
var BLAST_COUPLING = 0.1;
|
|
1643
1643
|
var FLAMMABLE_FLUIDS = ["gas", "fuel"];
|
|
1644
|
+
var CUT_SLICE = 1;
|
|
1645
|
+
var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
|
|
1644
1646
|
var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
1645
1647
|
var isLost = (p) => p.st.status === "broken" || p.st.status === "melted" || p.st.status === "ashes";
|
|
1646
1648
|
var clone = (v) => structuredClone(v);
|
|
1649
|
+
var overloadRate = (stress) => Math.min(0.6, 0.12 + 0.8 * (stress - 1));
|
|
1647
1650
|
var updateBound = (o) => {
|
|
1648
1651
|
if (!o.parts.length) return;
|
|
1649
1652
|
const c = [0, 0, 0];
|
|
@@ -1793,6 +1796,7 @@ var GameTestRoom = (() => {
|
|
|
1793
1796
|
fuel0,
|
|
1794
1797
|
area: shapeArea(ps.shape),
|
|
1795
1798
|
thickness: partThickness(ps),
|
|
1799
|
+
cutFactor: cutFactorOf(ps.shape),
|
|
1796
1800
|
dims: shapeDims(ps.shape),
|
|
1797
1801
|
bound: shapeBound(ps.shape),
|
|
1798
1802
|
rt: c?.rt ?? { ...freshRuntime(), prevTemp: pst.temperature },
|
|
@@ -1953,7 +1957,10 @@ var GameTestRoom = (() => {
|
|
|
1953
1957
|
for (const g of o.st.groups) {
|
|
1954
1958
|
const ps = g.map((id) => o.byId.get(id)).filter((p) => !isLost(p) && !o.collapsed.has(p.spec.id));
|
|
1955
1959
|
if (ps.length === 0 || g.some((id) => o.byId.get(id).anchored) || !ps.some((p) => p.structural)) continue;
|
|
1956
|
-
for (const p of ps)
|
|
1960
|
+
for (const p of ps) {
|
|
1961
|
+
o.collapsed.add(p.spec.id);
|
|
1962
|
+
p.st.status = "collapsed";
|
|
1963
|
+
}
|
|
1957
1964
|
emit({ ...base(o, src), type: "collapse", parts: ps.map((p) => p.spec.id), mass: ps.reduce((sum, p) => sum + p.st.mass, 0) });
|
|
1958
1965
|
}
|
|
1959
1966
|
}
|
|
@@ -2253,7 +2260,7 @@ var GameTestRoom = (() => {
|
|
|
2253
2260
|
const effM = p.thickness / Math.max(sin, 0.3);
|
|
2254
2261
|
const thr = mat.penetration * effM * 1e3;
|
|
2255
2262
|
const penetrated = E > thr;
|
|
2256
|
-
const scale2 = mat.damageScale?.ballistic ?? 1;
|
|
2263
|
+
const scale2 = (mat.damageScale?.ballistic ?? 1) * (mat.fracture === "shatter" ? fragility(mat.ductility, p.thickness) : 1);
|
|
2257
2264
|
const id = p.spec.id;
|
|
2258
2265
|
if (!penetrated && mat.ricochetDeg && angle < mat.ricochetDeg) {
|
|
2259
2266
|
const r = vec.sub(d, vec.scale(n, 2 * vec.dot(d, n)));
|
|
@@ -2314,7 +2321,7 @@ var GameTestRoom = (() => {
|
|
|
2314
2321
|
if (kind === "blunt") wearJoints(o, p, E * 0.3, void 0, 0, src);
|
|
2315
2322
|
else wearJoints(o, p, E * 0.3, point, 0.06, src);
|
|
2316
2323
|
const scale2 = mat.damageScale?.[kind] ?? 1;
|
|
2317
|
-
return applyDamage(o, p, Ec * DMG_PER_J * scale2, kind, point, d, E * 0.1, 12, src);
|
|
2324
|
+
return applyDamage(o, p, Ec * DMG_PER_J * scale2 * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
|
|
2318
2325
|
};
|
|
2319
2326
|
const blastObject = (o, point, energy, radius, src) => {
|
|
2320
2327
|
const start = cur.length;
|
|
@@ -2603,7 +2610,7 @@ var GameTestRoom = (() => {
|
|
|
2603
2610
|
p.rt.stressT = 0.5;
|
|
2604
2611
|
emit({ ...base(o), type: "stress", part: p.spec.id, stress, load: p.st.load ?? 0 });
|
|
2605
2612
|
}
|
|
2606
|
-
applyDamage(o, p, (stress
|
|
2613
|
+
applyDamage(o, p, overloadRate(stress) * p.st.maxHp * dt, "blunt", null, null, 5, 3);
|
|
2607
2614
|
} else if (stress > 0.8 && p.mat.ductility < 0.3 && p.rt.stressT <= 0 && p.st.cracks.length < 6) {
|
|
2608
2615
|
p.rt.stressT = 0.5;
|
|
2609
2616
|
const { c, q } = partXf(o, p);
|
|
@@ -3093,7 +3100,7 @@ var GameTestRoom = (() => {
|
|
|
3093
3100
|
wax: "wax",
|
|
3094
3101
|
soil: "soil"
|
|
3095
3102
|
},
|
|
3096
|
-
partStatus: { intact: "intact", damaged: "damaged", broken: "broken", melted: "melted", ashes: "ashes" },
|
|
3103
|
+
partStatus: { intact: "intact", damaged: "damaged", collapsed: "collapsed", broken: "broken", melted: "melted", ashes: "ashes" },
|
|
3097
3104
|
jointKinds: {
|
|
3098
3105
|
screw: "screw",
|
|
3099
3106
|
bolt: "nut and bolt",
|
|
@@ -3174,7 +3181,7 @@ var GameTestRoom = (() => {
|
|
|
3174
3181
|
wax: "cera",
|
|
3175
3182
|
soil: "terra"
|
|
3176
3183
|
},
|
|
3177
|
-
partStatus: { intact: "intacto", damaged: "danificado", broken: "quebrado", melted: "derretido", ashes: "cinzas" },
|
|
3184
|
+
partStatus: { intact: "intacto", damaged: "danificado", collapsed: "desabado", broken: "quebrado", melted: "derretido", ashes: "cinzas" },
|
|
3178
3185
|
jointKinds: {
|
|
3179
3186
|
screw: "parafuso",
|
|
3180
3187
|
bolt: "porca e parafuso",
|
|
@@ -4469,6 +4476,8 @@ void main() {
|
|
|
4469
4476
|
__publicField(this, "b");
|
|
4470
4477
|
__publicField(this, "m4");
|
|
4471
4478
|
__publicField(this, "box");
|
|
4479
|
+
__publicField(this, "t");
|
|
4480
|
+
__publicField(this, "lbox");
|
|
4472
4481
|
__publicField(this, "decalCount", 0);
|
|
4473
4482
|
__publicField(this, "tints", /* @__PURE__ */ new WeakMap());
|
|
4474
4483
|
this.kit = new DecalKit(THREE);
|
|
@@ -4476,9 +4485,22 @@ void main() {
|
|
|
4476
4485
|
this.b = tmpV(THREE);
|
|
4477
4486
|
this.m4 = new THREE.Matrix4();
|
|
4478
4487
|
this.box = new THREE.Box3();
|
|
4488
|
+
this.t = tmpV(THREE);
|
|
4489
|
+
this.lbox = new THREE.Box3();
|
|
4479
4490
|
}
|
|
4480
|
-
|
|
4481
|
-
|
|
4491
|
+
/**
|
|
4492
|
+
* @param range vertex range `[v0, v1)` of a merged mesh this part is drawn by (see `MeshRange`); `hosts` caches the shared state per mesh for one `bind`.
|
|
4493
|
+
*/
|
|
4494
|
+
makeMeshVis(mesh, home, range, hosts) {
|
|
4495
|
+
let host = null;
|
|
4496
|
+
if (range) {
|
|
4497
|
+
host = hosts?.get(mesh) ?? null;
|
|
4498
|
+
if (!host) {
|
|
4499
|
+
host = { mesh, origGeometry: mesh.geometry, origMaterial: mesh.material, ownGeo: false, work: null, ownMat: false, sharers: [] };
|
|
4500
|
+
hosts?.set(mesh, host);
|
|
4501
|
+
}
|
|
4502
|
+
}
|
|
4503
|
+
const mv = {
|
|
4482
4504
|
mesh,
|
|
4483
4505
|
home,
|
|
4484
4506
|
origGeometry: mesh.geometry,
|
|
@@ -4495,31 +4517,72 @@ void main() {
|
|
|
4495
4517
|
decals: [],
|
|
4496
4518
|
meltApplied: 0,
|
|
4497
4519
|
bottomY: 0,
|
|
4498
|
-
moved: false
|
|
4520
|
+
moved: false,
|
|
4521
|
+
host,
|
|
4522
|
+
range: range ? [range[0], range[1]] : null,
|
|
4523
|
+
merged: host && range ? { host, range: [range[0], range[1]] } : null,
|
|
4524
|
+
loose: false,
|
|
4525
|
+
collapsed: false,
|
|
4526
|
+
hidden: false,
|
|
4527
|
+
touched: false,
|
|
4528
|
+
charK: 0,
|
|
4529
|
+
charHex: 0,
|
|
4530
|
+
glowHex: 0,
|
|
4531
|
+
glowK: 0
|
|
4499
4532
|
};
|
|
4533
|
+
host?.sharers.push(mv);
|
|
4534
|
+
return mv;
|
|
4500
4535
|
}
|
|
4501
4536
|
vertexCount(mv) {
|
|
4537
|
+
if (mv.range) return mv.range[1] - mv.range[0];
|
|
4502
4538
|
return mv.mesh.geometry.getAttribute(POS_ATTR)?.count ?? 0;
|
|
4503
4539
|
}
|
|
4540
|
+
/** Is the part drawn? (A ranged part shares its mesh, so `mesh.visible` says nothing about it.) */
|
|
4541
|
+
shown(mv) {
|
|
4542
|
+
return mv.host ? !mv.hidden : mv.mesh.visible;
|
|
4543
|
+
}
|
|
4544
|
+
/** Box of the part's vertices in the space of its mesh (the whole geometry for a plain mesh). */
|
|
4545
|
+
localBox(mv, out) {
|
|
4546
|
+
out.makeEmpty();
|
|
4547
|
+
if (!mv.host || !mv.range) {
|
|
4548
|
+
const geo = mv.mesh.geometry;
|
|
4549
|
+
if (!geo.boundingBox) geo.computeBoundingBox();
|
|
4550
|
+
return geo.boundingBox ? out.copy(geo.boundingBox) : out;
|
|
4551
|
+
}
|
|
4552
|
+
const arr = mv.host.work ?? positionAttr(mv.mesh.geometry)?.array;
|
|
4553
|
+
if (!arr) return out;
|
|
4554
|
+
for (let i = mv.range[0] * 3; i < mv.range[1] * 3; i += 3) out.expandByPoint(this.t.set(arr[i], arr[i + 1], arr[i + 2]));
|
|
4555
|
+
return out;
|
|
4556
|
+
}
|
|
4557
|
+
/** Box of the part in world space. */
|
|
4558
|
+
worldBox(mv, out) {
|
|
4559
|
+
if (!mv.host) return out.setFromObject(mv.mesh);
|
|
4560
|
+
this.localBox(mv, out);
|
|
4561
|
+
mv.mesh.updateWorldMatrix(true, false);
|
|
4562
|
+
return out.isEmpty() ? out : out.applyMatrix4(mv.mesh.matrixWorld);
|
|
4563
|
+
}
|
|
4504
4564
|
/**
|
|
4505
4565
|
* Average vertex colour of a mesh whose material uses `vertexColors` (computed once per mesh, from its original geometry), else null. Fragments
|
|
4506
4566
|
* and debris of such a mesh multiply it by `material.color`; without it they come out white.
|
|
4507
4567
|
*/
|
|
4508
4568
|
vertexTint(mv) {
|
|
4509
|
-
if (this.tints.has(mv
|
|
4569
|
+
if (this.tints.has(mv)) return this.tints.get(mv) ?? null;
|
|
4510
4570
|
let tint = null;
|
|
4511
4571
|
const used = materialsOf(mv.origMaterial).some((m) => m.vertexColors === true);
|
|
4512
4572
|
const attr = used ? mv.origGeometry.getAttribute("color") : void 0;
|
|
4513
4573
|
if (attr && attr.count > 0 && attr.itemSize >= 3) {
|
|
4574
|
+
const from = mv.merged ? mv.merged.range[0] : 0;
|
|
4575
|
+
const to = mv.merged ? Math.min(attr.count, mv.merged.range[1]) : attr.count;
|
|
4514
4576
|
let r = 0, g = 0, b = 0;
|
|
4515
|
-
for (let i =
|
|
4577
|
+
for (let i = from; i < to; i++) {
|
|
4516
4578
|
r += attr.getX(i);
|
|
4517
4579
|
g += attr.getY(i);
|
|
4518
4580
|
b += attr.getZ(i);
|
|
4519
4581
|
}
|
|
4520
|
-
|
|
4582
|
+
const n = Math.max(1, to - from);
|
|
4583
|
+
tint = new this.THREE.Color(r / n, g / n, b / n);
|
|
4521
4584
|
}
|
|
4522
|
-
this.tints.set(mv
|
|
4585
|
+
this.tints.set(mv, tint);
|
|
4523
4586
|
return tint;
|
|
4524
4587
|
}
|
|
4525
4588
|
/** Colour of the original material (before any char) times the vertex tint, `0xRRGGBB`, or -1. */
|
|
@@ -4543,6 +4606,7 @@ void main() {
|
|
|
4543
4606
|
}
|
|
4544
4607
|
/** Clone the geometry (vertex edits); false when its positions cannot be edited. */
|
|
4545
4608
|
ownGeometry(mv) {
|
|
4609
|
+
if (mv.host) return this.ownHostGeometry(mv.host);
|
|
4546
4610
|
if (mv.ownGeo) return true;
|
|
4547
4611
|
const pos = positionAttr(mv.mesh.geometry);
|
|
4548
4612
|
if (!pos) return false;
|
|
@@ -4552,6 +4616,16 @@ void main() {
|
|
|
4552
4616
|
mv.work = new Float32Array(positionAttr(g).array);
|
|
4553
4617
|
return true;
|
|
4554
4618
|
}
|
|
4619
|
+
ownHostGeometry(host) {
|
|
4620
|
+
if (host.ownGeo) return true;
|
|
4621
|
+
const pos = positionAttr(host.mesh.geometry);
|
|
4622
|
+
if (!pos) return false;
|
|
4623
|
+
host.ownGeo = true;
|
|
4624
|
+
const g = host.mesh.geometry.clone();
|
|
4625
|
+
host.mesh.geometry = g;
|
|
4626
|
+
host.work = new Float32Array(positionAttr(g).array);
|
|
4627
|
+
return true;
|
|
4628
|
+
}
|
|
4555
4629
|
// ------------------------------------------------------------------------------------------------------------------------------------
|
|
4556
4630
|
// Dents
|
|
4557
4631
|
// ------------------------------------------------------------------------------------------------------------------------------------
|
|
@@ -4569,10 +4643,15 @@ void main() {
|
|
|
4569
4643
|
const r = dent.radius / s;
|
|
4570
4644
|
const depth = dent.depth / s;
|
|
4571
4645
|
const geo = mesh.geometry;
|
|
4572
|
-
|
|
4573
|
-
if (
|
|
4646
|
+
let bb;
|
|
4647
|
+
if (mv.host) bb = this.localBox(mv, this.lbox);
|
|
4648
|
+
else {
|
|
4649
|
+
if (!geo.boundingBox) geo.computeBoundingBox();
|
|
4650
|
+
bb = geo.boundingBox;
|
|
4651
|
+
}
|
|
4652
|
+
if (bb && !bb.isEmpty() && bb.distanceToPoint(lp) > r) return false;
|
|
4574
4653
|
if (!this.ownGeometry(mv)) return false;
|
|
4575
|
-
const work = mv.work;
|
|
4654
|
+
const work = mv.host ? mv.host.work : mv.work;
|
|
4576
4655
|
const pos = positionAttr(mesh.geometry);
|
|
4577
4656
|
const r2 = r * r;
|
|
4578
4657
|
let touched = false;
|
|
@@ -4582,7 +4661,9 @@ void main() {
|
|
|
4582
4661
|
const nx = ln.x;
|
|
4583
4662
|
const ny = ln.y;
|
|
4584
4663
|
const nz = ln.z;
|
|
4585
|
-
|
|
4664
|
+
const from = mv.range ? mv.range[0] * 3 : 0;
|
|
4665
|
+
const to = mv.range ? mv.range[1] * 3 : work.length;
|
|
4666
|
+
for (let i = from; i < to; i += 3) {
|
|
4586
4667
|
const dx = work[i] - px;
|
|
4587
4668
|
const dy = work[i + 1] - py;
|
|
4588
4669
|
const dz = work[i + 2] - pz;
|
|
@@ -4596,11 +4677,16 @@ void main() {
|
|
|
4596
4677
|
touched = true;
|
|
4597
4678
|
}
|
|
4598
4679
|
if (!touched) return false;
|
|
4680
|
+
mv.touched = true;
|
|
4599
4681
|
this.writePositions(mv, pos);
|
|
4600
4682
|
return true;
|
|
4601
4683
|
}
|
|
4602
4684
|
/** Copy `work` (dents) into the geometry, adding the melt sag when the mesh is melting. */
|
|
4603
4685
|
writePositions(mv, pos) {
|
|
4686
|
+
if (mv.host) {
|
|
4687
|
+
this.rebuildHost(mv.host);
|
|
4688
|
+
return;
|
|
4689
|
+
}
|
|
4604
4690
|
const work = mv.work;
|
|
4605
4691
|
const arr = pos.array;
|
|
4606
4692
|
const melt = mv.meltApplied;
|
|
@@ -4714,6 +4800,12 @@ void main() {
|
|
|
4714
4800
|
pv.charApplied = char;
|
|
4715
4801
|
const target = new this.THREE.Color(charColor);
|
|
4716
4802
|
for (const mv of pv.meshes) {
|
|
4803
|
+
if (mv.host) {
|
|
4804
|
+
mv.charK = clamp3(char, 0, 1);
|
|
4805
|
+
mv.charHex = charColor;
|
|
4806
|
+
this.paint(mv);
|
|
4807
|
+
continue;
|
|
4808
|
+
}
|
|
4717
4809
|
this.ownMaterial(mv);
|
|
4718
4810
|
const mats = materialsOf(mv.mesh.material);
|
|
4719
4811
|
mats.forEach((m, i) => {
|
|
@@ -4731,6 +4823,12 @@ void main() {
|
|
|
4731
4823
|
pv.glowApplied = key;
|
|
4732
4824
|
const intensity = hex === 0 ? 0 : clamp3(0.35 + (temperatureC - 450) / 900, 0, 1.4);
|
|
4733
4825
|
for (const mv of pv.meshes) {
|
|
4826
|
+
if (mv.host) {
|
|
4827
|
+
mv.glowHex = hex;
|
|
4828
|
+
mv.glowK = intensity;
|
|
4829
|
+
this.paint(mv);
|
|
4830
|
+
continue;
|
|
4831
|
+
}
|
|
4734
4832
|
this.ownMaterial(mv);
|
|
4735
4833
|
for (const m of materialsOf(mv.mesh.material)) {
|
|
4736
4834
|
const e = m.emissive;
|
|
@@ -4751,6 +4849,12 @@ void main() {
|
|
|
4751
4849
|
pv.meltApplied = m;
|
|
4752
4850
|
for (const mv of pv.meshes) {
|
|
4753
4851
|
const mesh = mv.mesh;
|
|
4852
|
+
if (mv.host) {
|
|
4853
|
+
mv.meltApplied = m;
|
|
4854
|
+
mv.touched = true;
|
|
4855
|
+
if (!(this.lowQuality && this.vertexCount(mv) > 2e4) && this.ownGeometry(mv)) this.rebuildHost(mv.host);
|
|
4856
|
+
continue;
|
|
4857
|
+
}
|
|
4754
4858
|
if (mv.meltApplied === 0 && m > 0) {
|
|
4755
4859
|
mesh.updateWorldMatrix(true, false);
|
|
4756
4860
|
mv.bottomY = this.box.setFromObject(mesh).min.y;
|
|
@@ -4781,10 +4885,270 @@ void main() {
|
|
|
4781
4885
|
// ------------------------------------------------------------------------------------------------------------------------------------
|
|
4782
4886
|
hide(pv, reason) {
|
|
4783
4887
|
pv.gone = reason;
|
|
4784
|
-
for (const mv of pv.meshes)
|
|
4888
|
+
for (const mv of pv.meshes) {
|
|
4889
|
+
if (!mv.host || mv.loose) {
|
|
4890
|
+
mv.mesh.visible = false;
|
|
4891
|
+
mv.hidden = true;
|
|
4892
|
+
continue;
|
|
4893
|
+
}
|
|
4894
|
+
mv.hidden = true;
|
|
4895
|
+
for (const d of mv.decals) d.obj.visible = false;
|
|
4896
|
+
this.collapse(mv);
|
|
4897
|
+
}
|
|
4898
|
+
}
|
|
4899
|
+
// ------------------------------------------------------------------------------------------------------------------------------------
|
|
4900
|
+
// Parts drawn by a vertex range of a merged mesh
|
|
4901
|
+
// ------------------------------------------------------------------------------------------------------------------------------------
|
|
4902
|
+
/** Collapse the part's vertices in the merged mesh to one point (its triangles become degenerate and vanish). */
|
|
4903
|
+
collapse(mv) {
|
|
4904
|
+
if (!mv.merged || !this.ownHostGeometry(mv.merged.host)) return;
|
|
4905
|
+
mv.collapsed = true;
|
|
4906
|
+
mv.touched = true;
|
|
4907
|
+
this.rebuildHost(mv.merged.host);
|
|
4908
|
+
}
|
|
4909
|
+
/** Write `work` into the geometry again with the melt of ranged parts and the collapsed ranges applied. */
|
|
4910
|
+
rebuildHost(host) {
|
|
4911
|
+
const geo = host.mesh.geometry;
|
|
4912
|
+
const pos = positionAttr(geo);
|
|
4913
|
+
const work = host.work;
|
|
4914
|
+
if (!pos || !work) return;
|
|
4915
|
+
const arr = pos.array;
|
|
4916
|
+
arr.set(work);
|
|
4917
|
+
for (const s of host.sharers) {
|
|
4918
|
+
if (s.merged && s.merged.host === host) {
|
|
4919
|
+
if (s.collapsed) this.collapseRange(arr, s.merged.range);
|
|
4920
|
+
else if (s.meltApplied > 0 && !s.loose) this.meltRange(arr, work, s.merged.range, s.meltApplied);
|
|
4921
|
+
} else if (s.host === host && s.range && s.meltApplied > 0) this.meltRange(arr, work, s.range, s.meltApplied);
|
|
4922
|
+
}
|
|
4923
|
+
pos.needsUpdate = true;
|
|
4924
|
+
geo.computeVertexNormals();
|
|
4925
|
+
geo.boundingBox = null;
|
|
4926
|
+
geo.boundingSphere = null;
|
|
4927
|
+
}
|
|
4928
|
+
collapseRange(arr, range) {
|
|
4929
|
+
const x = arr[range[0] * 3];
|
|
4930
|
+
const y = arr[range[0] * 3 + 1];
|
|
4931
|
+
const z = arr[range[0] * 3 + 2];
|
|
4932
|
+
for (let i = range[0] * 3; i < range[1] * 3; i += 3) {
|
|
4933
|
+
arr[i] = x;
|
|
4934
|
+
arr[i + 1] = y;
|
|
4935
|
+
arr[i + 2] = z;
|
|
4936
|
+
}
|
|
4937
|
+
}
|
|
4938
|
+
/** Melt of one range: shorter in Y (down to 25 %) with the bottom fixed, a bit wider around its centre, with the same noise as a whole mesh. */
|
|
4939
|
+
meltRange(arr, work, range, melt) {
|
|
4940
|
+
let minY = Infinity;
|
|
4941
|
+
let maxY = -Infinity;
|
|
4942
|
+
let cx = 0;
|
|
4943
|
+
let cz = 0;
|
|
4944
|
+
const n = Math.max(1, range[1] - range[0]);
|
|
4945
|
+
for (let i = range[0] * 3; i < range[1] * 3; i += 3) {
|
|
4946
|
+
minY = Math.min(minY, work[i + 1]);
|
|
4947
|
+
maxY = Math.max(maxY, work[i + 1]);
|
|
4948
|
+
cx += work[i];
|
|
4949
|
+
cz += work[i + 2];
|
|
4950
|
+
}
|
|
4951
|
+
cx /= n;
|
|
4952
|
+
cz /= n;
|
|
4953
|
+
const h = Math.max(1e-4, maxY - minY);
|
|
4954
|
+
const sy = lerp(1, 0.25, melt);
|
|
4955
|
+
const sxz = 1 + 0.18 * melt;
|
|
4956
|
+
for (let i = range[0] * 3, v = 0; i < range[1] * 3; i += 3, v++) {
|
|
4957
|
+
const noise = (Math.imul(v + 1, 2654435761) >>> 0) % 1e3 / 1e3;
|
|
4958
|
+
arr[i] = cx + (work[i] - cx) * sxz;
|
|
4959
|
+
arr[i + 2] = cz + (work[i + 2] - cz) * sxz;
|
|
4960
|
+
arr[i + 1] = Math.max(minY, minY + (work[i + 1] - minY) * sy - noise * melt * h * 0.04);
|
|
4961
|
+
}
|
|
4962
|
+
}
|
|
4963
|
+
/** The colour attribute of the host's current geometry (created white when there is none) with vertex colours switched on in its material. */
|
|
4964
|
+
ensureColors(host) {
|
|
4965
|
+
if (!this.ownHostGeometry(host)) return null;
|
|
4966
|
+
const geo = host.mesh.geometry;
|
|
4967
|
+
let attr = geo.getAttribute("color");
|
|
4968
|
+
if (!attr) {
|
|
4969
|
+
const n = geo.getAttribute(POS_ATTR).count;
|
|
4970
|
+
attr = new this.THREE.BufferAttribute(new Float32Array(n * 3).fill(1), 3);
|
|
4971
|
+
geo.setAttribute("color", attr);
|
|
4972
|
+
}
|
|
4973
|
+
if (!host.ownMat) {
|
|
4974
|
+
host.ownMat = true;
|
|
4975
|
+
const src = materialsOf(host.mesh.material);
|
|
4976
|
+
const cloned = src.map((m) => m.clone());
|
|
4977
|
+
host.mesh.material = Array.isArray(host.mesh.material) ? cloned : cloned[0];
|
|
4978
|
+
}
|
|
4979
|
+
for (const m of materialsOf(host.mesh.material)) {
|
|
4980
|
+
if (m.vertexColors !== true) {
|
|
4981
|
+
;
|
|
4982
|
+
m.vertexColors = true;
|
|
4983
|
+
m.needsUpdate = true;
|
|
4984
|
+
}
|
|
4985
|
+
}
|
|
4986
|
+
return attr;
|
|
4987
|
+
}
|
|
4988
|
+
/** Colour of the part's vertices from the original colours, its char and its heat. Only the part's range is written. */
|
|
4989
|
+
paint(mv) {
|
|
4990
|
+
if (!mv.host || !mv.range || !mv.merged) return;
|
|
4991
|
+
const idle = mv.charK <= 0 && mv.glowK <= 0;
|
|
4992
|
+
if (idle && !mv.touched) return;
|
|
4993
|
+
const attr = this.ensureColors(mv.host);
|
|
4994
|
+
if (!attr) return;
|
|
4995
|
+
const size = attr.itemSize;
|
|
4996
|
+
const arr = attr.array;
|
|
4997
|
+
const orig = mv.merged.host.origGeometry.getAttribute("color");
|
|
4998
|
+
const oSize = orig?.itemSize ?? 3;
|
|
4999
|
+
const oArr = orig?.array;
|
|
5000
|
+
const shift = mv.merged.range[0] - mv.range[0];
|
|
5001
|
+
const ch = new this.THREE.Color(mv.charHex);
|
|
5002
|
+
const heat = new this.THREE.Color(mv.glowHex);
|
|
5003
|
+
const k = clamp3(mv.charK, 0, 1);
|
|
5004
|
+
const gk = clamp3(mv.glowK, 0, 1);
|
|
5005
|
+
const boost = 1 + Math.max(0, mv.glowK - 1);
|
|
5006
|
+
for (let i = mv.range[0]; i < mv.range[1]; i++) {
|
|
5007
|
+
const o = (i + shift) * oSize;
|
|
5008
|
+
let r = oArr ? oArr[o] : 1;
|
|
5009
|
+
let g = oArr ? oArr[o + 1] : 1;
|
|
5010
|
+
let b = oArr ? oArr[o + 2] : 1;
|
|
5011
|
+
if (k > 0) {
|
|
5012
|
+
r = lerp(r, ch.r, k);
|
|
5013
|
+
g = lerp(g, ch.g, k);
|
|
5014
|
+
b = lerp(b, ch.b, k);
|
|
5015
|
+
}
|
|
5016
|
+
if (gk > 0) {
|
|
5017
|
+
r = lerp(r, heat.r * boost, gk);
|
|
5018
|
+
g = lerp(g, heat.g * boost, gk);
|
|
5019
|
+
b = lerp(b, heat.b * boost, gk);
|
|
5020
|
+
}
|
|
5021
|
+
arr[i * size] = r;
|
|
5022
|
+
arr[i * size + 1] = g;
|
|
5023
|
+
arr[i * size + 2] = b;
|
|
5024
|
+
}
|
|
5025
|
+
mv.touched = true;
|
|
5026
|
+
attr.needsUpdate = true;
|
|
5027
|
+
}
|
|
5028
|
+
/**
|
|
5029
|
+
* Move a ranged part to a mesh of its own: the faces whose vertices all lie in its range are copied (with the current dents and colours) into a
|
|
5030
|
+
* new mesh that takes the merged mesh's transform and material, then attached to `group`; the range is collapsed in the merged mesh. Decals
|
|
5031
|
+
* go with it.
|
|
5032
|
+
*/
|
|
5033
|
+
extract(mv, group) {
|
|
5034
|
+
if (!mv.host || !mv.range || !mv.merged || mv.loose) return;
|
|
5035
|
+
const host = mv.host;
|
|
5036
|
+
const [v0, v1] = mv.range;
|
|
5037
|
+
const src = host.mesh.geometry;
|
|
5038
|
+
const geo = new this.THREE.BufferGeometry();
|
|
5039
|
+
const count = v1 - v0;
|
|
5040
|
+
for (const name of Object.keys(src.attributes)) {
|
|
5041
|
+
const a = src.getAttribute(name);
|
|
5042
|
+
if (a.isInterleavedBufferAttribute) continue;
|
|
5043
|
+
const size = a.itemSize;
|
|
5044
|
+
const from = (name === POS_ATTR ? host.work : null) ?? a.array;
|
|
5045
|
+
const slice = from.slice(v0 * size, v1 * size);
|
|
5046
|
+
const Ctor = a.array.constructor;
|
|
5047
|
+
geo.setAttribute(name, new this.THREE.BufferAttribute(new Ctor(slice), size, a.normalized));
|
|
5048
|
+
}
|
|
5049
|
+
const index = src.getIndex();
|
|
5050
|
+
if (index) {
|
|
5051
|
+
const out = [];
|
|
5052
|
+
for (let i = 0; i + 2 < index.count; i += 3) {
|
|
5053
|
+
const a = index.getX(i);
|
|
5054
|
+
const b = index.getX(i + 1);
|
|
5055
|
+
const c = index.getX(i + 2);
|
|
5056
|
+
if (a >= v0 && a < v1 && b >= v0 && b < v1 && c >= v0 && c < v1) out.push(a - v0, b - v0, c - v0);
|
|
5057
|
+
}
|
|
5058
|
+
geo.setIndex(count > 65535 ? new this.THREE.Uint32BufferAttribute(out, 1) : new this.THREE.Uint16BufferAttribute(out, 1));
|
|
5059
|
+
}
|
|
5060
|
+
const mats = materialsOf(host.mesh.material).map((m) => m.clone());
|
|
5061
|
+
const loose = new this.THREE.Mesh(geo, mats[0]);
|
|
5062
|
+
loose.name = `${host.mesh.name || "mesh"}:${v0}-${v1}`;
|
|
5063
|
+
loose.castShadow = host.mesh.castShadow;
|
|
5064
|
+
loose.receiveShadow = host.mesh.receiveShadow;
|
|
5065
|
+
loose.renderOrder = host.mesh.renderOrder;
|
|
5066
|
+
loose.frustumCulled = host.mesh.frustumCulled;
|
|
5067
|
+
loose.layers.mask = host.mesh.layers.mask;
|
|
5068
|
+
loose.position.copy(host.mesh.position);
|
|
5069
|
+
loose.quaternion.copy(host.mesh.quaternion);
|
|
5070
|
+
loose.scale.copy(host.mesh.scale);
|
|
5071
|
+
const parent = host.mesh.parent;
|
|
5072
|
+
if (parent) parent.add(loose);
|
|
5073
|
+
loose.updateMatrixWorld(true);
|
|
5074
|
+
this.collapse(mv);
|
|
5075
|
+
for (const d of mv.decals) {
|
|
5076
|
+
d.obj.visible = true;
|
|
5077
|
+
loose.add(d.obj);
|
|
5078
|
+
}
|
|
5079
|
+
const lp = positionAttr(geo);
|
|
5080
|
+
const lh = {
|
|
5081
|
+
mesh: loose,
|
|
5082
|
+
origGeometry: geo,
|
|
5083
|
+
origMaterial: loose.material,
|
|
5084
|
+
ownGeo: true,
|
|
5085
|
+
work: new Float32Array(lp.array),
|
|
5086
|
+
ownMat: true,
|
|
5087
|
+
sharers: [mv]
|
|
5088
|
+
};
|
|
5089
|
+
mv.host = lh;
|
|
5090
|
+
mv.range = [0, count];
|
|
5091
|
+
mv.mesh = loose;
|
|
5092
|
+
mv.loose = true;
|
|
5093
|
+
mv.moved = true;
|
|
5094
|
+
group.attach(loose);
|
|
5095
|
+
for (const m of materialsOf(loose.material)) m.needsUpdate = true;
|
|
5096
|
+
}
|
|
5097
|
+
restoreRanged(mv) {
|
|
5098
|
+
const merged = mv.merged;
|
|
5099
|
+
const host = merged.host;
|
|
5100
|
+
for (const d of mv.decals) this.disposeDecal(d);
|
|
5101
|
+
mv.decals.length = 0;
|
|
5102
|
+
if (mv.loose) {
|
|
5103
|
+
const loose = mv.mesh;
|
|
5104
|
+
loose.parent?.remove(loose);
|
|
5105
|
+
loose.geometry.dispose();
|
|
5106
|
+
for (const m of materialsOf(loose.material)) m.dispose();
|
|
5107
|
+
mv.host = host;
|
|
5108
|
+
mv.range = [merged.range[0], merged.range[1]];
|
|
5109
|
+
mv.mesh = host.mesh;
|
|
5110
|
+
mv.loose = false;
|
|
5111
|
+
mv.moved = false;
|
|
5112
|
+
}
|
|
5113
|
+
mv.collapsed = false;
|
|
5114
|
+
mv.hidden = false;
|
|
5115
|
+
mv.meltApplied = 0;
|
|
5116
|
+
mv.charK = 0;
|
|
5117
|
+
mv.glowK = 0;
|
|
5118
|
+
mv.touched = false;
|
|
5119
|
+
if (!host.ownGeo) return;
|
|
5120
|
+
const work = host.work;
|
|
5121
|
+
const op = positionAttr(host.origGeometry);
|
|
5122
|
+
const [v0, v1] = merged.range;
|
|
5123
|
+
if (op) work.set(op.array.subarray(v0 * 3, v1 * 3), v0 * 3);
|
|
5124
|
+
const attr = host.mesh.geometry.getAttribute("color");
|
|
5125
|
+
if (attr) {
|
|
5126
|
+
const orig = host.origGeometry.getAttribute("color");
|
|
5127
|
+
const size = attr.itemSize;
|
|
5128
|
+
const a = attr.array;
|
|
5129
|
+
for (let i = v0; i < v1; i++) {
|
|
5130
|
+
for (let c = 0; c < 3; c++) a[i * size + c] = orig ? orig.array[i * orig.itemSize + c] : 1;
|
|
5131
|
+
}
|
|
5132
|
+
attr.needsUpdate = true;
|
|
5133
|
+
}
|
|
5134
|
+
if (host.sharers.every((s) => !s.touched)) {
|
|
5135
|
+
host.mesh.geometry.dispose();
|
|
5136
|
+
host.mesh.geometry = host.origGeometry;
|
|
5137
|
+
host.work = null;
|
|
5138
|
+
host.ownGeo = false;
|
|
5139
|
+
if (host.ownMat) {
|
|
5140
|
+
for (const m of materialsOf(host.mesh.material)) m.dispose();
|
|
5141
|
+
host.mesh.material = host.origMaterial;
|
|
5142
|
+
host.ownMat = false;
|
|
5143
|
+
}
|
|
5144
|
+
} else this.rebuildHost(host);
|
|
4785
5145
|
}
|
|
4786
5146
|
/** Bring one mesh back to how it was when it was bound. */
|
|
4787
5147
|
restoreMesh(mv) {
|
|
5148
|
+
if (mv.merged) {
|
|
5149
|
+
this.restoreRanged(mv);
|
|
5150
|
+
return;
|
|
5151
|
+
}
|
|
4788
5152
|
const mesh = mv.mesh;
|
|
4789
5153
|
for (const d of mv.decals) this.disposeDecal(d);
|
|
4790
5154
|
mv.decals.length = 0;
|
|
@@ -4882,6 +5246,10 @@ void main() {
|
|
|
4882
5246
|
function materialsOf2(m) {
|
|
4883
5247
|
return Array.isArray(m) ? m : [m];
|
|
4884
5248
|
}
|
|
5249
|
+
function isMeshRange(v) {
|
|
5250
|
+
const r = v;
|
|
5251
|
+
return !!r.mesh && Array.isArray(r.range);
|
|
5252
|
+
}
|
|
4885
5253
|
function createDestructionRenderer(THREE, options) {
|
|
4886
5254
|
return new Renderer(THREE, options);
|
|
4887
5255
|
}
|
|
@@ -4975,12 +5343,23 @@ void main() {
|
|
|
4975
5343
|
const partIds = new Set((state?.parts ?? []).map((p) => p.id));
|
|
4976
5344
|
const byPart = /* @__PURE__ */ new Map();
|
|
4977
5345
|
const taken = /* @__PURE__ */ new Set();
|
|
5346
|
+
const put = (pid, e) => {
|
|
5347
|
+
const l = byPart.get(pid);
|
|
5348
|
+
if (l) l.push(e);
|
|
5349
|
+
else byPart.set(pid, [e]);
|
|
5350
|
+
};
|
|
4978
5351
|
const add2 = (pid, m) => {
|
|
4979
5352
|
if (taken.has(m)) return;
|
|
4980
5353
|
taken.add(m);
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
5354
|
+
put(pid, { mesh: m });
|
|
5355
|
+
};
|
|
5356
|
+
const addRange = (pid, r) => {
|
|
5357
|
+
const n = r.mesh.geometry.getAttribute("position")?.count ?? 0;
|
|
5358
|
+
const [v0, v1] = r.range;
|
|
5359
|
+
if (!Number.isInteger(v0) || !Number.isInteger(v1) || v0 < 0 || v1 <= v0 || v1 > n) {
|
|
5360
|
+
throw new Error(`game-destruction-three: invalid vertex range [${v0}, ${v1}) for part "${pid}" (the mesh has ${n} vertices)`);
|
|
5361
|
+
}
|
|
5362
|
+
put(pid, { mesh: r.mesh, range: [v0, v1] });
|
|
4984
5363
|
};
|
|
4985
5364
|
const meshesIn = (o) => {
|
|
4986
5365
|
const out = [];
|
|
@@ -4991,7 +5370,10 @@ void main() {
|
|
|
4991
5370
|
};
|
|
4992
5371
|
if (options.parts) {
|
|
4993
5372
|
for (const [pid, v] of Object.entries(options.parts)) {
|
|
4994
|
-
for (const o of Array.isArray(v) ? v : [v])
|
|
5373
|
+
for (const o of Array.isArray(v) ? v : [v]) {
|
|
5374
|
+
if (isMeshRange(o)) addRange(pid, o);
|
|
5375
|
+
else for (const m of meshesIn(o)) add2(pid, m);
|
|
5376
|
+
}
|
|
4995
5377
|
}
|
|
4996
5378
|
} else {
|
|
4997
5379
|
const leftovers = [];
|
|
@@ -5004,14 +5386,15 @@ void main() {
|
|
|
5004
5386
|
if (leftovers.length) this.matchByBox(objectId, root, leftovers, add2);
|
|
5005
5387
|
}
|
|
5006
5388
|
const bound = { id: objectId, root, sync: options.syncTransform !== false, parts: /* @__PURE__ */ new Map(), lastPos: null, lastQuat: null, piece: null, isSplit: false };
|
|
5007
|
-
|
|
5389
|
+
const hosts = /* @__PURE__ */ new Map();
|
|
5390
|
+
for (const pid of partIds) bound.parts.set(pid, this.makePartVis(objectId, pid, state?.parts.find((p) => p.id === pid)?.material ?? "", byPart.get(pid) ?? [], hosts));
|
|
5008
5391
|
for (const [pid, meshes] of byPart) {
|
|
5009
|
-
if (!bound.parts.has(pid)) bound.parts.set(pid, this.makePartVis(objectId, pid, this.world.part(objectId, pid)?.material ?? "", meshes));
|
|
5392
|
+
if (!bound.parts.has(pid)) bound.parts.set(pid, this.makePartVis(objectId, pid, this.world.part(objectId, pid)?.material ?? "", meshes, hosts));
|
|
5010
5393
|
}
|
|
5011
5394
|
this.bounds.set(objectId, bound);
|
|
5012
5395
|
}
|
|
5013
|
-
makePartVis(home, id, material, meshes) {
|
|
5014
|
-
return { id, home, material, meshes: meshes.map((m) => this.visuals.makeMeshVis(m, home)), charApplied: 0, glowApplied: 0, meltApplied: 0, gone: "none" };
|
|
5396
|
+
makePartVis(home, id, material, meshes, hosts) {
|
|
5397
|
+
return { id, home, material, meshes: meshes.map((m) => this.visuals.makeMeshVis(m.mesh, home, m.range, hosts)), charApplied: 0, glowApplied: 0, meltApplied: 0, gone: "none" };
|
|
5015
5398
|
}
|
|
5016
5399
|
/** Match unlabelled meshes to the part whose shape contains the centre of the mesh's bounding box. */
|
|
5017
5400
|
matchByBox(objectId, root, meshes, add2) {
|
|
@@ -5071,11 +5454,13 @@ void main() {
|
|
|
5071
5454
|
const inv = root.matrixWorld.clone().invert();
|
|
5072
5455
|
const total = new this.THREE.Box3();
|
|
5073
5456
|
const m4 = new this.THREE.Matrix4();
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5457
|
+
const lb = new this.THREE.Box3();
|
|
5458
|
+
for (const mv of meshes) {
|
|
5459
|
+
const m = mv.mesh;
|
|
5460
|
+
m.updateWorldMatrix(true, false);
|
|
5461
|
+
this.visuals.localBox(mv, lb);
|
|
5462
|
+
if (lb.isEmpty()) continue;
|
|
5463
|
+
total.union(lb.clone().applyMatrix4(m4.multiplyMatrices(inv, m.matrixWorld)));
|
|
5079
5464
|
}
|
|
5080
5465
|
if (total.isEmpty()) return { half: [0.25, 0.25, 0.25], center: new this.THREE.Vector3() };
|
|
5081
5466
|
const size = total.getSize(new this.THREE.Vector3());
|
|
@@ -5083,7 +5468,7 @@ void main() {
|
|
|
5083
5468
|
}
|
|
5084
5469
|
meshesOf(b) {
|
|
5085
5470
|
const out = [];
|
|
5086
|
-
for (const pv of b.parts.values()) if (pv.gone === "none") for (const mv of pv.meshes) out.push(mv
|
|
5471
|
+
for (const pv of b.parts.values()) if (pv.gone === "none") for (const mv of pv.meshes) out.push(mv);
|
|
5087
5472
|
return out;
|
|
5088
5473
|
}
|
|
5089
5474
|
massOf(objectId) {
|
|
@@ -5214,6 +5599,9 @@ void main() {
|
|
|
5214
5599
|
case "detach":
|
|
5215
5600
|
this.onDetach(ev);
|
|
5216
5601
|
break;
|
|
5602
|
+
case "collapse":
|
|
5603
|
+
this.splitLoose(ev.object);
|
|
5604
|
+
break;
|
|
5217
5605
|
case "disassemble_progress": {
|
|
5218
5606
|
const k = `unscrew:${ev.joint}`;
|
|
5219
5607
|
if (this.time - (this.throttles.get(k) ?? -1) > 0.25) {
|
|
@@ -5332,8 +5720,8 @@ void main() {
|
|
|
5332
5720
|
let best = null;
|
|
5333
5721
|
let bd = Infinity;
|
|
5334
5722
|
for (const mv of pv.meshes) {
|
|
5335
|
-
if (!
|
|
5336
|
-
const d = this.
|
|
5723
|
+
if (!this.visuals.shown(mv)) continue;
|
|
5724
|
+
const d = this.visuals.worldBox(mv, this.box).distanceToPoint(p);
|
|
5337
5725
|
if (d < bd) {
|
|
5338
5726
|
bd = d;
|
|
5339
5727
|
best = mv;
|
|
@@ -5353,7 +5741,7 @@ void main() {
|
|
|
5353
5741
|
const wp = new this.THREE.Vector3(ev.worldPoint[0], ev.worldPoint[1], ev.worldPoint[2]);
|
|
5354
5742
|
const wn = this.worldDir(b, ev.dent.normal);
|
|
5355
5743
|
for (const mv of pv.meshes) {
|
|
5356
|
-
if (!
|
|
5744
|
+
if (!this.visuals.shown(mv)) continue;
|
|
5357
5745
|
this.visuals.applyDent(mv, wp, wn, ev.dent);
|
|
5358
5746
|
}
|
|
5359
5747
|
if (def?.tags?.includes("metal")) this.particles.burst("sparks", ev.worldPoint, [wn.x, wn.y, wn.z], 4 + Math.min(8, ev.dent.depth * 150), 3.2);
|
|
@@ -5444,16 +5832,20 @@ void main() {
|
|
|
5444
5832
|
}
|
|
5445
5833
|
onDetach(ev) {
|
|
5446
5834
|
this.sound("detach", this.rootPoint(ev.object), 0.5);
|
|
5835
|
+
this.splitLoose(ev.object);
|
|
5836
|
+
}
|
|
5837
|
+
/** Turn every loose group of the object (a detach, a collapse) into a piece of its own. */
|
|
5838
|
+
splitLoose(objectId) {
|
|
5447
5839
|
if (!this.autoSplit) return;
|
|
5448
|
-
const b = this.bounds.get(
|
|
5840
|
+
const b = this.bounds.get(objectId);
|
|
5449
5841
|
if (!b) return;
|
|
5450
|
-
const st = this.world.state(
|
|
5842
|
+
const st = this.world.state(objectId);
|
|
5451
5843
|
if (!st || st.groups.length < 2) return;
|
|
5452
5844
|
const loose = st.groups.length - 1;
|
|
5453
5845
|
for (let i = 0; i < loose; i++) {
|
|
5454
5846
|
let newId;
|
|
5455
5847
|
try {
|
|
5456
|
-
newId = this.world.splitGroup(
|
|
5848
|
+
newId = this.world.splitGroup(objectId, 1);
|
|
5457
5849
|
} catch {
|
|
5458
5850
|
break;
|
|
5459
5851
|
}
|
|
@@ -5464,7 +5856,7 @@ void main() {
|
|
|
5464
5856
|
const { half, center } = this.measure(b.root, this.meshesOf(b));
|
|
5465
5857
|
old.halfExtents = half;
|
|
5466
5858
|
this.pieceSys.setCenter(old, center);
|
|
5467
|
-
old.mass = Math.max(0.05, this.massOf(
|
|
5859
|
+
old.mass = Math.max(0.05, this.massOf(objectId));
|
|
5468
5860
|
old.sleeping = false;
|
|
5469
5861
|
}
|
|
5470
5862
|
}
|
|
@@ -5486,8 +5878,11 @@ void main() {
|
|
|
5486
5878
|
from.parts.delete(p.id);
|
|
5487
5879
|
nb.parts.set(p.id, pv);
|
|
5488
5880
|
for (const mv of pv.meshes) {
|
|
5489
|
-
|
|
5490
|
-
|
|
5881
|
+
if (mv.host && !mv.loose) this.visuals.extract(mv, group);
|
|
5882
|
+
else {
|
|
5883
|
+
group.attach(mv.mesh);
|
|
5884
|
+
mv.moved = true;
|
|
5885
|
+
}
|
|
5491
5886
|
}
|
|
5492
5887
|
}
|
|
5493
5888
|
this.bounds.set(newId, nb);
|
|
@@ -5603,7 +5998,7 @@ void main() {
|
|
|
5603
5998
|
}
|
|
5604
5999
|
onGone(objectId, pv, reason, def) {
|
|
5605
6000
|
const bb = new this.THREE.Box3();
|
|
5606
|
-
for (const mv of pv.meshes) bb.union(this.
|
|
6001
|
+
for (const mv of pv.meshes) bb.union(this.visuals.worldBox(mv, this.box));
|
|
5607
6002
|
this.visuals.hide(pv, reason);
|
|
5608
6003
|
if (bb.isEmpty()) return;
|
|
5609
6004
|
const c = bb.getCenter(new this.THREE.Vector3());
|
|
@@ -5620,7 +6015,7 @@ void main() {
|
|
|
5620
6015
|
const bb = this.box;
|
|
5621
6016
|
bb.makeEmpty();
|
|
5622
6017
|
const tmp = new this.THREE.Box3();
|
|
5623
|
-
for (const mv of pv.meshes) if (
|
|
6018
|
+
for (const mv of pv.meshes) if (this.visuals.shown(mv)) bb.union(this.visuals.worldBox(mv, tmp));
|
|
5624
6019
|
let cx = f.point[0];
|
|
5625
6020
|
let cy = f.point[1];
|
|
5626
6021
|
let cz = f.point[2];
|
|
@@ -10882,8 +11277,8 @@ void main() {
|
|
|
10882
11277
|
"sc.concrete_stops_bullet.check": "9 mm into 40 cm of concrete: went through: {through}, {dents}, {holes}, {cracks}, block {hp} hp (expected: stopped, wall behind untouched)",
|
|
10883
11278
|
"sc.hammer_dents_drum": "Hammer dents a steel drum",
|
|
10884
11279
|
"sc.hammer_dents_drum.check": "Hammer on the drum: {swings} swing(s), {dents} dent(s), first {depth} mm deep, body {hp} hp (expected: dented, not broken)",
|
|
10885
|
-
"sc.glass_shatters": "Glass pane shatters
|
|
10886
|
-
"sc.glass_shatters.check": "Glass pane: broken after {shots} shot(s) into {n} shards (expected: broken, at least 4 shards)",
|
|
11280
|
+
"sc.glass_shatters": "Glass pane shatters on the first 9 mm shot",
|
|
11281
|
+
"sc.glass_shatters.check": "Glass pane: broken after {shots} shot(s) into {n} shards (expected: broken on the first shot, at least 4 shards)",
|
|
10887
11282
|
"sc.shotgun_splinters_crate": "Shotgun splinters a wooden crate",
|
|
10888
11283
|
"sc.shotgun_splinters_crate.check": "12 ga on the crate: {shots} shot(s), {splinters} board(s) splintered, {holes} holes (expected: at least one splintered)",
|
|
10889
11284
|
"sc.kick_chair_leg": "A kick takes a chair leg off",
|
|
@@ -11171,8 +11566,8 @@ void main() {
|
|
|
11171
11566
|
"sc.concrete_stops_bullet.check": "9 mm em 40 cm de concreto: atravessou: {through}, {dents}, {holes}, {cracks}, bloco com {hp} hp (esperado: parou, parede atr\xE1s intacta)",
|
|
11172
11567
|
"sc.hammer_dents_drum": "Martelo amassa um tambor de a\xE7o",
|
|
11173
11568
|
"sc.hammer_dents_drum.check": "Martelo no tambor: {swings} golpe(s), {dents} amassado(s), o primeiro com {depth} mm, corpo com {hp} hp (esperado: amassado, sem quebrar)",
|
|
11174
|
-
"sc.glass_shatters": "Vidro estilha\xE7a
|
|
11175
|
-
"sc.glass_shatters.check": "Vidro: quebrou depois de {shots} tiro(s) em {n} cacos (esperado: quebrado, pelo menos 4 cacos)",
|
|
11569
|
+
"sc.glass_shatters": "Vidro estilha\xE7a no primeiro tiro de 9 mm",
|
|
11570
|
+
"sc.glass_shatters.check": "Vidro: quebrou depois de {shots} tiro(s) em {n} cacos (esperado: quebrado no primeiro tiro, pelo menos 4 cacos)",
|
|
11176
11571
|
"sc.shotgun_splinters_crate": "Escopeta lasca um caixote de madeira",
|
|
11177
11572
|
"sc.shotgun_splinters_crate.check": "Calibre 12 no caixote: {shots} tiro(s), {splinters} t\xE1bua(s) lascada(s), {holes} furos (esperado: pelo menos uma lascada)",
|
|
11178
11573
|
"sc.kick_chair_leg": "Um chute solta a perna da cadeira",
|
|
@@ -12614,7 +13009,7 @@ void main() {
|
|
|
12614
13009
|
step(0.5);
|
|
12615
13010
|
const fr = room.log.entries({ type: "fracture", objectId: id }).find((e) => e.part === "pane");
|
|
12616
13011
|
const n = Number(fr?.data?.fragments ?? 0);
|
|
12617
|
-
expect(part(room, id, "pane")?.status === "broken" && n >= 4, t("sc.glass_shatters.check", { shots, n }), { shots, fragments: n });
|
|
13012
|
+
expect(part(room, id, "pane")?.status === "broken" && shots === 1 && n >= 4, t("sc.glass_shatters.check", { shots, n }), { shots, fragments: n });
|
|
12618
13013
|
}),
|
|
12619
13014
|
sc("shotgun_splinters_crate", ({ room, step, expect }) => {
|
|
12620
13015
|
const id = at(room, "wooden_crate", 0, -4);
|