@adatechnology/game-vfx-three 0.3.4 → 0.3.6
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 +205 -88
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +186 -87
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -49,6 +49,12 @@ interface VfxRendererOptions {
|
|
|
49
49
|
pointScale?: number;
|
|
50
50
|
/** Multiplies the blast and fire lights. Default `Math.PI`: three.js r155+ lights are physical, the Lab's were not (r128). */
|
|
51
51
|
lightScale?: number;
|
|
52
|
+
/** Caps on the marks kept on the ground; once one is full the oldest is reused for the next, so nothing grows and nothing is allocated. Defaults: 64 scorch and dirt, 512 rocks, 256 blood. */
|
|
53
|
+
markLimits?: {
|
|
54
|
+
readonly maxGroundMarks?: number;
|
|
55
|
+
readonly maxRocks?: number;
|
|
56
|
+
readonly maxBlood?: number;
|
|
57
|
+
};
|
|
52
58
|
}
|
|
53
59
|
/** The world's counters flattened (`lastBlast` becomes `last_puffs`, `last_sparks`, `last_chunks`, `last_lights`), plus `drawCalls`. */
|
|
54
60
|
type VfxRendererStats = Readonly<Record<string, number>>;
|
|
@@ -146,7 +152,9 @@ interface MarkLayer {
|
|
|
146
152
|
/** The shared rock and plate shapes, so the debris and the rim rocks use the same geometry. */
|
|
147
153
|
handle(event: MarkEvent): void;
|
|
148
154
|
update(dt: number): void;
|
|
149
|
-
/**
|
|
155
|
+
/** Puts the instanced meshes in the scene now, so their shader programs link while loading instead of at the first mark. */
|
|
156
|
+
prepare(): void;
|
|
157
|
+
/** The draw calls the marks cost right now. */
|
|
150
158
|
count(): number;
|
|
151
159
|
dispose(): void;
|
|
152
160
|
}
|
|
@@ -155,6 +163,12 @@ interface MarkLayerOptions {
|
|
|
155
163
|
readonly createCanvas: (() => CanvasLike) | undefined;
|
|
156
164
|
readonly rockGeometry: THREE_NS.BufferGeometry;
|
|
157
165
|
readonly plateGeometry: THREE_NS.BufferGeometry;
|
|
166
|
+
/** Scorch and dirt meshes alive at once; the oldest is reused for the next. Default 64. */
|
|
167
|
+
readonly maxGroundMarks?: number;
|
|
168
|
+
/** Rim rocks and plates alive at once (each shape has its own instanced mesh of this size); the oldest is overwritten. Default 512. */
|
|
169
|
+
readonly maxRocks?: number;
|
|
170
|
+
/** Blood pools alive at once; the oldest is overwritten. Default 256. */
|
|
171
|
+
readonly maxBlood?: number;
|
|
158
172
|
}
|
|
159
173
|
/**
|
|
160
174
|
* The scorch, dirt, rim rocks and blood of the Lab as meshes (L443-476). A scorch or dirt mesh follows the ground as it was the moment the mark was
|
package/dist/index.global.js
CHANGED
|
@@ -6377,6 +6377,24 @@ var GameVfxThree = (() => {
|
|
|
6377
6377
|
for (const decal2 of this.decals) decal2.on = false;
|
|
6378
6378
|
this.count = 0;
|
|
6379
6379
|
}
|
|
6380
|
+
/**
|
|
6381
|
+
* 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
|
+
* 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
|
+
*/
|
|
6384
|
+
clearInside(center, half, rotation = [0, 0, 0, 1]) {
|
|
6385
|
+
const qx = -rotation[0], qy = -rotation[1], qz = -rotation[2], qw = rotation[3];
|
|
6386
|
+
let gone = 0;
|
|
6387
|
+
for (const decal2 of this.decals) {
|
|
6388
|
+
if (!decal2.on) continue;
|
|
6389
|
+
const vx2 = decal2.x - center[0], vy2 = decal2.y - center[1], vz2 = decal2.z - center[2];
|
|
6390
|
+
const tx = 2 * (qy * vz2 - qz * vy2), ty = 2 * (qz * vx2 - qx * vz2), tz = 2 * (qx * vy2 - qy * vx2);
|
|
6391
|
+
const lx = vx2 + qw * tx + (qy * tz - qz * ty), ly = vy2 + qw * ty + (qz * tx - qx * tz), lz = vz2 + qw * tz + (qx * ty - qy * tx);
|
|
6392
|
+
if (Math.abs(lx) > half[0] || Math.abs(ly) > half[1] || Math.abs(lz) > half[2]) continue;
|
|
6393
|
+
decal2.on = false;
|
|
6394
|
+
gone++;
|
|
6395
|
+
}
|
|
6396
|
+
return gone;
|
|
6397
|
+
}
|
|
6380
6398
|
update(dt) {
|
|
6381
6399
|
this.count = 0;
|
|
6382
6400
|
for (const decal2 of this.decals) {
|
|
@@ -8505,6 +8523,11 @@ void main(){
|
|
|
8505
8523
|
var MAX_CONFORM_STEPS = 48;
|
|
8506
8524
|
var MIN_CONFORM_STEPS = 16;
|
|
8507
8525
|
var BLOOD_GROW_RATE = 1.2;
|
|
8526
|
+
var BLOOD_START_GROWTH = 0.05;
|
|
8527
|
+
var DEFAULT_MAX_GROUND_MARKS = 64;
|
|
8528
|
+
var DEFAULT_MAX_ROCKS = 512;
|
|
8529
|
+
var DEFAULT_MAX_BLOOD = 256;
|
|
8530
|
+
var MAX_SPARE_GROUND_GEOMETRIES = 16;
|
|
8508
8531
|
function createMarkLayer(THREE, options) {
|
|
8509
8532
|
const group = new THREE.Group();
|
|
8510
8533
|
const textures = createMarkTextures(THREE, options.createCanvas);
|
|
@@ -8528,92 +8551,146 @@ void main(){
|
|
|
8528
8551
|
const bloodMaterial = new THREE.MeshStandardMaterial({ color: 6031882, roughness: 0.15, metalness: 0.1, polygonOffset: true, polygonOffsetFactor: -3, polygonOffsetUnits: -3 });
|
|
8529
8552
|
const bloodGeometry = new THREE.CircleGeometry(0.5, 9).rotateX(-Math.PI / 2);
|
|
8530
8553
|
owned.push(bloodMaterial, bloodGeometry);
|
|
8531
|
-
const
|
|
8532
|
-
const
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
|
|
8537
|
-
material = new THREE.MeshStandardMaterial({ color, roughness: 0.9, metalness: 0, flatShading: true });
|
|
8538
|
-
rockMaterials.set(color, material);
|
|
8539
|
-
owned.push(material);
|
|
8554
|
+
const maxGroundMarks = options.maxGroundMarks ?? DEFAULT_MAX_GROUND_MARKS;
|
|
8555
|
+
const instanced = {};
|
|
8556
|
+
function rockMaterial() {
|
|
8557
|
+
if (!instanced.rockMaterial) {
|
|
8558
|
+
instanced.rockMaterial = new THREE.MeshStandardMaterial({ color: 16777215, roughness: 0.9, metalness: 0, flatShading: true });
|
|
8559
|
+
owned.push(instanced.rockMaterial);
|
|
8540
8560
|
}
|
|
8541
|
-
return
|
|
8561
|
+
return instanced.rockMaterial;
|
|
8562
|
+
}
|
|
8563
|
+
function bloodMesh() {
|
|
8564
|
+
return instanced.blood ?? (instanced.blood = attach(createInstanced(bloodGeometry, bloodMaterial, options.maxBlood ?? DEFAULT_MAX_BLOOD)));
|
|
8565
|
+
}
|
|
8566
|
+
function rockMesh() {
|
|
8567
|
+
return instanced.rock ?? (instanced.rock = attach(createInstanced(options.rockGeometry, rockMaterial(), options.maxRocks ?? DEFAULT_MAX_ROCKS), true));
|
|
8568
|
+
}
|
|
8569
|
+
function plateMesh() {
|
|
8570
|
+
return instanced.plate ?? (instanced.plate = attach(createInstanced(options.plateGeometry, rockMaterial(), options.maxRocks ?? DEFAULT_MAX_ROCKS), true));
|
|
8571
|
+
}
|
|
8572
|
+
function attach(mesh2, castShadow = false) {
|
|
8573
|
+
mesh2.castShadow = castShadow;
|
|
8574
|
+
group.add(mesh2);
|
|
8575
|
+
return mesh2;
|
|
8576
|
+
}
|
|
8577
|
+
const groundMarks = [];
|
|
8578
|
+
const spareGeometries = [];
|
|
8579
|
+
const blood = [];
|
|
8580
|
+
const placed = { blood: 0, rock: 0, plate: 0 };
|
|
8581
|
+
const matrix = new THREE.Matrix4();
|
|
8582
|
+
const position = new THREE.Vector3();
|
|
8583
|
+
const quaternion = new THREE.Quaternion();
|
|
8584
|
+
const euler = new THREE.Euler();
|
|
8585
|
+
const scale = new THREE.Vector3();
|
|
8586
|
+
const color = new THREE.Color();
|
|
8587
|
+
const up = new THREE.Vector3(0, 1, 0);
|
|
8588
|
+
function createInstanced(geometry, material, capacity) {
|
|
8589
|
+
const mesh2 = new THREE.InstancedMesh(geometry, material, capacity);
|
|
8590
|
+
mesh2.count = 0;
|
|
8591
|
+
mesh2.frustumCulled = false;
|
|
8592
|
+
mesh2.receiveShadow = true;
|
|
8593
|
+
return mesh2;
|
|
8594
|
+
}
|
|
8595
|
+
function takeSpare(vertexCount) {
|
|
8596
|
+
const found = spareGeometries.findIndex((geometry) => geometry.getAttribute("position").count === vertexCount);
|
|
8597
|
+
return found < 0 ? void 0 : spareGeometries.splice(found, 1)[0];
|
|
8598
|
+
}
|
|
8599
|
+
function evictOldestGroundMark() {
|
|
8600
|
+
const oldest = groundMarks.shift();
|
|
8601
|
+
group.remove(oldest.mesh);
|
|
8602
|
+
spareGeometries.push(oldest.mesh.geometry);
|
|
8603
|
+
if (spareGeometries.length > MAX_SPARE_GROUND_GEOMETRIES) spareGeometries.shift().dispose();
|
|
8542
8604
|
}
|
|
8543
8605
|
function conform(mark, material) {
|
|
8544
8606
|
const radius = mark.radius;
|
|
8545
8607
|
const steps = Math.max(MIN_CONFORM_STEPS, Math.min(MAX_CONFORM_STEPS, Math.round(radius * 8)));
|
|
8546
|
-
|
|
8547
|
-
const
|
|
8608
|
+
if (groundMarks.length >= maxGroundMarks) evictOldestGroundMark();
|
|
8609
|
+
const geometry = takeSpare((steps + 1) * (steps + 1)) ?? new THREE.PlaneGeometry(2, 2, steps, steps).rotateX(-Math.PI / 2);
|
|
8610
|
+
const grid = geometry.attributes.position;
|
|
8611
|
+
const cell = 2 / steps;
|
|
8548
8612
|
const cos2 = Math.cos(mark.rotation);
|
|
8549
8613
|
const sin2 = Math.sin(mark.rotation);
|
|
8550
|
-
for (let index = 0; index <
|
|
8551
|
-
const localX =
|
|
8552
|
-
const localZ =
|
|
8614
|
+
for (let index = 0; index < grid.count; index++) {
|
|
8615
|
+
const localX = Math.fround(index % (steps + 1) * cell - 1) * radius;
|
|
8616
|
+
const localZ = Math.fround(Math.floor(index / (steps + 1)) * cell - 1) * radius;
|
|
8553
8617
|
const worldX = mark.x + localX * cos2 - localZ * sin2;
|
|
8554
8618
|
const worldZ = mark.z + localX * sin2 + localZ * cos2;
|
|
8555
|
-
|
|
8619
|
+
grid.setXYZ(index, worldX, (options.ground?.heightAt(worldX, worldZ) ?? 0) + mark.lift, worldZ);
|
|
8556
8620
|
}
|
|
8621
|
+
grid.needsUpdate = true;
|
|
8557
8622
|
geometry.computeVertexNormals();
|
|
8623
|
+
geometry.attributes.normal.needsUpdate = true;
|
|
8624
|
+
geometry.boundingSphere = null;
|
|
8625
|
+
geometry.boundingBox = null;
|
|
8558
8626
|
const mesh2 = new THREE.Mesh(geometry, material);
|
|
8559
8627
|
mesh2.receiveShadow = true;
|
|
8560
|
-
|
|
8628
|
+
group.add(mesh2);
|
|
8629
|
+
groundMarks.push({ mesh: mesh2, steps });
|
|
8561
8630
|
}
|
|
8562
8631
|
function addRock(mark) {
|
|
8563
|
-
const mesh2 =
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
mesh2.
|
|
8568
|
-
mesh2.
|
|
8569
|
-
|
|
8632
|
+
const mesh2 = mark.plate ? plateMesh() : rockMesh();
|
|
8633
|
+
const slot = mark.plate ? "plate" : "rock";
|
|
8634
|
+
const index = placed[slot]++ % mesh2.instanceMatrix.count;
|
|
8635
|
+
quaternion.setFromEuler(euler.set(mark.rotation[0], mark.rotation[1], mark.rotation[2]));
|
|
8636
|
+
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])));
|
|
8637
|
+
mesh2.setColorAt(index, color.set(mark.color));
|
|
8638
|
+
mesh2.count = Math.min(placed[slot], mesh2.instanceMatrix.count);
|
|
8639
|
+
mesh2.instanceMatrix.needsUpdate = true;
|
|
8640
|
+
mesh2.instanceColor.needsUpdate = true;
|
|
8641
|
+
}
|
|
8642
|
+
function writeBlood(index, drawn, growth) {
|
|
8643
|
+
quaternion.setFromAxisAngle(up, drawn.rotation);
|
|
8644
|
+
const mesh2 = bloodMesh();
|
|
8645
|
+
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)));
|
|
8646
|
+
mesh2.instanceMatrix.needsUpdate = true;
|
|
8570
8647
|
}
|
|
8571
8648
|
function addBlood(mark) {
|
|
8572
|
-
const
|
|
8573
|
-
|
|
8574
|
-
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
mesh2.scale.multiplyScalar(0.05);
|
|
8578
|
-
blood.push({ mesh: mesh2, scaleX: mark.scaleX, scaleZ: mark.scaleZ, age: 0 });
|
|
8579
|
-
return mesh2;
|
|
8649
|
+
const index = placed.blood++ % bloodMesh().instanceMatrix.count;
|
|
8650
|
+
const drawn = { position: [mark.x, mark.y, mark.z], rotation: mark.rotation, scaleX: mark.scaleX, scaleZ: mark.scaleZ, age: 0 };
|
|
8651
|
+
blood[index] = drawn;
|
|
8652
|
+
writeBlood(index, drawn, BLOOD_START_GROWTH);
|
|
8653
|
+
bloodMesh().count = Math.min(placed.blood, bloodMesh().instanceMatrix.count);
|
|
8580
8654
|
}
|
|
8581
8655
|
function build(mark) {
|
|
8582
|
-
if (mark.kind === "scorch")
|
|
8583
|
-
if (mark.kind === "dirt")
|
|
8584
|
-
if (mark.kind === "rock")
|
|
8585
|
-
|
|
8586
|
-
}
|
|
8587
|
-
function
|
|
8588
|
-
for (const
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8656
|
+
if (mark.kind === "scorch") conform(mark, scorchMaterials[mark.variant]);
|
|
8657
|
+
else if (mark.kind === "dirt") conform(mark, dirtMaterial);
|
|
8658
|
+
else if (mark.kind === "rock") addRock(mark);
|
|
8659
|
+
else addBlood(mark);
|
|
8660
|
+
}
|
|
8661
|
+
function clearMarks() {
|
|
8662
|
+
for (const drawn of groundMarks) group.remove(drawn.mesh);
|
|
8663
|
+
for (const drawn of groundMarks) drawn.mesh.geometry.dispose();
|
|
8664
|
+
groundMarks.length = 0;
|
|
8665
|
+
for (const geometry of spareGeometries) geometry.dispose();
|
|
8666
|
+
spareGeometries.length = 0;
|
|
8593
8667
|
blood.length = 0;
|
|
8594
|
-
|
|
8668
|
+
placed.blood = placed.rock = placed.plate = 0;
|
|
8669
|
+
for (const mesh2 of [instanced.blood, instanced.rock, instanced.plate]) if (mesh2) mesh2.count = 0;
|
|
8595
8670
|
}
|
|
8596
8671
|
return {
|
|
8597
8672
|
group,
|
|
8598
8673
|
handle(event) {
|
|
8599
|
-
if (event.type === "clear")
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
|
|
8674
|
+
if (event.type === "clear") clearMarks();
|
|
8675
|
+
else build(event.mark);
|
|
8676
|
+
},
|
|
8677
|
+
prepare() {
|
|
8678
|
+
bloodMesh();
|
|
8679
|
+
rockMesh();
|
|
8680
|
+
plateMesh();
|
|
8605
8681
|
},
|
|
8606
8682
|
update(dt) {
|
|
8607
|
-
for (
|
|
8608
|
-
|
|
8683
|
+
for (let index = 0; index < blood.length; index++) {
|
|
8684
|
+
const drawn = blood[index];
|
|
8685
|
+
if (!drawn || drawn.age > 1) continue;
|
|
8609
8686
|
drawn.age += dt * BLOOD_GROW_RATE;
|
|
8610
|
-
|
|
8611
|
-
drawn.mesh.scale.set(drawn.scaleX * Math.max(0.05, ease3), 1, drawn.scaleZ * Math.max(0.05, ease3));
|
|
8687
|
+
writeBlood(index, drawn, Math.max(BLOOD_START_GROWTH, 1 - Math.pow(1 - Math.min(1, drawn.age), 2.5)));
|
|
8612
8688
|
}
|
|
8613
8689
|
},
|
|
8614
|
-
count: () =>
|
|
8690
|
+
count: () => groundMarks.length + [instanced.blood, instanced.rock, instanced.plate].filter((mesh2) => mesh2 && mesh2.count > 0).length,
|
|
8615
8691
|
dispose() {
|
|
8616
|
-
|
|
8692
|
+
clearMarks();
|
|
8693
|
+
for (const mesh2 of [instanced.blood, instanced.rock, instanced.plate]) mesh2?.dispose();
|
|
8617
8694
|
for (const item of owned) item.dispose();
|
|
8618
8695
|
}
|
|
8619
8696
|
};
|
|
@@ -8622,18 +8699,22 @@ void main(){
|
|
|
8622
8699
|
// src/mesh-layer.ts
|
|
8623
8700
|
var SPHERE_DEFAULT_WIDTH = 8;
|
|
8624
8701
|
var SPHERE_DEFAULT_HEIGHT = 6;
|
|
8702
|
+
var MAX_PARKED_MESHES_PER_KIND = 256;
|
|
8625
8703
|
function geometryOf(THREE, particle) {
|
|
8626
8704
|
switch (particle.shape) {
|
|
8627
8705
|
case "cone":
|
|
8628
|
-
return new THREE.ConeGeometry(
|
|
8706
|
+
return new THREE.ConeGeometry(1, 1, particle.segments || void 0, particle.segments2 || 1, false);
|
|
8629
8707
|
case "cone_open":
|
|
8630
|
-
return new THREE.ConeGeometry(
|
|
8708
|
+
return new THREE.ConeGeometry(1, 1, particle.segments || void 0, particle.segments2 || 1, true);
|
|
8631
8709
|
case "sphere":
|
|
8632
|
-
return new THREE.SphereGeometry(
|
|
8710
|
+
return new THREE.SphereGeometry(1, particle.segments || SPHERE_DEFAULT_WIDTH, particle.segments2 || SPHERE_DEFAULT_HEIGHT);
|
|
8633
8711
|
case "icosahedron":
|
|
8634
|
-
return new THREE.IcosahedronGeometry(
|
|
8712
|
+
return new THREE.IcosahedronGeometry(1, particle.segments);
|
|
8635
8713
|
}
|
|
8636
8714
|
}
|
|
8715
|
+
function isCone(particle) {
|
|
8716
|
+
return particle.shape === "cone" || particle.shape === "cone_open";
|
|
8717
|
+
}
|
|
8637
8718
|
function materialOf(THREE, particle) {
|
|
8638
8719
|
if (particle.lit) return new THREE.MeshStandardMaterial({ color: particle.color, roughness: 1, metalness: 0, transparent: true, opacity: particle.opacity, depthWrite: false });
|
|
8639
8720
|
return new THREE.MeshBasicMaterial({ color: particle.color, transparent: true, opacity: particle.opacity, depthWrite: false, ...particle.additive ? { blending: THREE.AdditiveBlending } : {} });
|
|
@@ -8644,51 +8725,82 @@ void main(){
|
|
|
8644
8725
|
(kind) => new THREE.Mesh(geometry, materialOf(THREE, { ...kind, color: 8421504, opacity: 0.5 }))
|
|
8645
8726
|
);
|
|
8646
8727
|
}
|
|
8728
|
+
function geometryKeyOf(particle) {
|
|
8729
|
+
return `${particle.shape}:${particle.segments}:${particle.segments2}`;
|
|
8730
|
+
}
|
|
8647
8731
|
function createMeshLayer(THREE, pool) {
|
|
8648
8732
|
const group = new THREE.Group();
|
|
8649
8733
|
group.name = "workshop-meshes";
|
|
8650
8734
|
const drawn = /* @__PURE__ */ new Map();
|
|
8735
|
+
const geometries = /* @__PURE__ */ new Map();
|
|
8736
|
+
const parked = /* @__PURE__ */ new Map();
|
|
8651
8737
|
const frame = new THREE.Quaternion();
|
|
8652
8738
|
const euler = new THREE.Euler();
|
|
8653
8739
|
const turn = new THREE.Quaternion();
|
|
8654
8740
|
const offset = new THREE.Vector3();
|
|
8655
|
-
|
|
8656
|
-
|
|
8657
|
-
|
|
8658
|
-
|
|
8741
|
+
let stamp = 0;
|
|
8742
|
+
function geometryFor(particle) {
|
|
8743
|
+
const key = geometryKeyOf(particle);
|
|
8744
|
+
let geometry = geometries.get(key);
|
|
8745
|
+
if (!geometry) {
|
|
8746
|
+
geometry = geometryOf(THREE, particle);
|
|
8747
|
+
geometries.set(key, geometry);
|
|
8748
|
+
}
|
|
8749
|
+
return geometry;
|
|
8750
|
+
}
|
|
8751
|
+
function acquire(particle) {
|
|
8752
|
+
const parkingKey = `${geometryKeyOf(particle)}|${particle.lit}|${particle.additive}`;
|
|
8753
|
+
const mesh2 = parked.get(parkingKey)?.pop() ?? new THREE.Mesh(geometryFor(particle), materialOf(THREE, particle));
|
|
8754
|
+
mesh2.material.color.set(particle.color);
|
|
8755
|
+
mesh2.frustumCulled = false;
|
|
8756
|
+
mesh2.renderOrder = particle.additive ? 5 : 3;
|
|
8757
|
+
group.add(mesh2);
|
|
8758
|
+
return { mesh: mesh2, parkingKey, stamp };
|
|
8759
|
+
}
|
|
8760
|
+
function park(entry) {
|
|
8761
|
+
entry.mesh.removeFromParent();
|
|
8762
|
+
let free = parked.get(entry.parkingKey);
|
|
8763
|
+
if (!free) {
|
|
8764
|
+
free = [];
|
|
8765
|
+
parked.set(entry.parkingKey, free);
|
|
8766
|
+
}
|
|
8767
|
+
if (free.length < MAX_PARKED_MESHES_PER_KIND) free.push(entry.mesh);
|
|
8768
|
+
else entry.mesh.material.dispose();
|
|
8659
8769
|
}
|
|
8660
8770
|
return {
|
|
8661
8771
|
group,
|
|
8662
8772
|
sync() {
|
|
8663
|
-
|
|
8773
|
+
stamp++;
|
|
8664
8774
|
for (const particle of pool.particles) {
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
mesh2.frustumCulled = false;
|
|
8670
|
-
mesh2.renderOrder = particle.additive ? 5 : 3;
|
|
8671
|
-
drawn.set(particle.id, mesh2);
|
|
8672
|
-
group.add(mesh2);
|
|
8775
|
+
let entry = drawn.get(particle.id);
|
|
8776
|
+
if (!entry) {
|
|
8777
|
+
entry = acquire(particle);
|
|
8778
|
+
drawn.set(particle.id, entry);
|
|
8673
8779
|
}
|
|
8780
|
+
entry.stamp = stamp;
|
|
8781
|
+
const mesh2 = entry.mesh;
|
|
8674
8782
|
frame.set(particle.frame[0], particle.frame[1], particle.frame[2], particle.frame[3]);
|
|
8675
8783
|
offset.set(particle.at[0], particle.at[1], particle.at[2]).multiplyScalar(particle.scale).applyQuaternion(frame);
|
|
8676
8784
|
mesh2.position.set(particle.x + offset.x, particle.y + offset.y, particle.z + offset.z);
|
|
8677
8785
|
turn.setFromEuler(euler.set(particle.rx, particle.ry, particle.rz));
|
|
8678
8786
|
mesh2.quaternion.copy(frame).multiply(turn);
|
|
8679
|
-
mesh2.scale.
|
|
8787
|
+
mesh2.scale.set(particle.radius * particle.scale, (isCone(particle) ? particle.height : particle.radius) * particle.scale, particle.radius * particle.scale);
|
|
8680
8788
|
mesh2.material.opacity = particle.opacity;
|
|
8681
8789
|
}
|
|
8682
|
-
for (const [id,
|
|
8683
|
-
if (
|
|
8684
|
-
|
|
8790
|
+
for (const [id, entry] of drawn) {
|
|
8791
|
+
if (entry.stamp === stamp) continue;
|
|
8792
|
+
park(entry);
|
|
8685
8793
|
drawn.delete(id);
|
|
8686
8794
|
}
|
|
8687
8795
|
return pool.count;
|
|
8688
8796
|
},
|
|
8689
8797
|
dispose() {
|
|
8690
|
-
for (const
|
|
8798
|
+
for (const entry of drawn.values()) park(entry);
|
|
8691
8799
|
drawn.clear();
|
|
8800
|
+
for (const free of parked.values()) for (const mesh2 of free) mesh2.material.dispose();
|
|
8801
|
+
parked.clear();
|
|
8802
|
+
for (const geometry of geometries.values()) geometry.dispose();
|
|
8803
|
+
geometries.clear();
|
|
8692
8804
|
group.removeFromParent();
|
|
8693
8805
|
}
|
|
8694
8806
|
};
|
|
@@ -9273,7 +9385,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9273
9385
|
const shocks = createShockLayer(THREE, pools.shocks);
|
|
9274
9386
|
const sprites = createSpriteLayer(THREE, world.sprites(), options.pointScale ?? 700);
|
|
9275
9387
|
const lights = createLightLayer(THREE, pools.lights, options.lightScale ?? Math.PI);
|
|
9276
|
-
const marks = createMarkLayer(THREE, { ground: pools.ground, createCanvas: options.createCanvas, rockGeometry, plateGeometry });
|
|
9388
|
+
const marks = createMarkLayer(THREE, { ground: pools.ground, createCanvas: options.createCanvas, rockGeometry, plateGeometry, ...options.markLimits });
|
|
9277
9389
|
root.add(puffs.mesh, sparks.mesh, glows.mesh, chunks.rocks, chunks.plates, shocks.group, lights.group, sprites.additive, sprites.normal, marks.group);
|
|
9278
9390
|
const stopMarks = world.onMarks((event) => marks.handle(event));
|
|
9279
9391
|
const overlay = options.overlay ? createScreenOverlay(options.overlay) : void 0;
|
|
@@ -9299,6 +9411,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9299
9411
|
const position = options.camera.position;
|
|
9300
9412
|
world.setCamera([position.x, position.y, position.z]);
|
|
9301
9413
|
}
|
|
9414
|
+
function attachWorkshopLayers() {
|
|
9415
|
+
const engine = world.peekWorkshop();
|
|
9416
|
+
if (!engine || engine === workshopEngine) return;
|
|
9417
|
+
workshopLayers?.dispose();
|
|
9418
|
+
if (workshopLayers) root.remove(workshopLayers.group);
|
|
9419
|
+
workshopEngine = engine;
|
|
9420
|
+
workshopLayers = createWorkshopLayers(THREE, engine, options.pointScale ?? 700, 1);
|
|
9421
|
+
root.add(workshopLayers.group);
|
|
9422
|
+
applyLayers();
|
|
9423
|
+
}
|
|
9302
9424
|
function syncAll() {
|
|
9303
9425
|
lastDrawn.puffs = puffs.sync();
|
|
9304
9426
|
lastDrawn.sparks = sparks.sync();
|
|
@@ -9307,15 +9429,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9307
9429
|
lastDrawn.shocks = shocks.sync();
|
|
9308
9430
|
lastDrawn.sprites = sprites.sync();
|
|
9309
9431
|
lights.sync();
|
|
9310
|
-
|
|
9311
|
-
if (engine && engine !== workshopEngine) {
|
|
9312
|
-
workshopLayers?.dispose();
|
|
9313
|
-
if (workshopLayers) root.remove(workshopLayers.group);
|
|
9314
|
-
workshopEngine = engine;
|
|
9315
|
-
workshopLayers = createWorkshopLayers(THREE, engine, options.pointScale ?? 700, 1);
|
|
9316
|
-
root.add(workshopLayers.group);
|
|
9317
|
-
applyLayers();
|
|
9318
|
-
}
|
|
9432
|
+
attachWorkshopLayers();
|
|
9319
9433
|
const workshopCalls = workshopLayers?.sync() ?? 0;
|
|
9320
9434
|
const glow = pools.lights.glow;
|
|
9321
9435
|
uniforms.uGlowPos.value.set(glow.x, glow.y, glow.z);
|
|
@@ -9368,6 +9482,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9368
9482
|
},
|
|
9369
9483
|
warmUpProbes() {
|
|
9370
9484
|
releaseAnchors();
|
|
9485
|
+
marks.prepare();
|
|
9486
|
+
world.workshop();
|
|
9487
|
+
attachWorkshopLayers();
|
|
9371
9488
|
const meshes = createMeshLayerProbes(THREE);
|
|
9372
9489
|
for (const mesh2 of meshes) mesh2.frustumCulled = false;
|
|
9373
9490
|
const group = new THREE.Group();
|