babylon.quarks 0.23.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +101 -0
- package/dist/babylon.quarks.cjs +1 -1
- package/dist/babylon.quarks.cjs.map +1 -1
- package/dist/babylon.quarks.esm.js +1 -1
- package/dist/babylon.quarks.esm.js.map +1 -1
- package/dist/babylon.quarks.umd.min.js +1 -1
- package/dist/babylon.quarks.umd.min.js.map +1 -1
- package/dist/types/index.d.ts +529 -20
- package/package.json +2 -2
- package/src/BatchedRenderer.ts +146 -2
- package/src/EffectPool.ts +165 -0
- package/src/MeshSurfaceEmitter.ts +4 -4
- package/src/ParticleEmitter.ts +2 -1
- package/src/ParticleSystem.ts +592 -15
- package/src/QuarksUtil.ts +55 -7
- package/src/SpriteBatch.ts +127 -3
- package/src/TrailBatch.ts +49 -12
- package/src/VFXBatch.ts +154 -3
- package/src/index.ts +4 -1
- package/src/instanceSort.ts +121 -0
- package/src/shaders/chunks/fog.ts +127 -0
- package/src/shaders/local_particle_physics_vert.glsl.ts +4 -0
- package/src/shaders/local_particle_physics_vert.wgsl.ts +4 -0
- package/src/shaders/particle_frag.glsl.ts +4 -0
- package/src/shaders/particle_frag.wgsl.ts +4 -0
- package/src/shaders/particle_physics_frag.glsl.ts +4 -0
- package/src/shaders/particle_physics_frag.wgsl.ts +4 -0
- package/src/shaders/particle_vert.glsl.ts +23 -4
- package/src/shaders/particle_vert.wgsl.ts +23 -4
- package/src/shaders/stretched_bb_particle_vert.glsl.ts +17 -0
- package/src/shaders/stretched_bb_particle_vert.wgsl.ts +18 -1
- package/src/shaders/trail_frag.glsl.ts +4 -0
- package/src/shaders/trail_frag.wgsl.ts +4 -0
- package/src/shaders/trail_vert.glsl.ts +4 -0
- package/src/shaders/trail_vert.wgsl.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "babylon.quarks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "A High-Performance Particle System for Babylon.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./dist/types/index.d.ts",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"@babylonjs/core": ">=9.0.0"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@babylonjs/core": "^9.
|
|
66
|
+
"@babylonjs/core": "^9.31.0",
|
|
67
67
|
"@types/jest": "^30.0.0",
|
|
68
68
|
"quarks.core": "*"
|
|
69
69
|
}
|
package/src/BatchedRenderer.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import {Camera} from '@babylonjs/core/Cameras/camera';
|
|
1
2
|
import {BaseTexture} from '@babylonjs/core/Materials/Textures/baseTexture';
|
|
2
3
|
import {Texture} from '@babylonjs/core/Materials/Textures/texture';
|
|
4
|
+
import {Frustum} from '@babylonjs/core/Maths/math.frustum';
|
|
5
|
+
import {Plane} from '@babylonjs/core/Maths/math.plane';
|
|
3
6
|
import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
4
7
|
import {Scene} from '@babylonjs/core/scene';
|
|
5
8
|
import {EmitSubParticleSystem, IParticleSystem, Vector3} from 'quarks.core';
|
|
@@ -13,6 +16,7 @@ import {
|
|
|
13
16
|
MeshRim,
|
|
14
17
|
meshRimEqual,
|
|
15
18
|
RenderMode,
|
|
19
|
+
SortMode,
|
|
16
20
|
StoredBatchSettings,
|
|
17
21
|
VFXBatch,
|
|
18
22
|
} from './VFXBatch';
|
|
@@ -56,6 +60,17 @@ export interface VFXBatchSettings {
|
|
|
56
60
|
*/
|
|
57
61
|
reflectionAtlas: BaseTexture | null;
|
|
58
62
|
layerMask: number;
|
|
63
|
+
/**
|
|
64
|
+
* Babylon rendering group (`mesh.renderingGroupId`) the batch draws in,
|
|
65
|
+
* 0 by default. Systems with different groups are batched apart.
|
|
66
|
+
*/
|
|
67
|
+
renderingGroupId: number;
|
|
68
|
+
/**
|
|
69
|
+
* Whether the scene's fog applies to the particles, as it does to any other
|
|
70
|
+
* mesh; true by default. Additive particles fade toward black rather than
|
|
71
|
+
* the fog colour. Systems that differ are batched apart.
|
|
72
|
+
*/
|
|
73
|
+
applyFog: boolean;
|
|
59
74
|
/**
|
|
60
75
|
* Offset of billboard quads from their particle, in particle-size units
|
|
61
76
|
* (Unity's renderer Pivot). On billboards x/y move the quad in its own plane
|
|
@@ -64,6 +79,20 @@ export interface VFXBatchSettings {
|
|
|
64
79
|
* y across, and z is unused. Absent or zero: no offset. Meshes ignore it.
|
|
65
80
|
*/
|
|
66
81
|
pivot?: Vector3;
|
|
82
|
+
/** Draw order within the batch, see {@link SortMode}. Unset is `'none'`. Systems that differ are batched apart. */
|
|
83
|
+
sortMode?: SortMode;
|
|
84
|
+
/**
|
|
85
|
+
* Smallest size a billboard or stretched particle may have on screen, as a
|
|
86
|
+
* fraction of the viewport height (Unity's Min Particle Size); 0 by default.
|
|
87
|
+
* Meshes and trails ignore it. Systems that differ are batched apart.
|
|
88
|
+
*/
|
|
89
|
+
minParticleSize?: number;
|
|
90
|
+
/**
|
|
91
|
+
* Largest size a billboard or stretched particle may have on screen, as a
|
|
92
|
+
* fraction of the viewport height (Unity's Max Particle Size); unlimited by
|
|
93
|
+
* default. Meshes and trails ignore it. Systems that differ are batched apart.
|
|
94
|
+
*/
|
|
95
|
+
maxParticleSize?: number;
|
|
67
96
|
/**
|
|
68
97
|
* How mesh particles are lit (`'unlit'`, `'fixed'` or `'scene'`); unset is
|
|
69
98
|
* `'fixed'`. See {@link MeshLighting}. Other render modes ignore it.
|
|
@@ -148,10 +177,107 @@ export class BatchedRenderer extends TransformNode {
|
|
|
148
177
|
private lastAppliedQuality = Number.NaN;
|
|
149
178
|
private meshLighting: MeshLightingSettings = defaultMeshLighting();
|
|
150
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Whether systems whose {@link ParticleSystem.cullingMode} is not
|
|
182
|
+
* `'always'` are tested against the active cameras each frame and paused
|
|
183
|
+
* while unseen. Off, every system simulates every frame; the batches' own
|
|
184
|
+
* frustum culling of the draw stays on either way.
|
|
185
|
+
*/
|
|
186
|
+
cullingEnabled = true;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Time per frame, in milliseconds, that systems coming back into view may
|
|
190
|
+
* spend together on the steps they missed while culled (`'pauseAndCatchUp'`).
|
|
191
|
+
* Steps that do not fit are dropped, not deferred, so a crowd of effects
|
|
192
|
+
* reappearing at once costs at most this. 2 by default.
|
|
193
|
+
*/
|
|
194
|
+
catchUpBudgetMs = 2;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Time per frame, in milliseconds, after which the systems still to be
|
|
198
|
+
* advanced run one step instead of catching up on a long frame. The time
|
|
199
|
+
* the skipped steps would have covered is dropped, so a frame that already
|
|
200
|
+
* ran long cannot make the next one longer still. Unlimited by default;
|
|
201
|
+
* {@link configureAdaptivePerformance} does not set it.
|
|
202
|
+
*/
|
|
203
|
+
simulationBudgetMs = Infinity;
|
|
204
|
+
|
|
205
|
+
/** Frustum planes of the active cameras, six per camera, rebuilt each update. */
|
|
206
|
+
private frustumPlanes: Plane[] = [];
|
|
207
|
+
private readonly boundsMin = new Vector3();
|
|
208
|
+
private readonly boundsMax = new Vector3();
|
|
209
|
+
|
|
151
210
|
constructor(name: string, scene: Scene) {
|
|
152
211
|
super(name, scene);
|
|
153
212
|
}
|
|
154
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Gathers the frustum planes of the cameras the scene renders with, six per
|
|
216
|
+
* camera. Empty when there is no camera yet; systems are then never culled.
|
|
217
|
+
*/
|
|
218
|
+
private collectFrustumPlanes(): Plane[] {
|
|
219
|
+
const scene = this.getScene();
|
|
220
|
+
const planes = this.frustumPlanes;
|
|
221
|
+
const activeCameras = scene.activeCameras;
|
|
222
|
+
const activeCamera = scene.activeCamera;
|
|
223
|
+
const cameras: Camera[] =
|
|
224
|
+
activeCameras && activeCameras.length > 0 ? activeCameras : activeCamera ? [activeCamera] : [];
|
|
225
|
+
const needed = cameras.length * 6;
|
|
226
|
+
while (planes.length < needed) {
|
|
227
|
+
planes.push(new Plane(0, 0, 0, 0));
|
|
228
|
+
}
|
|
229
|
+
planes.length = needed;
|
|
230
|
+
for (let c = 0; c < cameras.length; c++) {
|
|
231
|
+
const camera = cameras[c];
|
|
232
|
+
// Both matrices are recomputed only on demand: a camera moved since
|
|
233
|
+
// the last render would otherwise be tested where it was, and one
|
|
234
|
+
// that has not rendered yet has no projection at all.
|
|
235
|
+
camera.getViewMatrix();
|
|
236
|
+
camera.getProjectionMatrix();
|
|
237
|
+
const transform = camera.getTransformationMatrix();
|
|
238
|
+
const slice = c === 0 ? planes : planes.slice(c * 6, c * 6 + 6);
|
|
239
|
+
Frustum.GetPlanesToRef(transform, slice);
|
|
240
|
+
if (c !== 0) {
|
|
241
|
+
for (let p = 0; p < 6; p++) {
|
|
242
|
+
planes[c * 6 + p] = slice[p];
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return planes;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* True when `system`'s world bounds reach into at least one camera's
|
|
251
|
+
* frustum, or when it has no bounds to test (empty, or not yet measured).
|
|
252
|
+
*/
|
|
253
|
+
private isSystemInView(system: ParticleSystem, planes: Plane[]): boolean {
|
|
254
|
+
const min = this.boundsMin;
|
|
255
|
+
const max = this.boundsMax;
|
|
256
|
+
if (!system.getWorldBounds(min, max)) {
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
for (let c = 0; c < planes.length; c += 6) {
|
|
260
|
+
let inside = true;
|
|
261
|
+
for (let p = c; p < c + 6; p++) {
|
|
262
|
+
const plane = planes[p];
|
|
263
|
+
const n = plane.normal;
|
|
264
|
+
// The box corner farthest along the plane's normal: if even it
|
|
265
|
+
// is behind the plane, the whole box is.
|
|
266
|
+
const x = n.x >= 0 ? max.x : min.x;
|
|
267
|
+
const y = n.y >= 0 ? max.y : min.y;
|
|
268
|
+
const z = n.z >= 0 ? max.z : min.z;
|
|
269
|
+
if (n.x * x + n.y * y + n.z * z + plane.d < 0) {
|
|
270
|
+
inside = false;
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (inside) {
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
|
|
155
281
|
private static clamp(value: number, min: number, max: number): number {
|
|
156
282
|
return Math.max(min, Math.min(max, value));
|
|
157
283
|
}
|
|
@@ -178,7 +304,12 @@ export class BatchedRenderer extends TransformNode {
|
|
|
178
304
|
a.instancingGeometry === b.instancingGeometry &&
|
|
179
305
|
a.renderOrder === b.renderOrder &&
|
|
180
306
|
a.layerMask === b.layerMask &&
|
|
307
|
+
a.renderingGroupId === b.renderingGroupId &&
|
|
308
|
+
a.applyFog === b.applyFog &&
|
|
181
309
|
pivotEqual(a.pivot, b.pivot) &&
|
|
310
|
+
a.sortMode === (b.sortMode ?? 'none') &&
|
|
311
|
+
a.minParticleSize === (b.minParticleSize ?? 0) &&
|
|
312
|
+
a.maxParticleSize === (b.maxParticleSize ?? Infinity) &&
|
|
182
313
|
(a.lighting ?? 'fixed') === (b.lighting ?? 'fixed') &&
|
|
183
314
|
meshRimEqual(a.rim, b.rim)
|
|
184
315
|
);
|
|
@@ -418,13 +549,26 @@ export class BatchedRenderer extends TransformNode {
|
|
|
418
549
|
this.applyAdaptiveQuality();
|
|
419
550
|
}
|
|
420
551
|
const systems = this.orderSystems();
|
|
552
|
+
const planes = this.cullingEnabled ? this.collectFrustumPlanes() : null;
|
|
553
|
+
const budgetStart =
|
|
554
|
+
this.simulationBudgetMs !== Infinity || this.catchUpBudgetMs !== Infinity ? performance.now() : 0;
|
|
555
|
+
const stepDeadline = this.simulationBudgetMs === Infinity ? Infinity : budgetStart + this.simulationBudgetMs;
|
|
556
|
+
const catchUpDeadline = this.catchUpBudgetMs === Infinity ? Infinity : budgetStart + this.catchUpBudgetMs;
|
|
421
557
|
for (let i = 0; i < systems.length; i++) {
|
|
422
|
-
const system = systems[i];
|
|
558
|
+
const system = systems[i] as ParticleSystem;
|
|
423
559
|
// A disabled emitter holds its particles frozen instead of simulating
|
|
424
560
|
// them unseen, matching how Unity treats a disabled ParticleSystem.
|
|
425
561
|
// Pooled effects parked with setEnabled(false) then cost nothing.
|
|
426
562
|
if (system.emitter.visible) {
|
|
427
|
-
|
|
563
|
+
// A system no camera sees pauses, by its own choice. One that
|
|
564
|
+
// only serves as a sub emitter target follows its parent.
|
|
565
|
+
system.culled =
|
|
566
|
+
planes !== null &&
|
|
567
|
+
planes.length > 0 &&
|
|
568
|
+
system.cullingMode !== 'always' &&
|
|
569
|
+
system.onlyUsedByOther !== true &&
|
|
570
|
+
!this.isSystemInView(system, planes);
|
|
571
|
+
system.update(delta, stepDeadline, catchUpDeadline);
|
|
428
572
|
}
|
|
429
573
|
// An autoDestroy system disposes itself from inside update and drops
|
|
430
574
|
// out of the list; stay on the slot it vacated.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import {Vector3} from '@babylonjs/core/Maths/math.vector';
|
|
2
|
+
import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
3
|
+
import {Node} from '@babylonjs/core/node';
|
|
4
|
+
import {BatchedRenderer} from './BatchedRenderer';
|
|
5
|
+
import {ParticleSystem} from './ParticleSystem';
|
|
6
|
+
import {QuarksUtil} from './QuarksUtil';
|
|
7
|
+
|
|
8
|
+
export interface EffectPoolOptions {
|
|
9
|
+
/** The renderer every copy the pool makes is added to. */
|
|
10
|
+
renderer: BatchedRenderer;
|
|
11
|
+
/** Copies made up front, so the first plays do not pay for cloning. Default 0. */
|
|
12
|
+
prewarm?: number;
|
|
13
|
+
/** Most copies the pool will ever hold; {@link EffectPool.acquire} returns null past it. Default unlimited. */
|
|
14
|
+
maxSize?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A pool of copies of one effect, for effects played many times over: hits,
|
|
19
|
+
* footsteps, muzzle flashes, pickups.
|
|
20
|
+
*
|
|
21
|
+
* {@link acquire} hands out an idle copy (or makes one), places it and plays
|
|
22
|
+
* it; a copy whose every system has finished goes back to the pool by itself,
|
|
23
|
+
* so a one-shot effect needs nothing more. A looping effect is ended with
|
|
24
|
+
* {@link stop}, which lets its particles die out before the copy returns, or
|
|
25
|
+
* dropped at once with {@link release}.
|
|
26
|
+
*
|
|
27
|
+
* Idle copies are parked disabled, which costs the renderer nothing, and keep
|
|
28
|
+
* their place in its batches, so acquiring one allocates nothing.
|
|
29
|
+
*
|
|
30
|
+
* The template itself is never played by the pool; it is only cloned. Disable
|
|
31
|
+
* it, or keep it out of the renderer, if it is not meant to be seen.
|
|
32
|
+
*/
|
|
33
|
+
export class EffectPool {
|
|
34
|
+
private readonly idle: TransformNode[] = [];
|
|
35
|
+
private readonly playing = new Set<TransformNode>();
|
|
36
|
+
private readonly systemsOf = new Map<TransformNode, ParticleSystem[]>();
|
|
37
|
+
private disposed = false;
|
|
38
|
+
|
|
39
|
+
constructor(
|
|
40
|
+
private readonly template: TransformNode,
|
|
41
|
+
private readonly options: EffectPoolOptions
|
|
42
|
+
) {
|
|
43
|
+
for (let i = 0; i < (options.prewarm ?? 0); i++) {
|
|
44
|
+
const copy = this.makeCopy();
|
|
45
|
+
if (copy === null) {
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
this.idle.push(copy);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Copies currently playing. */
|
|
53
|
+
get activeCount(): number {
|
|
54
|
+
return this.playing.size;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Copies parked and ready. */
|
|
58
|
+
get idleCount(): number {
|
|
59
|
+
return this.idle.length;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Takes an idle copy (making one when none is idle and the pool is under
|
|
64
|
+
* its size), parents it, places it and starts it from the beginning.
|
|
65
|
+
* Null when the pool is full or disposed.
|
|
66
|
+
*/
|
|
67
|
+
acquire(position?: Vector3, parent: Node | null = null): TransformNode | null {
|
|
68
|
+
if (this.disposed) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const copy = this.idle.pop() ?? this.makeCopy();
|
|
72
|
+
if (copy === null) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
copy.parent = parent;
|
|
76
|
+
if (position !== undefined) {
|
|
77
|
+
copy.position.copyFrom(position);
|
|
78
|
+
}
|
|
79
|
+
copy.setEnabled(true);
|
|
80
|
+
this.playing.add(copy);
|
|
81
|
+
QuarksUtil.restart(copy);
|
|
82
|
+
QuarksUtil.play(copy);
|
|
83
|
+
return copy;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Ends the emission of a playing copy; it returns to the pool on its own
|
|
88
|
+
* once its particles have died, looping systems included.
|
|
89
|
+
*/
|
|
90
|
+
stop(copy: TransformNode): void {
|
|
91
|
+
if (this.playing.has(copy)) {
|
|
92
|
+
QuarksUtil.endEmit(copy);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Puts a copy back at once, particles and all, parked and disabled. */
|
|
97
|
+
release(copy: TransformNode): void {
|
|
98
|
+
if (!this.playing.delete(copy)) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
QuarksUtil.stop(copy);
|
|
102
|
+
copy.setEnabled(false);
|
|
103
|
+
copy.parent = null;
|
|
104
|
+
if (!this.disposed) {
|
|
105
|
+
this.idle.push(copy);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Disposes every copy, playing or idle. The template is left alone. */
|
|
110
|
+
dispose(): void {
|
|
111
|
+
this.disposed = true;
|
|
112
|
+
for (const copy of [...this.playing, ...this.idle]) {
|
|
113
|
+
this.playing.delete(copy);
|
|
114
|
+
this.disposeCopy(copy);
|
|
115
|
+
}
|
|
116
|
+
this.idle.length = 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private makeCopy(): TransformNode | null {
|
|
120
|
+
const made = this.idle.length + this.playing.size;
|
|
121
|
+
if (made >= (this.options.maxSize ?? Infinity)) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const copy = QuarksUtil.instantiate(this.template);
|
|
125
|
+
const systems: ParticleSystem[] = [];
|
|
126
|
+
QuarksUtil.runOnAllParticleEmitters(copy, (emitter) => {
|
|
127
|
+
const system = emitter.system as unknown as ParticleSystem;
|
|
128
|
+
// The pool owns the copy's life; a system disposing itself would
|
|
129
|
+
// pull one emitter out of an effect that is meant to play again.
|
|
130
|
+
system.autoDestroy = false;
|
|
131
|
+
system.addEventListener('finished', this.onSystemFinished);
|
|
132
|
+
systems.push(system);
|
|
133
|
+
});
|
|
134
|
+
this.systemsOf.set(copy, systems);
|
|
135
|
+
QuarksUtil.addToBatchRenderer(copy, this.options.renderer);
|
|
136
|
+
copy.setEnabled(false);
|
|
137
|
+
copy.parent = null;
|
|
138
|
+
return copy;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private disposeCopy(copy: TransformNode): void {
|
|
142
|
+
const systems = this.systemsOf.get(copy) ?? [];
|
|
143
|
+
for (const system of systems) {
|
|
144
|
+
system.removeEventListener('finished', this.onSystemFinished);
|
|
145
|
+
this.options.renderer.deleteSystem(system);
|
|
146
|
+
system.dispose();
|
|
147
|
+
}
|
|
148
|
+
this.systemsOf.delete(copy);
|
|
149
|
+
copy.dispose();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** The copy a system belongs to is done when all of its systems are. */
|
|
153
|
+
private readonly onSystemFinished = (event: {particleSystem: unknown}): void => {
|
|
154
|
+
const system = event.particleSystem as ParticleSystem;
|
|
155
|
+
for (const copy of this.playing) {
|
|
156
|
+
const systems = this.systemsOf.get(copy);
|
|
157
|
+
if (systems !== undefined && systems.includes(system)) {
|
|
158
|
+
if (QuarksUtil.isEffectFinished(copy)) {
|
|
159
|
+
this.release(copy);
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {VertexBuffer} from '@babylonjs/core/Buffers/buffer';
|
|
2
2
|
import {Mesh} from '@babylonjs/core/Meshes/mesh';
|
|
3
|
-
import {EmitterShape, IParticleSystem, Particle, Plugin, ShapeJSON, ShapeTransform, Vector3} from 'quarks.core';
|
|
3
|
+
import {EmitterShape, IParticleSystem, Particle, Plugin, random, ShapeJSON, ShapeTransform, Vector3} from 'quarks.core';
|
|
4
4
|
|
|
5
5
|
export class MeshSurfaceEmitter implements EmitterShape {
|
|
6
6
|
type = 'mesh_surface';
|
|
@@ -72,7 +72,7 @@ export class MeshSurfaceEmitter implements EmitterShape {
|
|
|
72
72
|
const triCount = this._triangleIndexToArea.length - 1;
|
|
73
73
|
let left = 0,
|
|
74
74
|
right = triCount;
|
|
75
|
-
const target =
|
|
75
|
+
const target = random() * this._triangleIndexToArea[triCount];
|
|
76
76
|
|
|
77
77
|
while (left + 1 < right) {
|
|
78
78
|
const mid = Math.floor((left + right) / 2);
|
|
@@ -83,8 +83,8 @@ export class MeshSurfaceEmitter implements EmitterShape {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
let u1 =
|
|
87
|
-
let u2 =
|
|
86
|
+
let u1 = random();
|
|
87
|
+
let u2 = random();
|
|
88
88
|
if (u1 + u2 > 1) {
|
|
89
89
|
u1 = 1 - u1;
|
|
90
90
|
u2 = 1 - u2;
|
package/src/ParticleEmitter.ts
CHANGED
|
@@ -83,7 +83,8 @@ export class ParticleEmitter extends TransformNode {
|
|
|
83
83
|
if (this.rotationQuaternion) {
|
|
84
84
|
clonedEmitter.rotationQuaternion = this.rotationQuaternion.clone();
|
|
85
85
|
}
|
|
86
|
-
|
|
86
|
+
// Its own flag only: a clone of an emitter under a disabled parent is not itself disabled.
|
|
87
|
+
clonedEmitter.setEnabled(this.isEnabled(false));
|
|
87
88
|
return clonedEmitter;
|
|
88
89
|
}
|
|
89
90
|
|