sparcoon 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/emitter/FXEmitter.Internal.d.ts +50 -2
- package/dist/emitter/FXEmitter.Internal.js +1 -2
- package/dist/emitter/FXEmitter.d.ts +48 -1
- package/dist/emitter/FXEmitter.js +74 -11
- package/dist/emitter/behavior/FXBehavior.d.ts +12 -2
- package/dist/emitter/behavior/FXBehavior.js +3 -2
- package/dist/emitter/behavior/FXBehaviorDirectionalForce.d.ts +25 -0
- package/dist/emitter/behavior/FXBehaviorDirectionalForce.js +74 -0
- package/dist/emitter/behavior/FXBehaviorPointForce.d.ts +40 -0
- package/dist/emitter/behavior/FXBehaviorPointForce.js +98 -0
- package/dist/emitter/behavior/FXBehaviorScaleOverLife.d.ts +9 -8
- package/dist/emitter/behavior/FXBehaviorScaleOverLife.js +12 -29
- package/dist/emitter/behavior/FXBehaviorTorqueDamping.d.ts +4 -3
- package/dist/emitter/behavior/FXBehaviorTorqueDamping.js +3 -2
- package/dist/emitter/behavior/FXBehaviorTorqueNoise.d.ts +4 -3
- package/dist/emitter/behavior/FXBehaviorTorqueNoise.js +3 -2
- package/dist/emitter/behavior/FXBehaviorTorqueOverLife.d.ts +17 -0
- package/dist/emitter/behavior/FXBehaviorTorqueOverLife.js +34 -0
- package/dist/emitter/behavior/FXBehaviorVelocityDamping.d.ts +3 -2
- package/dist/emitter/behavior/FXBehaviorVelocityDamping.js +3 -2
- package/dist/emitter/behavior/FXBehaviorVelocityNoise.d.ts +6 -5
- package/dist/emitter/behavior/FXBehaviorVelocityNoise.js +6 -5
- package/dist/emitter/behavior/FXBehaviorVelocityOverLife.d.ts +20 -0
- package/dist/emitter/behavior/FXBehaviorVelocityOverLife.js +48 -0
- package/dist/emitter/spawn/FXSpawn.d.ts +4 -2
- package/dist/emitter/spawn/FXSpawn.js +3 -2
- package/dist/emitter/spawn/FXSpawnBox.d.ts +8 -16
- package/dist/emitter/spawn/FXSpawnBox.js +23 -39
- package/dist/emitter/spawn/{FXSpawnRandomLifetime.d.ts → FXSpawnLifetime.d.ts} +3 -5
- package/dist/emitter/spawn/{FXSpawnRandomLifetime.js → FXSpawnLifetime.js} +8 -10
- package/dist/emitter/spawn/FXSpawnOffset.d.ts +9 -0
- package/dist/emitter/spawn/FXSpawnOffset.js +9 -0
- package/dist/emitter/spawn/FXSpawnPoint.d.ts +8 -0
- package/dist/emitter/spawn/FXSpawnPoint.js +8 -0
- package/dist/emitter/spawn/{FXSpawnRandomRotation.d.ts → FXSpawnRotation.d.ts} +3 -5
- package/dist/emitter/spawn/{FXSpawnRandomRotation.js → FXSpawnRotation.js} +8 -10
- package/dist/emitter/spawn/{FXSpawnRandomScale.d.ts → FXSpawnScale.d.ts} +4 -6
- package/dist/emitter/spawn/{FXSpawnRandomScale.js → FXSpawnScale.js} +12 -14
- package/dist/emitter/spawn/FXSpawnSphere.d.ts +11 -6
- package/dist/emitter/spawn/FXSpawnSphere.js +11 -6
- package/dist/emitter/spawn/{FXSpawnRandomTorque.d.ts → FXSpawnTorque.d.ts} +3 -5
- package/dist/emitter/spawn/{FXSpawnRandomTorque.js → FXSpawnTorque.js} +8 -10
- package/dist/emitter/spawn/FXSpawnVelocity.d.ts +32 -0
- package/dist/emitter/spawn/{FXSpawnRandomVelocity.js → FXSpawnVelocity.js} +26 -11
- package/dist/index.d.ts +23 -16
- package/dist/index.js +20 -16
- package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.Internal.d.ts +4 -4
- package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.Internal.js +2 -2
- package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.d.ts +55 -5
- package/dist/materials/FXDiffuseMaterial/FXDiffuseMaterial.js +9 -2
- package/dist/materials/FXMaterial/FXDepthMaterial.Internal.d.ts +3 -3
- package/dist/materials/FXMaterial/FXDistanceMaterial.Internal.d.ts +3 -3
- package/dist/materials/FXMaterial/FXMaterial.d.ts +51 -5
- package/dist/materials/FXMaterial/FXMaterial.js +5 -0
- package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.Internal.d.ts +3 -3
- package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.d.ts +3 -3
- package/dist/materials/FXUnlitMaterial/FXUnlitMaterial.js +3 -3
- package/dist/miscellaneous/curve/FXCurve1D.Internal.d.ts +20 -0
- package/dist/miscellaneous/curve/FXCurve1D.d.ts +17 -0
- package/dist/miscellaneous/curve/FXCurve1D.js +94 -0
- package/dist/miscellaneous/miscellaneous.d.ts +3 -1
- package/dist/miscellaneous/miscellaneous.js +8 -4
- package/dist/miscellaneous/texture/FXTextureView.Internal.js +6 -0
- package/dist/miscellaneous/texture/FXTextureView.js +234 -0
- package/dist/nodes/color/FXNodeColor.d.ts +7 -0
- package/dist/nodes/color/FXNodeColor.js +11 -0
- package/dist/nodes/color/FXNodeColorOverLife.d.ts +25 -0
- package/dist/nodes/color/FXNodeColorOverLife.js +60 -0
- package/dist/nodes/color/FXNodeSphericalClip.d.ts +18 -0
- package/dist/nodes/color/FXNodeSphericalClip.js +45 -0
- package/dist/nodes/normal/FXNodeFlatNormal.d.ts +7 -0
- package/dist/nodes/normal/FXNodeFlatNormal.js +17 -0
- package/dist/nodes/normal/FXNodeNormal.d.ts +7 -0
- package/dist/nodes/normal/FXNodeNormal.js +7 -0
- package/dist/nodes/normal/FXNodeSphericalNormal.d.ts +7 -0
- package/dist/nodes/normal/{FXSphericalNormalNode.js → FXNodeSphericalNormal.js} +7 -4
- package/dist/nodes/texture/FXNodeAnimatedTexture.d.ts +47 -0
- package/dist/nodes/texture/FXNodeAnimatedTexture.js +137 -0
- package/dist/nodes/texture/FXNodeStaticTexture.d.ts +18 -0
- package/dist/nodes/texture/FXNodeStaticTexture.js +60 -0
- package/dist/nodes/texture/FXNodeTexture.d.ts +7 -0
- package/dist/nodes/texture/FXNodeTexture.js +11 -0
- package/package.json +2 -4
- package/dist/emitter/behavior/FXBehaviorDirectionalGravity.d.ts +0 -21
- package/dist/emitter/behavior/FXBehaviorDirectionalGravity.js +0 -51
- package/dist/emitter/behavior/FXBehaviorPointGravity.d.ts +0 -39
- package/dist/emitter/behavior/FXBehaviorPointGravity.js +0 -116
- package/dist/emitter/spawn/FXSpawnRandomVelocity.d.ts +0 -17
- package/dist/nodes/color/FXColorNode.d.ts +0 -3
- package/dist/nodes/color/FXColorNode.js +0 -8
- package/dist/nodes/color/FXColorOverLifeNode.d.ts +0 -6
- package/dist/nodes/color/FXColorOverLifeNode.js +0 -44
- package/dist/nodes/color/FXSphericalClipNode.d.ts +0 -5
- package/dist/nodes/color/FXSphericalClipNode.js +0 -30
- package/dist/nodes/normal/FXFlatNormalNode.d.ts +0 -4
- package/dist/nodes/normal/FXFlatNormalNode.js +0 -14
- package/dist/nodes/normal/FXNormalNode.d.ts +0 -3
- package/dist/nodes/normal/FXNormalNode.js +0 -4
- package/dist/nodes/normal/FXSphericalNormalNode.d.ts +0 -4
- package/dist/nodes/texture/FXAnimatedTextureNode.d.ts +0 -12
- package/dist/nodes/texture/FXAnimatedTextureNode.js +0 -88
- package/dist/nodes/texture/FXStaticTextureNode.d.ts +0 -6
- package/dist/nodes/texture/FXStaticTextureNode.js +0 -34
- package/dist/nodes/texture/FXTextureNode.d.ts +0 -3
- package/dist/nodes/texture/FXTextureNode.js +0 -8
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { Ferrsign3 } from 'ferrsign';
|
|
2
|
+
import { Matrix3, Vector2, Texture } from 'three';
|
|
3
|
+
import { assertValidPositiveNumber, assertValidNonNegativeNumber } from '../asserts.js';
|
|
4
|
+
import { TEXTURE_DEFAULT_TEXTURE, TEXTURE_DEFAULT_SIZE } from './FXTextureView.Internal.js';
|
|
5
|
+
|
|
6
|
+
/** Wrapper for Three.js texture with atlas and trim support. Does not own the underlying texture; user must dispose it. */
|
|
7
|
+
class FXTextureView {
|
|
8
|
+
/** @param config Texture or atlas configuration */
|
|
9
|
+
constructor(config) {
|
|
10
|
+
this._private_textureInternal = TEXTURE_DEFAULT_TEXTURE;
|
|
11
|
+
this._private_sourceWidth = TEXTURE_DEFAULT_SIZE;
|
|
12
|
+
this._private_sourceHeight = TEXTURE_DEFAULT_SIZE;
|
|
13
|
+
this._private_frameX = 0;
|
|
14
|
+
this._private_frameY = 0;
|
|
15
|
+
this._private_frameWidth = TEXTURE_DEFAULT_SIZE;
|
|
16
|
+
this._private_frameHeight = TEXTURE_DEFAULT_SIZE;
|
|
17
|
+
this._private_rotatedInternal = false;
|
|
18
|
+
this._private_scaleInternal = 1;
|
|
19
|
+
this._private_trimLeft = 0;
|
|
20
|
+
this._private_trimRight = 0;
|
|
21
|
+
this._private_trimTop = 0;
|
|
22
|
+
this._private_trimBottom = 0;
|
|
23
|
+
this._private_textureDirtyInternal = false;
|
|
24
|
+
this._private_uvTransformDirtyInternal = false;
|
|
25
|
+
this._private_trimDirtyInternal = false;
|
|
26
|
+
this._private_signalDiminsionsChangedInternal = new Ferrsign3();
|
|
27
|
+
if (config !== undefined) {
|
|
28
|
+
this.set(config);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Original width before trimming in world units */
|
|
32
|
+
get width() {
|
|
33
|
+
return this._private_sourceWidth / this._private_scaleInternal;
|
|
34
|
+
}
|
|
35
|
+
/** Original height before trimming in world units */
|
|
36
|
+
get height() {
|
|
37
|
+
return this._private_sourceHeight / this._private_scaleInternal;
|
|
38
|
+
}
|
|
39
|
+
/** Visible width after trimming in world units */
|
|
40
|
+
get trimmedWidth() {
|
|
41
|
+
const frameSize = this._private_rotatedInternal ? this._private_frameHeight : this._private_frameWidth;
|
|
42
|
+
return frameSize / this._private_scaleInternal;
|
|
43
|
+
}
|
|
44
|
+
/** Visible height after trimming in world units */
|
|
45
|
+
get trimmedHeight() {
|
|
46
|
+
const frameSize = this._private_rotatedInternal ? this._private_frameWidth : this._private_frameHeight;
|
|
47
|
+
return frameSize / this._private_scaleInternal;
|
|
48
|
+
}
|
|
49
|
+
/** Underlying Three.js texture */
|
|
50
|
+
get texture() {
|
|
51
|
+
return this._private_textureInternal;
|
|
52
|
+
}
|
|
53
|
+
/** Whether sprite is rotated in atlas */
|
|
54
|
+
get rotated() {
|
|
55
|
+
return this._private_rotatedInternal;
|
|
56
|
+
}
|
|
57
|
+
/** Scale factor for resolution independence */
|
|
58
|
+
get scale() {
|
|
59
|
+
return this._private_scaleInternal;
|
|
60
|
+
}
|
|
61
|
+
/** Transparent padding around visible content */
|
|
62
|
+
get trim() {
|
|
63
|
+
const scale = this._private_scaleInternal;
|
|
64
|
+
return {
|
|
65
|
+
left: this._private_trimLeft / scale,
|
|
66
|
+
right: this._private_trimRight / scale,
|
|
67
|
+
top: this._private_trimTop / scale,
|
|
68
|
+
bottom: this._private_trimBottom / scale,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
get textureDirty() {
|
|
72
|
+
return this._private_textureDirtyInternal;
|
|
73
|
+
}
|
|
74
|
+
/** Whether UV transform inputs changed since last check */
|
|
75
|
+
get uvTransformDirty() {
|
|
76
|
+
return this._private_uvTransformDirtyInternal;
|
|
77
|
+
}
|
|
78
|
+
/** Whether trim values changed since last check */
|
|
79
|
+
get trimDirty() {
|
|
80
|
+
return this._private_trimDirtyInternal;
|
|
81
|
+
}
|
|
82
|
+
get signalDiminsionsChanged() {
|
|
83
|
+
return this._private_signalDiminsionsChangedInternal;
|
|
84
|
+
}
|
|
85
|
+
setTextureDirtyFalse() {
|
|
86
|
+
this._private_textureDirtyInternal = false;
|
|
87
|
+
}
|
|
88
|
+
/** Marks UV transform as clean. @internal */
|
|
89
|
+
setUVTransformDirtyFalse() {
|
|
90
|
+
this._private_uvTransformDirtyInternal = false;
|
|
91
|
+
}
|
|
92
|
+
/** Marks trim as clean. @internal */
|
|
93
|
+
setTrimDirtyFalse() {
|
|
94
|
+
this._private_trimDirtyInternal = false;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Calculates UV transform matrix for shader sampling.
|
|
98
|
+
* @param result Matrix to store result in
|
|
99
|
+
* @returns UV transform matrix
|
|
100
|
+
*/
|
|
101
|
+
calculateUVTransform(result = new Matrix3()) {
|
|
102
|
+
const atlasWidth = this._private_textureInternal.image?.naturalWidth ?? TEXTURE_DEFAULT_SIZE;
|
|
103
|
+
const atlasHeight = this._private_textureInternal.image?.naturalHeight ?? TEXTURE_DEFAULT_SIZE;
|
|
104
|
+
if (this._private_rotatedInternal) {
|
|
105
|
+
const physicalWidth = this._private_frameHeight;
|
|
106
|
+
const physicalHeight = this._private_frameWidth;
|
|
107
|
+
const u0 = this._private_frameX / atlasWidth;
|
|
108
|
+
const v0 = (atlasHeight - this._private_frameY - physicalHeight) / atlasHeight;
|
|
109
|
+
const uSize = physicalWidth / atlasWidth;
|
|
110
|
+
const vSize = physicalHeight / atlasHeight;
|
|
111
|
+
result.set(0, uSize, u0, -vSize, 0, v0 + vSize, 0, 0, 1);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
const u0 = this._private_frameX / atlasWidth;
|
|
115
|
+
const v0 = (atlasHeight - this._private_frameY - this._private_frameHeight) / atlasHeight;
|
|
116
|
+
const uSize = this._private_frameWidth / atlasWidth;
|
|
117
|
+
const vSize = this._private_frameHeight / atlasHeight;
|
|
118
|
+
result.set(uSize, 0, u0, 0, vSize, v0, 0, 0, 1);
|
|
119
|
+
}
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Gets original dimensions before trimming.
|
|
124
|
+
* @param result Vector to store result in
|
|
125
|
+
* @returns Width and height in world units
|
|
126
|
+
*/
|
|
127
|
+
getResolution(result = new Vector2()) {
|
|
128
|
+
return result.set(this.width, this.height);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Replaces texture with new configuration.
|
|
132
|
+
* @param config Texture or atlas configuration
|
|
133
|
+
*/
|
|
134
|
+
set(config) {
|
|
135
|
+
const lastWidth = this.width;
|
|
136
|
+
const lastHeight = this.height;
|
|
137
|
+
if (config instanceof Texture) {
|
|
138
|
+
this._private_setFromTexture(config);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
this._private_setFromAtlasConfig(config);
|
|
142
|
+
}
|
|
143
|
+
if (this.width !== lastWidth || this.height !== lastHeight) {
|
|
144
|
+
this._private_signalDiminsionsChangedInternal.emit(this.width, this.height, this);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
_private_setFromTexture(texture) {
|
|
148
|
+
const width = texture.image?.naturalWidth ?? TEXTURE_DEFAULT_SIZE;
|
|
149
|
+
const height = texture.image?.naturalHeight ?? TEXTURE_DEFAULT_SIZE;
|
|
150
|
+
this._private_sourceWidth = width;
|
|
151
|
+
this._private_sourceHeight = height;
|
|
152
|
+
if (this.texture !== texture) {
|
|
153
|
+
this._private_textureInternal = texture;
|
|
154
|
+
this._private_textureDirtyInternal = true;
|
|
155
|
+
}
|
|
156
|
+
if (this._private_frameX !== 0 ||
|
|
157
|
+
this._private_frameY !== 0 ||
|
|
158
|
+
this._private_frameWidth !== width ||
|
|
159
|
+
this._private_frameHeight !== height ||
|
|
160
|
+
this._private_scaleInternal !== 1 || this._private_rotatedInternal) {
|
|
161
|
+
this._private_frameX = 0;
|
|
162
|
+
this._private_frameY = 0;
|
|
163
|
+
this._private_frameWidth = width;
|
|
164
|
+
this._private_frameHeight = height;
|
|
165
|
+
this._private_rotatedInternal = false;
|
|
166
|
+
this._private_scaleInternal = 1;
|
|
167
|
+
this._private_uvTransformDirtyInternal = true;
|
|
168
|
+
}
|
|
169
|
+
if (this._private_trimLeft !== 0 ||
|
|
170
|
+
this._private_trimRight !== 0 ||
|
|
171
|
+
this._private_trimTop !== 0 ||
|
|
172
|
+
this._private_trimBottom !== 0) {
|
|
173
|
+
this._private_trimLeft = 0;
|
|
174
|
+
this._private_trimRight = 0;
|
|
175
|
+
this._private_trimTop = 0;
|
|
176
|
+
this._private_trimBottom = 0;
|
|
177
|
+
this._private_trimDirtyInternal = true;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
_private_setFromAtlasConfig(config) {
|
|
181
|
+
assertValidPositiveNumber(config.sourceSize.w, "FXTextureView.setFromAtlasConfig.config.sourceSize.w");
|
|
182
|
+
assertValidPositiveNumber(config.sourceSize.h, "FXTextureView.setFromAtlasConfig.config.sourceSize.h");
|
|
183
|
+
assertValidNonNegativeNumber(config.frame.x, "FXTextureView.setFromAtlasConfig.config.frame.x");
|
|
184
|
+
assertValidNonNegativeNumber(config.frame.y, "FXTextureView.setFromAtlasConfig.config.frame.y");
|
|
185
|
+
assertValidPositiveNumber(config.frame.w, "FXTextureView.setFromAtlasConfig.config.frame.w");
|
|
186
|
+
assertValidPositiveNumber(config.frame.h, "FXTextureView.setFromAtlasConfig.config.frame.h");
|
|
187
|
+
assertValidNonNegativeNumber(config.spriteSourceSize.x, "FXTextureView.setFromAtlasConfig.config.spriteSourceSize.x");
|
|
188
|
+
assertValidNonNegativeNumber(config.spriteSourceSize.y, "FXTextureView.setFromAtlasConfig.config.spriteSourceSize.y");
|
|
189
|
+
assertValidPositiveNumber(config.spriteSourceSize.w, "FXTextureView.setFromAtlasConfig.config.spriteSourceSize.w");
|
|
190
|
+
assertValidPositiveNumber(config.spriteSourceSize.h, "FXTextureView.setFromAtlasConfig.config.spriteSourceSize.h");
|
|
191
|
+
if (config.scale !== undefined) {
|
|
192
|
+
assertValidPositiveNumber(config.scale, "FXTextureView.setFromAtlasConfig.config.scale");
|
|
193
|
+
}
|
|
194
|
+
this._private_sourceWidth = config.sourceSize.w;
|
|
195
|
+
this._private_sourceHeight = config.sourceSize.h;
|
|
196
|
+
if (this.texture !== config.texture) {
|
|
197
|
+
this._private_textureInternal = config.texture;
|
|
198
|
+
this._private_textureDirtyInternal = true;
|
|
199
|
+
}
|
|
200
|
+
const scale = config.scale ?? 1;
|
|
201
|
+
const rotated = config.rotated ?? false;
|
|
202
|
+
if (this._private_frameX !== 0 ||
|
|
203
|
+
this._private_frameY !== 0 ||
|
|
204
|
+
this._private_frameWidth !== config.frame.w ||
|
|
205
|
+
this._private_frameHeight !== config.frame.h ||
|
|
206
|
+
this._private_scaleInternal !== scale ||
|
|
207
|
+
this._private_rotatedInternal !== rotated) {
|
|
208
|
+
this._private_frameX = config.frame.x;
|
|
209
|
+
this._private_frameY = config.frame.y;
|
|
210
|
+
this._private_frameWidth = config.frame.w;
|
|
211
|
+
this._private_frameHeight = config.frame.h;
|
|
212
|
+
this._private_scaleInternal = scale;
|
|
213
|
+
this._private_rotatedInternal = rotated;
|
|
214
|
+
this._private_uvTransformDirtyInternal = true;
|
|
215
|
+
}
|
|
216
|
+
const spriteSource = config.spriteSourceSize;
|
|
217
|
+
const trimLeft = spriteSource.x;
|
|
218
|
+
const trimTop = spriteSource.y;
|
|
219
|
+
const trimRight = this._private_sourceWidth - spriteSource.x - spriteSource.w;
|
|
220
|
+
const trimBottom = this._private_sourceHeight - spriteSource.y - spriteSource.h;
|
|
221
|
+
if (this._private_trimLeft !== trimLeft ||
|
|
222
|
+
this._private_trimRight !== trimRight ||
|
|
223
|
+
this._private_trimTop !== trimTop ||
|
|
224
|
+
this._private_trimBottom !== trimBottom) {
|
|
225
|
+
this._private_trimLeft = trimLeft;
|
|
226
|
+
this._private_trimRight = trimRight;
|
|
227
|
+
this._private_trimTop = trimTop;
|
|
228
|
+
this._private_trimBottom = trimBottom;
|
|
229
|
+
this._private_trimDirtyInternal = true;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export { FXTextureView };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { FXColor } from "../../miscellaneous/color/FXColor";
|
|
2
|
+
import type { FXCurve1DConfig } from "../../miscellaneous/curve/FXCurve1D";
|
|
3
|
+
import { FXCurve1D } from "../../miscellaneous/curve/FXCurve1D";
|
|
4
|
+
import { FXNodeColor } from "./FXNodeColor";
|
|
5
|
+
/**
|
|
6
|
+
* Color node that animates particle color along a gradient curve over its lifetime
|
|
7
|
+
*/
|
|
8
|
+
export declare class FXNodeColorOverLife extends FXNodeColor {
|
|
9
|
+
private readonly uniformName;
|
|
10
|
+
private curveInternal;
|
|
11
|
+
/**
|
|
12
|
+
* @param curve - Color gradient defining the animation over the particle lifetime
|
|
13
|
+
*/
|
|
14
|
+
constructor(curve: FXCurve1DConfig<FXColor>);
|
|
15
|
+
/** Current color gradient curve */
|
|
16
|
+
get curve(): FXCurve1D<FXColor>;
|
|
17
|
+
/**
|
|
18
|
+
* Replaces the color gradient curve and rebuilds the backing texture
|
|
19
|
+
*
|
|
20
|
+
* @param value - New color gradient configuration
|
|
21
|
+
*/
|
|
22
|
+
set curve(value: FXCurve1DConfig<FXColor>);
|
|
23
|
+
/** Releases the backing gradient texture */
|
|
24
|
+
destroy(): void;
|
|
25
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { FXCurve1D } from '../../miscellaneous/curve/FXCurve1D.js';
|
|
2
|
+
import { buildGradientTexture, getNextInstanceId } from '../../miscellaneous/miscellaneous.js';
|
|
3
|
+
import { checkSRGBSupport } from '../../miscellaneous/webglCapabilities.js';
|
|
4
|
+
import { FXNodeColor } from './FXNodeColor.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Color node that animates particle color along a gradient curve over its lifetime
|
|
8
|
+
*/
|
|
9
|
+
class FXNodeColorOverLife extends FXNodeColor {
|
|
10
|
+
/**
|
|
11
|
+
* @param curve - Color gradient defining the animation over the particle lifetime
|
|
12
|
+
*/
|
|
13
|
+
constructor(curve) {
|
|
14
|
+
super();
|
|
15
|
+
const id = getNextInstanceId();
|
|
16
|
+
this._private_uniformName = `u_ColorOverLife_${id}`;
|
|
17
|
+
this._private_curveInternal = new FXCurve1D(curve);
|
|
18
|
+
this._private_gradientTexture = buildGradientTexture(this._private_curveInternal);
|
|
19
|
+
this.affectsDepth = true;
|
|
20
|
+
this.cacheKey = "color-over-life";
|
|
21
|
+
this.uniformDeclarations = [`uniform sampler2D ${this._private_uniformName};`];
|
|
22
|
+
this.uniforms = {
|
|
23
|
+
[this._private_uniformName]: { value: this._private_gradientTexture },
|
|
24
|
+
};
|
|
25
|
+
this.helperFunctions = `
|
|
26
|
+
vec4 fxSampleColorOverLife(sampler2D gradientTexture) {
|
|
27
|
+
// Normalized particle age [0, 1] maps to the gradient texture's x-axis
|
|
28
|
+
float lifetimeRatio = clamp(PARTICLE_AGE / PARTICLE_LIFETIME, 0.0, 1.0);
|
|
29
|
+
vec4 color = texture2D(gradientTexture, vec2(lifetimeRatio, 0.5));
|
|
30
|
+
|
|
31
|
+
// Apply gamma correction when sRGB textures are not natively supported
|
|
32
|
+
${checkSRGBSupport() ? "" : "color = vec4(pow(color.rgb, vec3(2.2)), color.a);"}
|
|
33
|
+
|
|
34
|
+
return color;
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
this.colorExpression = `fxSampleColorOverLife(${this._private_uniformName})`;
|
|
38
|
+
}
|
|
39
|
+
/** Current color gradient curve */
|
|
40
|
+
get curve() {
|
|
41
|
+
return this._private_curveInternal;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Replaces the color gradient curve and rebuilds the backing texture
|
|
45
|
+
*
|
|
46
|
+
* @param value - New color gradient configuration
|
|
47
|
+
*/
|
|
48
|
+
set curve(value) {
|
|
49
|
+
this._private_curveInternal = new FXCurve1D(value);
|
|
50
|
+
this._private_gradientTexture.dispose();
|
|
51
|
+
this._private_gradientTexture = buildGradientTexture(this._private_curveInternal);
|
|
52
|
+
this.uniforms[this._private_uniformName].value = this._private_gradientTexture;
|
|
53
|
+
}
|
|
54
|
+
/** Releases the backing gradient texture */
|
|
55
|
+
destroy() {
|
|
56
|
+
this._private_gradientTexture.dispose();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export { FXNodeColorOverLife };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { FXNodeColor } from "./FXNodeColor";
|
|
2
|
+
/**
|
|
3
|
+
* Color node that clips a particle to a circle with a configurable soft edge
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Fades alpha from fully opaque at `innerRadius` to fully transparent at the billboard edge
|
|
7
|
+
* (UV radius `0.5`). An `innerRadius` of `0.5` produces a hard circular clip with no gradient.
|
|
8
|
+
*/
|
|
9
|
+
export declare class FXNodeSphericalClip extends FXNodeColor {
|
|
10
|
+
private readonly uniformInnerRadius;
|
|
11
|
+
/**
|
|
12
|
+
* @param innerRadius - UV radius where the fade begins; must be non-negative. Defaults to `0.5`
|
|
13
|
+
*/
|
|
14
|
+
constructor(innerRadius?: number);
|
|
15
|
+
/** UV radius of the fully opaque inner area */
|
|
16
|
+
get innerRadius(): number;
|
|
17
|
+
set innerRadius(value: number);
|
|
18
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { assertValidNonNegativeNumber } from '../../miscellaneous/asserts.js';
|
|
2
|
+
import { getNextInstanceId } from '../../miscellaneous/miscellaneous.js';
|
|
3
|
+
import { FXNodeColor } from './FXNodeColor.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Color node that clips a particle to a circle with a configurable soft edge
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* Fades alpha from fully opaque at `innerRadius` to fully transparent at the billboard edge
|
|
10
|
+
* (UV radius `0.5`). An `innerRadius` of `0.5` produces a hard circular clip with no gradient.
|
|
11
|
+
*/
|
|
12
|
+
class FXNodeSphericalClip extends FXNodeColor {
|
|
13
|
+
/**
|
|
14
|
+
* @param innerRadius - UV radius where the fade begins; must be non-negative. Defaults to `0.5`
|
|
15
|
+
*/
|
|
16
|
+
constructor(innerRadius = 0.5) {
|
|
17
|
+
super();
|
|
18
|
+
assertValidNonNegativeNumber(innerRadius, "FXNodeSphericalClip.constructor.innerRadius");
|
|
19
|
+
const id = getNextInstanceId();
|
|
20
|
+
this._private_uniformInnerRadius = `u_SphericalClipInnerRadius_${id}`;
|
|
21
|
+
this.affectsDepth = true;
|
|
22
|
+
this.cacheKey = "spherical-clip";
|
|
23
|
+
this.uniformDeclarations = [`uniform float ${this._private_uniformInnerRadius};`];
|
|
24
|
+
this.uniforms = {
|
|
25
|
+
[this._private_uniformInnerRadius]: { value: innerRadius },
|
|
26
|
+
};
|
|
27
|
+
this.helperFunctions = `
|
|
28
|
+
vec4 fxApplySphericalClip(float innerRadius) {
|
|
29
|
+
float distanceToCenter = distance(p_uv, vec2(0.5, 0.5));
|
|
30
|
+
return vec4(1.0, 1.0, 1.0, 1.0 - smoothstep(innerRadius, 0.5, distanceToCenter));
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
33
|
+
this.colorExpression = `fxApplySphericalClip(${this._private_uniformInnerRadius})`;
|
|
34
|
+
}
|
|
35
|
+
/** UV radius of the fully opaque inner area */
|
|
36
|
+
get innerRadius() {
|
|
37
|
+
return this.uniforms[this._private_uniformInnerRadius].value;
|
|
38
|
+
}
|
|
39
|
+
set innerRadius(value) {
|
|
40
|
+
assertValidNonNegativeNumber(value, "FXNodeSphericalClip.innerRadius");
|
|
41
|
+
this.uniforms[this._private_uniformInnerRadius].value = value;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { FXNodeSphericalClip };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { FXNodeNormal } from './FXNodeNormal.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normal node that outputs a constant face-aligned normal pointing toward the camera
|
|
5
|
+
*/
|
|
6
|
+
class FXNodeFlatNormal extends FXNodeNormal {
|
|
7
|
+
constructor() {
|
|
8
|
+
super();
|
|
9
|
+
this.cacheKey = "flat-normal";
|
|
10
|
+
this.uniformDeclarations = [];
|
|
11
|
+
this.uniforms = {};
|
|
12
|
+
this.helperFunctions = "";
|
|
13
|
+
this.normalExpression = "vec3(0.0, 0.0, 1.0)";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { FXNodeFlatNormal };
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FXNodeNormal } from './FXNodeNormal.js';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Normal node that makes flat billboard particles appear as spheres under directional lighting
|
|
5
|
+
*/
|
|
6
|
+
class FXNodeSphericalNormal extends FXNodeNormal {
|
|
4
7
|
constructor() {
|
|
5
8
|
super();
|
|
6
|
-
this.cacheKey = "spherical";
|
|
9
|
+
this.cacheKey = "spherical-normal";
|
|
7
10
|
this.uniformDeclarations = [];
|
|
8
11
|
this.uniforms = {};
|
|
9
12
|
this.helperFunctions = `
|
|
@@ -16,4 +19,4 @@ class FXSphericalNormalNode extends FXNormalNode {
|
|
|
16
19
|
}
|
|
17
20
|
}
|
|
18
21
|
|
|
19
|
-
export {
|
|
22
|
+
export { FXNodeSphericalNormal };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { FXTextureView } from "../../miscellaneous/texture/FXTextureView";
|
|
2
|
+
import type { FXTextureConfig } from "../../miscellaneous/texture/FXTextureView.Internal";
|
|
3
|
+
import { FXNodeTexture } from "./FXNodeTexture";
|
|
4
|
+
/**
|
|
5
|
+
* Options for {@link FXNodeAnimatedTexture}
|
|
6
|
+
*/
|
|
7
|
+
export interface FXNodeAnimatedTextureOptions {
|
|
8
|
+
/** Texture or atlas configuration containing all animation frames */
|
|
9
|
+
texture: FXTextureConfig;
|
|
10
|
+
/** Number of frame columns in the sprite sheet */
|
|
11
|
+
columns: number;
|
|
12
|
+
/** Number of frame rows in the sprite sheet */
|
|
13
|
+
rows: number;
|
|
14
|
+
/**
|
|
15
|
+
* If true, blends between adjacent frames for smoother animation
|
|
16
|
+
*
|
|
17
|
+
* @defaultValue `false`
|
|
18
|
+
*/
|
|
19
|
+
interpolate?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Texture node that animates through sprite sheet frames over a particle's lifetime
|
|
23
|
+
*
|
|
24
|
+
* @remarks
|
|
25
|
+
* Frames advance from the first to the last as the particle ages. The total frame count
|
|
26
|
+
* is `columns * rows`. Both `columns` and `rows` must be positive integers.
|
|
27
|
+
*/
|
|
28
|
+
export declare class FXNodeAnimatedTexture extends FXNodeTexture {
|
|
29
|
+
private readonly uniformTexture;
|
|
30
|
+
private readonly uniformUVTransform;
|
|
31
|
+
private readonly uniformColumns;
|
|
32
|
+
private readonly uniformRows;
|
|
33
|
+
private readonly textureViewInternal;
|
|
34
|
+
/**
|
|
35
|
+
* @param options - Sprite sheet configuration
|
|
36
|
+
*/
|
|
37
|
+
constructor({ texture, columns, rows, interpolate }: FXNodeAnimatedTextureOptions);
|
|
38
|
+
/** View describing the texture and its atlas region */
|
|
39
|
+
get textureView(): FXTextureView;
|
|
40
|
+
/** Number of frame columns in the sprite sheet */
|
|
41
|
+
get columns(): number;
|
|
42
|
+
/** Number of frame rows in the sprite sheet */
|
|
43
|
+
get rows(): number;
|
|
44
|
+
set textureView(config: FXTextureConfig);
|
|
45
|
+
set columns(value: number);
|
|
46
|
+
set rows(value: number);
|
|
47
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { assertValidPositiveNumber } from '../../miscellaneous/asserts.js';
|
|
2
|
+
import { getNextInstanceId } from '../../miscellaneous/miscellaneous.js';
|
|
3
|
+
import { FXTextureView } from '../../miscellaneous/texture/FXTextureView.js';
|
|
4
|
+
import { checkSRGBSupport } from '../../miscellaneous/webglCapabilities.js';
|
|
5
|
+
import { FXNodeTexture } from './FXNodeTexture.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Texture node that animates through sprite sheet frames over a particle's lifetime
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* Frames advance from the first to the last as the particle ages. The total frame count
|
|
12
|
+
* is `columns * rows`. Both `columns` and `rows` must be positive integers.
|
|
13
|
+
*/
|
|
14
|
+
class FXNodeAnimatedTexture extends FXNodeTexture {
|
|
15
|
+
/**
|
|
16
|
+
* @param options - Sprite sheet configuration
|
|
17
|
+
*/
|
|
18
|
+
constructor({ texture, columns, rows, interpolate = false }) {
|
|
19
|
+
super();
|
|
20
|
+
assertValidPositiveNumber(columns, "FXNodeAnimatedTexture.constructor.columns");
|
|
21
|
+
assertValidPositiveNumber(rows, "FXNodeAnimatedTexture.constructor.rows");
|
|
22
|
+
const idx = getNextInstanceId();
|
|
23
|
+
this._private_uniformTexture = `u_AnimatedTexture_${idx}`;
|
|
24
|
+
this._private_uniformUVTransform = `u_AnimatedTextureUVTransform_${idx}`;
|
|
25
|
+
this._private_uniformColumns = `u_AnimatedTextureColumns_${idx}`;
|
|
26
|
+
this._private_uniformRows = `u_AnimatedTextureRows_${idx}`;
|
|
27
|
+
this._private_textureViewInternal = new FXTextureView(texture);
|
|
28
|
+
const uvTransform = this._private_textureViewInternal.calculateUVTransform();
|
|
29
|
+
this._private_textureViewInternal.setTextureDirtyFalse();
|
|
30
|
+
this._private_textureViewInternal.setUVTransformDirtyFalse();
|
|
31
|
+
const gammaCorrection = checkSRGBSupport()
|
|
32
|
+
? ""
|
|
33
|
+
: "color = vec4(pow(color.rgb, vec3(2.2)), color.a);";
|
|
34
|
+
this.affectsDepth = true;
|
|
35
|
+
this.cacheKey = interpolate ? "animated-texture-interpolated" : "animated-texture";
|
|
36
|
+
this.uniformDeclarations = [
|
|
37
|
+
`uniform sampler2D ${this._private_uniformTexture};`,
|
|
38
|
+
`uniform mat3 ${this._private_uniformUVTransform};`,
|
|
39
|
+
`uniform float ${this._private_uniformColumns};`,
|
|
40
|
+
`uniform float ${this._private_uniformRows};`,
|
|
41
|
+
];
|
|
42
|
+
this.uniforms = {
|
|
43
|
+
[this._private_uniformTexture]: { value: this._private_textureViewInternal.texture },
|
|
44
|
+
[this._private_uniformUVTransform]: { value: uvTransform },
|
|
45
|
+
[this._private_uniformColumns]: { value: columns },
|
|
46
|
+
[this._private_uniformRows]: { value: rows },
|
|
47
|
+
};
|
|
48
|
+
this.helperFunctions = interpolate
|
|
49
|
+
? `
|
|
50
|
+
vec2 fxFrameToUV(vec2 uv, float frame, float columns, float rows) {
|
|
51
|
+
float column = mod(frame, columns);
|
|
52
|
+
float row = floor(frame / columns);
|
|
53
|
+
return vec2((column + uv.x) / columns, 1.0 - (row + 1.0 - uv.y) / rows);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
vec4 fxSampleAnimatedTexture(sampler2D spriteSheet, mat3 uvTransform, vec2 uv, float columns, float rows) {
|
|
57
|
+
float totalFrames = columns * rows;
|
|
58
|
+
|
|
59
|
+
// Fractional frame position drives both frame selection and blend weight
|
|
60
|
+
float exactFrame = PARTICLE_AGE / PARTICLE_LIFETIME * totalFrames;
|
|
61
|
+
float currentFrame = clamp(floor(exactFrame), 0.0, totalFrames - 1.0);
|
|
62
|
+
float nextFrame = clamp(currentFrame + 1.0, 0.0, totalFrames - 1.0);
|
|
63
|
+
float blendWeight = fract(exactFrame);
|
|
64
|
+
|
|
65
|
+
vec2 currentUV = (uvTransform * vec3(fxFrameToUV(uv, currentFrame, columns, rows), 1.0)).xy;
|
|
66
|
+
vec2 nextUV = (uvTransform * vec3(fxFrameToUV(uv, nextFrame, columns, rows), 1.0)).xy;
|
|
67
|
+
|
|
68
|
+
vec4 color = mix(
|
|
69
|
+
texture2D(spriteSheet, currentUV),
|
|
70
|
+
texture2D(spriteSheet, nextUV),
|
|
71
|
+
blendWeight
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// Apply gamma correction when sRGB textures are not natively supported
|
|
75
|
+
${gammaCorrection}
|
|
76
|
+
|
|
77
|
+
return color;
|
|
78
|
+
}
|
|
79
|
+
`
|
|
80
|
+
: `
|
|
81
|
+
vec4 fxSampleAnimatedTexture(sampler2D spriteSheet, mat3 uvTransform, vec2 uv, float columns, float rows) {
|
|
82
|
+
// Determine which frame to display based on normalized particle age
|
|
83
|
+
float totalFrames = columns * rows;
|
|
84
|
+
float frame = floor(PARTICLE_AGE / PARTICLE_LIFETIME * totalFrames);
|
|
85
|
+
frame = clamp(frame, 0.0, totalFrames - 1.0);
|
|
86
|
+
|
|
87
|
+
// Locate the frame's column and row in the sprite sheet grid
|
|
88
|
+
float column = mod(frame, columns);
|
|
89
|
+
float row = floor(frame / columns);
|
|
90
|
+
|
|
91
|
+
// Remap UV to the frame's tile region, then apply atlas transform
|
|
92
|
+
vec2 frameUV = vec2((column + uv.x) / columns, 1.0 - (row + 1.0 - uv.y) / rows);
|
|
93
|
+
vec2 atlasUV = (uvTransform * vec3(frameUV, 1.0)).xy;
|
|
94
|
+
vec4 color = texture2D(spriteSheet, atlasUV);
|
|
95
|
+
|
|
96
|
+
// Apply gamma correction when sRGB textures are not natively supported
|
|
97
|
+
${gammaCorrection}
|
|
98
|
+
|
|
99
|
+
return color;
|
|
100
|
+
}
|
|
101
|
+
`;
|
|
102
|
+
this.colorExpression = `fxSampleAnimatedTexture(${this._private_uniformTexture}, ${this._private_uniformUVTransform}, p_uv, ${this._private_uniformColumns}, ${this._private_uniformRows})`;
|
|
103
|
+
}
|
|
104
|
+
/** View describing the texture and its atlas region */
|
|
105
|
+
get textureView() {
|
|
106
|
+
return this._private_textureViewInternal;
|
|
107
|
+
}
|
|
108
|
+
/** Number of frame columns in the sprite sheet */
|
|
109
|
+
get columns() {
|
|
110
|
+
return this.uniforms[this._private_uniformColumns].value;
|
|
111
|
+
}
|
|
112
|
+
/** Number of frame rows in the sprite sheet */
|
|
113
|
+
get rows() {
|
|
114
|
+
return this.uniforms[this._private_uniformRows].value;
|
|
115
|
+
}
|
|
116
|
+
set textureView(config) {
|
|
117
|
+
this._private_textureViewInternal.set(config);
|
|
118
|
+
if (this._private_textureViewInternal.textureDirty) {
|
|
119
|
+
this.uniforms[this._private_uniformTexture].value = this._private_textureViewInternal.texture;
|
|
120
|
+
this._private_textureViewInternal.setTextureDirtyFalse();
|
|
121
|
+
}
|
|
122
|
+
if (this._private_textureViewInternal.uvTransformDirty) {
|
|
123
|
+
this._private_textureViewInternal.calculateUVTransform(this.uniforms[this._private_uniformUVTransform].value);
|
|
124
|
+
this._private_textureViewInternal.setUVTransformDirtyFalse();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
set columns(value) {
|
|
128
|
+
assertValidPositiveNumber(value, "FXNodeAnimatedTexture.columns");
|
|
129
|
+
this.uniforms[this._private_uniformColumns].value = value;
|
|
130
|
+
}
|
|
131
|
+
set rows(value) {
|
|
132
|
+
assertValidPositiveNumber(value, "FXNodeAnimatedTexture.rows");
|
|
133
|
+
this.uniforms[this._private_uniformRows].value = value;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export { FXNodeAnimatedTexture };
|