sparcoon 0.7.3 → 0.9.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 +124 -76
- package/dist/artifact/FXArtifact.d.ts +120 -0
- package/dist/artifact/FXArtifact.js +1 -0
- package/dist/coreLayout.d.ts +22 -0
- package/dist/coreLayout.js +32 -0
- package/dist/editor/index.d.ts +8 -0
- package/dist/editor/index.js +17 -0
- package/dist/effect/FXEffect.d.ts +77 -0
- package/dist/effect/FXEffect.js +454 -0
- package/dist/effect/FXEffectSampling.Internal.d.ts +24 -0
- package/dist/effect/FXEffectSampling.Internal.js +165 -0
- package/dist/effect/FXEffectSpec.d.ts +115 -0
- package/dist/effect/FXEffectSpec.js +1 -0
- package/dist/emitter/FXEmitter.Internal.d.ts +23 -58
- package/dist/emitter/FXEmitter.Internal.js +13 -29
- package/dist/emitter/FXEmitter.d.ts +54 -46
- package/dist/emitter/FXEmitter.js +357 -186
- package/dist/emitter/FXFromArtifactsOptions.d.ts +13 -0
- package/dist/emitter/FXFromArtifactsOptions.js +1 -0
- package/dist/emitter/FXObjectMotion.Internal.d.ts +33 -0
- package/dist/emitter/FXObjectMotion.Internal.js +87 -0
- package/dist/emitter/FXSimulationHolder.d.ts +1 -0
- package/dist/emitter/FXSimulationHolder.js +43 -0
- package/dist/index.d.ts +11 -39
- package/dist/index.js +13 -36
- package/dist/instancedParticle/FXInstancedParticle.d.ts +1 -22
- package/dist/instancedParticle/FXInstancedParticle.js +145 -85
- package/dist/instancedParticle/glTypeInfo.d.ts +5 -0
- package/dist/instancedParticle/glTypeInfo.js +3 -0
- package/dist/instancedParticle/primitiveGeometry.d.ts +10 -0
- package/dist/instancedParticle/primitiveGeometry.js +30 -0
- package/dist/miscellaneous/asserts.d.ts +1 -18
- package/dist/miscellaneous/asserts.js +21 -28
- package/dist/miscellaneous/fxMath.d.ts +26 -0
- package/dist/miscellaneous/fxMath.js +261 -0
- package/dist/miscellaneous/math.d.ts +2 -7
- package/dist/miscellaneous/math.js +8 -9
- package/dist/miscellaneous/texture/fxDataTexture.d.ts +26 -0
- package/dist/miscellaneous/texture/fxDataTexture.js +20 -0
- package/dist/render/FXArtifactMaterial.Internal.d.ts +16 -0
- package/dist/render/FXArtifactMaterial.Internal.js +944 -0
- package/dist/render/FXArtifactMaterial.d.ts +1 -0
- package/dist/render/FXArtifactMaterial.js +79 -0
- package/dist/render/FXMaterialOptions.d.ts +27 -0
- package/dist/render/FXMaterialOptions.js +5 -0
- package/dist/render/FXMesh.d.ts +47 -0
- package/dist/render/FXMesh.js +99 -0
- package/dist/render/FXMeshMaterial.d.ts +40 -0
- package/dist/render/FXMeshMaterial.js +77 -0
- package/dist/world/FXWorld.d.ts +52 -0
- package/dist/world/FXWorld.js +104 -0
- package/package.json +49 -29
- package/dist/_virtual/2d.js +0 -3
- package/dist/_virtual/3d.js +0 -3
- package/dist/_virtual/4d.js +0 -3
- package/dist/_virtual/mod.js +0 -5
- package/dist/_virtual/mod2.js +0 -3
- package/dist/emitter/behavior/FXBehavior.d.ts +0 -24
- package/dist/emitter/behavior/FXBehavior.js +0 -10
- package/dist/emitter/behavior/FXBehaviorDirectionalForce.d.ts +0 -23
- package/dist/emitter/behavior/FXBehaviorDirectionalForce.js +0 -72
- package/dist/emitter/behavior/FXBehaviorPointForce.d.ts +0 -36
- package/dist/emitter/behavior/FXBehaviorPointForce.js +0 -94
- package/dist/emitter/behavior/FXBehaviorScaleOverLife.d.ts +0 -24
- package/dist/emitter/behavior/FXBehaviorScaleOverLife.js +0 -55
- package/dist/emitter/behavior/FXBehaviorTorqueDamping.d.ts +0 -20
- package/dist/emitter/behavior/FXBehaviorTorqueDamping.js +0 -48
- package/dist/emitter/behavior/FXBehaviorTorqueNoise.d.ts +0 -25
- package/dist/emitter/behavior/FXBehaviorTorqueNoise.js +0 -67
- package/dist/emitter/behavior/FXBehaviorTorqueOverLife.d.ts +0 -19
- package/dist/emitter/behavior/FXBehaviorTorqueOverLife.js +0 -41
- package/dist/emitter/behavior/FXBehaviorVelocityDamping.d.ts +0 -20
- package/dist/emitter/behavior/FXBehaviorVelocityDamping.js +0 -50
- package/dist/emitter/behavior/FXBehaviorVelocityNoise.d.ts +0 -25
- package/dist/emitter/behavior/FXBehaviorVelocityNoise.js +0 -76
- package/dist/emitter/behavior/FXBehaviorVelocityOverLife.d.ts +0 -22
- package/dist/emitter/behavior/FXBehaviorVelocityOverLife.js +0 -55
- package/dist/emitter/spawn/FXSpawn.d.ts +0 -11
- package/dist/emitter/spawn/FXSpawn.js +0 -10
- package/dist/emitter/spawn/FXSpawnBox.d.ts +0 -18
- package/dist/emitter/spawn/FXSpawnBox.js +0 -50
- package/dist/emitter/spawn/FXSpawnLifetime.d.ts +0 -17
- package/dist/emitter/spawn/FXSpawnLifetime.js +0 -44
- package/dist/emitter/spawn/FXSpawnOffset.d.ts +0 -21
- package/dist/emitter/spawn/FXSpawnOffset.js +0 -49
- package/dist/emitter/spawn/FXSpawnPoint.d.ts +0 -18
- package/dist/emitter/spawn/FXSpawnPoint.js +0 -46
- package/dist/emitter/spawn/FXSpawnRotation.d.ts +0 -17
- package/dist/emitter/spawn/FXSpawnRotation.js +0 -42
- package/dist/emitter/spawn/FXSpawnScale.d.ts +0 -22
- package/dist/emitter/spawn/FXSpawnScale.js +0 -56
- package/dist/emitter/spawn/FXSpawnSphere.d.ts +0 -36
- package/dist/emitter/spawn/FXSpawnSphere.js +0 -87
- package/dist/emitter/spawn/FXSpawnTorque.d.ts +0 -17
- package/dist/emitter/spawn/FXSpawnTorque.js +0 -42
- package/dist/emitter/spawn/FXSpawnVelocity.d.ts +0 -29
- package/dist/emitter/spawn/FXSpawnVelocity.js +0 -91
- package/dist/instancedParticle/FXInstancedParticle.Internal.d.ts +0 -2
- package/dist/instancedParticle/shared.d.ts +0 -19
- package/dist/instancedParticle/shared.js +0 -116
- package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.Internal.d.ts +0 -24
- package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.Internal.js +0 -275
- package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.d.ts +0 -75
- package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.js +0 -63
- package/dist/materials/FXMaterial/FXBlending.Internal.d.ts +0 -24
- package/dist/materials/FXMaterial/FXBlending.Internal.js +0 -64
- package/dist/materials/FXMaterial/FXDepthMaterial.Internal.d.ts +0 -4
- package/dist/materials/FXMaterial/FXDepthMaterial.Internal.js +0 -144
- package/dist/materials/FXMaterial/FXDistanceMaterial.Internal.d.ts +0 -4
- package/dist/materials/FXMaterial/FXDistanceMaterial.Internal.js +0 -175
- package/dist/materials/FXMaterial/FXMaterial.d.ts +0 -70
- package/dist/materials/FXMaterial/FXMaterial.js +0 -51
- package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.Internal.d.ts +0 -7
- package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.Internal.js +0 -103
- package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.d.ts +0 -7
- package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.js +0 -21
- package/dist/miscellaneous/color/FXColor.Internal.d.ts +0 -7
- package/dist/miscellaneous/color/FXColor.Internal.js +0 -42
- package/dist/miscellaneous/color/FXColor.d.ts +0 -234
- package/dist/miscellaneous/color/FXColor.js +0 -636
- package/dist/miscellaneous/curve/FXCurve1D.Internal.d.ts +0 -20
- package/dist/miscellaneous/curve/FXCurve1D.d.ts +0 -17
- package/dist/miscellaneous/curve/FXCurve1D.js +0 -94
- package/dist/miscellaneous/miscellaneous.d.ts +0 -34
- package/dist/miscellaneous/miscellaneous.js +0 -85
- package/dist/miscellaneous/texture/FXTextureView.Internal.d.ts +0 -49
- package/dist/miscellaneous/texture/FXTextureView.Internal.js +0 -6
- package/dist/miscellaneous/texture/FXTextureView.d.ts +0 -72
- package/dist/miscellaneous/texture/FXTextureView.js +0 -234
- package/dist/miscellaneous/webglCapabilities.d.ts +0 -1
- package/dist/miscellaneous/webglCapabilities.js +0 -45
- package/dist/node_modules/fast-simplex-noise/lib/2d.js +0 -93
- package/dist/node_modules/fast-simplex-noise/lib/3d.js +0 -142
- package/dist/node_modules/fast-simplex-noise/lib/4d.js +0 -196
- package/dist/node_modules/fast-simplex-noise/lib/mod.js +0 -24
- package/dist/nodes/FXNode.d.ts +0 -5
- package/dist/nodes/FXNode.js +0 -10
- package/dist/nodes/blending/FXNodeBlending.d.ts +0 -6
- package/dist/nodes/blending/FXNodeBlending.js +0 -9
- package/dist/nodes/blending/FXNodeLightnessBlendingMask.d.ts +0 -19
- package/dist/nodes/blending/FXNodeLightnessBlendingMask.js +0 -65
- package/dist/nodes/color/FXNodeColor.d.ts +0 -6
- package/dist/nodes/color/FXNodeColor.js +0 -9
- package/dist/nodes/color/FXNodeColorOverLife.d.ts +0 -22
- package/dist/nodes/color/FXNodeColorOverLife.js +0 -64
- package/dist/nodes/color/FXNodeSphericalClip.d.ts +0 -21
- package/dist/nodes/color/FXNodeSphericalClip.js +0 -54
- package/dist/nodes/normal/FXNodeFlatNormal.d.ts +0 -6
- package/dist/nodes/normal/FXNodeFlatNormal.js +0 -16
- package/dist/nodes/normal/FXNodeNormal.d.ts +0 -6
- package/dist/nodes/normal/FXNodeNormal.js +0 -9
- package/dist/nodes/normal/FXNodeSphericalNormal.d.ts +0 -6
- package/dist/nodes/normal/FXNodeSphericalNormal.js +0 -23
- package/dist/nodes/texture/FXNodeAnimatedTexture.d.ts +0 -52
- package/dist/nodes/texture/FXNodeAnimatedTexture.js +0 -151
- package/dist/nodes/texture/FXNodeStaticTexture.d.ts +0 -20
- package/dist/nodes/texture/FXNodeStaticTexture.js +0 -67
- package/dist/nodes/texture/FXNodeTexture.d.ts +0 -6
- package/dist/nodes/texture/FXNodeTexture.js +0 -9
|
@@ -1,64 +1,95 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { InstancedBufferAttribute, InstancedBufferGeometry, Mesh, StreamDrawUsage } from "three";
|
|
2
|
+
import { FX_AGE, FX_CORE_LIFECYCLE, FX_CORE_POSITION, FX_LIFETIME, FX_POSITION_X, FX_POSITION_Y, FX_POSITION_Z, } from "../coreLayout.js";
|
|
3
|
+
/**
|
|
4
|
+
* @internal Instanced mesh backing one {@link FXEmitter}: a base primitive drawn once per particle,
|
|
5
|
+
* plus packed per-particle state. Each field (core `position`/`lifecycle` and each user attribute)
|
|
6
|
+
* is one `InstancedBufferAttribute` in {@link propertyBuffers}, exposed as `a_<name>`. All buffers
|
|
7
|
+
* share one particle index and stay dense: births append, deaths compact survivors down, capacity
|
|
8
|
+
* grows in `capacityStep` blocks - each just iterates every buffer in lockstep.
|
|
9
|
+
*/
|
|
10
|
+
export class FXInstancedParticle extends Mesh {
|
|
11
|
+
constructor(varyings, expectedCapacity, capacityStep, material, baseGeometry) {
|
|
6
12
|
const instancedGeometry = new InstancedBufferGeometry();
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
+
// Copy the base geometry's position/uv/normal + index onto the instanced geometry so the
|
|
14
|
+
// geometry-agnostic vertex epilogue transforms real mesh vertices. `baseGeometry` is caller-
|
|
15
|
+
// owned (a primitive or a custom mesh asset, possibly shared across emitters) - never disposed
|
|
16
|
+
// here.
|
|
17
|
+
const baseIndex = baseGeometry.getIndex();
|
|
18
|
+
if (baseIndex !== null) {
|
|
19
|
+
instancedGeometry.setIndex(baseIndex);
|
|
20
|
+
}
|
|
21
|
+
instancedGeometry.setAttribute("position", baseGeometry.getAttribute("position"));
|
|
22
|
+
instancedGeometry.setAttribute("uv", baseGeometry.getAttribute("uv"));
|
|
23
|
+
instancedGeometry.setAttribute("normal", baseGeometry.getAttribute("normal"));
|
|
13
24
|
super(instancedGeometry, material);
|
|
14
|
-
this.
|
|
25
|
+
this.capacityStep = capacityStep;
|
|
15
26
|
this.propertyBuffers = {};
|
|
16
|
-
this.
|
|
17
|
-
this.
|
|
18
|
-
this.
|
|
27
|
+
this.bufferVersionInternal = 0;
|
|
28
|
+
this.sortingIndices = new Int32Array(0);
|
|
29
|
+
this.sortingSquaredDistances = new Float64Array(0);
|
|
30
|
+
this.sortingTemporaryBuffer = new Float32Array(0);
|
|
19
31
|
this.frustumCulled = false;
|
|
20
|
-
this.
|
|
21
|
-
this.
|
|
22
|
-
this.
|
|
23
|
-
this._private_capacity = Math.max(Math.ceil(expectedCapacity / this._private_capacityStep) * this._private_capacityStep, this._private_capacityStep);
|
|
32
|
+
this.instancedGeometry = instancedGeometry;
|
|
33
|
+
this.instancedGeometry.instanceCount = 0;
|
|
34
|
+
this.capacity = Math.max(Math.ceil(expectedCapacity / this.capacityStep) * this.capacityStep, this.capacityStep);
|
|
24
35
|
for (const name in varyings) {
|
|
25
36
|
const { bufferSize } = varyings[name];
|
|
26
|
-
const attribute = new InstancedBufferAttribute(new Float32Array(this.
|
|
37
|
+
const attribute = new InstancedBufferAttribute(new Float32Array(this.capacity * bufferSize), bufferSize);
|
|
27
38
|
attribute.setUsage(StreamDrawUsage);
|
|
28
|
-
this.
|
|
39
|
+
this.instancedGeometry.setAttribute(`a_${name}`, attribute);
|
|
29
40
|
this.propertyBuffers[name] = attribute;
|
|
30
41
|
}
|
|
31
42
|
}
|
|
32
43
|
get instanceCount() {
|
|
33
|
-
return this.
|
|
44
|
+
return this.instancedGeometry.instanceCount;
|
|
34
45
|
}
|
|
35
46
|
get instanceCapacity() {
|
|
36
|
-
return this.
|
|
47
|
+
return this.capacity;
|
|
48
|
+
}
|
|
49
|
+
// Bumped on every backing-array reallocation (capacity growth), so the emitter can cache its
|
|
50
|
+
// per-tick name -> Float32Array record and rebuild only when this moves.
|
|
51
|
+
get bufferVersion() {
|
|
52
|
+
return this.bufferVersionInternal;
|
|
37
53
|
}
|
|
38
54
|
createInstances(count) {
|
|
39
|
-
const currentInstanceCount = this.
|
|
40
|
-
this.
|
|
41
|
-
|
|
55
|
+
const currentInstanceCount = this.instancedGeometry.instanceCount;
|
|
56
|
+
this.ensureCapacity(currentInstanceCount + count);
|
|
57
|
+
// Rows freed by `removeDeadParticles` (copy-down, no zeroing) still hold the previous occupant's
|
|
58
|
+
// state - most dangerously a stale `age >= lifetime`, which no spawn graph can reset (the spawn
|
|
59
|
+
// target has no `age` slot). Zero the claimed range so births start clean; flag each buffer so
|
|
60
|
+
// the cleared rows reach the GPU even for buffers the spawn kernel does not itself write.
|
|
61
|
+
const rangeBegin = currentInstanceCount;
|
|
62
|
+
const rangeEnd = currentInstanceCount + count;
|
|
63
|
+
for (const attribute of Object.values(this.propertyBuffers)) {
|
|
64
|
+
const { array, itemSize } = attribute;
|
|
65
|
+
array.fill(0, rangeBegin * itemSize, rangeEnd * itemSize);
|
|
66
|
+
attribute.needsUpdate = true;
|
|
67
|
+
}
|
|
68
|
+
this.instancedGeometry.instanceCount += count;
|
|
42
69
|
}
|
|
43
70
|
removeDeadParticles() {
|
|
44
|
-
|
|
45
|
-
|
|
71
|
+
// Cull against core `lifecycle` (vec2 [age, lifetime]); alive while `age < lifetime`. Rides the
|
|
72
|
+
// same copy-down as every other property buffer.
|
|
73
|
+
const lifecycleBuffer = this.propertyBuffers[FX_CORE_LIFECYCLE];
|
|
74
|
+
if (lifecycleBuffer === undefined) {
|
|
46
75
|
return;
|
|
47
76
|
}
|
|
48
|
-
const { array:
|
|
49
|
-
const { instanceCount } = this.
|
|
77
|
+
const { array: lifecycleArray, itemSize: lifecycleItemSize } = lifecycleBuffer;
|
|
78
|
+
const { instanceCount } = this.instancedGeometry;
|
|
79
|
+
// Materialized once per call, not per survivor: a `for...in` inside the copy-down loop would
|
|
80
|
+
// re-enumerate keys for every moved particle.
|
|
81
|
+
const attributes = Object.values(this.propertyBuffers);
|
|
50
82
|
let writeIndex = 0;
|
|
51
83
|
let didCompact = false;
|
|
52
84
|
for (let readIndex = 0; readIndex < instanceCount; readIndex++) {
|
|
53
|
-
const offset = readIndex *
|
|
54
|
-
if (
|
|
85
|
+
const offset = readIndex * lifecycleItemSize;
|
|
86
|
+
if (lifecycleArray[offset + FX_AGE] < lifecycleArray[offset + FX_LIFETIME]) {
|
|
55
87
|
if (writeIndex !== readIndex) {
|
|
56
|
-
for (const
|
|
57
|
-
const attribute = this.propertyBuffers[name];
|
|
88
|
+
for (const attribute of attributes) {
|
|
58
89
|
const { itemSize: dataItemSize, array: dataArray } = attribute;
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
dataArray.copyWithin(
|
|
90
|
+
const sourceOffset = readIndex * dataItemSize;
|
|
91
|
+
const destinationOffset = writeIndex * dataItemSize;
|
|
92
|
+
dataArray.copyWithin(destinationOffset, sourceOffset, sourceOffset + dataItemSize);
|
|
62
93
|
}
|
|
63
94
|
didCompact = true;
|
|
64
95
|
}
|
|
@@ -66,36 +97,60 @@ class FXInstancedParticle extends Mesh {
|
|
|
66
97
|
}
|
|
67
98
|
}
|
|
68
99
|
if (didCompact) {
|
|
69
|
-
for (const
|
|
70
|
-
|
|
100
|
+
for (const attribute of attributes) {
|
|
101
|
+
attribute.needsUpdate = true;
|
|
71
102
|
}
|
|
72
103
|
}
|
|
73
|
-
this.
|
|
104
|
+
this.instancedGeometry.instanceCount = writeIndex;
|
|
74
105
|
}
|
|
75
|
-
|
|
76
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Reorders every per-particle buffer back-to-front for correct alpha blending. Distances are
|
|
108
|
+
* world-space (each center transformed by `meshWorldMatrix`), matching the shader's own
|
|
109
|
+
* `p_cameraDistance` - correct under any transform, including non-uniform scale and shear.
|
|
110
|
+
*/
|
|
111
|
+
sortByDistance(cameraWorldPosition, meshWorldMatrix) {
|
|
112
|
+
const { instanceCount } = this.instancedGeometry;
|
|
77
113
|
if (instanceCount < 2) {
|
|
78
114
|
return;
|
|
79
115
|
}
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
116
|
+
const positionBuffer = this.propertyBuffers[FX_CORE_POSITION];
|
|
117
|
+
if (positionBuffer === undefined) {
|
|
82
118
|
return;
|
|
83
119
|
}
|
|
84
|
-
if (this.
|
|
85
|
-
this.
|
|
86
|
-
this.
|
|
120
|
+
if (this.sortingIndices.length < instanceCount) {
|
|
121
|
+
this.sortingIndices = new Int32Array(this.capacity);
|
|
122
|
+
this.sortingSquaredDistances = new Float64Array(this.capacity);
|
|
87
123
|
}
|
|
88
|
-
const { array:
|
|
124
|
+
const { array: positionArray, itemSize: positionItemSize } = positionBuffer;
|
|
125
|
+
// Column-major elements, indexed directly to avoid a per-particle Vector3.
|
|
126
|
+
const elements = meshWorldMatrix.elements;
|
|
89
127
|
for (let particleIndex = 0; particleIndex < instanceCount; particleIndex++) {
|
|
90
|
-
this.
|
|
91
|
-
const itemOffset = particleIndex *
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
|
|
128
|
+
this.sortingIndices[particleIndex] = particleIndex;
|
|
129
|
+
const itemOffset = particleIndex * positionItemSize;
|
|
130
|
+
const positionX = positionArray[itemOffset + FX_POSITION_X];
|
|
131
|
+
const positionY = positionArray[itemOffset + FX_POSITION_Y];
|
|
132
|
+
const positionZ = positionArray[itemOffset + FX_POSITION_Z];
|
|
133
|
+
// meshWorldMatrix * vec4(center, 1.0) - bottom row is (0,0,0,1), so no w divide.
|
|
134
|
+
const deltaX = elements[0] * positionX +
|
|
135
|
+
elements[4] * positionY +
|
|
136
|
+
elements[8] * positionZ +
|
|
137
|
+
elements[12] -
|
|
138
|
+
cameraWorldPosition.x;
|
|
139
|
+
const deltaY = elements[1] * positionX +
|
|
140
|
+
elements[5] * positionY +
|
|
141
|
+
elements[9] * positionZ +
|
|
142
|
+
elements[13] -
|
|
143
|
+
cameraWorldPosition.y;
|
|
144
|
+
const deltaZ = elements[2] * positionX +
|
|
145
|
+
elements[6] * positionY +
|
|
146
|
+
elements[10] * positionZ +
|
|
147
|
+
elements[14] -
|
|
148
|
+
cameraWorldPosition.z;
|
|
149
|
+
this.sortingSquaredDistances[particleIndex] =
|
|
150
|
+
deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
|
|
96
151
|
}
|
|
97
|
-
const sortingSquaredDistances = this.
|
|
98
|
-
const sortingIndices = this.
|
|
152
|
+
const sortingSquaredDistances = this.sortingSquaredDistances;
|
|
153
|
+
const sortingIndices = this.sortingIndices.subarray(0, instanceCount);
|
|
99
154
|
sortingIndices.sort((indexA, indexB) => sortingSquaredDistances[indexB] - sortingSquaredDistances[indexA]);
|
|
100
155
|
let maximumItemSize = 0;
|
|
101
156
|
for (const name in this.propertyBuffers) {
|
|
@@ -104,8 +159,8 @@ class FXInstancedParticle extends Mesh {
|
|
|
104
159
|
}
|
|
105
160
|
}
|
|
106
161
|
const requiredTemporaryBufferSize = instanceCount * maximumItemSize;
|
|
107
|
-
if (this.
|
|
108
|
-
this.
|
|
162
|
+
if (this.sortingTemporaryBuffer.length < requiredTemporaryBufferSize) {
|
|
163
|
+
this.sortingTemporaryBuffer = new Float32Array(this.capacity * maximumItemSize);
|
|
109
164
|
}
|
|
110
165
|
for (const name in this.propertyBuffers) {
|
|
111
166
|
const attribute = this.propertyBuffers[name];
|
|
@@ -115,28 +170,38 @@ class FXInstancedParticle extends Mesh {
|
|
|
115
170
|
const sourceOffset = originalIndex * itemSize;
|
|
116
171
|
const destinationOffset = newIndex * itemSize;
|
|
117
172
|
for (let componentIndex = 0; componentIndex < itemSize; componentIndex++) {
|
|
118
|
-
this.
|
|
173
|
+
this.sortingTemporaryBuffer[destinationOffset + componentIndex] =
|
|
119
174
|
array[sourceOffset + componentIndex];
|
|
120
175
|
}
|
|
121
176
|
}
|
|
122
177
|
for (let i = 0; i < instanceCount * itemSize; i++) {
|
|
123
|
-
array[i] = this.
|
|
178
|
+
array[i] = this.sortingTemporaryBuffer[i];
|
|
124
179
|
}
|
|
125
180
|
attribute.needsUpdate = true;
|
|
126
181
|
}
|
|
127
182
|
}
|
|
128
183
|
drop() {
|
|
129
|
-
this.
|
|
184
|
+
this.instancedGeometry.instanceCount = 0;
|
|
130
185
|
}
|
|
186
|
+
// Disposes the instanced geometry and the currently mounted material - not the constructor one,
|
|
187
|
+
// which a live material hot-swap may have replaced and disposed.
|
|
131
188
|
destroy() {
|
|
132
|
-
|
|
133
|
-
|
|
189
|
+
// position/uv/normal + index were borrowed by reference from the caller-owned base geometry
|
|
190
|
+
// (constructor) - possibly shared with other live consumers of the same custom mesh asset.
|
|
191
|
+
// Detach them first so dispose()'s GPU-buffer cascade only touches the `a_<name>` buffers this
|
|
192
|
+
// instance actually owns, not a shared consumer's GPU cache.
|
|
193
|
+
this.instancedGeometry.deleteAttribute("position");
|
|
194
|
+
this.instancedGeometry.deleteAttribute("uv");
|
|
195
|
+
this.instancedGeometry.deleteAttribute("normal");
|
|
196
|
+
this.instancedGeometry.setIndex(null);
|
|
197
|
+
this.instancedGeometry.dispose();
|
|
198
|
+
this.material?.dispose();
|
|
134
199
|
}
|
|
135
|
-
|
|
136
|
-
if (requiredCapacity <= this.
|
|
200
|
+
ensureCapacity(requiredCapacity) {
|
|
201
|
+
if (requiredCapacity <= this.capacity) {
|
|
137
202
|
return;
|
|
138
203
|
}
|
|
139
|
-
const newCapacity = Math.ceil(requiredCapacity / this.
|
|
204
|
+
const newCapacity = Math.ceil(requiredCapacity / this.capacityStep) * this.capacityStep;
|
|
140
205
|
for (const name in this.propertyBuffers) {
|
|
141
206
|
const oldAttribute = this.propertyBuffers[name];
|
|
142
207
|
const { itemSize, array, usage } = oldAttribute;
|
|
@@ -144,27 +209,22 @@ class FXInstancedParticle extends Mesh {
|
|
|
144
209
|
newArray.set(array);
|
|
145
210
|
const newAttribute = new InstancedBufferAttribute(newArray, itemSize);
|
|
146
211
|
newAttribute.setUsage(usage);
|
|
147
|
-
this.
|
|
212
|
+
this.instancedGeometry.setAttribute(`a_${name}`, newAttribute);
|
|
148
213
|
this.propertyBuffers[name] = newAttribute;
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
oldAttribute.array =
|
|
214
|
+
// Dropping the CPU array does NOT free the GPU buffer: three's WebGLAttributes holds it in a
|
|
215
|
+
// WeakMap keyed by the (now-replaced) BufferAttribute and only deletes it when that wrapper is
|
|
216
|
+
// GC'd (no dispose() on BufferAttribute across r157-179). Best-effort, not deterministic.
|
|
217
|
+
oldAttribute.array = undefined;
|
|
153
218
|
}
|
|
154
|
-
this.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
// r157-r180 range. Deleting it is the standard workaround used across
|
|
163
|
-
// the Three.js ecosystem (see #19706, #26363, #27205).
|
|
164
|
-
//
|
|
219
|
+
this.capacity = newCapacity;
|
|
220
|
+
this.bufferVersionInternal++;
|
|
221
|
+
this.invalidateInstanceCap();
|
|
222
|
+
}
|
|
223
|
+
// Force the renderer to recompute `_maxInstanceCount` on the next setupVertexAttributes pass. It
|
|
224
|
+
// is an internal WebGLBindingStates cache, never promoted to public API across r157-r180; deleting
|
|
225
|
+
// it is the standard ecosystem workaround (three #19706, #26363, #27205).
|
|
226
|
+
invalidateInstanceCap() {
|
|
165
227
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
166
|
-
delete this.
|
|
228
|
+
delete this.instancedGeometry._maxInstanceCount;
|
|
167
229
|
}
|
|
168
230
|
}
|
|
169
|
-
|
|
170
|
-
export { FXInstancedParticle };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BufferGeometry } from "three";
|
|
2
|
+
import type { FXGeometrySource } from "../artifact/FXArtifact.js";
|
|
3
|
+
/**
|
|
4
|
+
* Resolves an artifact's {@link FXGeometrySource} to a concrete `BufferGeometry`: a
|
|
5
|
+
* built-in primitive is built fresh; a `"custom"` slot is looked up by name in `customGeometries`
|
|
6
|
+
* (app-supplied, e.g. from the exported module's `assets` or the editor's own live content
|
|
7
|
+
* library) - falling back to the `"plane"` primitive when the name is unbound, the same graceful
|
|
8
|
+
* degradation an unbound external texture slot gets.
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveGeometrySource(source: FXGeometrySource | undefined, customGeometries: Readonly<Record<string, BufferGeometry>>): BufferGeometry;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BoxGeometry, PlaneGeometry, SphereGeometry } from "three";
|
|
2
|
+
/**
|
|
3
|
+
* @internal Base (non-instanced) mesh a {@link FXInstancedParticle} instances once per particle.
|
|
4
|
+
* Carries standard `position`/`uv`/`normal` + index so the geometry-agnostic vertex epilogue can
|
|
5
|
+
* transform real mesh vertices. `"plane"` is the unit XY quad (+Z normal) used for billboards.
|
|
6
|
+
* The result is transient - the caller copies its attributes and discards it.
|
|
7
|
+
*/
|
|
8
|
+
export function buildPrimitiveGeometry(primitive) {
|
|
9
|
+
switch (primitive) {
|
|
10
|
+
case "plane":
|
|
11
|
+
return new PlaneGeometry(1, 1);
|
|
12
|
+
case "box":
|
|
13
|
+
return new BoxGeometry(1, 1, 1);
|
|
14
|
+
case "sphere":
|
|
15
|
+
return new SphereGeometry(0.5, 24, 16);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Resolves an artifact's {@link FXGeometrySource} to a concrete `BufferGeometry`: a
|
|
20
|
+
* built-in primitive is built fresh; a `"custom"` slot is looked up by name in `customGeometries`
|
|
21
|
+
* (app-supplied, e.g. from the exported module's `assets` or the editor's own live content
|
|
22
|
+
* library) - falling back to the `"plane"` primitive when the name is unbound, the same graceful
|
|
23
|
+
* degradation an unbound external texture slot gets.
|
|
24
|
+
*/
|
|
25
|
+
export function resolveGeometrySource(source, customGeometries) {
|
|
26
|
+
if (source === undefined || source.type === "primitive") {
|
|
27
|
+
return buildPrimitiveGeometry(source?.primitive ?? "plane");
|
|
28
|
+
}
|
|
29
|
+
return customGeometries[source.external] ?? buildPrimitiveGeometry("plane");
|
|
30
|
+
}
|
|
@@ -1,21 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validates value is finite number within safe range.
|
|
3
|
-
* @param value Number to validate
|
|
4
|
-
* @param subject Value name for error messages
|
|
5
|
-
* @throws If value is not finite or exceeds safe range
|
|
6
|
-
*/
|
|
7
1
|
export declare function assertValidNumber(value: number, subject: string): void;
|
|
8
|
-
/**
|
|
9
|
-
* Validates value is positive (>= EPSILON).
|
|
10
|
-
* @param value Number to validate
|
|
11
|
-
* @param subject Value name for error messages
|
|
12
|
-
* @throws If value is invalid or below EPSILON
|
|
13
|
-
*/
|
|
14
2
|
export declare function assertValidPositiveNumber(value: number, subject: string): void;
|
|
15
|
-
/**
|
|
16
|
-
* Validates value is non-negative (>= 0).
|
|
17
|
-
* @param value Number to validate
|
|
18
|
-
* @param subject Value name for error messages
|
|
19
|
-
* @throws If value is invalid or negative
|
|
20
|
-
*/
|
|
21
3
|
export declare function assertValidNonNegativeNumber(value: number, subject: string): void;
|
|
4
|
+
export declare function assertValidPositiveInteger(value: number, subject: string): void;
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import { EPSILON } from
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*/
|
|
9
|
-
function assertValidNumber(value, subject) {
|
|
10
|
-
if (process.env.NODE_ENV !== "production") {
|
|
1
|
+
import { EPSILON } from "./math.js";
|
|
2
|
+
// `typeof process` short-circuits before `process.env`, so the published ESM never throws a
|
|
3
|
+
// ReferenceError under import maps / a CDN (no bundler `process` shim). Bundlers still inline
|
|
4
|
+
// `process.env.NODE_ENV`, so DEV validation drops out of production builds.
|
|
5
|
+
const DEV = typeof process === "undefined" || process.env.NODE_ENV !== "production";
|
|
6
|
+
export function assertValidNumber(value, subject) {
|
|
7
|
+
if (DEV) {
|
|
11
8
|
if (!Number.isFinite(value)) {
|
|
12
9
|
throw new Error(`${subject}: value must be a finite number`);
|
|
13
10
|
}
|
|
@@ -16,33 +13,29 @@ function assertValidNumber(value, subject) {
|
|
|
16
13
|
}
|
|
17
14
|
}
|
|
18
15
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* @param value Number to validate
|
|
22
|
-
* @param subject Value name for error messages
|
|
23
|
-
* @throws If value is invalid or below EPSILON
|
|
24
|
-
*/
|
|
25
|
-
function assertValidPositiveNumber(value, subject) {
|
|
26
|
-
if (process.env.NODE_ENV !== "production") {
|
|
16
|
+
export function assertValidPositiveNumber(value, subject) {
|
|
17
|
+
if (DEV) {
|
|
27
18
|
assertValidNumber(value, subject);
|
|
28
19
|
if (value < EPSILON) {
|
|
29
20
|
throw new Error(`${subject}: value must be greater than or equal to ${EPSILON}`);
|
|
30
21
|
}
|
|
31
22
|
}
|
|
32
23
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
* @param value Number to validate
|
|
36
|
-
* @param subject Value name for error messages
|
|
37
|
-
* @throws If value is invalid or negative
|
|
38
|
-
*/
|
|
39
|
-
function assertValidNonNegativeNumber(value, subject) {
|
|
40
|
-
if (process.env.NODE_ENV !== "production") {
|
|
24
|
+
export function assertValidNonNegativeNumber(value, subject) {
|
|
25
|
+
if (DEV) {
|
|
41
26
|
assertValidNumber(value, subject);
|
|
42
27
|
if (value < 0) {
|
|
43
28
|
throw new Error(`${subject}: value must be greater than or equal to 0`);
|
|
44
29
|
}
|
|
45
30
|
}
|
|
46
31
|
}
|
|
47
|
-
|
|
48
|
-
|
|
32
|
+
// Guards counts/capacities that reach `instanceCount` or a `Float32Array` length, where a
|
|
33
|
+
// fractional value would draw a truncated instance count or throw a `RangeError`.
|
|
34
|
+
export function assertValidPositiveInteger(value, subject) {
|
|
35
|
+
if (DEV) {
|
|
36
|
+
assertValidPositiveNumber(value, subject);
|
|
37
|
+
if (!Number.isInteger(value)) {
|
|
38
|
+
throw new Error(`${subject}: value must be an integer`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CPU-side twins of the compiler's math/noise builtins (`fxMix`, `fxSnoise3`, ...). A behavior
|
|
3
|
+
* kernel's authored `spawn`/`update` body calls these by name - the editor's compiler prints those
|
|
4
|
+
* exact calls - so every exported name here is a frozen contract with already-compiled kernels,
|
|
5
|
+
* like the `a_`/`p_` buffer prefixes. Never rename one without updating the compiler side in
|
|
6
|
+
* lockstep.
|
|
7
|
+
*/
|
|
8
|
+
export declare function fxFract(x: number): number;
|
|
9
|
+
export declare function fxMod(a: number, b: number): number;
|
|
10
|
+
export declare function fxMix(a: number, b: number, t: number): number;
|
|
11
|
+
export declare function fxSmoothstep(edge0: number, edge1: number, x: number): number;
|
|
12
|
+
export declare function fxHash(n: number): number;
|
|
13
|
+
/** Cheap deterministic 1D value-noise (smoothed hash interpolation), range [-1, 1]. */
|
|
14
|
+
export declare function fxValueNoise(x: number): number;
|
|
15
|
+
/** Fractal Brownian motion over {@link fxValueNoise} (gain 0.5, lacunarity 2). */
|
|
16
|
+
export declare function fxFbm(x: number, octaves: number): number;
|
|
17
|
+
/** Linearly samples a curve LUT at `t` in [0, 1] (clamped), interpolating between entries. */
|
|
18
|
+
export declare function fxSampleLut(lut: ArrayLike<number>, t: number): number;
|
|
19
|
+
/**
|
|
20
|
+
* Ashima/Gustavson "webgl-noise" simplex noise, 2D - the CPU twin of the compiler's GLSL `snoise`
|
|
21
|
+
* helper. Component arguments (rather than a vector) so it mirrors how the scalarized JS backend calls
|
|
22
|
+
* it; the arithmetic tracks the GLSL version op-for-op. Output is roughly `[-1, 1]`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function fxSnoise2(vx: number, vy: number): number;
|
|
25
|
+
/** 3D twin of {@link fxSnoise2}, mirroring the GLSL `snoise(vec3)` overload op-for-op. */
|
|
26
|
+
export declare function fxSnoise3(vx: number, vy: number, vz: number): number;
|