@adatechnology/game-destruction-three 0.11.1 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/index.d.ts +26 -8
- package/dist/index.global.js +933 -240
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +445 -86
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/index.global.js
CHANGED
|
@@ -1357,11 +1357,231 @@ var GameDestructionThree = (() => {
|
|
|
1357
1357
|
glow.outIntensity = glow.i * Math.pow(1 - age, glow.curve) * flicker;
|
|
1358
1358
|
}
|
|
1359
1359
|
}
|
|
1360
|
+
var LEHMER_MULTIPLIER = 16807;
|
|
1361
|
+
var LEHMER_MODULUS = 2147483647;
|
|
1362
|
+
var HASH_MIX = 2654435761;
|
|
1363
|
+
function normalizeLehmerSeed(seed) {
|
|
1364
|
+
const whole = Math.floor(Math.abs(seed)) % (LEHMER_MODULUS - 1);
|
|
1365
|
+
return whole === 0 ? 1 : whole;
|
|
1366
|
+
}
|
|
1367
|
+
function lehmer(seed) {
|
|
1368
|
+
let state = normalizeLehmerSeed(seed);
|
|
1369
|
+
const next = () => {
|
|
1370
|
+
state = state * LEHMER_MULTIPLIER % LEHMER_MODULUS;
|
|
1371
|
+
return (state - 1) / (LEHMER_MODULUS - 1);
|
|
1372
|
+
};
|
|
1373
|
+
return {
|
|
1374
|
+
next,
|
|
1375
|
+
between: (min2, max2) => min2 + (max2 - min2) * next(),
|
|
1376
|
+
direction(out) {
|
|
1377
|
+
const up = next() * 2 - 1;
|
|
1378
|
+
const angle = next() * Math.PI * 2;
|
|
1379
|
+
const ring2 = Math.sqrt(1 - up * up);
|
|
1380
|
+
out.x = ring2 * Math.cos(angle);
|
|
1381
|
+
out.y = up;
|
|
1382
|
+
out.z = ring2 * Math.sin(angle);
|
|
1383
|
+
return out;
|
|
1384
|
+
},
|
|
1385
|
+
get state() {
|
|
1386
|
+
return state;
|
|
1387
|
+
}
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
function mixEventSeed(eventSeed) {
|
|
1391
|
+
let mixed = Math.imul((eventSeed | 0) ^ HASH_MIX, 2246822507);
|
|
1392
|
+
mixed ^= mixed >>> 13;
|
|
1393
|
+
mixed = Math.imul(mixed, 3266489909);
|
|
1394
|
+
mixed ^= mixed >>> 16;
|
|
1395
|
+
return (mixed >>> 0) % (LEHMER_MODULUS - 1) + 1;
|
|
1396
|
+
}
|
|
1397
|
+
function streamFor(eventSeed) {
|
|
1398
|
+
return lehmer(mixEventSeed(eventSeed));
|
|
1399
|
+
}
|
|
1400
|
+
var SKIN = 5e-3;
|
|
1401
|
+
var MAX_PAD = 0.45;
|
|
1402
|
+
function rotation(q) {
|
|
1403
|
+
const [x, y, z, w] = q ?? [0, 0, 0, 1];
|
|
1404
|
+
const n = Math.hypot(x, y, z, w) || 1;
|
|
1405
|
+
const qx = x / n;
|
|
1406
|
+
const qy = y / n;
|
|
1407
|
+
const qz = z / n;
|
|
1408
|
+
const qw = w / n;
|
|
1409
|
+
return new Float64Array([
|
|
1410
|
+
1 - 2 * (qy * qy + qz * qz),
|
|
1411
|
+
2 * (qx * qy - qz * qw),
|
|
1412
|
+
2 * (qx * qz + qy * qw),
|
|
1413
|
+
2 * (qx * qy + qz * qw),
|
|
1414
|
+
1 - 2 * (qx * qx + qz * qz),
|
|
1415
|
+
2 * (qy * qz - qx * qw),
|
|
1416
|
+
2 * (qx * qz - qy * qw),
|
|
1417
|
+
2 * (qy * qz + qx * qw),
|
|
1418
|
+
1 - 2 * (qx * qx + qy * qy)
|
|
1419
|
+
]);
|
|
1420
|
+
}
|
|
1421
|
+
var Enclosures = class {
|
|
1422
|
+
constructor(seed = 20973) {
|
|
1423
|
+
__publicField2(this, "spaces", /* @__PURE__ */ new Map());
|
|
1424
|
+
__publicField2(this, "list", []);
|
|
1425
|
+
__publicField2(this, "random");
|
|
1426
|
+
__publicField2(this, "out", { x: 0, y: 0, z: 0, nx: 0, ny: 0, nz: 0 });
|
|
1427
|
+
__publicField2(this, "l0", new Float64Array(3));
|
|
1428
|
+
__publicField2(this, "l1", new Float64Array(3));
|
|
1429
|
+
__publicField2(this, "q", new Float64Array(3));
|
|
1430
|
+
this.random = lehmer(seed);
|
|
1431
|
+
}
|
|
1432
|
+
get empty() {
|
|
1433
|
+
return this.list.length === 0;
|
|
1434
|
+
}
|
|
1435
|
+
get size() {
|
|
1436
|
+
return this.list.length;
|
|
1437
|
+
}
|
|
1438
|
+
/** Declares, moves or (with `null`) ends the space `id`. */
|
|
1439
|
+
set(id, enclosure) {
|
|
1440
|
+
if (!enclosure) {
|
|
1441
|
+
if (this.spaces.delete(id)) this.list = [...this.spaces.values()];
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
const [hx, hy, hz] = enclosure.halfSize;
|
|
1445
|
+
const space = {
|
|
1446
|
+
cx: enclosure.center[0],
|
|
1447
|
+
cy: enclosure.center[1],
|
|
1448
|
+
cz: enclosure.center[2],
|
|
1449
|
+
hx: Math.abs(hx),
|
|
1450
|
+
hy: Math.abs(hy),
|
|
1451
|
+
hz: Math.abs(hz),
|
|
1452
|
+
r: rotation(enclosure.orientation),
|
|
1453
|
+
radius: Math.hypot(hx, hy, hz),
|
|
1454
|
+
openings: enclosure.openings ?? [],
|
|
1455
|
+
leak: Math.max(0, enclosure.leak ?? 0)
|
|
1456
|
+
};
|
|
1457
|
+
const old = this.spaces.get(id);
|
|
1458
|
+
this.spaces.set(id, space);
|
|
1459
|
+
const at3 = old ? this.list.indexOf(old) : -1;
|
|
1460
|
+
if (at3 >= 0) this.list[at3] = space;
|
|
1461
|
+
else this.list.push(space);
|
|
1462
|
+
}
|
|
1463
|
+
get(id) {
|
|
1464
|
+
const s = this.spaces.get(id);
|
|
1465
|
+
if (!s) return void 0;
|
|
1466
|
+
return { center: [s.cx, s.cy, s.cz], halfSize: [s.hx, s.hy, s.hz], openings: s.openings, leak: s.leak };
|
|
1467
|
+
}
|
|
1468
|
+
ids() {
|
|
1469
|
+
return [...this.spaces.keys()];
|
|
1470
|
+
}
|
|
1471
|
+
clear() {
|
|
1472
|
+
this.spaces.clear();
|
|
1473
|
+
this.list = [];
|
|
1474
|
+
}
|
|
1475
|
+
/** Whether a point is inside any space (on the inside of its walls). */
|
|
1476
|
+
inside(x, y, z) {
|
|
1477
|
+
for (const s of this.list) {
|
|
1478
|
+
this.toLocal(s, x, y, z, this.l0);
|
|
1479
|
+
if (Math.abs(this.l0[0]) <= s.hx && Math.abs(this.l0[1]) <= s.hy && Math.abs(this.l0[2]) <= s.hz) return true;
|
|
1480
|
+
}
|
|
1481
|
+
return false;
|
|
1482
|
+
}
|
|
1483
|
+
toLocal(s, x, y, z, out) {
|
|
1484
|
+
const dx = x - s.cx;
|
|
1485
|
+
const dy = y - s.cy;
|
|
1486
|
+
const dz = z - s.cz;
|
|
1487
|
+
const r = s.r;
|
|
1488
|
+
out[0] = r[0] * dx + r[3] * dy + r[6] * dz;
|
|
1489
|
+
out[1] = r[1] * dx + r[4] * dy + r[7] * dz;
|
|
1490
|
+
out[2] = r[2] * dx + r[5] * dy + r[8] * dz;
|
|
1491
|
+
}
|
|
1492
|
+
/** Passes at `rate` per second. */
|
|
1493
|
+
chance(rate, dt) {
|
|
1494
|
+
return rate > 0 && this.random.next() < 1 - Math.exp(-rate * dt);
|
|
1495
|
+
}
|
|
1496
|
+
/** The opening the wall point `q` (local) is in lets it through now. */
|
|
1497
|
+
through(s, q, dt) {
|
|
1498
|
+
for (const o of s.openings) {
|
|
1499
|
+
if (Math.abs(q[0] - o.center[0]) > o.halfSize[0] || Math.abs(q[1] - o.center[1]) > o.halfSize[1] || Math.abs(q[2] - o.center[2]) > o.halfSize[2]) continue;
|
|
1500
|
+
if (o.rate === void 0 || this.chance(o.rate, dt)) return true;
|
|
1501
|
+
}
|
|
1502
|
+
return this.chance(s.leak, dt);
|
|
1503
|
+
}
|
|
1504
|
+
confine(x0, y0, z0, x1, y1, z1, pad, dt) {
|
|
1505
|
+
const list = this.list;
|
|
1506
|
+
if (list.length === 0) return false;
|
|
1507
|
+
const p = Math.min(Math.max(0, pad), MAX_PAD);
|
|
1508
|
+
const l0 = this.l0;
|
|
1509
|
+
const l1 = this.l1;
|
|
1510
|
+
const q = this.q;
|
|
1511
|
+
for (const s of list) {
|
|
1512
|
+
const reach = s.radius + p + 0.05;
|
|
1513
|
+
const dx = x1 - s.cx;
|
|
1514
|
+
const dy = y1 - s.cy;
|
|
1515
|
+
const dz = z1 - s.cz;
|
|
1516
|
+
if (dx * dx + dy * dy + dz * dz > reach * reach) {
|
|
1517
|
+
const ex = x0 - s.cx;
|
|
1518
|
+
const ey = y0 - s.cy;
|
|
1519
|
+
const ez = z0 - s.cz;
|
|
1520
|
+
if (ex * ex + ey * ey + ez * ez > reach * reach) continue;
|
|
1521
|
+
}
|
|
1522
|
+
this.toLocal(s, x0, y0, z0, l0);
|
|
1523
|
+
this.toLocal(s, x1, y1, z1, l1);
|
|
1524
|
+
const h = [s.hx, s.hy, s.hz];
|
|
1525
|
+
const in0 = Math.abs(l0[0]) <= s.hx && Math.abs(l0[1]) <= s.hy && Math.abs(l0[2]) <= s.hz;
|
|
1526
|
+
let axis2 = 0;
|
|
1527
|
+
let sign = 1;
|
|
1528
|
+
if (in0) {
|
|
1529
|
+
if (Math.abs(l1[0]) <= s.hx && Math.abs(l1[1]) <= s.hy && Math.abs(l1[2]) <= s.hz) continue;
|
|
1530
|
+
for (let a = 0; a < 3; a++) q[a] = Math.min(h[a], Math.max(-h[a], l1[a]));
|
|
1531
|
+
if (this.through(s, q, dt)) continue;
|
|
1532
|
+
let worst = -Infinity;
|
|
1533
|
+
for (let a = 0; a < 3; a++) {
|
|
1534
|
+
const over = Math.abs(l1[a]) - h[a];
|
|
1535
|
+
if (over > worst) {
|
|
1536
|
+
worst = over;
|
|
1537
|
+
axis2 = a;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
sign = l1[axis2] < 0 ? -1 : 1;
|
|
1541
|
+
for (let a = 0; a < 3; a++) l1[a] = Math.min(h[a] - SKIN, Math.max(-(h[a] - SKIN), l1[a]));
|
|
1542
|
+
sign = -sign;
|
|
1543
|
+
} else {
|
|
1544
|
+
const padded0 = Math.abs(l0[0]) <= s.hx + p && Math.abs(l0[1]) <= s.hy + p && Math.abs(l0[2]) <= s.hz + p;
|
|
1545
|
+
const bound = padded0 ? 0 : p;
|
|
1546
|
+
if (!(Math.abs(l1[0]) <= s.hx + bound && Math.abs(l1[1]) <= s.hy + bound && Math.abs(l1[2]) <= s.hz + bound)) continue;
|
|
1547
|
+
for (let a = 0; a < 3; a++) q[a] = Math.min(h[a], Math.max(-h[a], l1[a]));
|
|
1548
|
+
if (this.through(s, q, dt)) continue;
|
|
1549
|
+
let worst = -Infinity;
|
|
1550
|
+
for (let a = 0; a < 3; a++) {
|
|
1551
|
+
const over = Math.abs(l0[a]) - (h[a] + bound);
|
|
1552
|
+
if (over > worst) {
|
|
1553
|
+
worst = over;
|
|
1554
|
+
axis2 = a;
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
sign = l0[axis2] < 0 ? -1 : 1;
|
|
1558
|
+
l1[axis2] = sign * (h[axis2] + bound + SKIN);
|
|
1559
|
+
}
|
|
1560
|
+
const r = s.r;
|
|
1561
|
+
const out = this.out;
|
|
1562
|
+
out.x = s.cx + r[0] * l1[0] + r[1] * l1[1] + r[2] * l1[2];
|
|
1563
|
+
out.y = s.cy + r[3] * l1[0] + r[4] * l1[1] + r[5] * l1[2];
|
|
1564
|
+
out.z = s.cz + r[6] * l1[0] + r[7] * l1[1] + r[8] * l1[2];
|
|
1565
|
+
out.nx = r[axis2] * sign;
|
|
1566
|
+
out.ny = r[3 + axis2] * sign;
|
|
1567
|
+
out.nz = r[6 + axis2] * sign;
|
|
1568
|
+
return true;
|
|
1569
|
+
}
|
|
1570
|
+
return false;
|
|
1571
|
+
}
|
|
1572
|
+
};
|
|
1573
|
+
function slideAlong(out, vx2, vy2, vz2) {
|
|
1574
|
+
const into = vx2 * out.nx + vy2 * out.ny + vz2 * out.nz;
|
|
1575
|
+
if (into >= 0) return [vx2, vy2, vz2];
|
|
1576
|
+
return [vx2 - into * out.nx, vy2 - into * out.ny, vz2 - into * out.nz];
|
|
1577
|
+
}
|
|
1360
1578
|
var FLOOR_FRACTION = 0.4;
|
|
1361
1579
|
var FLOOR_BOUNCE = -0.2;
|
|
1362
1580
|
var ARRIVAL_FRAME = 30;
|
|
1363
1581
|
var ARRIVAL_BASE = 0.3;
|
|
1364
1582
|
var GROWTH_CAP_SECONDS = 3.5;
|
|
1583
|
+
var CARRIED_KINDS = [true, true, false, true, false];
|
|
1584
|
+
var PAD_OF_SIZE = 0.35;
|
|
1365
1585
|
function createPuffPool(capacity) {
|
|
1366
1586
|
const placeholder = { next: () => 0, between: (min2) => min2, direction: (out) => out, state: 0 };
|
|
1367
1587
|
return new OrderedPool(capacity, () => ({
|
|
@@ -1456,13 +1676,24 @@ var GameDestructionThree = (() => {
|
|
|
1456
1676
|
if (puff3.vy < 0) puff3.vy *= FLOOR_BOUNCE;
|
|
1457
1677
|
}
|
|
1458
1678
|
}
|
|
1459
|
-
function updatePuffs(pool, dt, wind) {
|
|
1679
|
+
function updatePuffs(pool, dt, wind, confiner) {
|
|
1460
1680
|
pool.compact(ageOrKeep2, dt);
|
|
1681
|
+
const confining = confiner !== void 0 && !confiner.empty;
|
|
1461
1682
|
for (const puff3 of pool.items) {
|
|
1462
1683
|
const age = puff3.t / puff3.life;
|
|
1463
1684
|
const heat = puff3.h0 > 0 ? puff3.h0 * Math.pow(Math.max(0, 1 - puff3.t / puff3.hl), 1.25) : 0;
|
|
1685
|
+
const x0 = puff3.x;
|
|
1686
|
+
const y0 = puff3.y;
|
|
1687
|
+
const z0 = puff3.z;
|
|
1464
1688
|
if (puff3.ring) moveOnRing(puff3, puff3.ring, wind);
|
|
1465
1689
|
else moveFree(puff3, dt, age, heat, wind);
|
|
1690
|
+
if (confining && CARRIED_KINDS[puff3.kind] && confiner.confine(x0, y0, z0, puff3.x, puff3.y, puff3.z, puff3.size * PAD_OF_SIZE, dt)) {
|
|
1691
|
+
const out = confiner.out;
|
|
1692
|
+
puff3.x = out.x;
|
|
1693
|
+
puff3.y = out.y;
|
|
1694
|
+
puff3.z = out.z;
|
|
1695
|
+
[puff3.vx, puff3.vy, puff3.vz] = slideAlong(out, puff3.vx, puff3.vy, puff3.vz);
|
|
1696
|
+
}
|
|
1466
1697
|
const size = puff3.s0 + (puff3.s1 - puff3.s0) * (1 - Math.exp(-puff3.t * puff3.gk)) + puff3.sg * Math.min(puff3.t, GROWTH_CAP_SECONDS);
|
|
1467
1698
|
puff3.size = size * Math.min(1, puff3.t * ARRIVAL_FRAME + ARRIVAL_BASE);
|
|
1468
1699
|
puff3.age = age;
|
|
@@ -1988,10 +2219,10 @@ var GameDestructionThree = (() => {
|
|
|
1988
2219
|
])
|
|
1989
2220
|
]
|
|
1990
2221
|
};
|
|
1991
|
-
function localToWorld(translation,
|
|
2222
|
+
function localToWorld(translation, rotation2, local2) {
|
|
1992
2223
|
const [x, y, z] = local2;
|
|
1993
|
-
const [a, c, e] = [Math.cos(
|
|
1994
|
-
const [b, d, f] = [Math.sin(
|
|
2224
|
+
const [a, c, e] = [Math.cos(rotation2[0]), Math.cos(rotation2[1]), Math.cos(rotation2[2])];
|
|
2225
|
+
const [b, d, f] = [Math.sin(rotation2[0]), Math.sin(rotation2[1]), Math.sin(rotation2[2])];
|
|
1995
2226
|
const [ae, af, be, bf] = [a * e, a * f, b * e, b * f];
|
|
1996
2227
|
return [
|
|
1997
2228
|
c * e * x + -c * f * y + d * z + translation[0],
|
|
@@ -3654,46 +3885,6 @@ var GameDestructionThree = (() => {
|
|
|
3654
3885
|
var ALL_EFFECTS = [BLOOD_HIT, HEADSHOT, DISMEMBER, BLOATER_CLOUD, SCREAMER_SHRIEK, CORPSE_DECAY, REANIMATION, EYES_IN_FOG, MUZZLE_FLASH, IMPACT_BY_MATERIAL, PENETRATION, CASINGS_AND_MAGAZINE, MOLOTOV, GRENADE, EXPLOSIVE_BARREL, SHOPFRONT_GLASS, DOOR_STAGES, BUILDING_COLLAPSE, BLOCK_WALL, METAL_DOOR_EFFECT, IRON_RAILING, WOODEN_FENCE, HIT_POINT_BREAKAGE, PRESSURE_CYLINDERS, BUILDING_FIRE, RAIN_AND_PUDDLES, FOG_AND_ASH, SCRAPING_SPARKS, ENGINE_SMOKE, FUEL_LEAK, CAR_EXPLOSION, WINDSHIELD, HEADLIGHTS_IN_RAIN, DAMAGE_AND_BITE, INFECTION_PHASES, PANIC, BLOATER_BLINDNESS, CAMPFIRE, COUGH_AND_BREATH];
|
|
3655
3886
|
var WORKSHOP_EFFECTS = Object.fromEntries(ALL_EFFECTS.map((effect) => [effect.id, effect]));
|
|
3656
3887
|
var GAME_EFFECTS = { ...WORKSHOP_EFFECTS, ...Object.fromEntries([...EVENT_EFFECTS, ...VIEWMODEL_EFFECTS, ...VEHICLE_EFFECTS].map((effect) => [effect.id, effect])) };
|
|
3657
|
-
var LEHMER_MULTIPLIER = 16807;
|
|
3658
|
-
var LEHMER_MODULUS = 2147483647;
|
|
3659
|
-
var HASH_MIX = 2654435761;
|
|
3660
|
-
function normalizeLehmerSeed(seed) {
|
|
3661
|
-
const whole = Math.floor(Math.abs(seed)) % (LEHMER_MODULUS - 1);
|
|
3662
|
-
return whole === 0 ? 1 : whole;
|
|
3663
|
-
}
|
|
3664
|
-
function lehmer(seed) {
|
|
3665
|
-
let state = normalizeLehmerSeed(seed);
|
|
3666
|
-
const next = () => {
|
|
3667
|
-
state = state * LEHMER_MULTIPLIER % LEHMER_MODULUS;
|
|
3668
|
-
return (state - 1) / (LEHMER_MODULUS - 1);
|
|
3669
|
-
};
|
|
3670
|
-
return {
|
|
3671
|
-
next,
|
|
3672
|
-
between: (min2, max2) => min2 + (max2 - min2) * next(),
|
|
3673
|
-
direction(out) {
|
|
3674
|
-
const up = next() * 2 - 1;
|
|
3675
|
-
const angle = next() * Math.PI * 2;
|
|
3676
|
-
const ring2 = Math.sqrt(1 - up * up);
|
|
3677
|
-
out.x = ring2 * Math.cos(angle);
|
|
3678
|
-
out.y = up;
|
|
3679
|
-
out.z = ring2 * Math.sin(angle);
|
|
3680
|
-
return out;
|
|
3681
|
-
},
|
|
3682
|
-
get state() {
|
|
3683
|
-
return state;
|
|
3684
|
-
}
|
|
3685
|
-
};
|
|
3686
|
-
}
|
|
3687
|
-
function mixEventSeed(eventSeed) {
|
|
3688
|
-
let mixed = Math.imul((eventSeed | 0) ^ HASH_MIX, 2246822507);
|
|
3689
|
-
mixed ^= mixed >>> 13;
|
|
3690
|
-
mixed = Math.imul(mixed, 3266489909);
|
|
3691
|
-
mixed ^= mixed >>> 16;
|
|
3692
|
-
return (mixed >>> 0) % (LEHMER_MODULUS - 1) + 1;
|
|
3693
|
-
}
|
|
3694
|
-
function streamFor(eventSeed) {
|
|
3695
|
-
return lehmer(mixEventSeed(eventSeed));
|
|
3696
|
-
}
|
|
3697
3888
|
var TABLE = {
|
|
3698
3889
|
concrete: { dust: 9078138, chip: 7236195, spark: 2 },
|
|
3699
3890
|
metal: { dust: 0, chip: 0, spark: 18 },
|
|
@@ -4997,8 +5188,8 @@ var GameDestructionThree = (() => {
|
|
|
4997
5188
|
function addScorch(store, random, x, z, radius) {
|
|
4998
5189
|
const variant = Math.floor(random.next() * SCORCH_VARIANTS);
|
|
4999
5190
|
const lift = 0.03 + nextLift(store) * 1e-3;
|
|
5000
|
-
const
|
|
5001
|
-
store.add({ kind: "scorch", x, z, radius, variant, lift, rotation });
|
|
5191
|
+
const rotation2 = random.next() * 6;
|
|
5192
|
+
store.add({ kind: "scorch", x, z, radius, variant, lift, rotation: rotation2 });
|
|
5002
5193
|
}
|
|
5003
5194
|
function addRimRocks(store, ground, random, x, z, radius) {
|
|
5004
5195
|
const count = Math.round(6 + radius * 7);
|
|
@@ -5027,8 +5218,8 @@ var GameDestructionThree = (() => {
|
|
|
5027
5218
|
function addCrater(store, ground, random, spec) {
|
|
5028
5219
|
ground?.dent(random, spec.x, spec.z, spec.radius, spec.depth);
|
|
5029
5220
|
if (!spec.marks) return;
|
|
5030
|
-
const
|
|
5031
|
-
store.add({ kind: "dirt", x: spec.x, z: spec.z, radius: spec.radius * 1.02, lift: 0.045, rotation });
|
|
5221
|
+
const rotation2 = random.next() * 6;
|
|
5222
|
+
store.add({ kind: "dirt", x: spec.x, z: spec.z, radius: spec.radius * 1.02, lift: 0.045, rotation: rotation2 });
|
|
5032
5223
|
addRimRocks(store, ground, random, spec.x, spec.z, spec.radius);
|
|
5033
5224
|
}
|
|
5034
5225
|
function addBloodSplat(store, ground, random, spec) {
|
|
@@ -5584,6 +5775,7 @@ var GameDestructionThree = (() => {
|
|
|
5584
5775
|
state.gas = Math.max(0, state.gas - dt / DECAY.gasSeconds);
|
|
5585
5776
|
state.beat *= Math.exp(-DECAY.beat * dt);
|
|
5586
5777
|
}
|
|
5778
|
+
var PAD_OF_SIZE2 = 0.35;
|
|
5587
5779
|
var clamp4 = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
|
|
5588
5780
|
var lerp4 = (a, b, t) => a + (b - a) * t;
|
|
5589
5781
|
function rgb(hex) {
|
|
@@ -5743,9 +5935,20 @@ var GameDestructionThree = (() => {
|
|
|
5743
5935
|
vy2 *= k;
|
|
5744
5936
|
vz2 *= k;
|
|
5745
5937
|
}
|
|
5746
|
-
|
|
5938
|
+
let x = this.position[i3] + vx2 * dt;
|
|
5747
5939
|
let y = this.position[i3 + 1] + vy2 * dt;
|
|
5748
|
-
|
|
5940
|
+
let z = this.position[i3 + 2] + vz2 * dt;
|
|
5941
|
+
const confiner = host.confiner;
|
|
5942
|
+
if (def.carried && confiner && !confiner.empty) {
|
|
5943
|
+
const pad = this.size0[i] * lerp4(1, def.grow, t) * PAD_OF_SIZE2;
|
|
5944
|
+
if (confiner.confine(this.position[i3], this.position[i3 + 1], this.position[i3 + 2], x, y, z, pad, dt)) {
|
|
5945
|
+
const out = confiner.out;
|
|
5946
|
+
x = out.x;
|
|
5947
|
+
y = out.y;
|
|
5948
|
+
z = out.z;
|
|
5949
|
+
[vx2, vy2, vz2] = slideAlong(out, vx2, vy2, vz2);
|
|
5950
|
+
}
|
|
5951
|
+
}
|
|
5749
5952
|
if (def.ground !== "none") {
|
|
5750
5953
|
const own = this.floor[i];
|
|
5751
5954
|
const gy = Number.isNaN(own) ? host.groundAt(x, z) + 5e-3 : own;
|
|
@@ -5831,7 +6034,8 @@ var GameDestructionThree = (() => {
|
|
|
5831
6034
|
alpha: 0.35,
|
|
5832
6035
|
fadeIn: 0.15,
|
|
5833
6036
|
cone: 0.6,
|
|
5834
|
-
ground: "none"
|
|
6037
|
+
ground: "none",
|
|
6038
|
+
carried: true
|
|
5835
6039
|
},
|
|
5836
6040
|
sparks: {
|
|
5837
6041
|
id: "sparks",
|
|
@@ -5875,7 +6079,8 @@ var GameDestructionThree = (() => {
|
|
|
5875
6079
|
alpha: 0.4,
|
|
5876
6080
|
fadeIn: 0.1,
|
|
5877
6081
|
cone: 0.4,
|
|
5878
|
-
ground: "none"
|
|
6082
|
+
ground: "none",
|
|
6083
|
+
carried: true
|
|
5879
6084
|
},
|
|
5880
6085
|
shards: {
|
|
5881
6086
|
id: "shards",
|
|
@@ -6033,7 +6238,8 @@ var GameDestructionThree = (() => {
|
|
|
6033
6238
|
fadeIn: 0.1,
|
|
6034
6239
|
cone: 0.5,
|
|
6035
6240
|
ground: "stop",
|
|
6036
|
-
flutter: 0.6
|
|
6241
|
+
flutter: 0.6,
|
|
6242
|
+
carried: true
|
|
6037
6243
|
},
|
|
6038
6244
|
steam: {
|
|
6039
6245
|
id: "steam",
|
|
@@ -6055,7 +6261,8 @@ var GameDestructionThree = (() => {
|
|
|
6055
6261
|
alpha: 0.35,
|
|
6056
6262
|
fadeIn: 0.15,
|
|
6057
6263
|
cone: 0.6,
|
|
6058
|
-
ground: "none"
|
|
6264
|
+
ground: "none",
|
|
6265
|
+
carried: true
|
|
6059
6266
|
},
|
|
6060
6267
|
flash: {
|
|
6061
6268
|
id: "flash",
|
|
@@ -6188,7 +6395,8 @@ var GameDestructionThree = (() => {
|
|
|
6188
6395
|
const host = {
|
|
6189
6396
|
groundAt: this.options.groundAt,
|
|
6190
6397
|
gravity: this.options.gravity,
|
|
6191
|
-
land: (owner, x, y, z, color, tag) => this.listeners.get(owner)?.(x, y, z, color, tag)
|
|
6398
|
+
land: (owner, x, y, z, color, tag) => this.listeners.get(owner)?.(x, y, z, color, tag),
|
|
6399
|
+
...this.options.confiner ? { confiner: this.options.confiner } : {}
|
|
6192
6400
|
};
|
|
6193
6401
|
if (this.additive.live > 0 || this.additive.drawCount > 0) this.additive.update(dt, host);
|
|
6194
6402
|
if (this.normal.live > 0 || this.normal.drawCount > 0) this.normal.update(dt, host);
|
|
@@ -6394,8 +6602,8 @@ var GameDestructionThree = (() => {
|
|
|
6394
6602
|
* Takes away the marks inside a box: world `center`, `half` extents along the axes of the world `rotation` (a quaternion, x y z w; none means the
|
|
6395
6603
|
* world axes). The surface they were on is gone (a brick came out of a wall with its bullet holes). Returns how many went.
|
|
6396
6604
|
*/
|
|
6397
|
-
clearInside(center, half,
|
|
6398
|
-
const qx = -
|
|
6605
|
+
clearInside(center, half, rotation2 = [0, 0, 0, 1]) {
|
|
6606
|
+
const qx = -rotation2[0], qy = -rotation2[1], qz = -rotation2[2], qw = rotation2[3];
|
|
6399
6607
|
let gone = 0;
|
|
6400
6608
|
for (const decal2 of this.decals) {
|
|
6401
6609
|
if (!decal2.on) continue;
|
|
@@ -6516,10 +6724,14 @@ var GameDestructionThree = (() => {
|
|
|
6516
6724
|
var DEFAULT_FADE_IN = 0.1;
|
|
6517
6725
|
var FLOATS_PER_VECTOR = 3;
|
|
6518
6726
|
var BYTE_MAX2 = 255;
|
|
6727
|
+
var CARRIED_GRAVITY = 3;
|
|
6728
|
+
var PAD_OF_SIZE3 = 0.35;
|
|
6519
6729
|
var SpritePool = class {
|
|
6520
|
-
|
|
6730
|
+
/** `carried`: the normal-blend pool (smoke, dust, steam), whose light sprites go round the closed spaces; the additive one (flames, sparks) does not. */
|
|
6731
|
+
constructor(capacity, context, carried = false) {
|
|
6521
6732
|
__publicField2(this, "capacity", capacity);
|
|
6522
6733
|
__publicField2(this, "context", context);
|
|
6734
|
+
__publicField2(this, "carried", carried);
|
|
6523
6735
|
__publicField2(this, "count", 0);
|
|
6524
6736
|
__publicField2(this, "position");
|
|
6525
6737
|
__publicField2(this, "velocity");
|
|
@@ -6619,6 +6831,7 @@ var GameDestructionThree = (() => {
|
|
|
6619
6831
|
}
|
|
6620
6832
|
update(dt, leaveDecal) {
|
|
6621
6833
|
const { position, velocity } = this;
|
|
6834
|
+
const confiner = this.context.confiner;
|
|
6622
6835
|
for (let index = this.count - 1; index >= 0; index--) {
|
|
6623
6836
|
this.life[index] -= dt;
|
|
6624
6837
|
if (this.life[index] <= 0) {
|
|
@@ -6632,9 +6845,25 @@ var GameDestructionThree = (() => {
|
|
|
6632
6845
|
velocity[offset] *= factor;
|
|
6633
6846
|
velocity[offset + 1] *= factor;
|
|
6634
6847
|
velocity[offset + 2] *= factor;
|
|
6848
|
+
const x0 = position[offset];
|
|
6849
|
+
const y0 = position[offset + 1];
|
|
6850
|
+
const z0 = position[offset + 2];
|
|
6635
6851
|
position[offset] += velocity[offset] * dt;
|
|
6636
6852
|
position[offset + 1] += velocity[offset + 1] * dt;
|
|
6637
6853
|
position[offset + 2] += velocity[offset + 2] * dt;
|
|
6854
|
+
if (this.carried && confiner && !confiner.empty && Math.abs(this.gravity[index]) < CARRIED_GRAVITY) {
|
|
6855
|
+
const size = this.startSize[index] * (1 + (this.growth[index] - 1) * age);
|
|
6856
|
+
if (confiner.confine(x0, y0, z0, position[offset], position[offset + 1], position[offset + 2], size * PAD_OF_SIZE3, dt)) {
|
|
6857
|
+
const out = confiner.out;
|
|
6858
|
+
position[offset] = out.x;
|
|
6859
|
+
position[offset + 1] = out.y;
|
|
6860
|
+
position[offset + 2] = out.z;
|
|
6861
|
+
const [vx2, vy2, vz2] = slideAlong(out, velocity[offset], velocity[offset + 1], velocity[offset + 2]);
|
|
6862
|
+
velocity[offset] = vx2;
|
|
6863
|
+
velocity[offset + 1] = vy2;
|
|
6864
|
+
velocity[offset + 2] = vz2;
|
|
6865
|
+
}
|
|
6866
|
+
}
|
|
6638
6867
|
const mode = this.groundMode[index];
|
|
6639
6868
|
if (mode) {
|
|
6640
6869
|
const groundHeight = this.context.groundAt(position[offset], position[offset + 2]);
|
|
@@ -6911,9 +7140,9 @@ var GameDestructionThree = (() => {
|
|
|
6911
7140
|
__publicField2(this, "placement");
|
|
6912
7141
|
__publicField2(this, "accumulators", /* @__PURE__ */ new Map());
|
|
6913
7142
|
const profile = options.profile ?? PROFILES.high;
|
|
6914
|
-
this.context = { random: options.random, groundAt: options.groundAt ?? workshopGround, quality: { level: 0, mult: 1 }, trace: options.trace };
|
|
7143
|
+
this.context = { random: options.random, groundAt: options.groundAt ?? workshopGround, quality: { level: 0, mult: 1 }, trace: options.trace, ...options.confiner ? { confiner: options.confiner } : {} };
|
|
6915
7144
|
this.add = new SpritePool(profile.sprites.add, this.context);
|
|
6916
|
-
this.normal = new SpritePool(profile.sprites.normal, this.context);
|
|
7145
|
+
this.normal = new SpritePool(profile.sprites.normal, this.context, true);
|
|
6917
7146
|
this.streaks = new StreakPool(profile.streaks, this.context);
|
|
6918
7147
|
this.debris = new DebrisPool(this.context);
|
|
6919
7148
|
this.decals = new DecalPool(this.context);
|
|
@@ -7101,12 +7330,14 @@ var GameDestructionThree = (() => {
|
|
|
7101
7330
|
const groundAt = options.groundAt ?? (ground ? (x, z) => ground.heightAt(x, z) : () => 0);
|
|
7102
7331
|
const handles = { next: 0, events: 0 };
|
|
7103
7332
|
const low = options.profile === "low";
|
|
7333
|
+
const enclosures = new Enclosures(20973 + (options.seed ?? 0));
|
|
7104
7334
|
const spriteSystem = new SpriteKindSystem({
|
|
7105
7335
|
capacity: options.sprites?.capacity ?? (low ? 1250 : 2500),
|
|
7106
7336
|
density: options.sprites?.density ?? (low ? 0.6 : 1),
|
|
7107
7337
|
seed: 2654435761 + (options.seed ?? 0),
|
|
7108
7338
|
groundAt,
|
|
7109
7339
|
gravity: options.gravity ?? DEFAULT_GRAVITY,
|
|
7340
|
+
confiner: enclosures,
|
|
7110
7341
|
...options.sprites?.kinds ? { kinds: options.sprites.kinds } : {}
|
|
7111
7342
|
});
|
|
7112
7343
|
const core = {
|
|
@@ -7156,7 +7387,7 @@ var GameDestructionThree = (() => {
|
|
|
7156
7387
|
return [camera2.x, camera2.y, camera2.z];
|
|
7157
7388
|
}, forward: (local2) => [local2[0] ?? 0, local2[1] ?? 0, local2[2] ?? 0] };
|
|
7158
7389
|
function workshop() {
|
|
7159
|
-
return engine ?? (engine = new WorkshopEngine({ random: sharedRandom, profile, groundAt }));
|
|
7390
|
+
return engine ?? (engine = new WorkshopEngine({ random: sharedRandom, profile, groundAt, confiner: enclosures }));
|
|
7160
7391
|
}
|
|
7161
7392
|
function effectPlayer() {
|
|
7162
7393
|
return player ?? (player = createEffectPlayer({ engine: workshop(), effects, view }));
|
|
@@ -7215,6 +7446,10 @@ var GameDestructionThree = (() => {
|
|
|
7215
7446
|
puff3.vz += dz * strength;
|
|
7216
7447
|
}
|
|
7217
7448
|
},
|
|
7449
|
+
enclose(id, enclosure) {
|
|
7450
|
+
enclosures.set(id, enclosure);
|
|
7451
|
+
},
|
|
7452
|
+
enclosures: () => enclosures,
|
|
7218
7453
|
stop(handle) {
|
|
7219
7454
|
player?.stop(handle);
|
|
7220
7455
|
const fires = core.fires;
|
|
@@ -7242,7 +7477,7 @@ var GameDestructionThree = (() => {
|
|
|
7242
7477
|
core.clock.time += dt;
|
|
7243
7478
|
core.schedule.update(core.clock.time);
|
|
7244
7479
|
updateRings(core.rings, dt);
|
|
7245
|
-
updatePuffs(core.puffs, dt, wind);
|
|
7480
|
+
updatePuffs(core.puffs, dt, wind, enclosures);
|
|
7246
7481
|
updateSparks(core.sparks, { dt, groundAt });
|
|
7247
7482
|
updateGlows(core.glows, dt);
|
|
7248
7483
|
updateShocks(core.shocks, core.puffs.items, dt);
|
|
@@ -7727,6 +7962,11 @@ void main(){
|
|
|
7727
7962
|
var MAX_CONFORM_STEPS = 48;
|
|
7728
7963
|
var MIN_CONFORM_STEPS = 16;
|
|
7729
7964
|
var BLOOD_GROW_RATE = 1.2;
|
|
7965
|
+
var BLOOD_START_GROWTH = 0.05;
|
|
7966
|
+
var DEFAULT_MAX_GROUND_MARKS = 64;
|
|
7967
|
+
var DEFAULT_MAX_ROCKS = 512;
|
|
7968
|
+
var DEFAULT_MAX_BLOOD = 256;
|
|
7969
|
+
var MAX_SPARE_GROUND_GEOMETRIES = 16;
|
|
7730
7970
|
function createMarkLayer(THREE, options) {
|
|
7731
7971
|
const group = new THREE.Group();
|
|
7732
7972
|
const textures = createMarkTextures(THREE, options.createCanvas);
|
|
@@ -7750,110 +7990,168 @@ void main(){
|
|
|
7750
7990
|
const bloodMaterial = new THREE.MeshStandardMaterial({ color: 6031882, roughness: 0.15, metalness: 0.1, polygonOffset: true, polygonOffsetFactor: -3, polygonOffsetUnits: -3 });
|
|
7751
7991
|
const bloodGeometry = new THREE.CircleGeometry(0.5, 9).rotateX(-Math.PI / 2);
|
|
7752
7992
|
owned.push(bloodMaterial, bloodGeometry);
|
|
7753
|
-
const
|
|
7754
|
-
const
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
|
|
7758
|
-
|
|
7759
|
-
material = new THREE.MeshStandardMaterial({ color, roughness: 0.9, metalness: 0, flatShading: true });
|
|
7760
|
-
rockMaterials.set(color, material);
|
|
7761
|
-
owned.push(material);
|
|
7993
|
+
const maxGroundMarks = options.maxGroundMarks ?? DEFAULT_MAX_GROUND_MARKS;
|
|
7994
|
+
const instanced = {};
|
|
7995
|
+
function rockMaterial() {
|
|
7996
|
+
if (!instanced.rockMaterial) {
|
|
7997
|
+
instanced.rockMaterial = new THREE.MeshStandardMaterial({ color: 16777215, roughness: 0.9, metalness: 0, flatShading: true });
|
|
7998
|
+
owned.push(instanced.rockMaterial);
|
|
7762
7999
|
}
|
|
7763
|
-
return
|
|
8000
|
+
return instanced.rockMaterial;
|
|
8001
|
+
}
|
|
8002
|
+
function bloodMesh() {
|
|
8003
|
+
return instanced.blood ?? (instanced.blood = attach(createInstanced(bloodGeometry, bloodMaterial, options.maxBlood ?? DEFAULT_MAX_BLOOD)));
|
|
8004
|
+
}
|
|
8005
|
+
function rockMesh() {
|
|
8006
|
+
return instanced.rock ?? (instanced.rock = attach(createInstanced(options.rockGeometry, rockMaterial(), options.maxRocks ?? DEFAULT_MAX_ROCKS), true));
|
|
8007
|
+
}
|
|
8008
|
+
function plateMesh() {
|
|
8009
|
+
return instanced.plate ?? (instanced.plate = attach(createInstanced(options.plateGeometry, rockMaterial(), options.maxRocks ?? DEFAULT_MAX_ROCKS), true));
|
|
8010
|
+
}
|
|
8011
|
+
function attach(mesh2, castShadow = false) {
|
|
8012
|
+
mesh2.castShadow = castShadow;
|
|
8013
|
+
group.add(mesh2);
|
|
8014
|
+
return mesh2;
|
|
8015
|
+
}
|
|
8016
|
+
const groundMarks = [];
|
|
8017
|
+
const spareGeometries = [];
|
|
8018
|
+
const blood = [];
|
|
8019
|
+
const placed = { blood: 0, rock: 0, plate: 0 };
|
|
8020
|
+
const matrix = new THREE.Matrix4();
|
|
8021
|
+
const position = new THREE.Vector3();
|
|
8022
|
+
const quaternion = new THREE.Quaternion();
|
|
8023
|
+
const euler = new THREE.Euler();
|
|
8024
|
+
const scale = new THREE.Vector3();
|
|
8025
|
+
const color = new THREE.Color();
|
|
8026
|
+
const up = new THREE.Vector3(0, 1, 0);
|
|
8027
|
+
function createInstanced(geometry, material, capacity) {
|
|
8028
|
+
const mesh2 = new THREE.InstancedMesh(geometry, material, capacity);
|
|
8029
|
+
mesh2.count = 0;
|
|
8030
|
+
mesh2.frustumCulled = false;
|
|
8031
|
+
mesh2.receiveShadow = true;
|
|
8032
|
+
return mesh2;
|
|
8033
|
+
}
|
|
8034
|
+
function takeSpare(vertexCount) {
|
|
8035
|
+
const found = spareGeometries.findIndex((geometry) => geometry.getAttribute("position").count === vertexCount);
|
|
8036
|
+
return found < 0 ? void 0 : spareGeometries.splice(found, 1)[0];
|
|
8037
|
+
}
|
|
8038
|
+
function evictOldestGroundMark() {
|
|
8039
|
+
const oldest = groundMarks.shift();
|
|
8040
|
+
group.remove(oldest.mesh);
|
|
8041
|
+
spareGeometries.push(oldest.mesh.geometry);
|
|
8042
|
+
if (spareGeometries.length > MAX_SPARE_GROUND_GEOMETRIES) spareGeometries.shift().dispose();
|
|
7764
8043
|
}
|
|
7765
8044
|
function conform(mark, material) {
|
|
7766
8045
|
const radius = mark.radius;
|
|
7767
8046
|
const steps = Math.max(MIN_CONFORM_STEPS, Math.min(MAX_CONFORM_STEPS, Math.round(radius * 8)));
|
|
7768
|
-
|
|
7769
|
-
const
|
|
8047
|
+
if (groundMarks.length >= maxGroundMarks) evictOldestGroundMark();
|
|
8048
|
+
const geometry = takeSpare((steps + 1) * (steps + 1)) ?? new THREE.PlaneGeometry(2, 2, steps, steps).rotateX(-Math.PI / 2);
|
|
8049
|
+
const grid = geometry.attributes.position;
|
|
8050
|
+
const cell = 2 / steps;
|
|
7770
8051
|
const cos2 = Math.cos(mark.rotation);
|
|
7771
8052
|
const sin2 = Math.sin(mark.rotation);
|
|
7772
|
-
for (let index = 0; index <
|
|
7773
|
-
const localX =
|
|
7774
|
-
const localZ =
|
|
8053
|
+
for (let index = 0; index < grid.count; index++) {
|
|
8054
|
+
const localX = Math.fround(index % (steps + 1) * cell - 1) * radius;
|
|
8055
|
+
const localZ = Math.fround(Math.floor(index / (steps + 1)) * cell - 1) * radius;
|
|
7775
8056
|
const worldX = mark.x + localX * cos2 - localZ * sin2;
|
|
7776
8057
|
const worldZ = mark.z + localX * sin2 + localZ * cos2;
|
|
7777
|
-
|
|
8058
|
+
grid.setXYZ(index, worldX, (options.ground?.heightAt(worldX, worldZ) ?? 0) + mark.lift, worldZ);
|
|
7778
8059
|
}
|
|
8060
|
+
grid.needsUpdate = true;
|
|
7779
8061
|
geometry.computeVertexNormals();
|
|
8062
|
+
geometry.attributes.normal.needsUpdate = true;
|
|
8063
|
+
geometry.boundingSphere = null;
|
|
8064
|
+
geometry.boundingBox = null;
|
|
7780
8065
|
const mesh2 = new THREE.Mesh(geometry, material);
|
|
7781
8066
|
mesh2.receiveShadow = true;
|
|
7782
|
-
|
|
8067
|
+
group.add(mesh2);
|
|
8068
|
+
groundMarks.push({ mesh: mesh2, steps });
|
|
7783
8069
|
}
|
|
7784
8070
|
function addRock(mark) {
|
|
7785
|
-
const mesh2 =
|
|
7786
|
-
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
mesh2.
|
|
7790
|
-
mesh2.
|
|
7791
|
-
|
|
8071
|
+
const mesh2 = mark.plate ? plateMesh() : rockMesh();
|
|
8072
|
+
const slot = mark.plate ? "plate" : "rock";
|
|
8073
|
+
const index = placed[slot]++ % mesh2.instanceMatrix.count;
|
|
8074
|
+
quaternion.setFromEuler(euler.set(mark.rotation[0], mark.rotation[1], mark.rotation[2]));
|
|
8075
|
+
mesh2.setMatrixAt(index, matrix.compose(position.set(mark.x, mark.y, mark.z), quaternion, scale.set(mark.scale[0], mark.scale[1], mark.scale[2])));
|
|
8076
|
+
mesh2.setColorAt(index, color.set(mark.color));
|
|
8077
|
+
mesh2.count = Math.min(placed[slot], mesh2.instanceMatrix.count);
|
|
8078
|
+
mesh2.instanceMatrix.needsUpdate = true;
|
|
8079
|
+
mesh2.instanceColor.needsUpdate = true;
|
|
8080
|
+
}
|
|
8081
|
+
function writeBlood(index, drawn, growth) {
|
|
8082
|
+
quaternion.setFromAxisAngle(up, drawn.rotation);
|
|
8083
|
+
const mesh2 = bloodMesh();
|
|
8084
|
+
mesh2.setMatrixAt(index, matrix.compose(position.set(drawn.position[0], drawn.position[1], drawn.position[2]), quaternion, scale.set(drawn.scaleX * growth, 1, drawn.scaleZ * growth)));
|
|
8085
|
+
mesh2.instanceMatrix.needsUpdate = true;
|
|
7792
8086
|
}
|
|
7793
8087
|
function addBlood(mark) {
|
|
7794
|
-
const
|
|
7795
|
-
|
|
7796
|
-
|
|
7797
|
-
|
|
7798
|
-
|
|
7799
|
-
mesh2.scale.multiplyScalar(0.05);
|
|
7800
|
-
blood.push({ mesh: mesh2, scaleX: mark.scaleX, scaleZ: mark.scaleZ, age: 0 });
|
|
7801
|
-
return mesh2;
|
|
8088
|
+
const index = placed.blood++ % bloodMesh().instanceMatrix.count;
|
|
8089
|
+
const drawn = { position: [mark.x, mark.y, mark.z], rotation: mark.rotation, scaleX: mark.scaleX, scaleZ: mark.scaleZ, age: 0 };
|
|
8090
|
+
blood[index] = drawn;
|
|
8091
|
+
writeBlood(index, drawn, BLOOD_START_GROWTH);
|
|
8092
|
+
bloodMesh().count = Math.min(placed.blood, bloodMesh().instanceMatrix.count);
|
|
7802
8093
|
}
|
|
7803
8094
|
function build(mark) {
|
|
7804
|
-
if (mark.kind === "scorch")
|
|
7805
|
-
if (mark.kind === "dirt")
|
|
7806
|
-
if (mark.kind === "rock")
|
|
7807
|
-
|
|
7808
|
-
}
|
|
7809
|
-
function
|
|
7810
|
-
for (const
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
7814
|
-
|
|
8095
|
+
if (mark.kind === "scorch") conform(mark, scorchMaterials[mark.variant]);
|
|
8096
|
+
else if (mark.kind === "dirt") conform(mark, dirtMaterial);
|
|
8097
|
+
else if (mark.kind === "rock") addRock(mark);
|
|
8098
|
+
else addBlood(mark);
|
|
8099
|
+
}
|
|
8100
|
+
function clearMarks() {
|
|
8101
|
+
for (const drawn of groundMarks) group.remove(drawn.mesh);
|
|
8102
|
+
for (const drawn of groundMarks) drawn.mesh.geometry.dispose();
|
|
8103
|
+
groundMarks.length = 0;
|
|
8104
|
+
for (const geometry of spareGeometries) geometry.dispose();
|
|
8105
|
+
spareGeometries.length = 0;
|
|
7815
8106
|
blood.length = 0;
|
|
7816
|
-
|
|
8107
|
+
placed.blood = placed.rock = placed.plate = 0;
|
|
8108
|
+
for (const mesh2 of [instanced.blood, instanced.rock, instanced.plate]) if (mesh2) mesh2.count = 0;
|
|
7817
8109
|
}
|
|
7818
8110
|
return {
|
|
7819
8111
|
group,
|
|
7820
8112
|
handle(event) {
|
|
7821
|
-
if (event.type === "clear")
|
|
7822
|
-
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
|
|
8113
|
+
if (event.type === "clear") clearMarks();
|
|
8114
|
+
else build(event.mark);
|
|
8115
|
+
},
|
|
8116
|
+
prepare() {
|
|
8117
|
+
bloodMesh();
|
|
8118
|
+
rockMesh();
|
|
8119
|
+
plateMesh();
|
|
7827
8120
|
},
|
|
7828
8121
|
update(dt) {
|
|
7829
|
-
for (
|
|
7830
|
-
|
|
8122
|
+
for (let index = 0; index < blood.length; index++) {
|
|
8123
|
+
const drawn = blood[index];
|
|
8124
|
+
if (!drawn || drawn.age > 1) continue;
|
|
7831
8125
|
drawn.age += dt * BLOOD_GROW_RATE;
|
|
7832
|
-
|
|
7833
|
-
drawn.mesh.scale.set(drawn.scaleX * Math.max(0.05, ease3), 1, drawn.scaleZ * Math.max(0.05, ease3));
|
|
8126
|
+
writeBlood(index, drawn, Math.max(BLOOD_START_GROWTH, 1 - Math.pow(1 - Math.min(1, drawn.age), 2.5)));
|
|
7834
8127
|
}
|
|
7835
8128
|
},
|
|
7836
|
-
count: () =>
|
|
8129
|
+
count: () => groundMarks.length + [instanced.blood, instanced.rock, instanced.plate].filter((mesh2) => mesh2 && mesh2.count > 0).length,
|
|
7837
8130
|
dispose() {
|
|
7838
|
-
|
|
8131
|
+
clearMarks();
|
|
8132
|
+
for (const mesh2 of [instanced.blood, instanced.rock, instanced.plate]) mesh2?.dispose();
|
|
7839
8133
|
for (const item of owned) item.dispose();
|
|
7840
8134
|
}
|
|
7841
8135
|
};
|
|
7842
8136
|
}
|
|
7843
8137
|
var SPHERE_DEFAULT_WIDTH = 8;
|
|
7844
8138
|
var SPHERE_DEFAULT_HEIGHT = 6;
|
|
8139
|
+
var MAX_PARKED_MESHES_PER_KIND = 256;
|
|
7845
8140
|
function geometryOf(THREE, particle) {
|
|
7846
8141
|
switch (particle.shape) {
|
|
7847
8142
|
case "cone":
|
|
7848
|
-
return new THREE.ConeGeometry(
|
|
8143
|
+
return new THREE.ConeGeometry(1, 1, particle.segments || void 0, particle.segments2 || 1, false);
|
|
7849
8144
|
case "cone_open":
|
|
7850
|
-
return new THREE.ConeGeometry(
|
|
8145
|
+
return new THREE.ConeGeometry(1, 1, particle.segments || void 0, particle.segments2 || 1, true);
|
|
7851
8146
|
case "sphere":
|
|
7852
|
-
return new THREE.SphereGeometry(
|
|
8147
|
+
return new THREE.SphereGeometry(1, particle.segments || SPHERE_DEFAULT_WIDTH, particle.segments2 || SPHERE_DEFAULT_HEIGHT);
|
|
7853
8148
|
case "icosahedron":
|
|
7854
|
-
return new THREE.IcosahedronGeometry(
|
|
8149
|
+
return new THREE.IcosahedronGeometry(1, particle.segments);
|
|
7855
8150
|
}
|
|
7856
8151
|
}
|
|
8152
|
+
function isCone(particle) {
|
|
8153
|
+
return particle.shape === "cone" || particle.shape === "cone_open";
|
|
8154
|
+
}
|
|
7857
8155
|
function materialOf(THREE, particle) {
|
|
7858
8156
|
if (particle.lit) return new THREE.MeshStandardMaterial({ color: particle.color, roughness: 1, metalness: 0, transparent: true, opacity: particle.opacity, depthWrite: false });
|
|
7859
8157
|
return new THREE.MeshBasicMaterial({ color: particle.color, transparent: true, opacity: particle.opacity, depthWrite: false, ...particle.additive ? { blending: THREE.AdditiveBlending } : {} });
|
|
@@ -7864,51 +8162,82 @@ void main(){
|
|
|
7864
8162
|
(kind) => new THREE.Mesh(geometry, materialOf(THREE, { ...kind, color: 8421504, opacity: 0.5 }))
|
|
7865
8163
|
);
|
|
7866
8164
|
}
|
|
8165
|
+
function geometryKeyOf(particle) {
|
|
8166
|
+
return `${particle.shape}:${particle.segments}:${particle.segments2}`;
|
|
8167
|
+
}
|
|
7867
8168
|
function createMeshLayer(THREE, pool) {
|
|
7868
8169
|
const group = new THREE.Group();
|
|
7869
8170
|
group.name = "workshop-meshes";
|
|
7870
8171
|
const drawn = /* @__PURE__ */ new Map();
|
|
8172
|
+
const geometries = /* @__PURE__ */ new Map();
|
|
8173
|
+
const parked = /* @__PURE__ */ new Map();
|
|
7871
8174
|
const frame = new THREE.Quaternion();
|
|
7872
8175
|
const euler = new THREE.Euler();
|
|
7873
8176
|
const turn = new THREE.Quaternion();
|
|
7874
8177
|
const offset = new THREE.Vector3();
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
8178
|
+
let stamp = 0;
|
|
8179
|
+
function geometryFor(particle) {
|
|
8180
|
+
const key = geometryKeyOf(particle);
|
|
8181
|
+
let geometry = geometries.get(key);
|
|
8182
|
+
if (!geometry) {
|
|
8183
|
+
geometry = geometryOf(THREE, particle);
|
|
8184
|
+
geometries.set(key, geometry);
|
|
8185
|
+
}
|
|
8186
|
+
return geometry;
|
|
8187
|
+
}
|
|
8188
|
+
function acquire(particle) {
|
|
8189
|
+
const parkingKey = `${geometryKeyOf(particle)}|${particle.lit}|${particle.additive}`;
|
|
8190
|
+
const mesh2 = parked.get(parkingKey)?.pop() ?? new THREE.Mesh(geometryFor(particle), materialOf(THREE, particle));
|
|
8191
|
+
mesh2.material.color.set(particle.color);
|
|
8192
|
+
mesh2.frustumCulled = false;
|
|
8193
|
+
mesh2.renderOrder = particle.additive ? 5 : 3;
|
|
8194
|
+
group.add(mesh2);
|
|
8195
|
+
return { mesh: mesh2, parkingKey, stamp };
|
|
8196
|
+
}
|
|
8197
|
+
function park(entry) {
|
|
8198
|
+
entry.mesh.removeFromParent();
|
|
8199
|
+
let free = parked.get(entry.parkingKey);
|
|
8200
|
+
if (!free) {
|
|
8201
|
+
free = [];
|
|
8202
|
+
parked.set(entry.parkingKey, free);
|
|
8203
|
+
}
|
|
8204
|
+
if (free.length < MAX_PARKED_MESHES_PER_KIND) free.push(entry.mesh);
|
|
8205
|
+
else entry.mesh.material.dispose();
|
|
7879
8206
|
}
|
|
7880
8207
|
return {
|
|
7881
8208
|
group,
|
|
7882
8209
|
sync() {
|
|
7883
|
-
|
|
8210
|
+
stamp++;
|
|
7884
8211
|
for (const particle of pool.particles) {
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
|
|
7888
|
-
|
|
7889
|
-
mesh2.frustumCulled = false;
|
|
7890
|
-
mesh2.renderOrder = particle.additive ? 5 : 3;
|
|
7891
|
-
drawn.set(particle.id, mesh2);
|
|
7892
|
-
group.add(mesh2);
|
|
8212
|
+
let entry = drawn.get(particle.id);
|
|
8213
|
+
if (!entry) {
|
|
8214
|
+
entry = acquire(particle);
|
|
8215
|
+
drawn.set(particle.id, entry);
|
|
7893
8216
|
}
|
|
8217
|
+
entry.stamp = stamp;
|
|
8218
|
+
const mesh2 = entry.mesh;
|
|
7894
8219
|
frame.set(particle.frame[0], particle.frame[1], particle.frame[2], particle.frame[3]);
|
|
7895
8220
|
offset.set(particle.at[0], particle.at[1], particle.at[2]).multiplyScalar(particle.scale).applyQuaternion(frame);
|
|
7896
8221
|
mesh2.position.set(particle.x + offset.x, particle.y + offset.y, particle.z + offset.z);
|
|
7897
8222
|
turn.setFromEuler(euler.set(particle.rx, particle.ry, particle.rz));
|
|
7898
8223
|
mesh2.quaternion.copy(frame).multiply(turn);
|
|
7899
|
-
mesh2.scale.
|
|
8224
|
+
mesh2.scale.set(particle.radius * particle.scale, (isCone(particle) ? particle.height : particle.radius) * particle.scale, particle.radius * particle.scale);
|
|
7900
8225
|
mesh2.material.opacity = particle.opacity;
|
|
7901
8226
|
}
|
|
7902
|
-
for (const [id,
|
|
7903
|
-
if (
|
|
7904
|
-
|
|
8227
|
+
for (const [id, entry] of drawn) {
|
|
8228
|
+
if (entry.stamp === stamp) continue;
|
|
8229
|
+
park(entry);
|
|
7905
8230
|
drawn.delete(id);
|
|
7906
8231
|
}
|
|
7907
8232
|
return pool.count;
|
|
7908
8233
|
},
|
|
7909
8234
|
dispose() {
|
|
7910
|
-
for (const
|
|
8235
|
+
for (const entry of drawn.values()) park(entry);
|
|
7911
8236
|
drawn.clear();
|
|
8237
|
+
for (const free of parked.values()) for (const mesh2 of free) mesh2.material.dispose();
|
|
8238
|
+
parked.clear();
|
|
8239
|
+
for (const geometry of geometries.values()) geometry.dispose();
|
|
8240
|
+
geometries.clear();
|
|
7912
8241
|
group.removeFromParent();
|
|
7913
8242
|
}
|
|
7914
8243
|
};
|
|
@@ -8476,7 +8805,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8476
8805
|
const shocks = createShockLayer(THREE, pools.shocks);
|
|
8477
8806
|
const sprites = createSpriteLayer(THREE, world.sprites(), options.pointScale ?? 700);
|
|
8478
8807
|
const lights = createLightLayer(THREE, pools.lights, options.lightScale ?? Math.PI);
|
|
8479
|
-
const marks = createMarkLayer(THREE, { ground: pools.ground, createCanvas: options.createCanvas, rockGeometry, plateGeometry });
|
|
8808
|
+
const marks = createMarkLayer(THREE, { ground: pools.ground, createCanvas: options.createCanvas, rockGeometry, plateGeometry, ...options.markLimits });
|
|
8480
8809
|
root.add(puffs.mesh, sparks.mesh, glows.mesh, chunks.rocks, chunks.plates, shocks.group, lights.group, sprites.additive, sprites.normal, marks.group);
|
|
8481
8810
|
const stopMarks = world.onMarks((event) => marks.handle(event));
|
|
8482
8811
|
const overlay = options.overlay ? createScreenOverlay(options.overlay) : void 0;
|
|
@@ -8502,6 +8831,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8502
8831
|
const position = options.camera.position;
|
|
8503
8832
|
world.setCamera([position.x, position.y, position.z]);
|
|
8504
8833
|
}
|
|
8834
|
+
function attachWorkshopLayers() {
|
|
8835
|
+
const engine = world.peekWorkshop();
|
|
8836
|
+
if (!engine || engine === workshopEngine) return;
|
|
8837
|
+
workshopLayers?.dispose();
|
|
8838
|
+
if (workshopLayers) root.remove(workshopLayers.group);
|
|
8839
|
+
workshopEngine = engine;
|
|
8840
|
+
workshopLayers = createWorkshopLayers(THREE, engine, options.pointScale ?? 700, 1);
|
|
8841
|
+
root.add(workshopLayers.group);
|
|
8842
|
+
applyLayers();
|
|
8843
|
+
}
|
|
8505
8844
|
function syncAll() {
|
|
8506
8845
|
lastDrawn.puffs = puffs.sync();
|
|
8507
8846
|
lastDrawn.sparks = sparks.sync();
|
|
@@ -8510,15 +8849,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8510
8849
|
lastDrawn.shocks = shocks.sync();
|
|
8511
8850
|
lastDrawn.sprites = sprites.sync();
|
|
8512
8851
|
lights.sync();
|
|
8513
|
-
|
|
8514
|
-
if (engine && engine !== workshopEngine) {
|
|
8515
|
-
workshopLayers?.dispose();
|
|
8516
|
-
if (workshopLayers) root.remove(workshopLayers.group);
|
|
8517
|
-
workshopEngine = engine;
|
|
8518
|
-
workshopLayers = createWorkshopLayers(THREE, engine, options.pointScale ?? 700, 1);
|
|
8519
|
-
root.add(workshopLayers.group);
|
|
8520
|
-
applyLayers();
|
|
8521
|
-
}
|
|
8852
|
+
attachWorkshopLayers();
|
|
8522
8853
|
const workshopCalls = workshopLayers?.sync() ?? 0;
|
|
8523
8854
|
const glow = pools.lights.glow;
|
|
8524
8855
|
uniforms.uGlowPos.value.set(glow.x, glow.y, glow.z);
|
|
@@ -8546,6 +8877,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8546
8877
|
});
|
|
8547
8878
|
},
|
|
8548
8879
|
stop: (handle) => world.stop(handle),
|
|
8880
|
+
// a module's closed space (a shut car's cabin): the world's smoke, dust and steam go round it
|
|
8881
|
+
enclose: (id, enclosure) => world.enclose(id, enclosure),
|
|
8549
8882
|
configure(settings) {
|
|
8550
8883
|
const band = settings.lod_band;
|
|
8551
8884
|
if (typeof band === "string" && band in LOD_BANDS) world.forceLod(LOD_BANDS[band] ?? null);
|
|
@@ -8571,6 +8904,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8571
8904
|
},
|
|
8572
8905
|
warmUpProbes() {
|
|
8573
8906
|
releaseAnchors();
|
|
8907
|
+
marks.prepare();
|
|
8908
|
+
world.workshop();
|
|
8909
|
+
attachWorkshopLayers();
|
|
8574
8910
|
const meshes = createMeshLayerProbes(THREE);
|
|
8575
8911
|
for (const mesh2 of meshes) mesh2.frustumCulled = false;
|
|
8576
8912
|
const group = new THREE.Group();
|
|
@@ -8691,6 +9027,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8691
9027
|
var SHEET_SWAY = 3;
|
|
8692
9028
|
var BIG_LENGTH = 0.2;
|
|
8693
9029
|
var BIG_WIDTH = 0.03;
|
|
9030
|
+
var FRAGMENT_SEED = 1374496523;
|
|
8694
9031
|
var isBig = (size) => {
|
|
8695
9032
|
const [, middle, longest] = [...size].sort((a, b) => a - b);
|
|
8696
9033
|
return longest >= BIG_LENGTH && middle >= BIG_WIDTH;
|
|
@@ -8710,7 +9047,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8710
9047
|
__publicField(this, "m");
|
|
8711
9048
|
__publicField(this, "v");
|
|
8712
9049
|
__publicField(this, "w");
|
|
8713
|
-
__publicField(this, "rng", createRng(
|
|
9050
|
+
__publicField(this, "rng", createRng(FRAGMENT_SEED));
|
|
8714
9051
|
/** Count of `update` calls: what makes the fragments of one burst tell apart from older ones. */
|
|
8715
9052
|
__publicField(this, "tick", 0);
|
|
8716
9053
|
__publicField(this, "markGeometry", null);
|
|
@@ -8811,6 +9148,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8811
9148
|
mesh2.scale.copy(sc);
|
|
8812
9149
|
mesh2.castShadow = true;
|
|
8813
9150
|
mesh2.userData.destructionDebris = true;
|
|
9151
|
+
mesh2.name = "fragment";
|
|
9152
|
+
markKitOrigin(mesh2, { owner: "game-destruction-three", kind: "fragment", id: f.material });
|
|
8814
9153
|
this.o.scene.add(mesh2);
|
|
8815
9154
|
const live = {
|
|
8816
9155
|
mesh: mesh2,
|
|
@@ -8827,7 +9166,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8827
9166
|
dying: false,
|
|
8828
9167
|
air: f.shape === "scrap" && sz[2] <= SHEET_MAX ? SHEET_AIR : 0,
|
|
8829
9168
|
body: null,
|
|
8830
|
-
born: this.tick
|
|
9169
|
+
born: this.tick,
|
|
9170
|
+
life: Math.max(0, typeof this.o.life === "function" ? this.o.life(f, home) : this.o.life)
|
|
8831
9171
|
};
|
|
8832
9172
|
if (f.shape !== "shard" && isBig(f.size)) {
|
|
8833
9173
|
live.body = {
|
|
@@ -8836,8 +9176,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8836
9176
|
live.vel.x = x;
|
|
8837
9177
|
live.vel.z = z;
|
|
8838
9178
|
live.vel.y = Math.max(live.vel.y, 0.3);
|
|
8839
|
-
live
|
|
8840
|
-
live.rest = 0;
|
|
9179
|
+
this.rouse(live);
|
|
8841
9180
|
}
|
|
8842
9181
|
};
|
|
8843
9182
|
}
|
|
@@ -8872,16 +9211,20 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8872
9211
|
d.y += k * 0.4;
|
|
8873
9212
|
f.vel.add(d);
|
|
8874
9213
|
f.ang.add(this.w.set(this.rng() - 0.5, this.rng() - 0.5, this.rng() - 0.5).multiplyScalar(k));
|
|
8875
|
-
f
|
|
8876
|
-
f.rest = 0;
|
|
9214
|
+
this.rouse(f);
|
|
8877
9215
|
}
|
|
8878
9216
|
}
|
|
9217
|
+
/** A fragment that came to rest stops having its matrix recomputed on every frame; whatever moves it again calls this first. */
|
|
9218
|
+
rouse(f) {
|
|
9219
|
+
f.asleep = false;
|
|
9220
|
+
f.rest = 0;
|
|
9221
|
+
f.mesh.matrixAutoUpdate = true;
|
|
9222
|
+
}
|
|
8879
9223
|
/** What lies inside the disc looks at the ground again: it was dug or heaped under it. */
|
|
8880
9224
|
wakeRegion(x, z, radius) {
|
|
8881
9225
|
for (const f of this.list) {
|
|
8882
9226
|
if (!f.asleep || Math.hypot(f.mesh.position.x - x, f.mesh.position.z - z) > radius) continue;
|
|
8883
|
-
f
|
|
8884
|
-
f.rest = 0;
|
|
9227
|
+
this.rouse(f);
|
|
8885
9228
|
}
|
|
8886
9229
|
}
|
|
8887
9230
|
update(dt) {
|
|
@@ -8890,6 +9233,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8890
9233
|
for (let i = this.list.length - 1; i >= 0; i--) {
|
|
8891
9234
|
const f = this.list[i];
|
|
8892
9235
|
if (f.dying) {
|
|
9236
|
+
f.mesh.matrixAutoUpdate = true;
|
|
8893
9237
|
f.fade -= dt / 0.6;
|
|
8894
9238
|
if (f.fade <= 0) {
|
|
8895
9239
|
this.o.scene.remove(f.mesh);
|
|
@@ -8900,7 +9244,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8900
9244
|
}
|
|
8901
9245
|
if (f.asleep) {
|
|
8902
9246
|
f.rest += dt;
|
|
8903
|
-
if (
|
|
9247
|
+
if (f.life > 0 && f.rest > f.life && !f.body) {
|
|
9248
|
+
f.dying = true;
|
|
9249
|
+
f.mesh.matrixAutoUpdate = true;
|
|
9250
|
+
}
|
|
8904
9251
|
continue;
|
|
8905
9252
|
}
|
|
8906
9253
|
this.step(f, dt, g);
|
|
@@ -8949,6 +9296,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8949
9296
|
f.still += dt;
|
|
8950
9297
|
if (f.still > 0.4) {
|
|
8951
9298
|
f.asleep = true;
|
|
9299
|
+
f.mesh.updateMatrix();
|
|
9300
|
+
f.mesh.matrixAutoUpdate = false;
|
|
8952
9301
|
f.vel.set(0, 0, 0);
|
|
8953
9302
|
f.ang.set(0, 0, 0);
|
|
8954
9303
|
}
|
|
@@ -8998,6 +9347,12 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8998
9347
|
for (const f of this.list) this.o.scene.remove(f.mesh);
|
|
8999
9348
|
this.list.length = 0;
|
|
9000
9349
|
}
|
|
9350
|
+
/** `clear`, the random stream back to its seed and the tick to 0: what comes next flies as on a new system. */
|
|
9351
|
+
reset() {
|
|
9352
|
+
this.clear();
|
|
9353
|
+
this.rng = createRng(FRAGMENT_SEED);
|
|
9354
|
+
this.tick = 0;
|
|
9355
|
+
}
|
|
9001
9356
|
dispose() {
|
|
9002
9357
|
this.clear();
|
|
9003
9358
|
this.box.dispose();
|
|
@@ -9096,6 +9451,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9096
9451
|
var SUB_DT = 1 / 120;
|
|
9097
9452
|
var MU = 0.7;
|
|
9098
9453
|
var SLEEP_AFTER = 0.5;
|
|
9454
|
+
var CALM_DRIFT = 0.01;
|
|
9455
|
+
var CALM_TURN = 0.05;
|
|
9456
|
+
var CALM_SLEEP_AFTER = 1.5;
|
|
9099
9457
|
var HARD_LANDING = 3;
|
|
9100
9458
|
var SLOP = 2e-3;
|
|
9101
9459
|
var NARROW = 0.3;
|
|
@@ -9104,6 +9462,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9104
9462
|
var TIP_LYING = 0.25;
|
|
9105
9463
|
var TIP_SECONDS = 3;
|
|
9106
9464
|
var STEP = 0.03;
|
|
9465
|
+
var WARM_SLOTS = 8 + 12 * 7;
|
|
9466
|
+
var SOLVER_PASSES = 10;
|
|
9467
|
+
var SIDE_BOUNCE = 0.1;
|
|
9468
|
+
var EDGE_POINTS = [-0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75];
|
|
9107
9469
|
var boxTopAt = (s, x, z) => {
|
|
9108
9470
|
const e = s.e;
|
|
9109
9471
|
const dx = x - s.c[0], dz = z - s.c[2];
|
|
@@ -9174,6 +9536,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9174
9536
|
localCenter: [localCenter.x, localCenter.y, localCenter.z],
|
|
9175
9537
|
centerV: localCenter.clone(),
|
|
9176
9538
|
still: 0,
|
|
9539
|
+
calmAt: null,
|
|
9540
|
+
calm: 0,
|
|
9177
9541
|
lastHit: -1,
|
|
9178
9542
|
onGround: false
|
|
9179
9543
|
};
|
|
@@ -9192,6 +9556,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9192
9556
|
clear() {
|
|
9193
9557
|
this.list.length = 0;
|
|
9194
9558
|
}
|
|
9559
|
+
/** `clear` and the clock back to 0. */
|
|
9560
|
+
reset() {
|
|
9561
|
+
this.clear();
|
|
9562
|
+
this.time = 0;
|
|
9563
|
+
}
|
|
9195
9564
|
/** World position of the centre of mass. */
|
|
9196
9565
|
centerOf(b, out) {
|
|
9197
9566
|
return out.copy(b.centerV).applyQuaternion(b.root.quaternion).add(b.root.position);
|
|
@@ -9216,6 +9585,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9216
9585
|
}
|
|
9217
9586
|
b.sleeping = false;
|
|
9218
9587
|
b.still = 0;
|
|
9588
|
+
b.calmAt = null;
|
|
9219
9589
|
}
|
|
9220
9590
|
/** I^-1 * (x, y, z) in world space for a box with the body's half extents and mass (needs `this.m` to hold the body's rotation). */
|
|
9221
9591
|
mulInvInertia(b, x, y, z) {
|
|
@@ -9233,12 +9603,51 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9233
9603
|
const lz = (e[8] * x + e[9] * y + e[10] * z) * iz;
|
|
9234
9604
|
return [e[0] * lx + e[4] * ly + e[8] * lz, e[1] * lx + e[5] * ly + e[9] * lz, e[2] * lx + e[6] * ly + e[10] * lz];
|
|
9235
9605
|
}
|
|
9606
|
+
/** The points of the box that touch the ground, in its own frame: the 8 corners, then (with `edges`) `EDGE_POINTS` along each of its 12 edges. */
|
|
9607
|
+
samples(b, edges) {
|
|
9608
|
+
const [hx, hy, hz] = b.halfExtents;
|
|
9609
|
+
const out = [];
|
|
9610
|
+
for (let i = 0; i < 8; i++) out.push([i & 1 ? hx : -hx, i & 2 ? hy : -hy, i & 4 ? hz : -hz]);
|
|
9611
|
+
if (!edges) return out;
|
|
9612
|
+
const h = [hx, hy, hz];
|
|
9613
|
+
for (let axis2 = 0; axis2 < 3; axis2++) {
|
|
9614
|
+
const a1 = (axis2 + 1) % 3;
|
|
9615
|
+
const a2 = (axis2 + 2) % 3;
|
|
9616
|
+
for (let k = 0; k < 4; k++) {
|
|
9617
|
+
for (const f of EDGE_POINTS) {
|
|
9618
|
+
const p = [0, 0, 0];
|
|
9619
|
+
p[axis2] = f * h[axis2];
|
|
9620
|
+
p[a1] = k & 1 ? h[a1] : -h[a1];
|
|
9621
|
+
p[a2] = k & 2 ? h[a2] : -h[a2];
|
|
9622
|
+
out.push(p);
|
|
9623
|
+
}
|
|
9624
|
+
}
|
|
9625
|
+
}
|
|
9626
|
+
return out;
|
|
9627
|
+
}
|
|
9628
|
+
/**
|
|
9629
|
+
* An impulse along the unit normal `n` at `r` (from the centre of mass, world) that stops the point closing in along `n` (with `bounce`); `e` holds the body's rotation.
|
|
9630
|
+
*/
|
|
9631
|
+
contactImpulse(b, e, r, n, bounce2) {
|
|
9632
|
+
const v = b.velocity;
|
|
9633
|
+
const w = b.angularVelocity;
|
|
9634
|
+
const vn = (v[0] + w[1] * r[2] - w[2] * r[1]) * n[0] + (v[1] + w[2] * r[0] - w[0] * r[2]) * n[1] + (v[2] + w[0] * r[1] - w[1] * r[0]) * n[2];
|
|
9635
|
+
if (vn >= 0) return;
|
|
9636
|
+
const a = this.mulInvInertiaWith(b, e, r[1] * n[2] - r[2] * n[1], r[2] * n[0] - r[0] * n[2], r[0] * n[1] - r[1] * n[0]);
|
|
9637
|
+
const k = (a[1] * r[2] - a[2] * r[1]) * n[0] + (a[2] * r[0] - a[0] * r[2]) * n[1] + (a[0] * r[1] - a[1] * r[0]) * n[2];
|
|
9638
|
+
const j = -(1 + bounce2) * vn / (1 / b.mass + k);
|
|
9639
|
+
for (let i = 0; i < 3; i++) {
|
|
9640
|
+
v[i] += j * n[i] / b.mass;
|
|
9641
|
+
w[i] += a[i] * j;
|
|
9642
|
+
}
|
|
9643
|
+
}
|
|
9236
9644
|
/** What lies inside the disc looks at the ground again: it was dug or heaped under it. */
|
|
9237
9645
|
wakeRegion(x, z, radius) {
|
|
9238
9646
|
for (const b of this.list) {
|
|
9239
9647
|
if (!b.sleeping || b.held || Math.hypot(b.root.position.x - x, b.root.position.z - z) > radius) continue;
|
|
9240
9648
|
b.sleeping = false;
|
|
9241
9649
|
b.still = 0;
|
|
9650
|
+
b.calmAt = null;
|
|
9242
9651
|
}
|
|
9243
9652
|
}
|
|
9244
9653
|
update(dt) {
|
|
@@ -9254,6 +9663,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9254
9663
|
b.angularVelocity = [0, 0, 0];
|
|
9255
9664
|
b.sleeping = false;
|
|
9256
9665
|
b.still = 0;
|
|
9666
|
+
b.calmAt = null;
|
|
9257
9667
|
continue;
|
|
9258
9668
|
}
|
|
9259
9669
|
if (b.sleeping) continue;
|
|
@@ -9278,7 +9688,6 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9278
9688
|
const q = this.q.copy(b.root.quaternion);
|
|
9279
9689
|
const v = b.velocity;
|
|
9280
9690
|
const w = b.angularVelocity;
|
|
9281
|
-
const [hx, hy, hz] = b.halfExtents;
|
|
9282
9691
|
const invM = 1 / b.mass;
|
|
9283
9692
|
const steps = clamp6(Math.ceil(dt / SUB_DT), 1, 8);
|
|
9284
9693
|
const h = dt / steps;
|
|
@@ -9287,7 +9696,30 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9287
9696
|
let contactedLast = false;
|
|
9288
9697
|
let touched = false;
|
|
9289
9698
|
let unstable = false;
|
|
9699
|
+
const corners = this.samples(b, false);
|
|
9700
|
+
this.m.makeRotationFromQuaternion(q);
|
|
9701
|
+
let gLow = Infinity;
|
|
9702
|
+
let gHigh = -Infinity;
|
|
9703
|
+
for (const [sx, sy, sz] of corners) {
|
|
9704
|
+
const e0 = this.m.elements;
|
|
9705
|
+
const y = pos[1] + e0[1] * sx + e0[5] * sy + e0[9] * sz;
|
|
9706
|
+
const x = pos[0] + e0[0] * sx + e0[4] * sy + e0[8] * sz;
|
|
9707
|
+
const z = pos[2] + e0[2] * sx + e0[6] * sy + e0[10] * sz;
|
|
9708
|
+
const g = Math.max(this.o.groundAt(x, z), this.o.staticTopAt?.(x, z, y, STEP) ?? -Infinity);
|
|
9709
|
+
gLow = Math.min(gLow, g);
|
|
9710
|
+
gHigh = Math.max(gHigh, g);
|
|
9711
|
+
}
|
|
9712
|
+
const local2 = gHigh - gLow > STEP ? this.samples(b, true) : corners;
|
|
9713
|
+
const prev = local2.map(() => [0, 0, 0]);
|
|
9290
9714
|
for (let s = 0; s < steps; s++) {
|
|
9715
|
+
this.m.makeRotationFromQuaternion(q);
|
|
9716
|
+
const e0 = this.m.elements;
|
|
9717
|
+
for (let i = 0; i < local2.length; i++) {
|
|
9718
|
+
const [sx, sy, sz] = local2[i];
|
|
9719
|
+
prev[i][0] = pos[0] + e0[0] * sx + e0[4] * sy + e0[8] * sz;
|
|
9720
|
+
prev[i][1] = pos[1] + e0[1] * sx + e0[5] * sy + e0[9] * sz;
|
|
9721
|
+
prev[i][2] = pos[2] + e0[2] * sx + e0[6] * sy + e0[10] * sz;
|
|
9722
|
+
}
|
|
9291
9723
|
v[1] -= this.o.gravity * h;
|
|
9292
9724
|
pos[0] += v[0] * h;
|
|
9293
9725
|
pos[1] += v[1] * h;
|
|
@@ -9304,20 +9736,60 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9304
9736
|
this.m.makeRotationFromQuaternion(q);
|
|
9305
9737
|
const e = this.m.elements;
|
|
9306
9738
|
const rs = [];
|
|
9739
|
+
const ids = [];
|
|
9307
9740
|
let maxPen = 0;
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
|
|
9311
|
-
|
|
9741
|
+
const rise = STEP + Math.max(0, -v[1]) * h * 2;
|
|
9742
|
+
let side = null;
|
|
9743
|
+
let lowest = Infinity;
|
|
9744
|
+
for (let i = 0; i < local2.length; i++) {
|
|
9745
|
+
const [sx, sy, sz] = local2[i];
|
|
9312
9746
|
const r = [e[0] * sx + e[4] * sy + e[8] * sz, e[1] * sx + e[5] * sy + e[9] * sz, e[2] * sx + e[6] * sy + e[10] * sz];
|
|
9747
|
+
const px = pos[0] + r[0];
|
|
9748
|
+
const pz = pos[2] + r[2];
|
|
9313
9749
|
const cy = pos[1] + r[1];
|
|
9314
|
-
const
|
|
9750
|
+
const was = prev[i];
|
|
9751
|
+
const raw = this.o.groundAt(px, pz);
|
|
9752
|
+
if (raw > cy - SLOP && was[1] < raw - STEP) {
|
|
9753
|
+
if (this.o.groundAt(was[0], was[2]) >= raw - STEP) {
|
|
9754
|
+
const floor2 = b.floor;
|
|
9755
|
+
if (floor2 !== void 0 && floor2 - cy > -SLOP && floor2 < raw - STEP) {
|
|
9756
|
+
rs.push(r);
|
|
9757
|
+
ids.push(i);
|
|
9758
|
+
maxPen = Math.max(maxPen, floor2 - cy);
|
|
9759
|
+
}
|
|
9760
|
+
continue;
|
|
9761
|
+
}
|
|
9762
|
+
const dx = px - was[0];
|
|
9763
|
+
const dz = pz - was[2];
|
|
9764
|
+
const len = Math.hypot(dx, dz);
|
|
9765
|
+
if (len > 1e-6) {
|
|
9766
|
+
let lo = 0;
|
|
9767
|
+
let hi = 1;
|
|
9768
|
+
for (let it = 0; it < 6; it++) {
|
|
9769
|
+
const mid = (lo + hi) / 2;
|
|
9770
|
+
if (this.o.groundAt(was[0] + dx * mid, was[2] + dz * mid) >= raw - STEP) hi = mid;
|
|
9771
|
+
else lo = mid;
|
|
9772
|
+
}
|
|
9773
|
+
const back = (1 - lo) * len + 1e-3;
|
|
9774
|
+
if (!side || back > side.back) side = { back, dir: [dx / len, dz / len], r };
|
|
9775
|
+
}
|
|
9776
|
+
continue;
|
|
9777
|
+
}
|
|
9778
|
+
const gy = i < 8 ? this.supportAt(b, px, cy, pz, rise) : Math.max(raw, this.o.staticTopAt?.(px, pz, cy, rise) ?? -Infinity);
|
|
9315
9779
|
const pen = gy - cy;
|
|
9316
9780
|
if (pen > -SLOP) {
|
|
9317
9781
|
rs.push(r);
|
|
9782
|
+
ids.push(i);
|
|
9318
9783
|
if (pen > maxPen) maxPen = pen;
|
|
9784
|
+
lowest = Math.min(lowest, gy);
|
|
9319
9785
|
}
|
|
9320
9786
|
}
|
|
9787
|
+
if (lowest < Infinity) b.floor = lowest;
|
|
9788
|
+
if (side) {
|
|
9789
|
+
pos[0] -= side.dir[0] * side.back;
|
|
9790
|
+
pos[2] -= side.dir[1] * side.back;
|
|
9791
|
+
this.contactImpulse(b, e, side.r, [-side.dir[0], 0, -side.dir[1]], SIDE_BOUNCE);
|
|
9792
|
+
}
|
|
9321
9793
|
contactedLast = rs.length > 0;
|
|
9322
9794
|
if (contactedLast) touched = true;
|
|
9323
9795
|
if (!contactedLast) continue;
|
|
@@ -9337,45 +9809,88 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9337
9809
|
const lz = pos[2] + sz / rs.length;
|
|
9338
9810
|
landPoint = [lx, pos[1] - Math.max(...rs.map((r) => -r[1])), lz];
|
|
9339
9811
|
}
|
|
9340
|
-
|
|
9341
|
-
|
|
9342
|
-
|
|
9343
|
-
|
|
9344
|
-
|
|
9345
|
-
|
|
9346
|
-
|
|
9812
|
+
const goal = rs.map((r) => {
|
|
9813
|
+
const vy2 = v[1] + (w[2] * r[0] - w[0] * r[2]);
|
|
9814
|
+
return vy2 < 0 ? -restitution * vy2 : 0;
|
|
9815
|
+
});
|
|
9816
|
+
const vx0 = v[0];
|
|
9817
|
+
const vz0 = v[2];
|
|
9818
|
+
if (!b.warm || b.warm.length !== WARM_SLOTS * 3) b.warm = new Float64Array(WARM_SLOTS * 3);
|
|
9819
|
+
const warm = b.warm;
|
|
9820
|
+
const accN = ids.map((id) => id < WARM_SLOTS ? warm[id * 3] : 0);
|
|
9821
|
+
const accT = ids.map((id) => id < WARM_SLOTS ? [warm[id * 3 + 1], warm[id * 3 + 2]] : [0, 0]);
|
|
9822
|
+
for (let k = 0; k < rs.length; k++) {
|
|
9823
|
+
const r = rs[k];
|
|
9824
|
+
const jn = accN[k];
|
|
9825
|
+
const [fx, fz] = accT[k];
|
|
9826
|
+
if (jn === 0 && fx === 0 && fz === 0) continue;
|
|
9827
|
+
v[0] += fx * invM;
|
|
9828
|
+
v[1] += jn * invM;
|
|
9829
|
+
v[2] += fz * invM;
|
|
9830
|
+
const dw = this.mulInvInertiaWith(b, e, r[1] * fz - r[2] * jn, r[2] * fx - r[0] * fz, r[0] * jn - r[1] * fx);
|
|
9831
|
+
w[0] += dw[0];
|
|
9832
|
+
w[1] += dw[1];
|
|
9833
|
+
w[2] += dw[2];
|
|
9834
|
+
}
|
|
9835
|
+
for (let it = 0; it < SOLVER_PASSES; it++) {
|
|
9836
|
+
for (let k = 0; k < rs.length; k++) {
|
|
9837
|
+
const r = rs[k];
|
|
9838
|
+
const vy2 = v[1] + (w[2] * r[0] - w[0] * r[2]);
|
|
9347
9839
|
const a = this.mulInvInertiaWith(b, e, -r[2], 0, r[0]);
|
|
9348
9840
|
const denom = invM + (a[2] * r[0] - a[0] * r[2]);
|
|
9349
|
-
const
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
|
|
9356
|
-
|
|
9357
|
-
|
|
9841
|
+
const total = Math.max(0, accN[k] + (goal[k] - vy2) / denom);
|
|
9842
|
+
const j = total - accN[k];
|
|
9843
|
+
accN[k] = total;
|
|
9844
|
+
if (j !== 0) {
|
|
9845
|
+
v[1] += j * invM;
|
|
9846
|
+
w[0] += a[0] * j;
|
|
9847
|
+
w[1] += a[1] * j;
|
|
9848
|
+
w[2] += a[2] * j;
|
|
9849
|
+
}
|
|
9850
|
+
const vx2 = v[0] + (w[1] * r[2] - w[2] * r[1]);
|
|
9851
|
+
const vz2 = v[2] + (w[0] * r[1] - w[1] * r[0]);
|
|
9358
9852
|
const vt = Math.hypot(vx2, vz2);
|
|
9359
|
-
if (vt
|
|
9853
|
+
if (vt < 1e-6 && accT[k][0] === 0 && accT[k][1] === 0) continue;
|
|
9854
|
+
let fx = accT[k][0];
|
|
9855
|
+
let fz = accT[k][1];
|
|
9856
|
+
if (vt > 1e-6) {
|
|
9360
9857
|
const tx = vx2 / vt;
|
|
9361
9858
|
const tz = vz2 / vt;
|
|
9362
|
-
const
|
|
9363
|
-
const ry = r[2] * tx - r[0] * tz;
|
|
9364
|
-
const rz = -r[1] * tx;
|
|
9365
|
-
const at3 = this.mulInvInertiaWith(b, e, rx, ry, rz);
|
|
9859
|
+
const at3 = this.mulInvInertiaWith(b, e, r[1] * tz, r[2] * tx - r[0] * tz, -r[1] * tx);
|
|
9366
9860
|
const cx = at3[1] * r[2] - at3[2] * r[1];
|
|
9367
9861
|
const cz = at3[0] * r[1] - at3[1] * r[0];
|
|
9368
|
-
const
|
|
9369
|
-
|
|
9370
|
-
|
|
9371
|
-
|
|
9372
|
-
|
|
9373
|
-
|
|
9374
|
-
|
|
9375
|
-
|
|
9862
|
+
const jt = vt / (invM + cx * tx + cz * tz);
|
|
9863
|
+
fx -= tx * jt;
|
|
9864
|
+
fz -= tz * jt;
|
|
9865
|
+
}
|
|
9866
|
+
const most = MU * accN[k];
|
|
9867
|
+
const f = Math.hypot(fx, fz);
|
|
9868
|
+
if (f > most) {
|
|
9869
|
+
fx *= most / f;
|
|
9870
|
+
fz *= most / f;
|
|
9376
9871
|
}
|
|
9872
|
+
const dx = fx - accT[k][0];
|
|
9873
|
+
const dz = fz - accT[k][1];
|
|
9874
|
+
accT[k] = [fx, fz];
|
|
9875
|
+
if (dx === 0 && dz === 0) continue;
|
|
9876
|
+
v[0] += dx * invM;
|
|
9877
|
+
v[2] += dz * invM;
|
|
9878
|
+
const dw = this.mulInvInertiaWith(b, e, r[1] * dz, r[2] * dx - r[0] * dz, -r[1] * dx);
|
|
9879
|
+
w[0] += dw[0];
|
|
9880
|
+
w[1] += dw[1];
|
|
9881
|
+
w[2] += dw[2];
|
|
9377
9882
|
}
|
|
9378
9883
|
}
|
|
9884
|
+
pos[0] += (v[0] - vx0) * h;
|
|
9885
|
+
pos[2] += (v[2] - vz0) * h;
|
|
9886
|
+
warm.fill(0);
|
|
9887
|
+
for (let k = 0; k < rs.length; k++) {
|
|
9888
|
+
const id = ids[k];
|
|
9889
|
+
if (id >= WARM_SLOTS) continue;
|
|
9890
|
+
warm[id * 3] = accN[k];
|
|
9891
|
+
warm[id * 3 + 1] = accT[k][0];
|
|
9892
|
+
warm[id * 3 + 2] = accT[k][1];
|
|
9893
|
+
}
|
|
9379
9894
|
let x0 = Infinity, x1 = -Infinity, z0 = Infinity, z1 = -Infinity, low = 0;
|
|
9380
9895
|
for (const r of rs) {
|
|
9381
9896
|
x0 = Math.min(x0, r[0]);
|
|
@@ -9390,7 +9905,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9390
9905
|
unstable = Math.min(wx, wz) < NARROW * height || x0 > 0 || x1 < 0 || z0 > 0 || z1 < 0;
|
|
9391
9906
|
if (b.tipping) {
|
|
9392
9907
|
b.tipping.seconds -= h;
|
|
9393
|
-
|
|
9908
|
+
const [hx, hy, hz] = b.halfExtents;
|
|
9909
|
+
const upHalf = Math.abs(e[1]) * hx + Math.abs(e[5]) * hy + Math.abs(e[9]) * hz;
|
|
9910
|
+
if (upHalf < TIP_LYING || b.tipping.seconds <= 0) b.tipping = null;
|
|
9394
9911
|
else {
|
|
9395
9912
|
w[0] += b.tipping.x * TIP_ACCELERATION * h;
|
|
9396
9913
|
w[2] += b.tipping.z * TIP_ACCELERATION * h;
|
|
@@ -9422,16 +9939,26 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9422
9939
|
this.o.onHardLanding(b, landPoint, landing);
|
|
9423
9940
|
}
|
|
9424
9941
|
const still = touched && !unstable && Math.hypot(v[0], v[1], v[2]) < 0.12 && Math.hypot(w[0], w[1], w[2]) < 0.45;
|
|
9425
|
-
if (still)
|
|
9426
|
-
|
|
9427
|
-
|
|
9428
|
-
|
|
9429
|
-
|
|
9430
|
-
|
|
9431
|
-
|
|
9432
|
-
}
|
|
9433
|
-
|
|
9434
|
-
|
|
9942
|
+
if (still) b.still += dt;
|
|
9943
|
+
else b.still = 0;
|
|
9944
|
+
if (b.still >= SLEEP_AFTER || this.staysInPlace(b, touched && !b.tipping, pos, q, dt)) {
|
|
9945
|
+
b.sleeping = true;
|
|
9946
|
+
b.calmAt = null;
|
|
9947
|
+
v[0] = v[1] = v[2] = 0;
|
|
9948
|
+
w[0] = w[1] = w[2] = 0;
|
|
9949
|
+
}
|
|
9950
|
+
}
|
|
9951
|
+
/** Whether the piece has kept to the same spot on the ground for `CALM_SLEEP_AFTER` (see `CALM_DRIFT`). */
|
|
9952
|
+
staysInPlace(b, touched, center, q, dt) {
|
|
9953
|
+
const at3 = b.calmAt;
|
|
9954
|
+
const turned = at3 ? 2 * Math.acos(Math.min(1, Math.abs(at3.quaternion[0] * q.x + at3.quaternion[1] * q.y + at3.quaternion[2] * q.z + at3.quaternion[3] * q.w))) : Infinity;
|
|
9955
|
+
if (!touched || !at3 || Math.hypot(center[0] - at3.center[0], center[1] - at3.center[1], center[2] - at3.center[2]) > CALM_DRIFT || turned > CALM_TURN) {
|
|
9956
|
+
b.calmAt = touched ? { center: [center[0], center[1], center[2]], quaternion: [q.x, q.y, q.z, q.w] } : null;
|
|
9957
|
+
b.calm = 0;
|
|
9958
|
+
return false;
|
|
9959
|
+
}
|
|
9960
|
+
b.calm += dt;
|
|
9961
|
+
return b.calm >= CALM_SLEEP_AFTER;
|
|
9435
9962
|
}
|
|
9436
9963
|
/** Half the height of the box of a piece as it is turned now. */
|
|
9437
9964
|
halfHeight(b) {
|
|
@@ -9449,8 +9976,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9449
9976
|
const a = this.list[i];
|
|
9450
9977
|
for (let j = i + 1; j < n; j++) {
|
|
9451
9978
|
const b = this.list[j];
|
|
9452
|
-
|
|
9453
|
-
|
|
9979
|
+
const fixedA = a.held || a.sleeping;
|
|
9980
|
+
const fixedB = b.held || b.sleeping;
|
|
9981
|
+
if (fixedA && fixedB) continue;
|
|
9454
9982
|
const ra = Math.hypot(a.halfExtents[0], a.halfExtents[1], a.halfExtents[2]);
|
|
9455
9983
|
const rb = Math.hypot(b.halfExtents[0], b.halfExtents[1], b.halfExtents[2]);
|
|
9456
9984
|
this.centerOf(a, ca);
|
|
@@ -9468,19 +9996,17 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9468
9996
|
d = 1;
|
|
9469
9997
|
}
|
|
9470
9998
|
const push = Math.min(maxMove, (rh - d) * 0.5);
|
|
9471
|
-
const wa =
|
|
9472
|
-
const wb =
|
|
9999
|
+
const wa = fixedA ? 0 : fixedB ? 1 : b.mass / (a.mass + b.mass);
|
|
10000
|
+
const wb = fixedB ? 0 : fixedA ? 1 : a.mass / (a.mass + b.mass);
|
|
9473
10001
|
const ux = dx / (d || 1);
|
|
9474
10002
|
const uz = dz / (d || 1);
|
|
9475
|
-
if (!
|
|
10003
|
+
if (!fixedA) {
|
|
9476
10004
|
a.root.position.x -= ux * push * 2 * wa;
|
|
9477
10005
|
a.root.position.z -= uz * push * 2 * wa;
|
|
9478
|
-
a.sleeping = false;
|
|
9479
10006
|
}
|
|
9480
|
-
if (!
|
|
10007
|
+
if (!fixedB) {
|
|
9481
10008
|
b.root.position.x += ux * push * 2 * wb;
|
|
9482
10009
|
b.root.position.z += uz * push * 2 * wb;
|
|
9483
|
-
b.sleeping = false;
|
|
9484
10010
|
}
|
|
9485
10011
|
}
|
|
9486
10012
|
}
|
|
@@ -10889,6 +11415,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10889
11415
|
}
|
|
10890
11416
|
return out;
|
|
10891
11417
|
};
|
|
11418
|
+
var RESTING_HOLD_COS = Math.cos(Math.PI / 6);
|
|
10892
11419
|
|
|
10893
11420
|
// src/spec.ts
|
|
10894
11421
|
function containsOf(value) {
|
|
@@ -10947,6 +11474,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10947
11474
|
const fill = options.fillOf?.(o, material);
|
|
10948
11475
|
const tags = Array.isArray(ud.destructionTags) ? ud.destructionTags : void 0;
|
|
10949
11476
|
const contains = containsOf(ud.destructionContains);
|
|
11477
|
+
const powder = typeof ud.destructionPowder?.color === "number" ? { color: ud.destructionPowder.color } : void 0;
|
|
10950
11478
|
const thickness = typeof ud.destructionThickness === "number" && ud.destructionThickness > 0 ? ud.destructionThickness : void 0;
|
|
10951
11479
|
const existing = explicit ? byKey.get(explicit) : void 0;
|
|
10952
11480
|
if (existing) {
|
|
@@ -10960,9 +11488,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10960
11488
|
existing.skin = existing.skin && skin;
|
|
10961
11489
|
if (fill !== void 0) existing.fill = fill;
|
|
10962
11490
|
if (contains) existing.contains = contains;
|
|
11491
|
+
if (powder) existing.powder = powder;
|
|
10963
11492
|
if (thickness !== void 0) existing.thickness = Math.min(existing.thickness ?? thickness, thickness);
|
|
10964
11493
|
} else {
|
|
10965
|
-
const e = { key: explicit ?? `m${entries.length}`, meshes: [o], box, material, explicitId: explicit, fill, hollow, skin, tags, fixedId: !!explicit, ...contains ? { contains } : {}, ...thickness !== void 0 ? { thickness } : {}, name: o.name, parent: hint?.parent, jointKind: hint?.kind };
|
|
11494
|
+
const e = { key: explicit ?? `m${entries.length}`, meshes: [o], box, material, explicitId: explicit, fill, hollow, skin, tags, fixedId: !!explicit, ...contains ? { contains } : {}, ...powder ? { powder } : {}, ...thickness !== void 0 ? { thickness } : {}, name: o.name, parent: hint?.parent, jointKind: hint?.kind };
|
|
10966
11495
|
entries.push(e);
|
|
10967
11496
|
if (explicit) byKey.set(explicit, e);
|
|
10968
11497
|
}
|
|
@@ -11037,6 +11566,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11037
11566
|
}
|
|
11038
11567
|
if (e.tags) part.tags = [...e.tags];
|
|
11039
11568
|
if (e.contains) part.contains = { ...e.contains };
|
|
11569
|
+
if (e.powder) part.powder = { ...e.powder };
|
|
11040
11570
|
const ao = options.anchorBelow ?? -1;
|
|
11041
11571
|
if (ao >= 0 && e.box.min.y <= ao) part.anchored = true;
|
|
11042
11572
|
parts.push(part);
|
|
@@ -11214,6 +11744,22 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11214
11744
|
var SKIN_SLACK = 5e-4;
|
|
11215
11745
|
var SKIN_LAYER_SLACK = 4;
|
|
11216
11746
|
var NEAREST_MARGIN = 1.5;
|
|
11747
|
+
var basePositions = (host) => {
|
|
11748
|
+
const p0 = host.mesh.userData._p0;
|
|
11749
|
+
if (p0) return p0;
|
|
11750
|
+
const op = positionAttr(host.origGeometry);
|
|
11751
|
+
return op ? op.array : null;
|
|
11752
|
+
};
|
|
11753
|
+
var collapseForeign = (mesh2, arr) => {
|
|
11754
|
+
for (const [v0, v1] of mesh2.userData.collapsedRanges ?? []) {
|
|
11755
|
+
for (let k = v0 + 1; k < v1; k++) {
|
|
11756
|
+
arr[k * 3] = arr[v0 * 3];
|
|
11757
|
+
arr[k * 3 + 1] = arr[v0 * 3 + 1];
|
|
11758
|
+
arr[k * 3 + 2] = arr[v0 * 3 + 2];
|
|
11759
|
+
}
|
|
11760
|
+
}
|
|
11761
|
+
};
|
|
11762
|
+
var DECAL_SNAP = 0.03;
|
|
11217
11763
|
var Visuals = class {
|
|
11218
11764
|
constructor(THREE, lowQuality, decalCap) {
|
|
11219
11765
|
__publicField(this, "THREE", THREE);
|
|
@@ -11223,6 +11769,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11223
11769
|
__publicField(this, "a");
|
|
11224
11770
|
__publicField(this, "b");
|
|
11225
11771
|
__publicField(this, "m4");
|
|
11772
|
+
__publicField(this, "ray");
|
|
11226
11773
|
__publicField(this, "box");
|
|
11227
11774
|
__publicField(this, "t");
|
|
11228
11775
|
__publicField(this, "lbox");
|
|
@@ -11233,6 +11780,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11233
11780
|
this.a = tmpV(THREE);
|
|
11234
11781
|
this.b = tmpV(THREE);
|
|
11235
11782
|
this.m4 = new THREE.Matrix4();
|
|
11783
|
+
this.ray = new THREE.Raycaster();
|
|
11236
11784
|
this.box = new THREE.Box3();
|
|
11237
11785
|
this.t = tmpV(THREE);
|
|
11238
11786
|
this.lbox = new THREE.Box3();
|
|
@@ -11386,7 +11934,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11386
11934
|
host.ownGeo = true;
|
|
11387
11935
|
const g = host.mesh.geometry.clone();
|
|
11388
11936
|
host.mesh.geometry = g;
|
|
11389
|
-
host.work = new Float32Array(positionAttr(g).array);
|
|
11937
|
+
host.work = new Float32Array(basePositions(host) ?? positionAttr(g).array);
|
|
11390
11938
|
return true;
|
|
11391
11939
|
}
|
|
11392
11940
|
// ------------------------------------------------------------------------------------------------------------------------------------
|
|
@@ -11556,12 +12104,38 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11556
12104
|
// ------------------------------------------------------------------------------------------------------------------------------------
|
|
11557
12105
|
// Decals
|
|
11558
12106
|
// ------------------------------------------------------------------------------------------------------------------------------------
|
|
12107
|
+
/**
|
|
12108
|
+
* The point on the drawn face of `mesh` under a world point, within `DECAL_SNAP` metres along the normal: the world's box of a part and the face
|
|
12109
|
+
* the renderer drew (a coat of plaster, a merged house mesh) can be a few millimetres apart, and a mark behind the drawn face does not show.
|
|
12110
|
+
*/
|
|
12111
|
+
onFace(mesh2, worldPoint, worldNormal) {
|
|
12112
|
+
if (!mesh2.isMesh || !mesh2.geometry?.attributes?.position) return worldPoint;
|
|
12113
|
+
const n = worldNormal.clone().normalize();
|
|
12114
|
+
this.ray.set(worldPoint.clone().addScaledVector(n, DECAL_SNAP), n.clone().negate());
|
|
12115
|
+
this.ray.near = 0;
|
|
12116
|
+
this.ray.far = 2 * DECAL_SNAP;
|
|
12117
|
+
const hits = [];
|
|
12118
|
+
mesh2.raycast(this.ray, hits);
|
|
12119
|
+
for (const c of mesh2.parent?.children ?? []) {
|
|
12120
|
+
if (c === mesh2 || c.name !== "breach-shell") continue;
|
|
12121
|
+
c.raycast(this.ray, hits);
|
|
12122
|
+
const behind = c.userData.behind?.();
|
|
12123
|
+
if (behind) behind.raycast(this.ray, hits);
|
|
12124
|
+
}
|
|
12125
|
+
let best = null;
|
|
12126
|
+
for (const h of hits) {
|
|
12127
|
+
if (h.object.userData.cutAt?.(h.point)) continue;
|
|
12128
|
+
if (h.face && h.face.normal.clone().transformDirection(h.object.matrixWorld).dot(n) <= 0.3) continue;
|
|
12129
|
+
if (!best || Math.abs(h.distance - DECAL_SNAP) < Math.abs(best.distance - DECAL_SNAP)) best = h;
|
|
12130
|
+
}
|
|
12131
|
+
return best ? best.point : worldPoint;
|
|
12132
|
+
}
|
|
11559
12133
|
/** Decal pose in mesh space: position and a rotation taking +Z to the surface normal. */
|
|
11560
12134
|
decalPose(mesh2, worldPoint, worldNormal, lift) {
|
|
11561
12135
|
mesh2.updateWorldMatrix(true, false);
|
|
11562
12136
|
const s = mesh2.matrixWorld.getMaxScaleOnAxis() || 1;
|
|
11563
12137
|
const ln = worldNormal.clone().transformDirection(this.m4.copy(mesh2.matrixWorld).invert()).normalize();
|
|
11564
|
-
const pos = mesh2.worldToLocal(worldPoint.clone()).addScaledVector(ln, lift / s);
|
|
12138
|
+
const pos = mesh2.worldToLocal(this.onFace(mesh2, worldPoint, worldNormal).clone()).addScaledVector(ln, lift / s);
|
|
11565
12139
|
const quat2 = new this.THREE.Quaternion().setFromUnitVectors(new this.THREE.Vector3(0, 0, 1), ln);
|
|
11566
12140
|
return { s, pos, quat: quat2 };
|
|
11567
12141
|
}
|
|
@@ -11585,8 +12159,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11585
12159
|
* Takes away the decals of a mesh that sit inside a box (world `center`, `half` extents along the axes of the world `rotation`): the surface they
|
|
11586
12160
|
* marked is gone (a unit came out of the wall with its bullet holes and spalls).
|
|
11587
12161
|
*/
|
|
11588
|
-
clearDecals(mv, center, half,
|
|
11589
|
-
const inv =
|
|
12162
|
+
clearDecals(mv, center, half, rotation2) {
|
|
12163
|
+
const inv = rotation2.clone().invert();
|
|
11590
12164
|
const p = new this.THREE.Vector3();
|
|
11591
12165
|
for (let i = mv.decals.length - 1; i >= 0; i--) {
|
|
11592
12166
|
const d = mv.decals[i];
|
|
@@ -11800,6 +12374,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11800
12374
|
if (!pos || !work) return;
|
|
11801
12375
|
const arr = pos.array;
|
|
11802
12376
|
arr.set(work);
|
|
12377
|
+
collapseForeign(host.mesh, arr);
|
|
11803
12378
|
for (const s of host.sharers) {
|
|
11804
12379
|
if (s.merged && s.merged.host === host) {
|
|
11805
12380
|
if (s.collapsed) this.collapseRange(arr, s.merged.range);
|
|
@@ -12004,9 +12579,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12004
12579
|
mv.touched = false;
|
|
12005
12580
|
if (!host.ownGeo) return;
|
|
12006
12581
|
const work = host.work;
|
|
12007
|
-
const op =
|
|
12582
|
+
const op = basePositions(host);
|
|
12008
12583
|
const [v0, v1] = merged.range;
|
|
12009
|
-
if (op) work.set(op.
|
|
12584
|
+
if (op) work.set(op.subarray(v0 * 3, v1 * 3), v0 * 3);
|
|
12010
12585
|
const attr = host.mesh.geometry.getAttribute("color");
|
|
12011
12586
|
if (attr) {
|
|
12012
12587
|
const orig = host.origGeometry.getAttribute("color");
|
|
@@ -12020,6 +12595,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12020
12595
|
if (host.sharers.every((s) => !s.touched)) {
|
|
12021
12596
|
host.mesh.geometry.dispose();
|
|
12022
12597
|
host.mesh.geometry = host.origGeometry;
|
|
12598
|
+
const base = basePositions(host), back = positionAttr(host.origGeometry);
|
|
12599
|
+
if (base && back) {
|
|
12600
|
+
const arr = back.array;
|
|
12601
|
+
arr.set(base);
|
|
12602
|
+
collapseForeign(host.mesh, arr);
|
|
12603
|
+
back.needsUpdate = true;
|
|
12604
|
+
}
|
|
12023
12605
|
host.work = null;
|
|
12024
12606
|
host.ownGeo = false;
|
|
12025
12607
|
if (host.ownMat) {
|
|
@@ -12079,9 +12661,12 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12079
12661
|
var rendererCount = 0;
|
|
12080
12662
|
var COLLAPSE_CLOUD_KG = 1e3;
|
|
12081
12663
|
var CHIP_DECAL_EDGE = 0.05;
|
|
12664
|
+
var OPAQUE_CRACK = 0.1;
|
|
12665
|
+
var WOOD_RIM = 1.5;
|
|
12082
12666
|
var IMPACT_TAGS = ["metal", "glass", "wood"];
|
|
12083
12667
|
var GLOW_FROM_C = 430;
|
|
12084
12668
|
var QUEUE_CAP = 4e3;
|
|
12669
|
+
var MIN_FRAGMENTS_PER_SLICE = 8;
|
|
12085
12670
|
var STATIC_REFRESH = 0.5;
|
|
12086
12671
|
var STATIC_SPAN = 3;
|
|
12087
12672
|
var FEET_BAND = 0.03;
|
|
@@ -12157,6 +12742,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12157
12742
|
const r = v;
|
|
12158
12743
|
return !!r.mesh && Array.isArray(r.range);
|
|
12159
12744
|
}
|
|
12745
|
+
var RNG_SEED = 12648430;
|
|
12160
12746
|
function createDestructionRenderer(THREE, options) {
|
|
12161
12747
|
return new Renderer(THREE, options);
|
|
12162
12748
|
}
|
|
@@ -12172,6 +12758,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12172
12758
|
__publicField(this, "gravity");
|
|
12173
12759
|
__publicField(this, "autoSplit");
|
|
12174
12760
|
__publicField(this, "maxFragments");
|
|
12761
|
+
__publicField(this, "eventBudgetMs");
|
|
12175
12762
|
__publicField(this, "bounds", /* @__PURE__ */ new Map());
|
|
12176
12763
|
/** World boxes (min xyz, max xyz) of the drawn meshes of the bound objects that are not pieces, for the pieces to land on. */
|
|
12177
12764
|
__publicField(this, "staticBoxes", /* @__PURE__ */ new Map());
|
|
@@ -12182,6 +12769,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12182
12769
|
__publicField(this, "pieceBoxes", /* @__PURE__ */ new WeakMap());
|
|
12183
12770
|
__publicField(this, "staticAt", 0);
|
|
12184
12771
|
__publicField(this, "queue", []);
|
|
12772
|
+
/** Presentation work an event left for later frames (fragments still to spawn, loose groups still to split), run in order within the frame budget. */
|
|
12773
|
+
__publicField(this, "slices", []);
|
|
12774
|
+
/** When the work of this frame must stop; `Infinity` outside `drainQueue`, where a call finishes what it started. */
|
|
12775
|
+
__publicField(this, "sliceDeadline", Infinity);
|
|
12185
12776
|
__publicField(this, "unsubscribe");
|
|
12186
12777
|
__publicField(this, "visuals");
|
|
12187
12778
|
__publicField(this, "fragments");
|
|
@@ -12195,7 +12786,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12195
12786
|
__publicField(this, "leaks", []);
|
|
12196
12787
|
__publicField(this, "homeTags", []);
|
|
12197
12788
|
__publicField(this, "throttles", /* @__PURE__ */ new Map());
|
|
12198
|
-
__publicField(this, "rng", createRng(
|
|
12789
|
+
__publicField(this, "rng", createRng(RNG_SEED));
|
|
12199
12790
|
__publicField(this, "vfx");
|
|
12200
12791
|
/** Names this renderer to the world's drips, so a shared world tells each renderer where its own landed. */
|
|
12201
12792
|
__publicField(this, "owner", `destruction-${++rendererCount}`);
|
|
@@ -12219,6 +12810,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12219
12810
|
this.gravity = o.gravity ?? 9.81;
|
|
12220
12811
|
this.autoSplit = o.autoSplit !== false;
|
|
12221
12812
|
this.maxFragments = o.maxFragments ?? 160;
|
|
12813
|
+
this.eventBudgetMs = o.eventBudgetMs ?? Infinity;
|
|
12222
12814
|
this.box = new THREE.Box3();
|
|
12223
12815
|
this.tmpQ = new THREE.Quaternion();
|
|
12224
12816
|
this.v1 = new THREE.Vector3();
|
|
@@ -12304,6 +12896,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12304
12896
|
if (leftovers.length) this.matchByBox(objectId, root, leftovers, add2);
|
|
12305
12897
|
}
|
|
12306
12898
|
const bound = { id: objectId, root, sync: options.syncTransform !== false, parts: /* @__PURE__ */ new Map(), lastPos: null, lastQuat: null, piece: null, isSplit: false };
|
|
12899
|
+
if (options.onFrame) bound.onFrame = options.onFrame;
|
|
12307
12900
|
const hosts = /* @__PURE__ */ new Map();
|
|
12308
12901
|
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));
|
|
12309
12902
|
for (const [pid, meshes] of byPart) {
|
|
@@ -12463,6 +13056,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12463
13056
|
dt = clamp6(dt, 0, 0.1);
|
|
12464
13057
|
this.time += dt;
|
|
12465
13058
|
this.drainQueue();
|
|
13059
|
+
for (const b of this.bounds.values()) b.onFrame?.(dt);
|
|
12466
13060
|
for (const b of this.bounds.values()) if (b.soilNode && this.world.state(b.id)?.soil?.status === "yielding") this.onSettle(b.id);
|
|
12467
13061
|
this.updateActive(dt);
|
|
12468
13062
|
this.updateLeaks(dt);
|
|
@@ -12474,11 +13068,32 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12474
13068
|
}
|
|
12475
13069
|
drainQueue() {
|
|
12476
13070
|
const batch = this.queue.splice(0, this.queue.length);
|
|
12477
|
-
|
|
13071
|
+
const deadline = performance.now() + this.eventBudgetMs;
|
|
13072
|
+
this.sliceDeadline = deadline;
|
|
13073
|
+
try {
|
|
13074
|
+
this.runSlices(deadline);
|
|
13075
|
+
for (let i = 0; i < batch.length; i++) {
|
|
13076
|
+
if (i > 0 && performance.now() >= deadline) {
|
|
13077
|
+
this.queue.unshift(...batch.slice(i));
|
|
13078
|
+
return;
|
|
13079
|
+
}
|
|
13080
|
+
try {
|
|
13081
|
+
this.apply(batch[i]);
|
|
13082
|
+
} catch (err) {
|
|
13083
|
+
if (typeof console !== "undefined") console.error("game-destruction-three: failed to apply", batch[i].type, err);
|
|
13084
|
+
}
|
|
13085
|
+
}
|
|
13086
|
+
} finally {
|
|
13087
|
+
this.sliceDeadline = Infinity;
|
|
13088
|
+
}
|
|
13089
|
+
}
|
|
13090
|
+
/** Runs the work left by earlier frames until the deadline; at least one piece per frame, so it always finishes. */
|
|
13091
|
+
runSlices(deadline) {
|
|
13092
|
+
for (let ran = 0; this.slices.length > 0 && (ran === 0 || performance.now() < deadline); ran++) {
|
|
12478
13093
|
try {
|
|
12479
|
-
this.
|
|
13094
|
+
this.slices.shift()();
|
|
12480
13095
|
} catch (err) {
|
|
12481
|
-
if (typeof console !== "undefined") console.error("game-destruction-three: failed to
|
|
13096
|
+
if (typeof console !== "undefined") console.error("game-destruction-three: failed to run deferred work", err);
|
|
12482
13097
|
}
|
|
12483
13098
|
}
|
|
12484
13099
|
}
|
|
@@ -12876,12 +13491,19 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12876
13491
|
const seed = hashNumbers(ev.time, point[0], point[1], point[2]);
|
|
12877
13492
|
this.visuals.addCrack(mv, wp, normal, 7, Math.max(0.05, ev.hole.diameter * 5), 15267071, seed);
|
|
12878
13493
|
} else {
|
|
13494
|
+
const fresh = def?.penetrationClass === "wood" ? def.visual?.innerColor : void 0;
|
|
13495
|
+
if (fresh !== void 0) this.visuals.addSpall(mv, wp, normal, Math.max(6e-3, ev.hole.diameter * WOOD_RIM), fresh);
|
|
12879
13496
|
this.visuals.addHole(mv, wp, normal, ev.hole.diameter);
|
|
12880
13497
|
}
|
|
12881
13498
|
};
|
|
12882
13499
|
draw(ev.worldPoint, n);
|
|
12883
13500
|
if (ev.hole.through && ev.exitPoint) draw(ev.exitPoint, n.clone().negate());
|
|
12884
13501
|
if (def?.tags?.includes("metal")) this.vfx.burst("sparks", ev.worldPoint, [n.x, n.y, n.z], 6, 3.5);
|
|
13502
|
+
if (ev.powder) {
|
|
13503
|
+
const puff3 = { color: ev.powder.color, size: 0.6, life: 1.6, alpha: 1.3 };
|
|
13504
|
+
this.vfx.burst("dust", ev.worldPoint, [n.x, n.y, n.z], this.low ? 5 : 10, 0.7, puff3);
|
|
13505
|
+
if (ev.hole.through && ev.exitPoint) this.vfx.burst("dust", ev.exitPoint, [-n.x, -n.y, -n.z], this.low ? 10 : 20, 1.3, puff3);
|
|
13506
|
+
}
|
|
12885
13507
|
this.sound(impactKey(def), ev.worldPoint, 0.6);
|
|
12886
13508
|
}
|
|
12887
13509
|
onCrack(ev) {
|
|
@@ -12892,8 +13514,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12892
13514
|
const wp = new this.THREE.Vector3(ev.worldPoint[0], ev.worldPoint[1], ev.worldPoint[2]);
|
|
12893
13515
|
const mv = this.nearestMesh(pv, wp);
|
|
12894
13516
|
if (mv) {
|
|
12895
|
-
const
|
|
12896
|
-
|
|
13517
|
+
const glass = !!def?.visual?.transparent;
|
|
13518
|
+
const own = this.partColor(pv, def) ?? fallbackColor(pv.material, def);
|
|
13519
|
+
const color = glass ? 15267071 : new this.THREE.Color(own).multiplyScalar(0.45).getHex();
|
|
13520
|
+
const length = glass ? ev.crack.length : Math.min(ev.crack.length, OPAQUE_CRACK);
|
|
13521
|
+
this.visuals.addCrack(mv, wp, this.worldDir(b, ev.crack.normal), ev.crack.rays, length, color, hashNumbers(ev.time, ev.worldPoint[0], ev.worldPoint[1], ev.worldPoint[2]));
|
|
12897
13522
|
}
|
|
12898
13523
|
this.sound(impactKey(def), ev.worldPoint, 0.5);
|
|
12899
13524
|
}
|
|
@@ -12931,6 +13556,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12931
13556
|
const core = def?.visual?.innerColor;
|
|
12932
13557
|
if (core !== void 0) this.vfx.burst("chips", ev.point, [normal.x, normal.y, normal.z], 5, 2.5, { color: core });
|
|
12933
13558
|
else this.playEvent({ kind: "spall", material: this.impactMaterial(def) }, ev.point);
|
|
13559
|
+
if (mv.mesh.userData.drawnSpalls?.includes(ev.part)) return;
|
|
12934
13560
|
this.visuals.addSpall(mv, wp, normal, Math.sqrt(ev.area / Math.PI), core ?? fallbackColor(pv.material, def));
|
|
12935
13561
|
}
|
|
12936
13562
|
/**
|
|
@@ -12968,11 +13594,15 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12968
13594
|
const tone = fr.inner && fdef?.visual?.innerColor !== void 0 ? fdef.visual.innerColor : fdef === def ? color : fallbackColor(fr.material, fdef);
|
|
12969
13595
|
this.fragments.spawn(fr, this.fragments.materialFor(fr.material, tone, false), ev.object, maxAlive);
|
|
12970
13596
|
}
|
|
13597
|
+
if (ev.torn) {
|
|
13598
|
+
this.sound(impactKey(def), ev.center, 0.5);
|
|
13599
|
+
return;
|
|
13600
|
+
}
|
|
12971
13601
|
const T = this.THREE;
|
|
12972
13602
|
const center = new T.Vector3(ev.center[0], ev.center[1], ev.center[2]);
|
|
12973
13603
|
const half = new T.Vector3(ev.size[0] / 2 + CHIP_DECAL_EDGE, ev.size[1] / 2 + CHIP_DECAL_EDGE, ev.size[2] / 2 + CHIP_DECAL_EDGE);
|
|
12974
|
-
const
|
|
12975
|
-
for (const part of this.bounds.get(ev.object)?.parts.values() ?? []) for (const mv of part.meshes) this.visuals.clearDecals(mv, center, half,
|
|
13604
|
+
const rotation2 = new T.Quaternion(ev.rotation[0], ev.rotation[1], ev.rotation[2], ev.rotation[3]);
|
|
13605
|
+
for (const part of this.bounds.get(ev.object)?.parts.values() ?? []) for (const mv of part.meshes) this.visuals.clearDecals(mv, center, half, rotation2);
|
|
12976
13606
|
this.vfx.peekWorkshop()?.decals.clearInside(ev.center, [half.x, half.y, half.z], ev.rotation);
|
|
12977
13607
|
this.vfx.burst("dust", ev.center, [0, 1, 0], 8, 0.8, { color });
|
|
12978
13608
|
this.sound(impactKey(def), ev.center, 0.7);
|
|
@@ -12992,15 +13622,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12992
13622
|
if (m && m.transparent) opacity = clamp6(m.opacity, 0.2, 0.8);
|
|
12993
13623
|
this.visuals.hide(pv, "broken");
|
|
12994
13624
|
}
|
|
12995
|
-
|
|
12996
|
-
const step = this.low ? 2 : 1;
|
|
12997
|
-
for (let i = 0; i < ev.fragments.length; i += step) {
|
|
12998
|
-
const fr = ev.fragments[i];
|
|
12999
|
-
const fdef = this.world.materials.get(fr.material);
|
|
13000
|
-
const tone = fr.inner && fdef?.visual?.innerColor !== void 0 ? fdef.visual.innerColor : fdef === def || !fdef ? color : fallbackColor(fr.material, fdef);
|
|
13001
|
-
const mat = this.fragments.materialFor(fr.material, tone, transparent || fdef?.visual?.transparent === true, opacity);
|
|
13002
|
-
this.fragments.spawn(fr, mat, ev.object, maxAlive);
|
|
13003
|
-
}
|
|
13625
|
+
this.spawnFragments(ev, 0, { def, color, opacity, transparent });
|
|
13004
13626
|
const up = [0, 1, 0];
|
|
13005
13627
|
const sizeK = clamp6(ev.fragments.length / 10, 0.5, 2);
|
|
13006
13628
|
switch (ev.style) {
|
|
@@ -13026,6 +13648,22 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13026
13648
|
}
|
|
13027
13649
|
this.sound(fractureKey(def, ev.style), ev.point, clamp6(0.4 + ev.fragments.length * 0.03, 0.4, 1));
|
|
13028
13650
|
}
|
|
13651
|
+
/** Spawns the fragments of a fracture from index `from`; what does not fit in this frame's budget is spawned by the next frames. */
|
|
13652
|
+
spawnFragments(ev, from, look) {
|
|
13653
|
+
const maxAlive = this.low ? Math.floor(this.maxFragments / 2) : this.maxFragments;
|
|
13654
|
+
const step = this.low ? 2 : 1;
|
|
13655
|
+
for (let i = from; i < ev.fragments.length; i += step) {
|
|
13656
|
+
if (i - from >= MIN_FRAGMENTS_PER_SLICE * step && performance.now() >= this.sliceDeadline) {
|
|
13657
|
+
this.slices.push(() => this.spawnFragments(ev, i, look));
|
|
13658
|
+
return;
|
|
13659
|
+
}
|
|
13660
|
+
const fr = ev.fragments[i];
|
|
13661
|
+
const fdef = this.world.materials.get(fr.material);
|
|
13662
|
+
const tone = fr.inner && fdef?.visual?.innerColor !== void 0 ? fdef.visual.innerColor : fdef === look.def || !fdef ? look.color : fallbackColor(fr.material, fdef);
|
|
13663
|
+
const mat = this.fragments.materialFor(fr.material, tone, look.transparent || fdef?.visual?.transparent === true, look.opacity);
|
|
13664
|
+
this.fragments.spawn(fr, mat, ev.object, maxAlive);
|
|
13665
|
+
}
|
|
13666
|
+
}
|
|
13029
13667
|
onDetach(ev) {
|
|
13030
13668
|
this.sound("detach", this.rootPoint(ev.object), 0.5);
|
|
13031
13669
|
this.splitLoose(ev.object);
|
|
@@ -13039,6 +13677,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13039
13677
|
if (!st || st.groups.length < 2) return;
|
|
13040
13678
|
const loose = st.groups.length - 1;
|
|
13041
13679
|
for (let i = 0; i < loose; i++) {
|
|
13680
|
+
if (i > 0 && performance.now() >= this.sliceDeadline) {
|
|
13681
|
+
this.slices.push(() => {
|
|
13682
|
+
this.staticDirty.add(objectId);
|
|
13683
|
+
this.splitLoose(objectId);
|
|
13684
|
+
});
|
|
13685
|
+
return;
|
|
13686
|
+
}
|
|
13042
13687
|
let newId;
|
|
13043
13688
|
try {
|
|
13044
13689
|
newId = this.world.splitGroup(objectId, 1);
|
|
@@ -13055,8 +13700,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13055
13700
|
old.mass = Math.max(0.05, this.massOf(objectId));
|
|
13056
13701
|
old.sleeping = false;
|
|
13057
13702
|
} else if (kitOriginOf(b.root)?.kind === "furniture") {
|
|
13058
|
-
this.makePiece(objectId);
|
|
13703
|
+
const piece = this.makePiece(objectId);
|
|
13059
13704
|
this.tipIfTopHeavy(b);
|
|
13705
|
+
this.o.onLoosened?.(objectId, piece, "apart");
|
|
13060
13706
|
}
|
|
13061
13707
|
}
|
|
13062
13708
|
/** The renderer side of `splitGroup`: a new group holding the meshes of the new object, made a piece. */
|
|
@@ -13165,7 +13811,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13165
13811
|
const piece = this.makePiece(b.id, { mass });
|
|
13166
13812
|
const body = this.pieceSys.get(b.id);
|
|
13167
13813
|
if (!body) continue;
|
|
13168
|
-
this.o.onLoosened?.(b.id, piece);
|
|
13814
|
+
this.o.onLoosened?.(b.id, piece, "blast");
|
|
13169
13815
|
const spread = half[0];
|
|
13170
13816
|
const momentum = hit.speed * mass;
|
|
13171
13817
|
this.pieceSys.impulse(body, [hit.direction[0] * momentum, hit.direction[1] * momentum, hit.direction[2] * momentum], [world.x + (this.rng() - 0.5) * spread, world.y + spread * 0.5, world.z + (this.rng() - 0.5) * spread]);
|
|
@@ -13360,6 +14006,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13360
14006
|
}
|
|
13361
14007
|
clear() {
|
|
13362
14008
|
this.queue.length = 0;
|
|
14009
|
+
this.slices.length = 0;
|
|
13363
14010
|
this.active.clear();
|
|
13364
14011
|
for (const key of [...this.fires.keys()]) this.stopFire(key);
|
|
13365
14012
|
this.leaks.length = 0;
|
|
@@ -13379,6 +14026,15 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13379
14026
|
this.vfx.sprites().clear();
|
|
13380
14027
|
this.ownVfx?.world.clear();
|
|
13381
14028
|
}
|
|
14029
|
+
reset() {
|
|
14030
|
+
this.clear();
|
|
14031
|
+
this.rng = createRng(RNG_SEED);
|
|
14032
|
+
this.time = 0;
|
|
14033
|
+
this.staticAt = 0;
|
|
14034
|
+
this.throttles.clear();
|
|
14035
|
+
this.fragments.reset();
|
|
14036
|
+
this.pieceSys.reset();
|
|
14037
|
+
}
|
|
13382
14038
|
wakeRegion(x, z, radius) {
|
|
13383
14039
|
this.fragments.wakeRegion(x, z, radius);
|
|
13384
14040
|
this.pieceSys.wakeRegion(x, z, radius);
|
|
@@ -13683,7 +14339,18 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13683
14339
|
drag("drag_masonry", [sceneNoise({ type: "bandpass", f: 260, q: 0.9, d: 0.3, vol: 0.62 }), ...sceneMany(3, () => sceneNoise({ type: "bandpass", f: [600, 1200], q: 2, d: 0.04, vol: 0.14, delay: [0, 0.2] }))]),
|
|
13684
14340
|
drag("drag_soft", [sceneNoise({ type: "lowpass", f: 420, d: 0.28, vol: 0.3 })])
|
|
13685
14341
|
];
|
|
13686
|
-
var
|
|
14342
|
+
var leaf = (id, layers) => ({ ...sceneRecipe(id, layers, ""), sources: ["sdk: a door, lid or drawer worked by hand"] });
|
|
14343
|
+
var LEAF_SOUNDS = [
|
|
14344
|
+
leaf("latch", [sceneNoise({ type: "bandpass", f: 2400, q: 4, d: 0.03, vol: 0.35 }), sceneTone({ f: 1900, f2: 1500, d: 0.025, vol: 0.06, delay: 0.035 })]),
|
|
14345
|
+
leaf("creak_wood", [sceneTone({ f: [190, 240], f2: 310, d: 0.5, vol: 0.05 }), sceneNoise({ type: "bandpass", f: 520, q: 6, a: 0.05, d: 0.45, vol: 0.12 })]),
|
|
14346
|
+
leaf("creak_metal", [sceneTone({ f: [620, 760], f2: 900, d: 0.35, vol: 0.035 }), sceneNoise({ type: "bandpass", f: 1400, q: 8, a: 0.04, d: 0.3, vol: 0.08 })]),
|
|
14347
|
+
leaf("shut_wood", [sceneTone({ f: 120, f2: 60, d: 0.14, vol: 0.5 }), sceneNoise({ type: "bandpass", f: 600, q: 1.2, d: 0.1, vol: 0.55, wet: 0.25 }), sceneNoise({ type: "bandpass", f: 2400, q: 4, d: 0.03, vol: 0.25, delay: 0.02 })]),
|
|
14348
|
+
leaf("shut_metal", [sceneTone({ f: 160, f2: 85, d: 0.18, vol: 0.55 }), sceneNoise({ type: "bandpass", f: 900, q: 1.6, d: 0.12, vol: 0.45, wet: 0.3 }), sceneTone({ f: 1250, f2: 1150, d: 0.12, vol: 0.06 })]),
|
|
14349
|
+
leaf("shut_soft", [sceneTone({ f: 95, f2: 55, d: 0.12, vol: 0.35 }), sceneNoise({ type: "lowpass", f: 500, d: 0.1, vol: 0.3 })]),
|
|
14350
|
+
leaf("rattle", sceneMany(5, () => sceneNoise({ type: "bandpass", f: [1600, 2600], q: 3, d: 0.035, vol: 0.32, delay: [0, 0.45] }))),
|
|
14351
|
+
leaf("slide", [sceneNoise({ type: "bandpass", f: 700, q: 2, a: 0.04, d: 0.35, vol: 0.22 }), sceneNoise({ type: "bandpass", f: 2200, q: 5, d: 0.3, vol: 0.05 })])
|
|
14352
|
+
];
|
|
14353
|
+
var DESTRUCTION_SOUND_BANK = { id: "destruction_three", sounds: [...SCENE_SOUNDS, ...DRAG_SOUNDS, ...LEAF_SOUNDS, EXPLOSION_BOOM], loops: [BURN_LOOP] };
|
|
13687
14354
|
|
|
13688
14355
|
// src/audio/sound.ts
|
|
13689
14356
|
var DESTRUCTION_SOUND_KEYS = {
|
|
@@ -13698,6 +14365,32 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13698
14365
|
drag_glass: { sound: "drag_glass" },
|
|
13699
14366
|
drag_masonry: { sound: "drag_masonry" },
|
|
13700
14367
|
drag_soft: { sound: "drag_soft" },
|
|
14368
|
+
// a leaf worked by hand (doors, lids, drawers): the latch, the hinge turning, the leaf meeting the frame, a locked one shaken
|
|
14369
|
+
leaf_latch: { sound: "latch" },
|
|
14370
|
+
leaf_creak_wood: { sound: "creak_wood" },
|
|
14371
|
+
leaf_creak_metal: { sound: "creak_metal" },
|
|
14372
|
+
leaf_shut_wood: { sound: "shut_wood" },
|
|
14373
|
+
leaf_shut_metal: { sound: "shut_metal" },
|
|
14374
|
+
leaf_shut_glass: { sound: "shut_metal", volumeScale: 0.6 },
|
|
14375
|
+
leaf_shut_masonry: { sound: "shut_wood" },
|
|
14376
|
+
leaf_shut_soft: { sound: "shut_soft" },
|
|
14377
|
+
leaf_rattle: { sound: "rattle" },
|
|
14378
|
+
leaf_slide: { sound: "slide" },
|
|
14379
|
+
// the keys of the item clips (furniture and vehicle doors, hoods, trunks, drawers)
|
|
14380
|
+
door_latch: { sound: "latch" },
|
|
14381
|
+
hood_latch: { sound: "latch" },
|
|
14382
|
+
trunk_latch: { sound: "latch" },
|
|
14383
|
+
door_open: { sound: "latch" },
|
|
14384
|
+
door_shut: { sound: "shut_metal" },
|
|
14385
|
+
hood_shut: { sound: "shut_metal" },
|
|
14386
|
+
trunk_shut: { sound: "shut_metal" },
|
|
14387
|
+
door_close: { sound: "shut_wood", volumeScale: 0.7 },
|
|
14388
|
+
drawer_open: { sound: "slide" },
|
|
14389
|
+
drawer_close: { sound: "slide" },
|
|
14390
|
+
lid_open: { sound: "latch" },
|
|
14391
|
+
lid_close: { sound: "shut_soft" },
|
|
14392
|
+
flap_open: { sound: "latch" },
|
|
14393
|
+
flap_close: { sound: "shut_soft" },
|
|
13701
14394
|
shatter_glass: { sound: "glass_shatter" },
|
|
13702
14395
|
crumble: { sound: "crumble" },
|
|
13703
14396
|
splinter: { sound: "crack" },
|