sparcoon 0.4.3 → 0.4.5
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/emitter/FXEmitter.Internal.d.ts +18 -0
- package/dist/emitter/FXEmitter.Internal.js +4 -1
- package/dist/emitter/FXEmitter.d.ts +10 -17
- package/dist/emitter/FXEmitter.js +136 -77
- package/dist/emitter/spawn/FXSpawnSphere.d.ts +2 -2
- package/dist/emitter/spawn/FXSpawnSphere.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/materials/FXMaterial/FXDistanceMaterial.Internal.js +5 -5
- package/dist/nodes/color/FXSphericalClipNode.d.ts +5 -0
- package/dist/nodes/color/FXSphericalClipNode.js +30 -0
- package/package.json +1 -1
|
@@ -1,13 +1,31 @@
|
|
|
1
|
+
import type { Camera } from "three";
|
|
1
2
|
import type { FXPropertyName, GLTypeInfo } from "../instancedParticle/shared";
|
|
3
|
+
import type { FXEmitter } from "./FXEmitter";
|
|
4
|
+
export interface FXEmitterOptions {
|
|
5
|
+
expectedCapacity: number;
|
|
6
|
+
capacityStep: number;
|
|
7
|
+
automaticallyDestroyModules: boolean;
|
|
8
|
+
castShadow: boolean;
|
|
9
|
+
receiveShadow: boolean;
|
|
10
|
+
sortCamera: Camera;
|
|
11
|
+
sortFraction: number;
|
|
12
|
+
}
|
|
13
|
+
export interface FXEmitterBurstOptions {
|
|
14
|
+
delay: number;
|
|
15
|
+
}
|
|
2
16
|
export interface FXEmitterPlayOptions {
|
|
17
|
+
delay: number;
|
|
3
18
|
duration: number;
|
|
4
19
|
}
|
|
20
|
+
export declare const EMITTERS: FXEmitter[];
|
|
5
21
|
export declare const EMITTER_DEFAULT_EXPECTED_CAPACITY = 32;
|
|
6
22
|
export declare const EMITTER_DEFAULT_CAPACITY_STEP = 32;
|
|
7
23
|
export declare const EMITTER_DEFAULT_AUTOMATICALLY_DESTROY_MODULES = true;
|
|
8
24
|
export declare const EMITTER_DEFAULT_CAST_SHADOW = false;
|
|
9
25
|
export declare const EMITTER_DEFAULT_RECEIVE_SHADOW = false;
|
|
10
26
|
export declare const EMITTER_DEFAULT_SORT_FRACTION: number;
|
|
27
|
+
export declare const EMITTER_DEFAULT_PREWARM_MIN_STEP_DURATION: number;
|
|
28
|
+
export declare const EMITTER_DEFAULT_PREWARM_MAX_STEP_COUNT = 10;
|
|
11
29
|
export declare function collectProperties(keeper: Record<string, GLTypeInfo>, modules: readonly {
|
|
12
30
|
requiredProperties?: Record<string, FXPropertyName>;
|
|
13
31
|
}[], debugContext: string): void;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { resolveGLSLTypeInfo } from '../instancedParticle/shared.js';
|
|
2
2
|
|
|
3
|
+
const EMITTERS = new Array();
|
|
3
4
|
const EMITTER_DEFAULT_EXPECTED_CAPACITY = 32;
|
|
4
5
|
const EMITTER_DEFAULT_CAPACITY_STEP = 32;
|
|
5
6
|
const EMITTER_DEFAULT_AUTOMATICALLY_DESTROY_MODULES = true;
|
|
6
7
|
const EMITTER_DEFAULT_CAST_SHADOW = false;
|
|
7
8
|
const EMITTER_DEFAULT_RECEIVE_SHADOW = false;
|
|
8
9
|
const EMITTER_DEFAULT_SORT_FRACTION = 1 / 10;
|
|
10
|
+
const EMITTER_DEFAULT_PREWARM_MIN_STEP_DURATION = 1 / 60;
|
|
11
|
+
const EMITTER_DEFAULT_PREWARM_MAX_STEP_COUNT = 10;
|
|
9
12
|
function collectProperties(keeper, modules, debugContext) {
|
|
10
13
|
for (const module of modules) {
|
|
11
14
|
if (module.requiredProperties === undefined) {
|
|
@@ -24,4 +27,4 @@ function collectProperties(keeper, modules, debugContext) {
|
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
29
|
|
|
27
|
-
export { EMITTER_DEFAULT_AUTOMATICALLY_DESTROY_MODULES, EMITTER_DEFAULT_CAPACITY_STEP, EMITTER_DEFAULT_CAST_SHADOW, EMITTER_DEFAULT_EXPECTED_CAPACITY, EMITTER_DEFAULT_RECEIVE_SHADOW, EMITTER_DEFAULT_SORT_FRACTION, collectProperties };
|
|
30
|
+
export { EMITTERS, EMITTER_DEFAULT_AUTOMATICALLY_DESTROY_MODULES, EMITTER_DEFAULT_CAPACITY_STEP, EMITTER_DEFAULT_CAST_SHADOW, EMITTER_DEFAULT_EXPECTED_CAPACITY, EMITTER_DEFAULT_PREWARM_MAX_STEP_COUNT, EMITTER_DEFAULT_PREWARM_MIN_STEP_DURATION, EMITTER_DEFAULT_RECEIVE_SHADOW, EMITTER_DEFAULT_SORT_FRACTION, collectProperties };
|
|
@@ -2,17 +2,8 @@ import type { Camera } from "three";
|
|
|
2
2
|
import { Object3D } from "three";
|
|
3
3
|
import type { FXMaterial } from "../materials/FXMaterial/FXMaterial";
|
|
4
4
|
import type { FXBehavior } from "./behavior/FXBehavior";
|
|
5
|
-
import type { FXEmitterPlayOptions } from "./FXEmitter.Internal";
|
|
5
|
+
import type { FXEmitterBurstOptions, FXEmitterOptions, FXEmitterPlayOptions } from "./FXEmitter.Internal";
|
|
6
6
|
import type { FXSpawn } from "./spawn/FXSpawn";
|
|
7
|
-
export interface FXEmitterOptions {
|
|
8
|
-
expectedCapacity: number;
|
|
9
|
-
capacityStep: number;
|
|
10
|
-
automaticallyDestroyModules: boolean;
|
|
11
|
-
castShadow: boolean;
|
|
12
|
-
receiveShadow: boolean;
|
|
13
|
-
sortCamera: Camera;
|
|
14
|
-
sortFraction: number;
|
|
15
|
-
}
|
|
16
7
|
export declare class FXEmitter extends Object3D {
|
|
17
8
|
private readonly spawnSequence;
|
|
18
9
|
private readonly behaviorSequence;
|
|
@@ -21,18 +12,20 @@ export declare class FXEmitter extends Object3D {
|
|
|
21
12
|
sortFraction: number;
|
|
22
13
|
private readonly mesh;
|
|
23
14
|
private readonly material;
|
|
24
|
-
private
|
|
25
|
-
private
|
|
26
|
-
private
|
|
27
|
-
private emissionElapsed;
|
|
15
|
+
private nextHandler;
|
|
16
|
+
private readonly pendingBursts;
|
|
17
|
+
private readonly activePlays;
|
|
28
18
|
private sortingAccumulator;
|
|
29
19
|
private readonly sortingCameraWorldPosition;
|
|
30
20
|
constructor(spawnSequence: FXSpawn[], behaviorSequence: readonly FXBehavior[], material: FXMaterial, options?: Partial<FXEmitterOptions>);
|
|
31
21
|
static onWillRender: (deltaTime: number) => void;
|
|
32
22
|
destroy(): void;
|
|
33
|
-
burst(count: number):
|
|
23
|
+
burst(count: number, options?: Partial<FXEmitterBurstOptions>): number;
|
|
24
|
+
play(rate: number, options?: Partial<FXEmitterPlayOptions>): number;
|
|
25
|
+
stop(handler?: number): boolean;
|
|
26
|
+
prewarm(duration: number, stepDuration?: number): void;
|
|
34
27
|
reset(): void;
|
|
35
|
-
|
|
36
|
-
stop(): void;
|
|
28
|
+
private tick;
|
|
37
29
|
private readonly onRendering;
|
|
30
|
+
private spawnBurst;
|
|
38
31
|
}
|
|
@@ -2,76 +2,21 @@ import { Object3D, Vector3 } from 'three';
|
|
|
2
2
|
import { FXInstancedParticle } from '../instancedParticle/FXInstancedParticle.js';
|
|
3
3
|
import { resolveGLSLTypeInfo } from '../instancedParticle/shared.js';
|
|
4
4
|
import { BUILTIN_OFFSET_AGE, BUILTIN_OFFSET_POSITION_X, BUILTIN_OFFSET_VELOCITY_X, BUILTIN_OFFSET_POSITION_Y, BUILTIN_OFFSET_VELOCITY_Y, BUILTIN_OFFSET_POSITION_Z, BUILTIN_OFFSET_VELOCITY_Z, BUILTIN_OFFSET_ROTATION, BUILTIN_OFFSET_TORQUE, BUILTIN_OFFSET_RANDOM_A, BUILTIN_OFFSET_RANDOM_B, BUILTIN_OFFSET_RANDOM_C } from '../miscellaneous/miscellaneous.js';
|
|
5
|
-
import { collectProperties, EMITTER_DEFAULT_EXPECTED_CAPACITY, EMITTER_DEFAULT_CAPACITY_STEP, EMITTER_DEFAULT_CAST_SHADOW, EMITTER_DEFAULT_RECEIVE_SHADOW, EMITTER_DEFAULT_AUTOMATICALLY_DESTROY_MODULES, EMITTER_DEFAULT_SORT_FRACTION } from './FXEmitter.Internal.js';
|
|
5
|
+
import { EMITTERS, collectProperties, EMITTER_DEFAULT_EXPECTED_CAPACITY, EMITTER_DEFAULT_CAPACITY_STEP, EMITTER_DEFAULT_CAST_SHADOW, EMITTER_DEFAULT_RECEIVE_SHADOW, EMITTER_DEFAULT_AUTOMATICALLY_DESTROY_MODULES, EMITTER_DEFAULT_SORT_FRACTION, EMITTER_DEFAULT_PREWARM_MIN_STEP_DURATION, EMITTER_DEFAULT_PREWARM_MAX_STEP_COUNT } from './FXEmitter.Internal.js';
|
|
6
6
|
|
|
7
|
-
const EMITTERS = new Array();
|
|
8
7
|
class FXEmitter extends Object3D {
|
|
9
8
|
constructor(_private_spawnSequence, _private_behaviorSequence, material, options = {}) {
|
|
10
9
|
super();
|
|
11
10
|
this._private_spawnSequence = _private_spawnSequence;
|
|
12
11
|
this._private_behaviorSequence = _private_behaviorSequence;
|
|
13
|
-
this.
|
|
14
|
-
this.
|
|
15
|
-
this.
|
|
16
|
-
this._private_emissionElapsed = 0;
|
|
12
|
+
this._private_nextHandler = 0;
|
|
13
|
+
this._private_pendingBursts = [];
|
|
14
|
+
this._private_activePlays = [];
|
|
17
15
|
this._private_sortingAccumulator = 0;
|
|
18
16
|
this._private_sortingCameraWorldPosition = new Vector3();
|
|
19
17
|
this._private_onRendering = (deltaTime) => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const { array, itemSize } = builtin;
|
|
23
|
-
for (let i = 0; i < this._private_mesh.instanceCount; i++) {
|
|
24
|
-
array[i * itemSize + BUILTIN_OFFSET_AGE] += deltaTime;
|
|
25
|
-
}
|
|
26
|
-
builtin.needsUpdate = true;
|
|
27
|
-
this._private_mesh.removeDeadParticles();
|
|
28
|
-
}
|
|
29
|
-
if (this._private_emissionRate > 0) {
|
|
30
|
-
this._private_emissionElapsed += deltaTime;
|
|
31
|
-
if (this._private_emissionElapsed >= this._private_emissionDuration) {
|
|
32
|
-
this.stop();
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
this._private_emissionAccumulator += this._private_emissionRate * deltaTime;
|
|
36
|
-
const particlesToSpawn = Math.floor(this._private_emissionAccumulator);
|
|
37
|
-
if (particlesToSpawn > 0) {
|
|
38
|
-
this.burst(particlesToSpawn);
|
|
39
|
-
this._private_emissionAccumulator -= particlesToSpawn;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
if (this._private_mesh.instanceCount === 0) {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
{
|
|
47
|
-
const { propertyBuffers, instanceCount } = this._private_mesh;
|
|
48
|
-
for (const module of this._private_behaviorSequence) {
|
|
49
|
-
module.update(propertyBuffers, instanceCount, deltaTime);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
const { builtin } = this._private_mesh.propertyBuffers;
|
|
53
|
-
const { array, itemSize } = builtin;
|
|
54
|
-
{
|
|
55
|
-
for (let i = 0; i < this._private_mesh.instanceCount; i++) {
|
|
56
|
-
const itemOffset = i * itemSize;
|
|
57
|
-
array[itemOffset + BUILTIN_OFFSET_POSITION_X] +=
|
|
58
|
-
array[itemOffset + BUILTIN_OFFSET_VELOCITY_X] * deltaTime;
|
|
59
|
-
array[itemOffset + BUILTIN_OFFSET_POSITION_Y] +=
|
|
60
|
-
array[itemOffset + BUILTIN_OFFSET_VELOCITY_Y] * deltaTime;
|
|
61
|
-
array[itemOffset + BUILTIN_OFFSET_POSITION_Z] +=
|
|
62
|
-
array[itemOffset + BUILTIN_OFFSET_VELOCITY_Z] * deltaTime;
|
|
63
|
-
}
|
|
64
|
-
builtin.needsUpdate = true;
|
|
65
|
-
}
|
|
66
|
-
{
|
|
67
|
-
for (let i = 0; i < this._private_mesh.instanceCount; i++) {
|
|
68
|
-
const itemOffset = i * itemSize;
|
|
69
|
-
array[itemOffset + BUILTIN_OFFSET_ROTATION] +=
|
|
70
|
-
array[itemOffset + BUILTIN_OFFSET_TORQUE] * deltaTime;
|
|
71
|
-
}
|
|
72
|
-
builtin.needsUpdate = true;
|
|
73
|
-
}
|
|
74
|
-
if (this.sortCamera !== undefined) {
|
|
18
|
+
this._private_tick(deltaTime);
|
|
19
|
+
if (this.sortCamera !== undefined && this._private_mesh.instanceCount > 0) {
|
|
75
20
|
this._private_sortingAccumulator += this.sortFraction;
|
|
76
21
|
if (this._private_sortingAccumulator >= 1) {
|
|
77
22
|
this._private_sortingAccumulator -= 1;
|
|
@@ -119,7 +64,136 @@ class FXEmitter extends Object3D {
|
|
|
119
64
|
}
|
|
120
65
|
this.removeFromParent();
|
|
121
66
|
}
|
|
122
|
-
burst(count) {
|
|
67
|
+
burst(count, options = {}) {
|
|
68
|
+
const handler = this._private_nextHandler++;
|
|
69
|
+
const delay = options.delay ?? 0;
|
|
70
|
+
if (delay <= 0) {
|
|
71
|
+
this._private_spawnBurst(count);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
this._private_pendingBursts.push({ handler, count, delay });
|
|
75
|
+
}
|
|
76
|
+
return handler;
|
|
77
|
+
}
|
|
78
|
+
play(rate, options = {}) {
|
|
79
|
+
const handler = this._private_nextHandler++;
|
|
80
|
+
this._private_activePlays.push({
|
|
81
|
+
handler,
|
|
82
|
+
rate,
|
|
83
|
+
duration: options.duration ?? Infinity,
|
|
84
|
+
elapsed: 0,
|
|
85
|
+
accumulator: 0,
|
|
86
|
+
delay: options.delay ?? 0,
|
|
87
|
+
});
|
|
88
|
+
return handler;
|
|
89
|
+
}
|
|
90
|
+
stop(handler) {
|
|
91
|
+
if (handler === undefined) {
|
|
92
|
+
const hadAnything = this._private_pendingBursts.length > 0 || this._private_activePlays.length > 0;
|
|
93
|
+
this._private_pendingBursts.length = 0;
|
|
94
|
+
this._private_activePlays.length = 0;
|
|
95
|
+
return hadAnything;
|
|
96
|
+
}
|
|
97
|
+
const burstIndex = this._private_pendingBursts.findIndex((b) => b.handler === handler);
|
|
98
|
+
if (burstIndex !== -1) {
|
|
99
|
+
this._private_pendingBursts.splice(burstIndex, 1);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
const playIndex = this._private_activePlays.findIndex((p) => p.handler === handler);
|
|
103
|
+
if (playIndex !== -1) {
|
|
104
|
+
this._private_activePlays.splice(playIndex, 1);
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
prewarm(duration, stepDuration = EMITTER_DEFAULT_PREWARM_MIN_STEP_DURATION) {
|
|
110
|
+
const stepCount = Math.min(duration / Math.max(stepDuration, EMITTER_DEFAULT_PREWARM_MIN_STEP_DURATION), EMITTER_DEFAULT_PREWARM_MAX_STEP_COUNT);
|
|
111
|
+
const step = duration / stepCount;
|
|
112
|
+
let remaining = duration;
|
|
113
|
+
while (remaining > 0) {
|
|
114
|
+
const deltaTime = Math.min(remaining, step);
|
|
115
|
+
this._private_tick(deltaTime);
|
|
116
|
+
remaining -= deltaTime;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
reset() {
|
|
120
|
+
this._private_pendingBursts.length = 0;
|
|
121
|
+
this._private_activePlays.length = 0;
|
|
122
|
+
this._private_mesh.drop();
|
|
123
|
+
}
|
|
124
|
+
_private_tick(deltaTime) {
|
|
125
|
+
// Age increment & cull dead particles
|
|
126
|
+
{
|
|
127
|
+
const { builtin } = this._private_mesh.propertyBuffers;
|
|
128
|
+
const { array, itemSize } = builtin;
|
|
129
|
+
for (let i = 0; i < this._private_mesh.instanceCount; i++) {
|
|
130
|
+
array[i * itemSize + BUILTIN_OFFSET_AGE] += deltaTime;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
this._private_mesh.removeDeadParticles();
|
|
134
|
+
// Pending bursts (delayed)
|
|
135
|
+
for (let i = this._private_pendingBursts.length - 1; i >= 0; i--) {
|
|
136
|
+
const pending = this._private_pendingBursts[i];
|
|
137
|
+
pending.delay -= deltaTime;
|
|
138
|
+
if (pending.delay <= 0) {
|
|
139
|
+
this._private_pendingBursts.splice(i, 1);
|
|
140
|
+
this._private_spawnBurst(pending.count);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Active plays
|
|
144
|
+
for (let i = this._private_activePlays.length - 1; i >= 0; i--) {
|
|
145
|
+
const play = this._private_activePlays[i];
|
|
146
|
+
let effectiveDeltaTime = deltaTime;
|
|
147
|
+
if (play.delay > 0) {
|
|
148
|
+
play.delay -= deltaTime;
|
|
149
|
+
if (play.delay > 0) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
// Delay just expired - use the overshoot as effective dt this tick
|
|
153
|
+
effectiveDeltaTime = -play.delay;
|
|
154
|
+
play.delay = 0;
|
|
155
|
+
}
|
|
156
|
+
play.elapsed += effectiveDeltaTime;
|
|
157
|
+
if (play.elapsed >= play.duration) {
|
|
158
|
+
this._private_activePlays.splice(i, 1);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
play.accumulator += play.rate * effectiveDeltaTime;
|
|
162
|
+
const particlesToSpawn = Math.floor(play.accumulator);
|
|
163
|
+
if (particlesToSpawn > 0) {
|
|
164
|
+
this._private_spawnBurst(particlesToSpawn);
|
|
165
|
+
play.accumulator -= particlesToSpawn;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (this._private_mesh.instanceCount === 0) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
{
|
|
172
|
+
const { propertyBuffers, instanceCount } = this._private_mesh;
|
|
173
|
+
for (const module of this._private_behaviorSequence) {
|
|
174
|
+
module.update(propertyBuffers, instanceCount, deltaTime);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
{
|
|
178
|
+
const { builtin } = this._private_mesh.propertyBuffers;
|
|
179
|
+
const { array, itemSize } = builtin;
|
|
180
|
+
for (let i = 0; i < this._private_mesh.instanceCount; i++) {
|
|
181
|
+
const offset = i * itemSize;
|
|
182
|
+
// Position += velocity * dt
|
|
183
|
+
array[offset + BUILTIN_OFFSET_POSITION_X] +=
|
|
184
|
+
array[offset + BUILTIN_OFFSET_VELOCITY_X] * deltaTime;
|
|
185
|
+
array[offset + BUILTIN_OFFSET_POSITION_Y] +=
|
|
186
|
+
array[offset + BUILTIN_OFFSET_VELOCITY_Y] * deltaTime;
|
|
187
|
+
array[offset + BUILTIN_OFFSET_POSITION_Z] +=
|
|
188
|
+
array[offset + BUILTIN_OFFSET_VELOCITY_Z] * deltaTime;
|
|
189
|
+
// Rotation += torque * dt
|
|
190
|
+
array[offset + BUILTIN_OFFSET_ROTATION] +=
|
|
191
|
+
array[offset + BUILTIN_OFFSET_TORQUE] * deltaTime;
|
|
192
|
+
}
|
|
193
|
+
builtin.needsUpdate = true;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
_private_spawnBurst(count) {
|
|
123
197
|
const instanceBegin = this._private_mesh.instanceCount;
|
|
124
198
|
this._private_mesh.createInstances(count);
|
|
125
199
|
const instanceEnd = this._private_mesh.instanceCount;
|
|
@@ -138,21 +212,6 @@ class FXEmitter extends Object3D {
|
|
|
138
212
|
spawnModule.spawn(this._private_mesh.propertyBuffers, instanceBegin, instanceEnd);
|
|
139
213
|
}
|
|
140
214
|
}
|
|
141
|
-
reset() {
|
|
142
|
-
this._private_mesh.drop();
|
|
143
|
-
}
|
|
144
|
-
play(rate, options = {}) {
|
|
145
|
-
this._private_emissionRate = rate;
|
|
146
|
-
this._private_emissionAccumulator = 0;
|
|
147
|
-
this._private_emissionDuration = options.duration ?? Infinity;
|
|
148
|
-
this._private_emissionElapsed = 0;
|
|
149
|
-
}
|
|
150
|
-
stop() {
|
|
151
|
-
this._private_emissionRate = 0;
|
|
152
|
-
this._private_emissionAccumulator = 0;
|
|
153
|
-
this._private_emissionDuration = Infinity;
|
|
154
|
-
this._private_emissionElapsed = 0;
|
|
155
|
-
}
|
|
156
215
|
}
|
|
157
216
|
FXEmitter.onWillRender = (deltaTime) => {
|
|
158
217
|
for (const instance of EMITTERS) {
|
|
@@ -21,8 +21,8 @@ export declare class FXSpawnSphere extends FXSpawn<{
|
|
|
21
21
|
constructor(innerRadius?: number, outerRadius?: number, angle?: number);
|
|
22
22
|
/**
|
|
23
23
|
* Polar half-angle from the equatorial plane.
|
|
24
|
-
* - `0`
|
|
25
|
-
* - `Math.PI / 2`
|
|
24
|
+
* - `0` - flat disk
|
|
25
|
+
* - `Math.PI / 2` - full sphere
|
|
26
26
|
*/
|
|
27
27
|
get angle(): number;
|
|
28
28
|
/** Minimum spawn radius */
|
|
@@ -30,8 +30,8 @@ class FXSpawnSphere extends FXSpawn {
|
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
32
|
* Polar half-angle from the equatorial plane.
|
|
33
|
-
* - `0`
|
|
34
|
-
* - `Math.PI / 2`
|
|
33
|
+
* - `0` - flat disk
|
|
34
|
+
* - `Math.PI / 2` - full sphere
|
|
35
35
|
*/
|
|
36
36
|
get angle() {
|
|
37
37
|
return this._private_angleInternal;
|
package/dist/index.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export { FXUnlitMaterial } from "./materials/FXUnlitMaterial/FXUnlitMaterial";
|
|
|
20
20
|
export { FXColor } from "./miscellaneous/color/FXColor";
|
|
21
21
|
export { FXColorNode } from "./nodes/color/FXColorNode";
|
|
22
22
|
export { FXColorOverLifeNode } from "./nodes/color/FXColorOverLifeNode";
|
|
23
|
+
export { FXSphericalClipNode } from "./nodes/color/FXSphericalClipNode";
|
|
23
24
|
export { FXFlatNormalNode } from "./nodes/normal/FXFlatNormalNode";
|
|
24
25
|
export { FXNormalNode } from "./nodes/normal/FXNormalNode";
|
|
25
26
|
export { FXSphericalNormalNode } from "./nodes/normal/FXSphericalNormalNode";
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ export { FXUnlitMaterial } from './materials/FXUnlitMaterial/FXUnlitMaterial.js'
|
|
|
20
20
|
export { FXColor } from './miscellaneous/color/FXColor.js';
|
|
21
21
|
export { FXColorNode } from './nodes/color/FXColorNode.js';
|
|
22
22
|
export { FXColorOverLifeNode } from './nodes/color/FXColorOverLifeNode.js';
|
|
23
|
+
export { FXSphericalClipNode } from './nodes/color/FXSphericalClipNode.js';
|
|
23
24
|
export { FXFlatNormalNode } from './nodes/normal/FXFlatNormalNode.js';
|
|
24
25
|
export { FXNormalNode } from './nodes/normal/FXNormalNode.js';
|
|
25
26
|
export { FXSphericalNormalNode } from './nodes/normal/FXSphericalNormalNode.js';
|
|
@@ -11,7 +11,7 @@ import { PARTICLE_DEFINES } from '../../miscellaneous/miscellaneous.js';
|
|
|
11
11
|
*
|
|
12
12
|
* `vWorldPosition` is a Three.js convention consumed by the distance fragment
|
|
13
13
|
* shader. `viewMatrix` here is the shadow camera's view matrix, so the
|
|
14
|
-
* billboard faces the light
|
|
14
|
+
* billboard faces the light - which is correct for shadow shape.
|
|
15
15
|
*/
|
|
16
16
|
const DISTANCE_VERTEX_SHADER = `
|
|
17
17
|
#define DISTANCE
|
|
@@ -77,9 +77,9 @@ const DISTANCE_VERTEX_SHADER = `
|
|
|
77
77
|
* the smaller scale dimension. Fragments outside the sphere are discarded.
|
|
78
78
|
*
|
|
79
79
|
* The depth offset is subtracted from the flat distance, bringing the
|
|
80
|
-
* perceived surface closer to the light. This is an approximation
|
|
80
|
+
* perceived surface closer to the light. This is an approximation - the
|
|
81
81
|
* true offset direction depends on the light-to-fragment vector, not the
|
|
82
|
-
* camera-to-fragment vector
|
|
82
|
+
* camera-to-fragment vector - but for smoke particles the error is
|
|
83
83
|
* negligible.
|
|
84
84
|
*/
|
|
85
85
|
const SPHERICAL_DISTANCE_SNIPPET = `
|
|
@@ -130,7 +130,7 @@ function buildDistanceMaterial(albedoNodes, distanceAlphaTest, useDistanceAlphaH
|
|
|
130
130
|
Object.assign(shader.uniforms, node.uniforms);
|
|
131
131
|
}
|
|
132
132
|
let fragmentShader = shader.fragmentShader;
|
|
133
|
-
// Alpha from nodes
|
|
133
|
+
// Alpha from nodes - MeshDistanceMaterial may or may not include
|
|
134
134
|
// <map_fragment> depending on the Three.js version. If the chunk is
|
|
135
135
|
// absent, insert the alpha discard before the distance calculation.
|
|
136
136
|
if (distanceNodes.length > 0) {
|
|
@@ -149,7 +149,7 @@ function buildDistanceMaterial(albedoNodes, distanceAlphaTest, useDistanceAlphaH
|
|
|
149
149
|
"This may indicate a Three.js version mismatch.");
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
|
-
// Spherical distance adjustment
|
|
152
|
+
// Spherical distance adjustment - offset the flat-billboard distance by
|
|
153
153
|
// the sphere's front surface depth.
|
|
154
154
|
if (useSphericalDepth) {
|
|
155
155
|
if (!DISTANCE_CALCULATION_PATTERN.test(fragmentShader)) {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { FXColorNode } from './FXColorNode.js';
|
|
2
|
+
|
|
3
|
+
const INSTANCES = [];
|
|
4
|
+
class FXSphericalClipNode extends FXColorNode {
|
|
5
|
+
constructor(innerRadius = 0.5) {
|
|
6
|
+
super();
|
|
7
|
+
INSTANCES.push(this);
|
|
8
|
+
const uniformName = `u_SphericalClip_${INSTANCES.length - 1}`;
|
|
9
|
+
this.cacheKey = "spherical-clip";
|
|
10
|
+
this.uniformDeclarations = [`uniform float ${uniformName};`];
|
|
11
|
+
this.uniforms = {
|
|
12
|
+
[uniformName]: { value: innerRadius },
|
|
13
|
+
};
|
|
14
|
+
this.helperFunctions = `
|
|
15
|
+
vec4 applySphericalClip(float innerRadius) {
|
|
16
|
+
float distanceToCenter = distance(p_uv, vec2(0.5, 0.5));
|
|
17
|
+
return vec4(1.0, 1.0, 1.0, 1.0 - smoothstep(innerRadius, 0.5, distanceToCenter));
|
|
18
|
+
}
|
|
19
|
+
`;
|
|
20
|
+
this.colorExpression = `applySphericalClip(${uniformName})`;
|
|
21
|
+
}
|
|
22
|
+
destroy() {
|
|
23
|
+
const index = INSTANCES.indexOf(this);
|
|
24
|
+
if (index !== -1) {
|
|
25
|
+
INSTANCES.splice(index, 1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { FXSphericalClipNode };
|