@adatechnology/game-vfx-three 0.3.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +15 -1
- package/dist/index.global.js +486 -150
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +188 -87
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.global.js
CHANGED
|
@@ -1344,11 +1344,231 @@ var GameVfxThree = (() => {
|
|
|
1344
1344
|
glow.outIntensity = glow.i * Math.pow(1 - age, glow.curve) * flicker;
|
|
1345
1345
|
}
|
|
1346
1346
|
}
|
|
1347
|
+
var LEHMER_MULTIPLIER = 16807;
|
|
1348
|
+
var LEHMER_MODULUS = 2147483647;
|
|
1349
|
+
var HASH_MIX = 2654435761;
|
|
1350
|
+
function normalizeLehmerSeed(seed) {
|
|
1351
|
+
const whole = Math.floor(Math.abs(seed)) % (LEHMER_MODULUS - 1);
|
|
1352
|
+
return whole === 0 ? 1 : whole;
|
|
1353
|
+
}
|
|
1354
|
+
function lehmer(seed) {
|
|
1355
|
+
let state = normalizeLehmerSeed(seed);
|
|
1356
|
+
const next = () => {
|
|
1357
|
+
state = state * LEHMER_MULTIPLIER % LEHMER_MODULUS;
|
|
1358
|
+
return (state - 1) / (LEHMER_MODULUS - 1);
|
|
1359
|
+
};
|
|
1360
|
+
return {
|
|
1361
|
+
next,
|
|
1362
|
+
between: (min2, max2) => min2 + (max2 - min2) * next(),
|
|
1363
|
+
direction(out) {
|
|
1364
|
+
const up = next() * 2 - 1;
|
|
1365
|
+
const angle = next() * Math.PI * 2;
|
|
1366
|
+
const ring2 = Math.sqrt(1 - up * up);
|
|
1367
|
+
out.x = ring2 * Math.cos(angle);
|
|
1368
|
+
out.y = up;
|
|
1369
|
+
out.z = ring2 * Math.sin(angle);
|
|
1370
|
+
return out;
|
|
1371
|
+
},
|
|
1372
|
+
get state() {
|
|
1373
|
+
return state;
|
|
1374
|
+
}
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
function mixEventSeed(eventSeed) {
|
|
1378
|
+
let mixed = Math.imul((eventSeed | 0) ^ HASH_MIX, 2246822507);
|
|
1379
|
+
mixed ^= mixed >>> 13;
|
|
1380
|
+
mixed = Math.imul(mixed, 3266489909);
|
|
1381
|
+
mixed ^= mixed >>> 16;
|
|
1382
|
+
return (mixed >>> 0) % (LEHMER_MODULUS - 1) + 1;
|
|
1383
|
+
}
|
|
1384
|
+
function streamFor(eventSeed) {
|
|
1385
|
+
return lehmer(mixEventSeed(eventSeed));
|
|
1386
|
+
}
|
|
1387
|
+
var SKIN = 5e-3;
|
|
1388
|
+
var MAX_PAD = 0.45;
|
|
1389
|
+
function rotation(q) {
|
|
1390
|
+
const [x, y, z, w] = q ?? [0, 0, 0, 1];
|
|
1391
|
+
const n = Math.hypot(x, y, z, w) || 1;
|
|
1392
|
+
const qx = x / n;
|
|
1393
|
+
const qy = y / n;
|
|
1394
|
+
const qz = z / n;
|
|
1395
|
+
const qw = w / n;
|
|
1396
|
+
return new Float64Array([
|
|
1397
|
+
1 - 2 * (qy * qy + qz * qz),
|
|
1398
|
+
2 * (qx * qy - qz * qw),
|
|
1399
|
+
2 * (qx * qz + qy * qw),
|
|
1400
|
+
2 * (qx * qy + qz * qw),
|
|
1401
|
+
1 - 2 * (qx * qx + qz * qz),
|
|
1402
|
+
2 * (qy * qz - qx * qw),
|
|
1403
|
+
2 * (qx * qz - qy * qw),
|
|
1404
|
+
2 * (qy * qz + qx * qw),
|
|
1405
|
+
1 - 2 * (qx * qx + qy * qy)
|
|
1406
|
+
]);
|
|
1407
|
+
}
|
|
1408
|
+
var Enclosures = class {
|
|
1409
|
+
constructor(seed = 20973) {
|
|
1410
|
+
__publicField(this, "spaces", /* @__PURE__ */ new Map());
|
|
1411
|
+
__publicField(this, "list", []);
|
|
1412
|
+
__publicField(this, "random");
|
|
1413
|
+
__publicField(this, "out", { x: 0, y: 0, z: 0, nx: 0, ny: 0, nz: 0 });
|
|
1414
|
+
__publicField(this, "l0", new Float64Array(3));
|
|
1415
|
+
__publicField(this, "l1", new Float64Array(3));
|
|
1416
|
+
__publicField(this, "q", new Float64Array(3));
|
|
1417
|
+
this.random = lehmer(seed);
|
|
1418
|
+
}
|
|
1419
|
+
get empty() {
|
|
1420
|
+
return this.list.length === 0;
|
|
1421
|
+
}
|
|
1422
|
+
get size() {
|
|
1423
|
+
return this.list.length;
|
|
1424
|
+
}
|
|
1425
|
+
/** Declares, moves or (with `null`) ends the space `id`. */
|
|
1426
|
+
set(id, enclosure) {
|
|
1427
|
+
if (!enclosure) {
|
|
1428
|
+
if (this.spaces.delete(id)) this.list = [...this.spaces.values()];
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
const [hx, hy, hz] = enclosure.halfSize;
|
|
1432
|
+
const space = {
|
|
1433
|
+
cx: enclosure.center[0],
|
|
1434
|
+
cy: enclosure.center[1],
|
|
1435
|
+
cz: enclosure.center[2],
|
|
1436
|
+
hx: Math.abs(hx),
|
|
1437
|
+
hy: Math.abs(hy),
|
|
1438
|
+
hz: Math.abs(hz),
|
|
1439
|
+
r: rotation(enclosure.orientation),
|
|
1440
|
+
radius: Math.hypot(hx, hy, hz),
|
|
1441
|
+
openings: enclosure.openings ?? [],
|
|
1442
|
+
leak: Math.max(0, enclosure.leak ?? 0)
|
|
1443
|
+
};
|
|
1444
|
+
const old = this.spaces.get(id);
|
|
1445
|
+
this.spaces.set(id, space);
|
|
1446
|
+
const at3 = old ? this.list.indexOf(old) : -1;
|
|
1447
|
+
if (at3 >= 0) this.list[at3] = space;
|
|
1448
|
+
else this.list.push(space);
|
|
1449
|
+
}
|
|
1450
|
+
get(id) {
|
|
1451
|
+
const s = this.spaces.get(id);
|
|
1452
|
+
if (!s) return void 0;
|
|
1453
|
+
return { center: [s.cx, s.cy, s.cz], halfSize: [s.hx, s.hy, s.hz], openings: s.openings, leak: s.leak };
|
|
1454
|
+
}
|
|
1455
|
+
ids() {
|
|
1456
|
+
return [...this.spaces.keys()];
|
|
1457
|
+
}
|
|
1458
|
+
clear() {
|
|
1459
|
+
this.spaces.clear();
|
|
1460
|
+
this.list = [];
|
|
1461
|
+
}
|
|
1462
|
+
/** Whether a point is inside any space (on the inside of its walls). */
|
|
1463
|
+
inside(x, y, z) {
|
|
1464
|
+
for (const s of this.list) {
|
|
1465
|
+
this.toLocal(s, x, y, z, this.l0);
|
|
1466
|
+
if (Math.abs(this.l0[0]) <= s.hx && Math.abs(this.l0[1]) <= s.hy && Math.abs(this.l0[2]) <= s.hz) return true;
|
|
1467
|
+
}
|
|
1468
|
+
return false;
|
|
1469
|
+
}
|
|
1470
|
+
toLocal(s, x, y, z, out) {
|
|
1471
|
+
const dx = x - s.cx;
|
|
1472
|
+
const dy = y - s.cy;
|
|
1473
|
+
const dz = z - s.cz;
|
|
1474
|
+
const r = s.r;
|
|
1475
|
+
out[0] = r[0] * dx + r[3] * dy + r[6] * dz;
|
|
1476
|
+
out[1] = r[1] * dx + r[4] * dy + r[7] * dz;
|
|
1477
|
+
out[2] = r[2] * dx + r[5] * dy + r[8] * dz;
|
|
1478
|
+
}
|
|
1479
|
+
/** Passes at `rate` per second. */
|
|
1480
|
+
chance(rate, dt) {
|
|
1481
|
+
return rate > 0 && this.random.next() < 1 - Math.exp(-rate * dt);
|
|
1482
|
+
}
|
|
1483
|
+
/** The opening the wall point `q` (local) is in lets it through now. */
|
|
1484
|
+
through(s, q, dt) {
|
|
1485
|
+
for (const o of s.openings) {
|
|
1486
|
+
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;
|
|
1487
|
+
if (o.rate === void 0 || this.chance(o.rate, dt)) return true;
|
|
1488
|
+
}
|
|
1489
|
+
return this.chance(s.leak, dt);
|
|
1490
|
+
}
|
|
1491
|
+
confine(x0, y0, z0, x1, y1, z1, pad, dt) {
|
|
1492
|
+
const list = this.list;
|
|
1493
|
+
if (list.length === 0) return false;
|
|
1494
|
+
const p = Math.min(Math.max(0, pad), MAX_PAD);
|
|
1495
|
+
const l0 = this.l0;
|
|
1496
|
+
const l1 = this.l1;
|
|
1497
|
+
const q = this.q;
|
|
1498
|
+
for (const s of list) {
|
|
1499
|
+
const reach = s.radius + p + 0.05;
|
|
1500
|
+
const dx = x1 - s.cx;
|
|
1501
|
+
const dy = y1 - s.cy;
|
|
1502
|
+
const dz = z1 - s.cz;
|
|
1503
|
+
if (dx * dx + dy * dy + dz * dz > reach * reach) {
|
|
1504
|
+
const ex = x0 - s.cx;
|
|
1505
|
+
const ey = y0 - s.cy;
|
|
1506
|
+
const ez = z0 - s.cz;
|
|
1507
|
+
if (ex * ex + ey * ey + ez * ez > reach * reach) continue;
|
|
1508
|
+
}
|
|
1509
|
+
this.toLocal(s, x0, y0, z0, l0);
|
|
1510
|
+
this.toLocal(s, x1, y1, z1, l1);
|
|
1511
|
+
const h = [s.hx, s.hy, s.hz];
|
|
1512
|
+
const in0 = Math.abs(l0[0]) <= s.hx && Math.abs(l0[1]) <= s.hy && Math.abs(l0[2]) <= s.hz;
|
|
1513
|
+
let axis2 = 0;
|
|
1514
|
+
let sign = 1;
|
|
1515
|
+
if (in0) {
|
|
1516
|
+
if (Math.abs(l1[0]) <= s.hx && Math.abs(l1[1]) <= s.hy && Math.abs(l1[2]) <= s.hz) continue;
|
|
1517
|
+
for (let a = 0; a < 3; a++) q[a] = Math.min(h[a], Math.max(-h[a], l1[a]));
|
|
1518
|
+
if (this.through(s, q, dt)) continue;
|
|
1519
|
+
let worst = -Infinity;
|
|
1520
|
+
for (let a = 0; a < 3; a++) {
|
|
1521
|
+
const over = Math.abs(l1[a]) - h[a];
|
|
1522
|
+
if (over > worst) {
|
|
1523
|
+
worst = over;
|
|
1524
|
+
axis2 = a;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
sign = l1[axis2] < 0 ? -1 : 1;
|
|
1528
|
+
for (let a = 0; a < 3; a++) l1[a] = Math.min(h[a] - SKIN, Math.max(-(h[a] - SKIN), l1[a]));
|
|
1529
|
+
sign = -sign;
|
|
1530
|
+
} else {
|
|
1531
|
+
const padded0 = Math.abs(l0[0]) <= s.hx + p && Math.abs(l0[1]) <= s.hy + p && Math.abs(l0[2]) <= s.hz + p;
|
|
1532
|
+
const bound = padded0 ? 0 : p;
|
|
1533
|
+
if (!(Math.abs(l1[0]) <= s.hx + bound && Math.abs(l1[1]) <= s.hy + bound && Math.abs(l1[2]) <= s.hz + bound)) continue;
|
|
1534
|
+
for (let a = 0; a < 3; a++) q[a] = Math.min(h[a], Math.max(-h[a], l1[a]));
|
|
1535
|
+
if (this.through(s, q, dt)) continue;
|
|
1536
|
+
let worst = -Infinity;
|
|
1537
|
+
for (let a = 0; a < 3; a++) {
|
|
1538
|
+
const over = Math.abs(l0[a]) - (h[a] + bound);
|
|
1539
|
+
if (over > worst) {
|
|
1540
|
+
worst = over;
|
|
1541
|
+
axis2 = a;
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
sign = l0[axis2] < 0 ? -1 : 1;
|
|
1545
|
+
l1[axis2] = sign * (h[axis2] + bound + SKIN);
|
|
1546
|
+
}
|
|
1547
|
+
const r = s.r;
|
|
1548
|
+
const out = this.out;
|
|
1549
|
+
out.x = s.cx + r[0] * l1[0] + r[1] * l1[1] + r[2] * l1[2];
|
|
1550
|
+
out.y = s.cy + r[3] * l1[0] + r[4] * l1[1] + r[5] * l1[2];
|
|
1551
|
+
out.z = s.cz + r[6] * l1[0] + r[7] * l1[1] + r[8] * l1[2];
|
|
1552
|
+
out.nx = r[axis2] * sign;
|
|
1553
|
+
out.ny = r[3 + axis2] * sign;
|
|
1554
|
+
out.nz = r[6 + axis2] * sign;
|
|
1555
|
+
return true;
|
|
1556
|
+
}
|
|
1557
|
+
return false;
|
|
1558
|
+
}
|
|
1559
|
+
};
|
|
1560
|
+
function slideAlong(out, vx2, vy2, vz2) {
|
|
1561
|
+
const into = vx2 * out.nx + vy2 * out.ny + vz2 * out.nz;
|
|
1562
|
+
if (into >= 0) return [vx2, vy2, vz2];
|
|
1563
|
+
return [vx2 - into * out.nx, vy2 - into * out.ny, vz2 - into * out.nz];
|
|
1564
|
+
}
|
|
1347
1565
|
var FLOOR_FRACTION = 0.4;
|
|
1348
1566
|
var FLOOR_BOUNCE = -0.2;
|
|
1349
1567
|
var ARRIVAL_FRAME = 30;
|
|
1350
1568
|
var ARRIVAL_BASE = 0.3;
|
|
1351
1569
|
var GROWTH_CAP_SECONDS = 3.5;
|
|
1570
|
+
var CARRIED_KINDS = [true, true, false, true, false];
|
|
1571
|
+
var PAD_OF_SIZE = 0.35;
|
|
1352
1572
|
function createPuffPool(capacity) {
|
|
1353
1573
|
const placeholder = { next: () => 0, between: (min2) => min2, direction: (out) => out, state: 0 };
|
|
1354
1574
|
return new OrderedPool(capacity, () => ({
|
|
@@ -1443,13 +1663,24 @@ var GameVfxThree = (() => {
|
|
|
1443
1663
|
if (puff3.vy < 0) puff3.vy *= FLOOR_BOUNCE;
|
|
1444
1664
|
}
|
|
1445
1665
|
}
|
|
1446
|
-
function updatePuffs(pool, dt, wind) {
|
|
1666
|
+
function updatePuffs(pool, dt, wind, confiner) {
|
|
1447
1667
|
pool.compact(ageOrKeep2, dt);
|
|
1668
|
+
const confining = confiner !== void 0 && !confiner.empty;
|
|
1448
1669
|
for (const puff3 of pool.items) {
|
|
1449
1670
|
const age = puff3.t / puff3.life;
|
|
1450
1671
|
const heat = puff3.h0 > 0 ? puff3.h0 * Math.pow(Math.max(0, 1 - puff3.t / puff3.hl), 1.25) : 0;
|
|
1672
|
+
const x0 = puff3.x;
|
|
1673
|
+
const y0 = puff3.y;
|
|
1674
|
+
const z0 = puff3.z;
|
|
1451
1675
|
if (puff3.ring) moveOnRing(puff3, puff3.ring, wind);
|
|
1452
1676
|
else moveFree(puff3, dt, age, heat, wind);
|
|
1677
|
+
if (confining && CARRIED_KINDS[puff3.kind] && confiner.confine(x0, y0, z0, puff3.x, puff3.y, puff3.z, puff3.size * PAD_OF_SIZE, dt)) {
|
|
1678
|
+
const out = confiner.out;
|
|
1679
|
+
puff3.x = out.x;
|
|
1680
|
+
puff3.y = out.y;
|
|
1681
|
+
puff3.z = out.z;
|
|
1682
|
+
[puff3.vx, puff3.vy, puff3.vz] = slideAlong(out, puff3.vx, puff3.vy, puff3.vz);
|
|
1683
|
+
}
|
|
1453
1684
|
const size = puff3.s0 + (puff3.s1 - puff3.s0) * (1 - Math.exp(-puff3.t * puff3.gk)) + puff3.sg * Math.min(puff3.t, GROWTH_CAP_SECONDS);
|
|
1454
1685
|
puff3.size = size * Math.min(1, puff3.t * ARRIVAL_FRAME + ARRIVAL_BASE);
|
|
1455
1686
|
puff3.age = age;
|
|
@@ -1975,10 +2206,10 @@ var GameVfxThree = (() => {
|
|
|
1975
2206
|
])
|
|
1976
2207
|
]
|
|
1977
2208
|
};
|
|
1978
|
-
function localToWorld(translation,
|
|
2209
|
+
function localToWorld(translation, rotation2, local2) {
|
|
1979
2210
|
const [x, y, z] = local2;
|
|
1980
|
-
const [a, c, e] = [Math.cos(
|
|
1981
|
-
const [b, d, f] = [Math.sin(
|
|
2211
|
+
const [a, c, e] = [Math.cos(rotation2[0]), Math.cos(rotation2[1]), Math.cos(rotation2[2])];
|
|
2212
|
+
const [b, d, f] = [Math.sin(rotation2[0]), Math.sin(rotation2[1]), Math.sin(rotation2[2])];
|
|
1982
2213
|
const [ae, af, be, bf] = [a * e, a * f, b * e, b * f];
|
|
1983
2214
|
return [
|
|
1984
2215
|
c * e * x + -c * f * y + d * z + translation[0],
|
|
@@ -3641,46 +3872,6 @@ var GameVfxThree = (() => {
|
|
|
3641
3872
|
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];
|
|
3642
3873
|
var WORKSHOP_EFFECTS = Object.fromEntries(ALL_EFFECTS.map((effect) => [effect.id, effect]));
|
|
3643
3874
|
var GAME_EFFECTS = { ...WORKSHOP_EFFECTS, ...Object.fromEntries([...EVENT_EFFECTS, ...VIEWMODEL_EFFECTS, ...VEHICLE_EFFECTS].map((effect) => [effect.id, effect])) };
|
|
3644
|
-
var LEHMER_MULTIPLIER = 16807;
|
|
3645
|
-
var LEHMER_MODULUS = 2147483647;
|
|
3646
|
-
var HASH_MIX = 2654435761;
|
|
3647
|
-
function normalizeLehmerSeed(seed) {
|
|
3648
|
-
const whole = Math.floor(Math.abs(seed)) % (LEHMER_MODULUS - 1);
|
|
3649
|
-
return whole === 0 ? 1 : whole;
|
|
3650
|
-
}
|
|
3651
|
-
function lehmer(seed) {
|
|
3652
|
-
let state = normalizeLehmerSeed(seed);
|
|
3653
|
-
const next = () => {
|
|
3654
|
-
state = state * LEHMER_MULTIPLIER % LEHMER_MODULUS;
|
|
3655
|
-
return (state - 1) / (LEHMER_MODULUS - 1);
|
|
3656
|
-
};
|
|
3657
|
-
return {
|
|
3658
|
-
next,
|
|
3659
|
-
between: (min2, max2) => min2 + (max2 - min2) * next(),
|
|
3660
|
-
direction(out) {
|
|
3661
|
-
const up = next() * 2 - 1;
|
|
3662
|
-
const angle = next() * Math.PI * 2;
|
|
3663
|
-
const ring2 = Math.sqrt(1 - up * up);
|
|
3664
|
-
out.x = ring2 * Math.cos(angle);
|
|
3665
|
-
out.y = up;
|
|
3666
|
-
out.z = ring2 * Math.sin(angle);
|
|
3667
|
-
return out;
|
|
3668
|
-
},
|
|
3669
|
-
get state() {
|
|
3670
|
-
return state;
|
|
3671
|
-
}
|
|
3672
|
-
};
|
|
3673
|
-
}
|
|
3674
|
-
function mixEventSeed(eventSeed) {
|
|
3675
|
-
let mixed = Math.imul((eventSeed | 0) ^ HASH_MIX, 2246822507);
|
|
3676
|
-
mixed ^= mixed >>> 13;
|
|
3677
|
-
mixed = Math.imul(mixed, 3266489909);
|
|
3678
|
-
mixed ^= mixed >>> 16;
|
|
3679
|
-
return (mixed >>> 0) % (LEHMER_MODULUS - 1) + 1;
|
|
3680
|
-
}
|
|
3681
|
-
function streamFor(eventSeed) {
|
|
3682
|
-
return lehmer(mixEventSeed(eventSeed));
|
|
3683
|
-
}
|
|
3684
3875
|
var TABLE = {
|
|
3685
3876
|
concrete: { dust: 9078138, chip: 7236195, spark: 2 },
|
|
3686
3877
|
metal: { dust: 0, chip: 0, spark: 18 },
|
|
@@ -4984,8 +5175,8 @@ var GameVfxThree = (() => {
|
|
|
4984
5175
|
function addScorch(store, random, x, z, radius) {
|
|
4985
5176
|
const variant = Math.floor(random.next() * SCORCH_VARIANTS);
|
|
4986
5177
|
const lift = 0.03 + nextLift(store) * 1e-3;
|
|
4987
|
-
const
|
|
4988
|
-
store.add({ kind: "scorch", x, z, radius, variant, lift, rotation });
|
|
5178
|
+
const rotation2 = random.next() * 6;
|
|
5179
|
+
store.add({ kind: "scorch", x, z, radius, variant, lift, rotation: rotation2 });
|
|
4989
5180
|
}
|
|
4990
5181
|
function addRimRocks(store, ground, random, x, z, radius) {
|
|
4991
5182
|
const count = Math.round(6 + radius * 7);
|
|
@@ -5014,8 +5205,8 @@ var GameVfxThree = (() => {
|
|
|
5014
5205
|
function addCrater(store, ground, random, spec) {
|
|
5015
5206
|
ground?.dent(random, spec.x, spec.z, spec.radius, spec.depth);
|
|
5016
5207
|
if (!spec.marks) return;
|
|
5017
|
-
const
|
|
5018
|
-
store.add({ kind: "dirt", x: spec.x, z: spec.z, radius: spec.radius * 1.02, lift: 0.045, rotation });
|
|
5208
|
+
const rotation2 = random.next() * 6;
|
|
5209
|
+
store.add({ kind: "dirt", x: spec.x, z: spec.z, radius: spec.radius * 1.02, lift: 0.045, rotation: rotation2 });
|
|
5019
5210
|
addRimRocks(store, ground, random, spec.x, spec.z, spec.radius);
|
|
5020
5211
|
}
|
|
5021
5212
|
function addBloodSplat(store, ground, random, spec) {
|
|
@@ -5571,6 +5762,7 @@ var GameVfxThree = (() => {
|
|
|
5571
5762
|
state.gas = Math.max(0, state.gas - dt / DECAY.gasSeconds);
|
|
5572
5763
|
state.beat *= Math.exp(-DECAY.beat * dt);
|
|
5573
5764
|
}
|
|
5765
|
+
var PAD_OF_SIZE2 = 0.35;
|
|
5574
5766
|
var clamp4 = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
|
|
5575
5767
|
var lerp4 = (a, b, t) => a + (b - a) * t;
|
|
5576
5768
|
function rgb(hex) {
|
|
@@ -5730,9 +5922,20 @@ var GameVfxThree = (() => {
|
|
|
5730
5922
|
vy2 *= k;
|
|
5731
5923
|
vz2 *= k;
|
|
5732
5924
|
}
|
|
5733
|
-
|
|
5925
|
+
let x = this.position[i3] + vx2 * dt;
|
|
5734
5926
|
let y = this.position[i3 + 1] + vy2 * dt;
|
|
5735
|
-
|
|
5927
|
+
let z = this.position[i3 + 2] + vz2 * dt;
|
|
5928
|
+
const confiner = host.confiner;
|
|
5929
|
+
if (def.carried && confiner && !confiner.empty) {
|
|
5930
|
+
const pad = this.size0[i] * lerp4(1, def.grow, t) * PAD_OF_SIZE2;
|
|
5931
|
+
if (confiner.confine(this.position[i3], this.position[i3 + 1], this.position[i3 + 2], x, y, z, pad, dt)) {
|
|
5932
|
+
const out = confiner.out;
|
|
5933
|
+
x = out.x;
|
|
5934
|
+
y = out.y;
|
|
5935
|
+
z = out.z;
|
|
5936
|
+
[vx2, vy2, vz2] = slideAlong(out, vx2, vy2, vz2);
|
|
5937
|
+
}
|
|
5938
|
+
}
|
|
5736
5939
|
if (def.ground !== "none") {
|
|
5737
5940
|
const own = this.floor[i];
|
|
5738
5941
|
const gy = Number.isNaN(own) ? host.groundAt(x, z) + 5e-3 : own;
|
|
@@ -5818,7 +6021,8 @@ var GameVfxThree = (() => {
|
|
|
5818
6021
|
alpha: 0.35,
|
|
5819
6022
|
fadeIn: 0.15,
|
|
5820
6023
|
cone: 0.6,
|
|
5821
|
-
ground: "none"
|
|
6024
|
+
ground: "none",
|
|
6025
|
+
carried: true
|
|
5822
6026
|
},
|
|
5823
6027
|
sparks: {
|
|
5824
6028
|
id: "sparks",
|
|
@@ -5862,7 +6066,8 @@ var GameVfxThree = (() => {
|
|
|
5862
6066
|
alpha: 0.4,
|
|
5863
6067
|
fadeIn: 0.1,
|
|
5864
6068
|
cone: 0.4,
|
|
5865
|
-
ground: "none"
|
|
6069
|
+
ground: "none",
|
|
6070
|
+
carried: true
|
|
5866
6071
|
},
|
|
5867
6072
|
shards: {
|
|
5868
6073
|
id: "shards",
|
|
@@ -6020,7 +6225,8 @@ var GameVfxThree = (() => {
|
|
|
6020
6225
|
fadeIn: 0.1,
|
|
6021
6226
|
cone: 0.5,
|
|
6022
6227
|
ground: "stop",
|
|
6023
|
-
flutter: 0.6
|
|
6228
|
+
flutter: 0.6,
|
|
6229
|
+
carried: true
|
|
6024
6230
|
},
|
|
6025
6231
|
steam: {
|
|
6026
6232
|
id: "steam",
|
|
@@ -6042,7 +6248,8 @@ var GameVfxThree = (() => {
|
|
|
6042
6248
|
alpha: 0.35,
|
|
6043
6249
|
fadeIn: 0.15,
|
|
6044
6250
|
cone: 0.6,
|
|
6045
|
-
ground: "none"
|
|
6251
|
+
ground: "none",
|
|
6252
|
+
carried: true
|
|
6046
6253
|
},
|
|
6047
6254
|
flash: {
|
|
6048
6255
|
id: "flash",
|
|
@@ -6175,7 +6382,8 @@ var GameVfxThree = (() => {
|
|
|
6175
6382
|
const host = {
|
|
6176
6383
|
groundAt: this.options.groundAt,
|
|
6177
6384
|
gravity: this.options.gravity,
|
|
6178
|
-
land: (owner, x, y, z, color, tag) => this.listeners.get(owner)?.(x, y, z, color, tag)
|
|
6385
|
+
land: (owner, x, y, z, color, tag) => this.listeners.get(owner)?.(x, y, z, color, tag),
|
|
6386
|
+
...this.options.confiner ? { confiner: this.options.confiner } : {}
|
|
6179
6387
|
};
|
|
6180
6388
|
if (this.additive.live > 0 || this.additive.drawCount > 0) this.additive.update(dt, host);
|
|
6181
6389
|
if (this.normal.live > 0 || this.normal.drawCount > 0) this.normal.update(dt, host);
|
|
@@ -6381,8 +6589,8 @@ var GameVfxThree = (() => {
|
|
|
6381
6589
|
* 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
|
|
6382
6590
|
* world axes). The surface they were on is gone (a brick came out of a wall with its bullet holes). Returns how many went.
|
|
6383
6591
|
*/
|
|
6384
|
-
clearInside(center, half,
|
|
6385
|
-
const qx = -
|
|
6592
|
+
clearInside(center, half, rotation2 = [0, 0, 0, 1]) {
|
|
6593
|
+
const qx = -rotation2[0], qy = -rotation2[1], qz = -rotation2[2], qw = rotation2[3];
|
|
6386
6594
|
let gone = 0;
|
|
6387
6595
|
for (const decal2 of this.decals) {
|
|
6388
6596
|
if (!decal2.on) continue;
|
|
@@ -6503,10 +6711,14 @@ var GameVfxThree = (() => {
|
|
|
6503
6711
|
var DEFAULT_FADE_IN = 0.1;
|
|
6504
6712
|
var FLOATS_PER_VECTOR = 3;
|
|
6505
6713
|
var BYTE_MAX2 = 255;
|
|
6714
|
+
var CARRIED_GRAVITY = 3;
|
|
6715
|
+
var PAD_OF_SIZE3 = 0.35;
|
|
6506
6716
|
var SpritePool = class {
|
|
6507
|
-
|
|
6717
|
+
/** `carried`: the normal-blend pool (smoke, dust, steam), whose light sprites go round the closed spaces; the additive one (flames, sparks) does not. */
|
|
6718
|
+
constructor(capacity, context, carried = false) {
|
|
6508
6719
|
__publicField(this, "capacity", capacity);
|
|
6509
6720
|
__publicField(this, "context", context);
|
|
6721
|
+
__publicField(this, "carried", carried);
|
|
6510
6722
|
__publicField(this, "count", 0);
|
|
6511
6723
|
__publicField(this, "position");
|
|
6512
6724
|
__publicField(this, "velocity");
|
|
@@ -6606,6 +6818,7 @@ var GameVfxThree = (() => {
|
|
|
6606
6818
|
}
|
|
6607
6819
|
update(dt, leaveDecal) {
|
|
6608
6820
|
const { position, velocity } = this;
|
|
6821
|
+
const confiner = this.context.confiner;
|
|
6609
6822
|
for (let index = this.count - 1; index >= 0; index--) {
|
|
6610
6823
|
this.life[index] -= dt;
|
|
6611
6824
|
if (this.life[index] <= 0) {
|
|
@@ -6619,9 +6832,25 @@ var GameVfxThree = (() => {
|
|
|
6619
6832
|
velocity[offset] *= factor;
|
|
6620
6833
|
velocity[offset + 1] *= factor;
|
|
6621
6834
|
velocity[offset + 2] *= factor;
|
|
6835
|
+
const x0 = position[offset];
|
|
6836
|
+
const y0 = position[offset + 1];
|
|
6837
|
+
const z0 = position[offset + 2];
|
|
6622
6838
|
position[offset] += velocity[offset] * dt;
|
|
6623
6839
|
position[offset + 1] += velocity[offset + 1] * dt;
|
|
6624
6840
|
position[offset + 2] += velocity[offset + 2] * dt;
|
|
6841
|
+
if (this.carried && confiner && !confiner.empty && Math.abs(this.gravity[index]) < CARRIED_GRAVITY) {
|
|
6842
|
+
const size = this.startSize[index] * (1 + (this.growth[index] - 1) * age);
|
|
6843
|
+
if (confiner.confine(x0, y0, z0, position[offset], position[offset + 1], position[offset + 2], size * PAD_OF_SIZE3, dt)) {
|
|
6844
|
+
const out = confiner.out;
|
|
6845
|
+
position[offset] = out.x;
|
|
6846
|
+
position[offset + 1] = out.y;
|
|
6847
|
+
position[offset + 2] = out.z;
|
|
6848
|
+
const [vx2, vy2, vz2] = slideAlong(out, velocity[offset], velocity[offset + 1], velocity[offset + 2]);
|
|
6849
|
+
velocity[offset] = vx2;
|
|
6850
|
+
velocity[offset + 1] = vy2;
|
|
6851
|
+
velocity[offset + 2] = vz2;
|
|
6852
|
+
}
|
|
6853
|
+
}
|
|
6625
6854
|
const mode = this.groundMode[index];
|
|
6626
6855
|
if (mode) {
|
|
6627
6856
|
const groundHeight = this.context.groundAt(position[offset], position[offset + 2]);
|
|
@@ -6898,9 +7127,9 @@ var GameVfxThree = (() => {
|
|
|
6898
7127
|
__publicField(this, "placement");
|
|
6899
7128
|
__publicField(this, "accumulators", /* @__PURE__ */ new Map());
|
|
6900
7129
|
const profile = options.profile ?? PROFILES.high;
|
|
6901
|
-
this.context = { random: options.random, groundAt: options.groundAt ?? workshopGround, quality: { level: 0, mult: 1 }, trace: options.trace };
|
|
7130
|
+
this.context = { random: options.random, groundAt: options.groundAt ?? workshopGround, quality: { level: 0, mult: 1 }, trace: options.trace, ...options.confiner ? { confiner: options.confiner } : {} };
|
|
6902
7131
|
this.add = new SpritePool(profile.sprites.add, this.context);
|
|
6903
|
-
this.normal = new SpritePool(profile.sprites.normal, this.context);
|
|
7132
|
+
this.normal = new SpritePool(profile.sprites.normal, this.context, true);
|
|
6904
7133
|
this.streaks = new StreakPool(profile.streaks, this.context);
|
|
6905
7134
|
this.debris = new DebrisPool(this.context);
|
|
6906
7135
|
this.decals = new DecalPool(this.context);
|
|
@@ -7088,12 +7317,14 @@ var GameVfxThree = (() => {
|
|
|
7088
7317
|
const groundAt = options.groundAt ?? (ground ? (x, z) => ground.heightAt(x, z) : () => 0);
|
|
7089
7318
|
const handles = { next: 0, events: 0 };
|
|
7090
7319
|
const low = options.profile === "low";
|
|
7320
|
+
const enclosures = new Enclosures(20973 + (options.seed ?? 0));
|
|
7091
7321
|
const spriteSystem = new SpriteKindSystem({
|
|
7092
7322
|
capacity: options.sprites?.capacity ?? (low ? 1250 : 2500),
|
|
7093
7323
|
density: options.sprites?.density ?? (low ? 0.6 : 1),
|
|
7094
7324
|
seed: 2654435761 + (options.seed ?? 0),
|
|
7095
7325
|
groundAt,
|
|
7096
7326
|
gravity: options.gravity ?? DEFAULT_GRAVITY,
|
|
7327
|
+
confiner: enclosures,
|
|
7097
7328
|
...options.sprites?.kinds ? { kinds: options.sprites.kinds } : {}
|
|
7098
7329
|
});
|
|
7099
7330
|
const core = {
|
|
@@ -7143,7 +7374,7 @@ var GameVfxThree = (() => {
|
|
|
7143
7374
|
return [camera2.x, camera2.y, camera2.z];
|
|
7144
7375
|
}, forward: (local2) => [local2[0] ?? 0, local2[1] ?? 0, local2[2] ?? 0] };
|
|
7145
7376
|
function workshop() {
|
|
7146
|
-
return engine ?? (engine = new WorkshopEngine({ random: sharedRandom, profile, groundAt }));
|
|
7377
|
+
return engine ?? (engine = new WorkshopEngine({ random: sharedRandom, profile, groundAt, confiner: enclosures }));
|
|
7147
7378
|
}
|
|
7148
7379
|
function effectPlayer() {
|
|
7149
7380
|
return player ?? (player = createEffectPlayer({ engine: workshop(), effects, view }));
|
|
@@ -7202,6 +7433,10 @@ var GameVfxThree = (() => {
|
|
|
7202
7433
|
puff3.vz += dz * strength;
|
|
7203
7434
|
}
|
|
7204
7435
|
},
|
|
7436
|
+
enclose(id, enclosure) {
|
|
7437
|
+
enclosures.set(id, enclosure);
|
|
7438
|
+
},
|
|
7439
|
+
enclosures: () => enclosures,
|
|
7205
7440
|
stop(handle) {
|
|
7206
7441
|
player?.stop(handle);
|
|
7207
7442
|
const fires = core.fires;
|
|
@@ -7229,7 +7464,7 @@ var GameVfxThree = (() => {
|
|
|
7229
7464
|
core.clock.time += dt;
|
|
7230
7465
|
core.schedule.update(core.clock.time);
|
|
7231
7466
|
updateRings(core.rings, dt);
|
|
7232
|
-
updatePuffs(core.puffs, dt, wind);
|
|
7467
|
+
updatePuffs(core.puffs, dt, wind, enclosures);
|
|
7233
7468
|
updateSparks(core.sparks, { dt, groundAt });
|
|
7234
7469
|
updateGlows(core.glows, dt);
|
|
7235
7470
|
updateShocks(core.shocks, core.puffs.items, dt);
|
|
@@ -8523,6 +8758,11 @@ void main(){
|
|
|
8523
8758
|
var MAX_CONFORM_STEPS = 48;
|
|
8524
8759
|
var MIN_CONFORM_STEPS = 16;
|
|
8525
8760
|
var BLOOD_GROW_RATE = 1.2;
|
|
8761
|
+
var BLOOD_START_GROWTH = 0.05;
|
|
8762
|
+
var DEFAULT_MAX_GROUND_MARKS = 64;
|
|
8763
|
+
var DEFAULT_MAX_ROCKS = 512;
|
|
8764
|
+
var DEFAULT_MAX_BLOOD = 256;
|
|
8765
|
+
var MAX_SPARE_GROUND_GEOMETRIES = 16;
|
|
8526
8766
|
function createMarkLayer(THREE, options) {
|
|
8527
8767
|
const group = new THREE.Group();
|
|
8528
8768
|
const textures = createMarkTextures(THREE, options.createCanvas);
|
|
@@ -8546,92 +8786,146 @@ void main(){
|
|
|
8546
8786
|
const bloodMaterial = new THREE.MeshStandardMaterial({ color: 6031882, roughness: 0.15, metalness: 0.1, polygonOffset: true, polygonOffsetFactor: -3, polygonOffsetUnits: -3 });
|
|
8547
8787
|
const bloodGeometry = new THREE.CircleGeometry(0.5, 9).rotateX(-Math.PI / 2);
|
|
8548
8788
|
owned.push(bloodMaterial, bloodGeometry);
|
|
8549
|
-
const
|
|
8550
|
-
const
|
|
8551
|
-
|
|
8552
|
-
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
material = new THREE.MeshStandardMaterial({ color, roughness: 0.9, metalness: 0, flatShading: true });
|
|
8556
|
-
rockMaterials.set(color, material);
|
|
8557
|
-
owned.push(material);
|
|
8789
|
+
const maxGroundMarks = options.maxGroundMarks ?? DEFAULT_MAX_GROUND_MARKS;
|
|
8790
|
+
const instanced = {};
|
|
8791
|
+
function rockMaterial() {
|
|
8792
|
+
if (!instanced.rockMaterial) {
|
|
8793
|
+
instanced.rockMaterial = new THREE.MeshStandardMaterial({ color: 16777215, roughness: 0.9, metalness: 0, flatShading: true });
|
|
8794
|
+
owned.push(instanced.rockMaterial);
|
|
8558
8795
|
}
|
|
8559
|
-
return
|
|
8796
|
+
return instanced.rockMaterial;
|
|
8797
|
+
}
|
|
8798
|
+
function bloodMesh() {
|
|
8799
|
+
return instanced.blood ?? (instanced.blood = attach(createInstanced(bloodGeometry, bloodMaterial, options.maxBlood ?? DEFAULT_MAX_BLOOD)));
|
|
8800
|
+
}
|
|
8801
|
+
function rockMesh() {
|
|
8802
|
+
return instanced.rock ?? (instanced.rock = attach(createInstanced(options.rockGeometry, rockMaterial(), options.maxRocks ?? DEFAULT_MAX_ROCKS), true));
|
|
8803
|
+
}
|
|
8804
|
+
function plateMesh() {
|
|
8805
|
+
return instanced.plate ?? (instanced.plate = attach(createInstanced(options.plateGeometry, rockMaterial(), options.maxRocks ?? DEFAULT_MAX_ROCKS), true));
|
|
8806
|
+
}
|
|
8807
|
+
function attach(mesh2, castShadow = false) {
|
|
8808
|
+
mesh2.castShadow = castShadow;
|
|
8809
|
+
group.add(mesh2);
|
|
8810
|
+
return mesh2;
|
|
8811
|
+
}
|
|
8812
|
+
const groundMarks = [];
|
|
8813
|
+
const spareGeometries = [];
|
|
8814
|
+
const blood = [];
|
|
8815
|
+
const placed = { blood: 0, rock: 0, plate: 0 };
|
|
8816
|
+
const matrix = new THREE.Matrix4();
|
|
8817
|
+
const position = new THREE.Vector3();
|
|
8818
|
+
const quaternion = new THREE.Quaternion();
|
|
8819
|
+
const euler = new THREE.Euler();
|
|
8820
|
+
const scale = new THREE.Vector3();
|
|
8821
|
+
const color = new THREE.Color();
|
|
8822
|
+
const up = new THREE.Vector3(0, 1, 0);
|
|
8823
|
+
function createInstanced(geometry, material, capacity) {
|
|
8824
|
+
const mesh2 = new THREE.InstancedMesh(geometry, material, capacity);
|
|
8825
|
+
mesh2.count = 0;
|
|
8826
|
+
mesh2.frustumCulled = false;
|
|
8827
|
+
mesh2.receiveShadow = true;
|
|
8828
|
+
return mesh2;
|
|
8829
|
+
}
|
|
8830
|
+
function takeSpare(vertexCount) {
|
|
8831
|
+
const found = spareGeometries.findIndex((geometry) => geometry.getAttribute("position").count === vertexCount);
|
|
8832
|
+
return found < 0 ? void 0 : spareGeometries.splice(found, 1)[0];
|
|
8833
|
+
}
|
|
8834
|
+
function evictOldestGroundMark() {
|
|
8835
|
+
const oldest = groundMarks.shift();
|
|
8836
|
+
group.remove(oldest.mesh);
|
|
8837
|
+
spareGeometries.push(oldest.mesh.geometry);
|
|
8838
|
+
if (spareGeometries.length > MAX_SPARE_GROUND_GEOMETRIES) spareGeometries.shift().dispose();
|
|
8560
8839
|
}
|
|
8561
8840
|
function conform(mark, material) {
|
|
8562
8841
|
const radius = mark.radius;
|
|
8563
8842
|
const steps = Math.max(MIN_CONFORM_STEPS, Math.min(MAX_CONFORM_STEPS, Math.round(radius * 8)));
|
|
8564
|
-
|
|
8565
|
-
const
|
|
8843
|
+
if (groundMarks.length >= maxGroundMarks) evictOldestGroundMark();
|
|
8844
|
+
const geometry = takeSpare((steps + 1) * (steps + 1)) ?? new THREE.PlaneGeometry(2, 2, steps, steps).rotateX(-Math.PI / 2);
|
|
8845
|
+
const grid = geometry.attributes.position;
|
|
8846
|
+
const cell = 2 / steps;
|
|
8566
8847
|
const cos2 = Math.cos(mark.rotation);
|
|
8567
8848
|
const sin2 = Math.sin(mark.rotation);
|
|
8568
|
-
for (let index = 0; index <
|
|
8569
|
-
const localX =
|
|
8570
|
-
const localZ =
|
|
8849
|
+
for (let index = 0; index < grid.count; index++) {
|
|
8850
|
+
const localX = Math.fround(index % (steps + 1) * cell - 1) * radius;
|
|
8851
|
+
const localZ = Math.fround(Math.floor(index / (steps + 1)) * cell - 1) * radius;
|
|
8571
8852
|
const worldX = mark.x + localX * cos2 - localZ * sin2;
|
|
8572
8853
|
const worldZ = mark.z + localX * sin2 + localZ * cos2;
|
|
8573
|
-
|
|
8854
|
+
grid.setXYZ(index, worldX, (options.ground?.heightAt(worldX, worldZ) ?? 0) + mark.lift, worldZ);
|
|
8574
8855
|
}
|
|
8856
|
+
grid.needsUpdate = true;
|
|
8575
8857
|
geometry.computeVertexNormals();
|
|
8858
|
+
geometry.attributes.normal.needsUpdate = true;
|
|
8859
|
+
geometry.boundingSphere = null;
|
|
8860
|
+
geometry.boundingBox = null;
|
|
8576
8861
|
const mesh2 = new THREE.Mesh(geometry, material);
|
|
8577
8862
|
mesh2.receiveShadow = true;
|
|
8578
|
-
|
|
8863
|
+
group.add(mesh2);
|
|
8864
|
+
groundMarks.push({ mesh: mesh2, steps });
|
|
8579
8865
|
}
|
|
8580
8866
|
function addRock(mark) {
|
|
8581
|
-
const mesh2 =
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
mesh2.
|
|
8586
|
-
mesh2.
|
|
8587
|
-
|
|
8867
|
+
const mesh2 = mark.plate ? plateMesh() : rockMesh();
|
|
8868
|
+
const slot = mark.plate ? "plate" : "rock";
|
|
8869
|
+
const index = placed[slot]++ % mesh2.instanceMatrix.count;
|
|
8870
|
+
quaternion.setFromEuler(euler.set(mark.rotation[0], mark.rotation[1], mark.rotation[2]));
|
|
8871
|
+
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])));
|
|
8872
|
+
mesh2.setColorAt(index, color.set(mark.color));
|
|
8873
|
+
mesh2.count = Math.min(placed[slot], mesh2.instanceMatrix.count);
|
|
8874
|
+
mesh2.instanceMatrix.needsUpdate = true;
|
|
8875
|
+
mesh2.instanceColor.needsUpdate = true;
|
|
8876
|
+
}
|
|
8877
|
+
function writeBlood(index, drawn, growth) {
|
|
8878
|
+
quaternion.setFromAxisAngle(up, drawn.rotation);
|
|
8879
|
+
const mesh2 = bloodMesh();
|
|
8880
|
+
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)));
|
|
8881
|
+
mesh2.instanceMatrix.needsUpdate = true;
|
|
8588
8882
|
}
|
|
8589
8883
|
function addBlood(mark) {
|
|
8590
|
-
const
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
mesh2.scale.multiplyScalar(0.05);
|
|
8596
|
-
blood.push({ mesh: mesh2, scaleX: mark.scaleX, scaleZ: mark.scaleZ, age: 0 });
|
|
8597
|
-
return mesh2;
|
|
8884
|
+
const index = placed.blood++ % bloodMesh().instanceMatrix.count;
|
|
8885
|
+
const drawn = { position: [mark.x, mark.y, mark.z], rotation: mark.rotation, scaleX: mark.scaleX, scaleZ: mark.scaleZ, age: 0 };
|
|
8886
|
+
blood[index] = drawn;
|
|
8887
|
+
writeBlood(index, drawn, BLOOD_START_GROWTH);
|
|
8888
|
+
bloodMesh().count = Math.min(placed.blood, bloodMesh().instanceMatrix.count);
|
|
8598
8889
|
}
|
|
8599
8890
|
function build(mark) {
|
|
8600
|
-
if (mark.kind === "scorch")
|
|
8601
|
-
if (mark.kind === "dirt")
|
|
8602
|
-
if (mark.kind === "rock")
|
|
8603
|
-
|
|
8604
|
-
}
|
|
8605
|
-
function
|
|
8606
|
-
for (const
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
|
|
8891
|
+
if (mark.kind === "scorch") conform(mark, scorchMaterials[mark.variant]);
|
|
8892
|
+
else if (mark.kind === "dirt") conform(mark, dirtMaterial);
|
|
8893
|
+
else if (mark.kind === "rock") addRock(mark);
|
|
8894
|
+
else addBlood(mark);
|
|
8895
|
+
}
|
|
8896
|
+
function clearMarks() {
|
|
8897
|
+
for (const drawn of groundMarks) group.remove(drawn.mesh);
|
|
8898
|
+
for (const drawn of groundMarks) drawn.mesh.geometry.dispose();
|
|
8899
|
+
groundMarks.length = 0;
|
|
8900
|
+
for (const geometry of spareGeometries) geometry.dispose();
|
|
8901
|
+
spareGeometries.length = 0;
|
|
8611
8902
|
blood.length = 0;
|
|
8612
|
-
|
|
8903
|
+
placed.blood = placed.rock = placed.plate = 0;
|
|
8904
|
+
for (const mesh2 of [instanced.blood, instanced.rock, instanced.plate]) if (mesh2) mesh2.count = 0;
|
|
8613
8905
|
}
|
|
8614
8906
|
return {
|
|
8615
8907
|
group,
|
|
8616
8908
|
handle(event) {
|
|
8617
|
-
if (event.type === "clear")
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8909
|
+
if (event.type === "clear") clearMarks();
|
|
8910
|
+
else build(event.mark);
|
|
8911
|
+
},
|
|
8912
|
+
prepare() {
|
|
8913
|
+
bloodMesh();
|
|
8914
|
+
rockMesh();
|
|
8915
|
+
plateMesh();
|
|
8623
8916
|
},
|
|
8624
8917
|
update(dt) {
|
|
8625
|
-
for (
|
|
8626
|
-
|
|
8918
|
+
for (let index = 0; index < blood.length; index++) {
|
|
8919
|
+
const drawn = blood[index];
|
|
8920
|
+
if (!drawn || drawn.age > 1) continue;
|
|
8627
8921
|
drawn.age += dt * BLOOD_GROW_RATE;
|
|
8628
|
-
|
|
8629
|
-
drawn.mesh.scale.set(drawn.scaleX * Math.max(0.05, ease3), 1, drawn.scaleZ * Math.max(0.05, ease3));
|
|
8922
|
+
writeBlood(index, drawn, Math.max(BLOOD_START_GROWTH, 1 - Math.pow(1 - Math.min(1, drawn.age), 2.5)));
|
|
8630
8923
|
}
|
|
8631
8924
|
},
|
|
8632
|
-
count: () =>
|
|
8925
|
+
count: () => groundMarks.length + [instanced.blood, instanced.rock, instanced.plate].filter((mesh2) => mesh2 && mesh2.count > 0).length,
|
|
8633
8926
|
dispose() {
|
|
8634
|
-
|
|
8927
|
+
clearMarks();
|
|
8928
|
+
for (const mesh2 of [instanced.blood, instanced.rock, instanced.plate]) mesh2?.dispose();
|
|
8635
8929
|
for (const item of owned) item.dispose();
|
|
8636
8930
|
}
|
|
8637
8931
|
};
|
|
@@ -8640,18 +8934,22 @@ void main(){
|
|
|
8640
8934
|
// src/mesh-layer.ts
|
|
8641
8935
|
var SPHERE_DEFAULT_WIDTH = 8;
|
|
8642
8936
|
var SPHERE_DEFAULT_HEIGHT = 6;
|
|
8937
|
+
var MAX_PARKED_MESHES_PER_KIND = 256;
|
|
8643
8938
|
function geometryOf(THREE, particle) {
|
|
8644
8939
|
switch (particle.shape) {
|
|
8645
8940
|
case "cone":
|
|
8646
|
-
return new THREE.ConeGeometry(
|
|
8941
|
+
return new THREE.ConeGeometry(1, 1, particle.segments || void 0, particle.segments2 || 1, false);
|
|
8647
8942
|
case "cone_open":
|
|
8648
|
-
return new THREE.ConeGeometry(
|
|
8943
|
+
return new THREE.ConeGeometry(1, 1, particle.segments || void 0, particle.segments2 || 1, true);
|
|
8649
8944
|
case "sphere":
|
|
8650
|
-
return new THREE.SphereGeometry(
|
|
8945
|
+
return new THREE.SphereGeometry(1, particle.segments || SPHERE_DEFAULT_WIDTH, particle.segments2 || SPHERE_DEFAULT_HEIGHT);
|
|
8651
8946
|
case "icosahedron":
|
|
8652
|
-
return new THREE.IcosahedronGeometry(
|
|
8947
|
+
return new THREE.IcosahedronGeometry(1, particle.segments);
|
|
8653
8948
|
}
|
|
8654
8949
|
}
|
|
8950
|
+
function isCone(particle) {
|
|
8951
|
+
return particle.shape === "cone" || particle.shape === "cone_open";
|
|
8952
|
+
}
|
|
8655
8953
|
function materialOf(THREE, particle) {
|
|
8656
8954
|
if (particle.lit) return new THREE.MeshStandardMaterial({ color: particle.color, roughness: 1, metalness: 0, transparent: true, opacity: particle.opacity, depthWrite: false });
|
|
8657
8955
|
return new THREE.MeshBasicMaterial({ color: particle.color, transparent: true, opacity: particle.opacity, depthWrite: false, ...particle.additive ? { blending: THREE.AdditiveBlending } : {} });
|
|
@@ -8662,51 +8960,82 @@ void main(){
|
|
|
8662
8960
|
(kind) => new THREE.Mesh(geometry, materialOf(THREE, { ...kind, color: 8421504, opacity: 0.5 }))
|
|
8663
8961
|
);
|
|
8664
8962
|
}
|
|
8963
|
+
function geometryKeyOf(particle) {
|
|
8964
|
+
return `${particle.shape}:${particle.segments}:${particle.segments2}`;
|
|
8965
|
+
}
|
|
8665
8966
|
function createMeshLayer(THREE, pool) {
|
|
8666
8967
|
const group = new THREE.Group();
|
|
8667
8968
|
group.name = "workshop-meshes";
|
|
8668
8969
|
const drawn = /* @__PURE__ */ new Map();
|
|
8970
|
+
const geometries = /* @__PURE__ */ new Map();
|
|
8971
|
+
const parked = /* @__PURE__ */ new Map();
|
|
8669
8972
|
const frame = new THREE.Quaternion();
|
|
8670
8973
|
const euler = new THREE.Euler();
|
|
8671
8974
|
const turn = new THREE.Quaternion();
|
|
8672
8975
|
const offset = new THREE.Vector3();
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
|
|
8976
|
+
let stamp = 0;
|
|
8977
|
+
function geometryFor(particle) {
|
|
8978
|
+
const key = geometryKeyOf(particle);
|
|
8979
|
+
let geometry = geometries.get(key);
|
|
8980
|
+
if (!geometry) {
|
|
8981
|
+
geometry = geometryOf(THREE, particle);
|
|
8982
|
+
geometries.set(key, geometry);
|
|
8983
|
+
}
|
|
8984
|
+
return geometry;
|
|
8985
|
+
}
|
|
8986
|
+
function acquire(particle) {
|
|
8987
|
+
const parkingKey = `${geometryKeyOf(particle)}|${particle.lit}|${particle.additive}`;
|
|
8988
|
+
const mesh2 = parked.get(parkingKey)?.pop() ?? new THREE.Mesh(geometryFor(particle), materialOf(THREE, particle));
|
|
8989
|
+
mesh2.material.color.set(particle.color);
|
|
8990
|
+
mesh2.frustumCulled = false;
|
|
8991
|
+
mesh2.renderOrder = particle.additive ? 5 : 3;
|
|
8992
|
+
group.add(mesh2);
|
|
8993
|
+
return { mesh: mesh2, parkingKey, stamp };
|
|
8994
|
+
}
|
|
8995
|
+
function park(entry) {
|
|
8996
|
+
entry.mesh.removeFromParent();
|
|
8997
|
+
let free = parked.get(entry.parkingKey);
|
|
8998
|
+
if (!free) {
|
|
8999
|
+
free = [];
|
|
9000
|
+
parked.set(entry.parkingKey, free);
|
|
9001
|
+
}
|
|
9002
|
+
if (free.length < MAX_PARKED_MESHES_PER_KIND) free.push(entry.mesh);
|
|
9003
|
+
else entry.mesh.material.dispose();
|
|
8677
9004
|
}
|
|
8678
9005
|
return {
|
|
8679
9006
|
group,
|
|
8680
9007
|
sync() {
|
|
8681
|
-
|
|
9008
|
+
stamp++;
|
|
8682
9009
|
for (const particle of pool.particles) {
|
|
8683
|
-
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
mesh2.frustumCulled = false;
|
|
8688
|
-
mesh2.renderOrder = particle.additive ? 5 : 3;
|
|
8689
|
-
drawn.set(particle.id, mesh2);
|
|
8690
|
-
group.add(mesh2);
|
|
9010
|
+
let entry = drawn.get(particle.id);
|
|
9011
|
+
if (!entry) {
|
|
9012
|
+
entry = acquire(particle);
|
|
9013
|
+
drawn.set(particle.id, entry);
|
|
8691
9014
|
}
|
|
9015
|
+
entry.stamp = stamp;
|
|
9016
|
+
const mesh2 = entry.mesh;
|
|
8692
9017
|
frame.set(particle.frame[0], particle.frame[1], particle.frame[2], particle.frame[3]);
|
|
8693
9018
|
offset.set(particle.at[0], particle.at[1], particle.at[2]).multiplyScalar(particle.scale).applyQuaternion(frame);
|
|
8694
9019
|
mesh2.position.set(particle.x + offset.x, particle.y + offset.y, particle.z + offset.z);
|
|
8695
9020
|
turn.setFromEuler(euler.set(particle.rx, particle.ry, particle.rz));
|
|
8696
9021
|
mesh2.quaternion.copy(frame).multiply(turn);
|
|
8697
|
-
mesh2.scale.
|
|
9022
|
+
mesh2.scale.set(particle.radius * particle.scale, (isCone(particle) ? particle.height : particle.radius) * particle.scale, particle.radius * particle.scale);
|
|
8698
9023
|
mesh2.material.opacity = particle.opacity;
|
|
8699
9024
|
}
|
|
8700
|
-
for (const [id,
|
|
8701
|
-
if (
|
|
8702
|
-
|
|
9025
|
+
for (const [id, entry] of drawn) {
|
|
9026
|
+
if (entry.stamp === stamp) continue;
|
|
9027
|
+
park(entry);
|
|
8703
9028
|
drawn.delete(id);
|
|
8704
9029
|
}
|
|
8705
9030
|
return pool.count;
|
|
8706
9031
|
},
|
|
8707
9032
|
dispose() {
|
|
8708
|
-
for (const
|
|
9033
|
+
for (const entry of drawn.values()) park(entry);
|
|
8709
9034
|
drawn.clear();
|
|
9035
|
+
for (const free of parked.values()) for (const mesh2 of free) mesh2.material.dispose();
|
|
9036
|
+
parked.clear();
|
|
9037
|
+
for (const geometry of geometries.values()) geometry.dispose();
|
|
9038
|
+
geometries.clear();
|
|
8710
9039
|
group.removeFromParent();
|
|
8711
9040
|
}
|
|
8712
9041
|
};
|
|
@@ -9291,7 +9620,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9291
9620
|
const shocks = createShockLayer(THREE, pools.shocks);
|
|
9292
9621
|
const sprites = createSpriteLayer(THREE, world.sprites(), options.pointScale ?? 700);
|
|
9293
9622
|
const lights = createLightLayer(THREE, pools.lights, options.lightScale ?? Math.PI);
|
|
9294
|
-
const marks = createMarkLayer(THREE, { ground: pools.ground, createCanvas: options.createCanvas, rockGeometry, plateGeometry });
|
|
9623
|
+
const marks = createMarkLayer(THREE, { ground: pools.ground, createCanvas: options.createCanvas, rockGeometry, plateGeometry, ...options.markLimits });
|
|
9295
9624
|
root.add(puffs.mesh, sparks.mesh, glows.mesh, chunks.rocks, chunks.plates, shocks.group, lights.group, sprites.additive, sprites.normal, marks.group);
|
|
9296
9625
|
const stopMarks = world.onMarks((event) => marks.handle(event));
|
|
9297
9626
|
const overlay = options.overlay ? createScreenOverlay(options.overlay) : void 0;
|
|
@@ -9317,6 +9646,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9317
9646
|
const position = options.camera.position;
|
|
9318
9647
|
world.setCamera([position.x, position.y, position.z]);
|
|
9319
9648
|
}
|
|
9649
|
+
function attachWorkshopLayers() {
|
|
9650
|
+
const engine = world.peekWorkshop();
|
|
9651
|
+
if (!engine || engine === workshopEngine) return;
|
|
9652
|
+
workshopLayers?.dispose();
|
|
9653
|
+
if (workshopLayers) root.remove(workshopLayers.group);
|
|
9654
|
+
workshopEngine = engine;
|
|
9655
|
+
workshopLayers = createWorkshopLayers(THREE, engine, options.pointScale ?? 700, 1);
|
|
9656
|
+
root.add(workshopLayers.group);
|
|
9657
|
+
applyLayers();
|
|
9658
|
+
}
|
|
9320
9659
|
function syncAll() {
|
|
9321
9660
|
lastDrawn.puffs = puffs.sync();
|
|
9322
9661
|
lastDrawn.sparks = sparks.sync();
|
|
@@ -9325,15 +9664,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9325
9664
|
lastDrawn.shocks = shocks.sync();
|
|
9326
9665
|
lastDrawn.sprites = sprites.sync();
|
|
9327
9666
|
lights.sync();
|
|
9328
|
-
|
|
9329
|
-
if (engine && engine !== workshopEngine) {
|
|
9330
|
-
workshopLayers?.dispose();
|
|
9331
|
-
if (workshopLayers) root.remove(workshopLayers.group);
|
|
9332
|
-
workshopEngine = engine;
|
|
9333
|
-
workshopLayers = createWorkshopLayers(THREE, engine, options.pointScale ?? 700, 1);
|
|
9334
|
-
root.add(workshopLayers.group);
|
|
9335
|
-
applyLayers();
|
|
9336
|
-
}
|
|
9667
|
+
attachWorkshopLayers();
|
|
9337
9668
|
const workshopCalls = workshopLayers?.sync() ?? 0;
|
|
9338
9669
|
const glow = pools.lights.glow;
|
|
9339
9670
|
uniforms.uGlowPos.value.set(glow.x, glow.y, glow.z);
|
|
@@ -9361,6 +9692,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9361
9692
|
});
|
|
9362
9693
|
},
|
|
9363
9694
|
stop: (handle) => world.stop(handle),
|
|
9695
|
+
// a module's closed space (a shut car's cabin): the world's smoke, dust and steam go round it
|
|
9696
|
+
enclose: (id, enclosure) => world.enclose(id, enclosure),
|
|
9364
9697
|
configure(settings) {
|
|
9365
9698
|
const band = settings.lod_band;
|
|
9366
9699
|
if (typeof band === "string" && band in LOD_BANDS) world.forceLod(LOD_BANDS[band] ?? null);
|
|
@@ -9386,6 +9719,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9386
9719
|
},
|
|
9387
9720
|
warmUpProbes() {
|
|
9388
9721
|
releaseAnchors();
|
|
9722
|
+
marks.prepare();
|
|
9723
|
+
world.workshop();
|
|
9724
|
+
attachWorkshopLayers();
|
|
9389
9725
|
const meshes = createMeshLayerProbes(THREE);
|
|
9390
9726
|
for (const mesh2 of meshes) mesh2.frustumCulled = false;
|
|
9391
9727
|
const group = new THREE.Group();
|