babylon.quarks 0.26.0 → 0.27.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 +769 -8
- package/README.md +10 -8
- 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.cts +6683 -0
- package/dist/types/index.d.ts +1959 -588
- package/package.json +12 -6
- package/src/BatchedRenderer.ts +204 -101
- package/src/EffectPool.ts +4 -1
- package/src/MeshSurfaceEmitter.ts +29 -4
- package/src/ParticleEmitter.ts +45 -15
- package/src/ParticleSystem.ts +909 -2061
- package/src/QuarksLoader.ts +421 -67
- package/src/QuarksPrefab.ts +91 -4
- package/src/QuarksSerializer.ts +484 -0
- package/src/QuarksUtil.ts +15 -0
- package/src/SpriteBatch.ts +106 -246
- package/src/TrailBatch.ts +533 -342
- package/src/VFXBatch.ts +236 -136
- package/src/batchKey.ts +237 -0
- package/src/effectResources.ts +79 -0
- package/src/envAtlas.ts +119 -17
- package/src/index.ts +32 -33
- package/src/nodeId.ts +13 -0
- package/src/shaderProgram.ts +325 -0
- package/src/shaderVariants.ts +225 -0
- package/src/shaders/chunks/fog.ts +4 -2
- package/src/shaders/chunks/sheet.ts +111 -0
- package/src/shaders/chunks/soft.ts +84 -0
- package/src/shaders/chunks/surface.ts +10 -0
- package/src/shaders/local_particle_physics_vert.glsl.ts +14 -28
- package/src/shaders/local_particle_physics_vert.wgsl.ts +16 -29
- package/src/shaders/particle_frag.glsl.ts +3 -22
- package/src/shaders/particle_frag.wgsl.ts +3 -23
- package/src/shaders/particle_physics_frag.glsl.ts +12 -25
- package/src/shaders/particle_physics_frag.wgsl.ts +12 -26
- package/src/shaders/particle_vert.glsl.ts +5 -27
- package/src/shaders/particle_vert.wgsl.ts +5 -26
- package/src/shaders/stretched_bb_particle_vert.glsl.ts +7 -33
- package/src/shaders/stretched_bb_particle_vert.wgsl.ts +7 -33
- package/src/shaders/trail_frag.glsl.ts +11 -1
- package/src/shaders/trail_frag.wgsl.ts +11 -0
- package/src/shaders/trail_vert.glsl.ts +45 -36
- package/src/shaders/trail_vert.wgsl.ts +46 -40
- package/src/softDepth.ts +136 -0
- package/src/system/BehaviorPlan.ts +85 -0
- package/src/system/BoundsTracker.ts +181 -0
- package/src/system/EmissionController.ts +360 -0
- package/src/system/EventDispatcher.ts +44 -0
- package/src/system/ParticleSpawner.ts +274 -0
- package/src/system/SimulationClock.ts +130 -0
- package/src/system/geometry.ts +111 -0
- package/src/system/integrate.ts +428 -0
- package/src/system/materialResolve.ts +116 -0
- package/src/system/normalize.ts +95 -0
- package/src/system/serialization.ts +401 -0
- package/src/system/settingsSchema.ts +317 -0
- package/src/system/types.ts +410 -0
- package/src/textureSource.ts +30 -0
- package/src/trailSettings.ts +51 -0
- package/src/BatchedParticleRenderer.ts +0 -3
- package/src/materials/ParticleMaterials.ts +0 -23
- package/src/materials/index.ts +0 -1
- package/src/shaders/chunks/index.ts +0 -1
- package/src/shaders/chunks/register-shader-chunks.ts +0 -32
- package/src/shaders/chunks/soft_fragment.glsl.ts +0 -13
- package/src/shaders/chunks/soft_pars_fragment.glsl.ts +0 -20
- package/src/shaders/chunks/soft_pars_vertex.glsl.ts +0 -6
- package/src/shaders/chunks/soft_vertex.glsl.ts +0 -6
- package/src/shaders/chunks/tile_fragment.glsl.ts +0 -9
- package/src/shaders/chunks/tile_pars_fragment.glsl.ts +0 -9
- package/src/shaders/chunks/tile_pars_vertex.glsl.ts +0 -28
- package/src/shaders/chunks/tile_vertex.glsl.ts +0 -18
- package/src/shaders/index.ts +0 -9
- package/src/shaders/local_particle_vert.glsl.ts +0 -50
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "babylon.quarks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "A High-Performance Particle System for Babylon.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./dist/types/index.d.ts",
|
|
@@ -10,10 +10,16 @@
|
|
|
10
10
|
"jsdelivr": "./dist/babylon.quarks.umd.min.js",
|
|
11
11
|
"exports": {
|
|
12
12
|
".": {
|
|
13
|
-
"import":
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
"import": {
|
|
14
|
+
"types": "./dist/types/index.d.ts",
|
|
15
|
+
"default": "./dist/babylon.quarks.esm.js"
|
|
16
|
+
},
|
|
17
|
+
"require": {
|
|
18
|
+
"types": "./dist/types/index.d.cts",
|
|
19
|
+
"default": "./dist/babylon.quarks.cjs"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"./package.json": "./package.json"
|
|
17
23
|
},
|
|
18
24
|
"files": [
|
|
19
25
|
"dist",
|
|
@@ -29,7 +35,7 @@
|
|
|
29
35
|
"build": "npm run clean && npm run build:types && npm run build:js",
|
|
30
36
|
"test": "jest",
|
|
31
37
|
"docs": "typedoc",
|
|
32
|
-
"
|
|
38
|
+
"docs:check": "typedoc --out node_modules/.cache/typedoc-check",
|
|
33
39
|
"prettier": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\""
|
|
34
40
|
},
|
|
35
41
|
"repository": {
|
package/src/BatchedRenderer.ts
CHANGED
|
@@ -4,72 +4,102 @@ import {Texture} from '@babylonjs/core/Materials/Textures/texture';
|
|
|
4
4
|
import {Frustum} from '@babylonjs/core/Maths/math.frustum';
|
|
5
5
|
import {Plane} from '@babylonjs/core/Maths/math.plane';
|
|
6
6
|
import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
7
|
+
import type {DepthRenderer} from '@babylonjs/core/Rendering/depthRenderer';
|
|
7
8
|
import {Scene} from '@babylonjs/core/scene';
|
|
8
9
|
import {EmitSubParticleSystem, IParticleSystem, Vector3} from 'quarks.core';
|
|
10
|
+
import {batchSettingsEqual, shaderKeyEqual, storedBatchSettings} from './batchKey';
|
|
9
11
|
import {ParticleSystem} from './ParticleSystem';
|
|
12
|
+
import {depthBinding, DepthBinding, DepthTextureOptions, SoftDepthEncoding} from './softDepth';
|
|
10
13
|
import {SpriteBatch} from './SpriteBatch';
|
|
11
14
|
import {TrailBatch} from './TrailBatch';
|
|
12
15
|
import {
|
|
13
16
|
defaultMeshLighting,
|
|
14
|
-
erosionEqual,
|
|
15
|
-
gradientMapEqual,
|
|
16
|
-
mapUVEqual,
|
|
17
17
|
MeshLighting,
|
|
18
18
|
MeshLightingSettings,
|
|
19
19
|
MeshRim,
|
|
20
|
-
meshRimEqual,
|
|
21
20
|
ParticleErosion,
|
|
22
21
|
ParticleGradientMap,
|
|
23
22
|
ParticleUVDistortion,
|
|
24
|
-
readsUV1,
|
|
25
23
|
RenderMode,
|
|
26
24
|
SortMode,
|
|
27
|
-
StoredBatchSettings,
|
|
28
25
|
TextureUV,
|
|
29
|
-
uvDistortionEqual,
|
|
30
26
|
VFXBatch,
|
|
31
27
|
} from './VFXBatch';
|
|
32
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Everything about a system that decides how it is drawn: {@link ParticleSystem.getRendererSettings}
|
|
31
|
+
* returns it, and the renderer draws systems whose settings match from one batch. Only the
|
|
32
|
+
* settings a render mode reads count: two billboards that differ in a mesh-only setting, such as
|
|
33
|
+
* `rim`, share a batch.
|
|
34
|
+
*
|
|
35
|
+
* A batch is one draw with one material, so its settings fall into two groups, as a Unity
|
|
36
|
+
* material's keywords and texture slots against its MaterialPropertyBlock values, or an Unreal
|
|
37
|
+
* material against a dynamic material instance's parameters. The shader key (render mode,
|
|
38
|
+
* geometry, textures, blend and depth state, and which effects are on) decides the material.
|
|
39
|
+
* The live parameters (the fade distances, alpha cutoff, pivot, size limits, reflection level,
|
|
40
|
+
* rim colour, each effect's numbers, and each texture's tiling, offset and scroll) are uniforms:
|
|
41
|
+
* changing one on a system that is alone in its batch writes the material in place, with no new
|
|
42
|
+
* batch or material. A texture's UV channel is part of the shader key, so changing it rebuilds.
|
|
43
|
+
*
|
|
44
|
+
* Prefer the system's own setters, which also move it to a matching batch; after changing a
|
|
45
|
+
* field directly, set {@link ParticleSystem.neededToUpdateRender}.
|
|
46
|
+
*/
|
|
33
47
|
export interface VFXBatchSettings {
|
|
48
|
+
/** Mesh particles' vertex positions, see {@link ParticleSystem.instancingGeometry}. A quad for other modes. */
|
|
34
49
|
instancingGeometry: Float32Array;
|
|
50
|
+
/** Triangle indices into {@link instancingGeometry}. */
|
|
35
51
|
instancingIndices: Uint32Array | Uint16Array;
|
|
52
|
+
/** The mesh's first UV set, two values per vertex. */
|
|
36
53
|
instancingUVs?: Float32Array;
|
|
37
54
|
/** The mesh's second UV set, see {@link UVChannel}. */
|
|
38
55
|
instancingUVs2?: Float32Array;
|
|
56
|
+
/** The mesh's vertex normals; mesh particles fall back to +Z. */
|
|
39
57
|
instancingNormals?: Float32Array;
|
|
58
|
+
/** See {@link ParticleSystem.renderMode}. */
|
|
40
59
|
renderMode: RenderMode;
|
|
60
|
+
/** See {@link ParticleSystem.renderOrder}. */
|
|
41
61
|
renderOrder: number;
|
|
62
|
+
/** See {@link ParticleSystem.uTileCount}. */
|
|
42
63
|
uTileCount: number;
|
|
64
|
+
/** See {@link ParticleSystem.vTileCount}. */
|
|
43
65
|
vTileCount: number;
|
|
66
|
+
/** See {@link ParticleSystem.blendTiles}; read with a sheet, and not by trails. */
|
|
44
67
|
blendTiles: boolean;
|
|
68
|
+
/** See {@link ParticleSystem.softParticles}. */
|
|
45
69
|
softParticles: boolean;
|
|
70
|
+
/** See {@link ParticleSystem.softNearFade}. */
|
|
46
71
|
softNearFade: number;
|
|
72
|
+
/** See {@link ParticleSystem.softFarFade}. */
|
|
47
73
|
softFarFade: number;
|
|
74
|
+
/** Babylon alpha mode, see {@link ParticleSystem.blending}. */
|
|
48
75
|
materialBlendMode: number;
|
|
76
|
+
/** Whether the batch draws in the transparent pass. */
|
|
49
77
|
materialTransparent: boolean;
|
|
78
|
+
/** Whether particles are hidden behind nearer geometry. */
|
|
50
79
|
materialDepthTest: boolean;
|
|
80
|
+
/** Whether particles write depth. */
|
|
51
81
|
materialDepthWrite: boolean;
|
|
82
|
+
/** Alpha below which a fragment is discarded; 0 discards nothing. */
|
|
52
83
|
materialAlphaTest: number;
|
|
84
|
+
/** See {@link ParticleSystem.texture}. */
|
|
53
85
|
texture: Texture | null;
|
|
54
86
|
/**
|
|
55
|
-
*
|
|
56
|
-
* `
|
|
57
|
-
*
|
|
87
|
+
* The reflection a mesh particle's material carries (a Babylon material's `reflectionTexture`
|
|
88
|
+
* or `environmentTexture`). It is not sampled itself: a cube texture is unfolded into
|
|
89
|
+
* {@link reflectionAtlas} once it has loaded and sampled from there; anything else is kept
|
|
90
|
+
* only to tell materials apart. Other render modes ignore it.
|
|
58
91
|
*/
|
|
59
92
|
reflectionTexture: BaseTexture | null;
|
|
60
93
|
/** Multiplier for the reflection sample — mirrors `texture.level`. */
|
|
61
94
|
reflectionLevel: number;
|
|
62
95
|
/**
|
|
63
|
-
*
|
|
64
|
-
* `
|
|
65
|
-
* GL_INVALID_OPERATION
|
|
66
|
-
|
|
67
|
-
reflectionFaces: BaseTexture[] | null;
|
|
68
|
-
/**
|
|
69
|
-
* Single 3×2 atlas of cube faces (px py pz / nx ny nz). One sampler2D —
|
|
70
|
-
* the path that stays valid on iOS WebKit ShaderMaterial draws.
|
|
96
|
+
* A 3×2 atlas of cube faces (+X +Y +Z over -X -Y -Z) that mesh particles reflect, through one
|
|
97
|
+
* `sampler2D`: the path that stays valid on iOS WebKit ShaderMaterial draws, where a
|
|
98
|
+
* `samplerCube` or six face samplers raise GL_INVALID_OPERATION. Built from
|
|
99
|
+
* {@link reflectionTexture} when that is a cube texture. Other render modes ignore it.
|
|
71
100
|
*/
|
|
72
101
|
reflectionAtlas: BaseTexture | null;
|
|
102
|
+
/** Camera layer mask of the batch mesh, see {@link ParticleSystem.layers}. */
|
|
73
103
|
layerMask: number;
|
|
74
104
|
/**
|
|
75
105
|
* Babylon rendering group (`mesh.renderingGroupId`) the batch draws in,
|
|
@@ -78,8 +108,11 @@ export interface VFXBatchSettings {
|
|
|
78
108
|
renderingGroupId: number;
|
|
79
109
|
/**
|
|
80
110
|
* Whether the scene's fog applies to the particles, as it does to any other
|
|
81
|
-
* mesh; true by default.
|
|
82
|
-
*
|
|
111
|
+
* mesh; true by default. Particles that add to the frame (`ALPHA_ADD`,
|
|
112
|
+
* `ALPHA_ONEONE`) fade toward black rather than the fog colour, which would
|
|
113
|
+
* fog the whole screen; every other blend mode mixes toward the fog colour,
|
|
114
|
+
* which is right for alpha blending and only approximate for a multiply or
|
|
115
|
+
* subtract. Systems that differ are batched apart.
|
|
83
116
|
*/
|
|
84
117
|
applyFog: boolean;
|
|
85
118
|
/**
|
|
@@ -121,47 +154,63 @@ export interface VFXBatchSettings {
|
|
|
121
154
|
erosion?: ParticleErosion;
|
|
122
155
|
}
|
|
123
156
|
|
|
157
|
+
/**
|
|
158
|
+
* How {@link BatchedRenderer.configureAdaptivePerformance} trades particles for frame time: each
|
|
159
|
+
* update that runs over `targetFrameMs` lowers every system's quality factor (see
|
|
160
|
+
* {@link ParticleSystem.setQualityFactor}) by `decreaseStep`, each one under it raises the factor
|
|
161
|
+
* by `increaseStep`, within `minQuality` and `maxQuality`.
|
|
162
|
+
*/
|
|
124
163
|
export interface AdaptivePerformanceOptions {
|
|
164
|
+
/** CPU time in milliseconds the renderer's update may take before quality drops. Default 16.7. */
|
|
125
165
|
targetFrameMs: number;
|
|
166
|
+
/** Lowest quality factor, 0.1 to 1 ({@link ParticleSystem.setQualityFactor} clamps lower values). Default 0.35. */
|
|
126
167
|
minQuality: number;
|
|
168
|
+
/** Highest quality factor, `minQuality` to 1. Default 1. */
|
|
127
169
|
maxQuality: number;
|
|
170
|
+
/** Quality lost per update over budget. Default 0.08. */
|
|
128
171
|
decreaseStep: number;
|
|
172
|
+
/** Quality regained per update within budget. Default 0.02. */
|
|
129
173
|
increaseStep: number;
|
|
130
174
|
}
|
|
131
175
|
|
|
132
|
-
/**
|
|
133
|
-
function pivotEqual(a: Vector3 | undefined, b: Vector3 | undefined): boolean {
|
|
134
|
-
return (a?.x ?? 0) === (b?.x ?? 0) && (a?.y ?? 0) === (b?.y ?? 0) && (a?.z ?? 0) === (b?.z ?? 0);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/** True when both face lists are the same six texture references (or both empty). */
|
|
138
|
-
function reflectionFacesEqual(a: BaseTexture[] | null | undefined, b: BaseTexture[] | null | undefined): boolean {
|
|
139
|
-
if (a === b) {
|
|
140
|
-
return true;
|
|
141
|
-
}
|
|
142
|
-
if (!a || !b || a.length !== b.length) {
|
|
143
|
-
return false;
|
|
144
|
-
}
|
|
145
|
-
for (let i = 0; i < a.length; i++) {
|
|
146
|
-
if (a[i] !== b[i]) {
|
|
147
|
-
return false;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
return true;
|
|
151
|
-
}
|
|
152
|
-
|
|
176
|
+
/** A snapshot of the renderer's adaptive performance, from {@link BatchedRenderer.getAdaptivePerformanceState}. */
|
|
153
177
|
export interface AdaptivePerformanceState extends AdaptivePerformanceOptions {
|
|
178
|
+
/** Whether adaptive performance is on. */
|
|
154
179
|
enabled: boolean;
|
|
180
|
+
/** The quality factor the systems run at now. */
|
|
155
181
|
currentQuality: number;
|
|
182
|
+
/** CPU time the last measured update took, in milliseconds; kept from the last update while off. */
|
|
156
183
|
lastFrameCpuMs: number;
|
|
157
184
|
}
|
|
158
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Draws particle systems, batching the ones that draw alike into one instanced mesh each, and
|
|
188
|
+
* advances them every frame. It is a node in the scene; make one per scene, add each system with
|
|
189
|
+
* {@link addSystem}, and call {@link update} once per frame:
|
|
190
|
+
*
|
|
191
|
+
* ```ts
|
|
192
|
+
* const renderer = new BatchedRenderer('particles', scene);
|
|
193
|
+
* renderer.addSystem(system);
|
|
194
|
+
* scene.onBeforeRenderObservable.add(() => renderer.update(scene.getEngine().getDeltaTime() / 1000));
|
|
195
|
+
* ```
|
|
196
|
+
*
|
|
197
|
+
* Systems step on a fixed 60 Hz clock, so an effect plays out the same at any frame rate.
|
|
198
|
+
*
|
|
199
|
+
* The renderer is a node so the batches have a parent to live under; leave it at the origin,
|
|
200
|
+
* unturned and unscaled. Particles are placed in the world by their emitters, and a transform on
|
|
201
|
+
* the renderer would move every one of them again (it warns once when it finds one). A system
|
|
202
|
+
* belongs to one renderer at a time: adding it to another takes it out of the first.
|
|
203
|
+
*/
|
|
159
204
|
export class BatchedRenderer extends TransformNode {
|
|
205
|
+
/** Whether the renderer has warned about its own transform; see the class description. */
|
|
206
|
+
private warnedAboutTransform = false;
|
|
207
|
+
/** The batches, one draw each. Read to inspect what is drawn; the renderer owns them. */
|
|
160
208
|
batches: Array<VFXBatch> = [];
|
|
161
209
|
/** Seconds this renderer has been updated for; the clock UV and erosion scrolling run on. */
|
|
162
210
|
private elapsed = 0;
|
|
211
|
+
/** Index into {@link batches} of each system's batch. The renderer owns it. */
|
|
163
212
|
systemToBatchIndex: Map<IParticleSystem, number> = new Map<IParticleSystem, number>();
|
|
164
|
-
|
|
213
|
+
private depth: DepthBinding | null = null;
|
|
165
214
|
/** Systems in insertion order — iterated every frame, unlike the map. */
|
|
166
215
|
private systems: Array<IParticleSystem> = [];
|
|
167
216
|
/**
|
|
@@ -303,52 +352,25 @@ export class BatchedRenderer extends TransformNode {
|
|
|
303
352
|
return Math.max(min, Math.min(max, value));
|
|
304
353
|
}
|
|
305
354
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
a.reflectionTexture === b.reflectionTexture &&
|
|
315
|
-
a.reflectionLevel === b.reflectionLevel &&
|
|
316
|
-
reflectionFacesEqual(a.reflectionFaces, b.reflectionFaces) &&
|
|
317
|
-
a.reflectionAtlas === b.reflectionAtlas &&
|
|
318
|
-
a.renderMode === b.renderMode &&
|
|
319
|
-
a.blendTiles === b.blendTiles &&
|
|
320
|
-
a.softParticles === b.softParticles &&
|
|
321
|
-
a.softFarFade === b.softFarFade &&
|
|
322
|
-
a.softNearFade === b.softNearFade &&
|
|
323
|
-
a.uTileCount === b.uTileCount &&
|
|
324
|
-
a.vTileCount === b.vTileCount &&
|
|
325
|
-
a.instancingGeometry === b.instancingGeometry &&
|
|
326
|
-
// Trails draw their own strip and read none of the instanced geometry's UVs.
|
|
327
|
-
(a.renderMode === RenderMode.Trail || a.instancingUVs === b.instancingUVs) &&
|
|
328
|
-
// Normals, and the second UV set when read, only matter to meshes.
|
|
329
|
-
(a.renderMode !== RenderMode.Mesh || a.instancingNormals === b.instancingNormals) &&
|
|
330
|
-
(a.renderMode !== RenderMode.Mesh || !readsUV1(a) || a.instancingUVs2 === b.instancingUVs2) &&
|
|
331
|
-
a.renderOrder === b.renderOrder &&
|
|
332
|
-
a.layerMask === b.layerMask &&
|
|
333
|
-
a.renderingGroupId === b.renderingGroupId &&
|
|
334
|
-
a.applyFog === b.applyFog &&
|
|
335
|
-
pivotEqual(a.pivot, b.pivot) &&
|
|
336
|
-
a.sortMode === (b.sortMode ?? 'none') &&
|
|
337
|
-
a.minParticleSize === (b.minParticleSize ?? 0) &&
|
|
338
|
-
a.maxParticleSize === (b.maxParticleSize ?? Infinity) &&
|
|
339
|
-
(a.lighting ?? 'fixed') === (b.lighting ?? 'fixed') &&
|
|
340
|
-
meshRimEqual(a.rim, b.rim) &&
|
|
341
|
-
mapUVEqual(a.mapUV, b.mapUV) &&
|
|
342
|
-
uvDistortionEqual(a.uvDistortion, b.uvDistortion) &&
|
|
343
|
-
gradientMapEqual(a.gradientMap, b.gradientMap) &&
|
|
344
|
-
erosionEqual(a.erosion, b.erosion)
|
|
345
|
-
);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
addSystem(system: IParticleSystem) {
|
|
355
|
+
/**
|
|
356
|
+
* Starts drawing and updating `system`, in the batch whose settings match its own (a new one if
|
|
357
|
+
* none does). Add each system of an effect, or use `QuarksUtil.addToBatchRenderer` on its root.
|
|
358
|
+
*/
|
|
359
|
+
addSystem(system: IParticleSystem): void {
|
|
360
|
+
if (this.isDisposed()) {
|
|
361
|
+
throw new Error('BatchedRenderer.addSystem: the renderer has been disposed');
|
|
362
|
+
}
|
|
349
363
|
const particleSystem = system as ParticleSystem;
|
|
364
|
+
const previous = particleSystem._renderer;
|
|
365
|
+
if (previous !== undefined && previous !== this) {
|
|
366
|
+
// One renderer at a time, or two would step and draw it.
|
|
367
|
+
previous.deleteSystem(system);
|
|
368
|
+
}
|
|
350
369
|
particleSystem._renderer = this;
|
|
351
|
-
|
|
370
|
+
// Added again after its settings changed: out of the old batch first, so it lands in the
|
|
371
|
+
// one that matches now rather than in both.
|
|
372
|
+
this.removeFromBatch(system);
|
|
373
|
+
if (this.systems.indexOf(system) === -1) {
|
|
352
374
|
this.systems.push(system);
|
|
353
375
|
this.orderedSystemsStale = true;
|
|
354
376
|
}
|
|
@@ -362,8 +384,9 @@ export class BatchedRenderer extends TransformNode {
|
|
|
362
384
|
private assignToBatch(particleSystem: ParticleSystem) {
|
|
363
385
|
const system = particleSystem as IParticleSystem;
|
|
364
386
|
const settings = particleSystem.getRendererSettings();
|
|
387
|
+
const stored = storedBatchSettings(settings);
|
|
365
388
|
for (let i = 0; i < this.batches.length; i++) {
|
|
366
|
-
if (
|
|
389
|
+
if (batchSettingsEqual(this.batches[i].settings, stored)) {
|
|
367
390
|
this.batches[i].addSystem(system);
|
|
368
391
|
this.systemToBatchIndex.set(system, i);
|
|
369
392
|
return;
|
|
@@ -373,7 +396,7 @@ export class BatchedRenderer extends TransformNode {
|
|
|
373
396
|
const scene = this.getScene();
|
|
374
397
|
switch (settings.renderMode) {
|
|
375
398
|
case RenderMode.Trail:
|
|
376
|
-
batch = new TrailBatch(settings, scene);
|
|
399
|
+
batch = new TrailBatch(settings, scene, this.depth);
|
|
377
400
|
break;
|
|
378
401
|
case RenderMode.Mesh:
|
|
379
402
|
case RenderMode.BillBoard:
|
|
@@ -381,15 +404,12 @@ export class BatchedRenderer extends TransformNode {
|
|
|
381
404
|
case RenderMode.HorizontalBillBoard:
|
|
382
405
|
case RenderMode.StretchedBillBoard:
|
|
383
406
|
case RenderMode.Beam:
|
|
384
|
-
batch = new SpriteBatch(settings, scene);
|
|
407
|
+
batch = new SpriteBatch(settings, scene, this.depth);
|
|
385
408
|
break;
|
|
386
409
|
default:
|
|
387
410
|
throw new Error(`Unsupported render mode: ${settings.renderMode}`);
|
|
388
411
|
}
|
|
389
412
|
batch.mesh.parent = this;
|
|
390
|
-
if (this.depthTexture) {
|
|
391
|
-
batch.applyDepthTexture(this.depthTexture);
|
|
392
|
-
}
|
|
393
413
|
batch.applyMeshLighting(this.meshLighting);
|
|
394
414
|
batch.addSystem(system);
|
|
395
415
|
this.batches.push(batch);
|
|
@@ -397,7 +417,11 @@ export class BatchedRenderer extends TransformNode {
|
|
|
397
417
|
this.systemToBatchIndex.set(system, this.batches.length - 1);
|
|
398
418
|
}
|
|
399
419
|
|
|
400
|
-
|
|
420
|
+
/**
|
|
421
|
+
* Stops drawing and updating `system`; disposing the system calls it. Its particles are left as they are.
|
|
422
|
+
* @returns Whether the system was in one of this renderer's batches.
|
|
423
|
+
*/
|
|
424
|
+
deleteSystem(system: IParticleSystem): boolean {
|
|
401
425
|
const removed = this.removeFromBatch(system);
|
|
402
426
|
const index = this.systems.indexOf(system);
|
|
403
427
|
if (index !== -1) {
|
|
@@ -426,21 +450,66 @@ export class BatchedRenderer extends TransformNode {
|
|
|
426
450
|
}
|
|
427
451
|
|
|
428
452
|
/**
|
|
429
|
-
*
|
|
430
|
-
*
|
|
453
|
+
* Takes a system's changed render settings. When only live parameters changed (see
|
|
454
|
+
* {@link VFXBatchSettings}) and the system is alone in its batch, the batch's material is
|
|
455
|
+
* written in place. Otherwise the system moves to the batch its settings now match. The
|
|
456
|
+
* system keeps its place in the update order, so this is safe to call mid-frame.
|
|
431
457
|
*/
|
|
432
458
|
updateSystem(system: IParticleSystem) {
|
|
459
|
+
const batchIndex = this.systemToBatchIndex.get(system);
|
|
460
|
+
if (batchIndex !== undefined) {
|
|
461
|
+
const batch = this.batches[batchIndex];
|
|
462
|
+
const settings = storedBatchSettings((system as ParticleSystem).getRendererSettings());
|
|
463
|
+
if (
|
|
464
|
+
batch.systems.size === 1 &&
|
|
465
|
+
shaderKeyEqual(batch.settings, settings) &&
|
|
466
|
+
// A batch that already draws with these values takes the system instead, so two
|
|
467
|
+
// batches never hold the same settings.
|
|
468
|
+
!this.batches.some((other) => other !== batch && batchSettingsEqual(other.settings, settings))
|
|
469
|
+
) {
|
|
470
|
+
batch.applyLiveSettings(settings);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
433
474
|
this.removeFromBatch(system);
|
|
434
475
|
this.addSystem(system);
|
|
435
476
|
}
|
|
436
477
|
|
|
437
|
-
|
|
438
|
-
|
|
478
|
+
/**
|
|
479
|
+
* Sets the scene depth that soft particles fade against. Without one, soft particles are not
|
|
480
|
+
* compiled in and draw hard, as Unity does for a camera with no depth texture.
|
|
481
|
+
*
|
|
482
|
+
* Pass the `DepthRenderer` itself to have its encoding read off it, for example
|
|
483
|
+
* `renderer.setDepthTexture(scene.enableDepthRenderer(camera))`. A bare texture is read as
|
|
484
|
+
* Babylon's default `'linear'` metric unless `options.encoding` says otherwise; see
|
|
485
|
+
* {@link SoftDepthEncoding}. The depth should be drawn by the camera that draws the particles.
|
|
486
|
+
* The texture is held, not the renderer: set it again if the `DepthRenderer` is disposed or
|
|
487
|
+
* replaced.
|
|
488
|
+
*
|
|
489
|
+
* @param depth - A depth texture, a Babylon `DepthRenderer`, or null to turn soft particles off.
|
|
490
|
+
* @param options - What the texture stores, when it cannot be read off a `DepthRenderer`.
|
|
491
|
+
*/
|
|
492
|
+
setDepthTexture(depth: BaseTexture | DepthRenderer | null, options: DepthTextureOptions = {}) {
|
|
493
|
+
this.depth = depth ? depthBinding(depth, options) : null;
|
|
439
494
|
for (const batch of this.batches) {
|
|
440
|
-
batch.applyDepthTexture(
|
|
495
|
+
batch.applyDepthTexture(this.depth);
|
|
441
496
|
}
|
|
442
497
|
}
|
|
443
498
|
|
|
499
|
+
/** The depth texture soft particles fade against, see {@link setDepthTexture}. */
|
|
500
|
+
get depthTexture(): BaseTexture | null {
|
|
501
|
+
return this.depth?.texture ?? null;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
set depthTexture(texture: BaseTexture | null) {
|
|
505
|
+
this.setDepthTexture(texture);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/** What the depth texture stores, see {@link setDepthTexture}. */
|
|
509
|
+
get depthEncoding(): SoftDepthEncoding | null {
|
|
510
|
+
return this.depth?.encoding ?? null;
|
|
511
|
+
}
|
|
512
|
+
|
|
444
513
|
/**
|
|
445
514
|
* Sets the light mesh particles with `lighting: 'fixed'` (the default) are
|
|
446
515
|
* lit by, for every mesh batch of this renderer, including ones created
|
|
@@ -478,7 +547,12 @@ export class BatchedRenderer extends TransformNode {
|
|
|
478
547
|
};
|
|
479
548
|
}
|
|
480
549
|
|
|
481
|
-
|
|
550
|
+
/**
|
|
551
|
+
* Turns on adaptive performance: the renderer measures its own update and thins every system's
|
|
552
|
+
* emission while it runs over budget, see {@link AdaptivePerformanceOptions}. Options left out
|
|
553
|
+
* keep their current values.
|
|
554
|
+
*/
|
|
555
|
+
configureAdaptivePerformance(options: Partial<AdaptivePerformanceOptions> = {}): void {
|
|
482
556
|
const state = this.adaptivePerformanceState;
|
|
483
557
|
const minQuality = BatchedRenderer.clamp(options.minQuality ?? state.minQuality, 0.05, 1);
|
|
484
558
|
const maxQuality = BatchedRenderer.clamp(options.maxQuality ?? state.maxQuality, minQuality, 1);
|
|
@@ -492,7 +566,8 @@ export class BatchedRenderer extends TransformNode {
|
|
|
492
566
|
this.lastAppliedQuality = Number.NaN;
|
|
493
567
|
}
|
|
494
568
|
|
|
495
|
-
|
|
569
|
+
/** Turns adaptive performance off, and by default puts every system back at `maxQuality`. */
|
|
570
|
+
disableAdaptivePerformance(resetQuality = true): void {
|
|
496
571
|
this.adaptivePerformanceState.enabled = false;
|
|
497
572
|
if (resetQuality) {
|
|
498
573
|
this.adaptivePerformanceState.currentQuality = this.adaptivePerformanceState.maxQuality;
|
|
@@ -500,6 +575,7 @@ export class BatchedRenderer extends TransformNode {
|
|
|
500
575
|
}
|
|
501
576
|
}
|
|
502
577
|
|
|
578
|
+
/** A copy of the adaptive performance settings and the current quality. */
|
|
503
579
|
getAdaptivePerformanceState(): AdaptivePerformanceState {
|
|
504
580
|
return {...this.adaptivePerformanceState};
|
|
505
581
|
}
|
|
@@ -573,8 +649,19 @@ export class BatchedRenderer extends TransformNode {
|
|
|
573
649
|
return ordered;
|
|
574
650
|
}
|
|
575
651
|
|
|
576
|
-
|
|
652
|
+
/**
|
|
653
|
+
* Advances every visible system by `delta` seconds and refills the batches. Call it once per
|
|
654
|
+
* frame. A system whose emitter is disabled is frozen; one no camera sees pauses if its
|
|
655
|
+
* {@link ParticleSystem.cullingMode} says so.
|
|
656
|
+
*/
|
|
657
|
+
update(delta: number): void {
|
|
577
658
|
this.elapsed += delta;
|
|
659
|
+
if (!this.warnedAboutTransform && !this.getWorldMatrix().isIdentity()) {
|
|
660
|
+
this.warnedAboutTransform = true;
|
|
661
|
+
console.warn(
|
|
662
|
+
`[babylon.quarks] BatchedRenderer "${this.name}" is transformed; particles are placed in the world by their emitters, so the renderer's own transform moves them again. Keep the renderer at the origin.`
|
|
663
|
+
);
|
|
664
|
+
}
|
|
578
665
|
const adaptiveState = this.adaptivePerformanceState;
|
|
579
666
|
const frameStart = adaptiveState.enabled ? performance.now() : 0;
|
|
580
667
|
if (adaptiveState.enabled) {
|
|
@@ -669,10 +756,15 @@ export class BatchedRenderer extends TransformNode {
|
|
|
669
756
|
|
|
670
757
|
/** Per-batch particle vs GPU instance counts — for diagnosing stale render state. */
|
|
671
758
|
getBatchRenderStats(): Array<{
|
|
759
|
+
/** Index into {@link batches}. */
|
|
672
760
|
batchIndex: number;
|
|
761
|
+
/** The batch's render mode. */
|
|
673
762
|
renderMode: RenderMode;
|
|
763
|
+
/** Instances the batch mesh draws. */
|
|
674
764
|
instanceCount: number;
|
|
765
|
+
/** Live particles of the batch's systems. */
|
|
675
766
|
particleCount: number;
|
|
767
|
+
/** The emitter names of the batch's systems. */
|
|
676
768
|
systemNames: string[];
|
|
677
769
|
}> {
|
|
678
770
|
return this.batches.map((batch, batchIndex) => {
|
|
@@ -693,13 +785,24 @@ export class BatchedRenderer extends TransformNode {
|
|
|
693
785
|
});
|
|
694
786
|
}
|
|
695
787
|
|
|
788
|
+
/**
|
|
789
|
+
* Disposes the batches and their meshes and materials, and lets the systems go: they keep
|
|
790
|
+
* their particles and can be added to another renderer. The renderer cannot be used again.
|
|
791
|
+
*/
|
|
696
792
|
dispose(): void {
|
|
697
793
|
for (const batch of this.batches) {
|
|
698
794
|
batch.dispose();
|
|
699
795
|
}
|
|
700
796
|
this.batches = [];
|
|
701
797
|
this.batchEmptyFrames.length = 0;
|
|
798
|
+
for (const system of this.systems) {
|
|
799
|
+
if ((system as ParticleSystem)._renderer === this) {
|
|
800
|
+
(system as ParticleSystem)._renderer = undefined;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
702
803
|
this.systems.length = 0;
|
|
804
|
+
this.orderedSystems = [];
|
|
805
|
+
this.orderedSystemsStale = true;
|
|
703
806
|
this.systemToBatchIndex.clear();
|
|
704
807
|
super.dispose();
|
|
705
808
|
}
|
package/src/EffectPool.ts
CHANGED
|
@@ -2,9 +2,11 @@ import {Vector3} from '@babylonjs/core/Maths/math.vector';
|
|
|
2
2
|
import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
3
3
|
import {Node} from '@babylonjs/core/node';
|
|
4
4
|
import {BatchedRenderer} from './BatchedRenderer';
|
|
5
|
+
import {disposeEffect} from './effectResources';
|
|
5
6
|
import {ParticleSystem} from './ParticleSystem';
|
|
6
7
|
import {QuarksUtil} from './QuarksUtil';
|
|
7
8
|
|
|
9
|
+
/** Options for a new {@link EffectPool}. */
|
|
8
10
|
export interface EffectPoolOptions {
|
|
9
11
|
/** The renderer every copy the pool makes is added to. */
|
|
10
12
|
renderer: BatchedRenderer;
|
|
@@ -146,7 +148,8 @@ export class EffectPool {
|
|
|
146
148
|
system.dispose();
|
|
147
149
|
}
|
|
148
150
|
this.systemsOf.delete(copy);
|
|
149
|
-
copy.
|
|
151
|
+
// Lets go of the textures the copy shares with the template.
|
|
152
|
+
disposeEffect(copy);
|
|
150
153
|
}
|
|
151
154
|
|
|
152
155
|
/** The copy a system belongs to is done when all of its systems are. */
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import {VertexBuffer} from '@babylonjs/core/Buffers/buffer';
|
|
2
2
|
import {Mesh} from '@babylonjs/core/Meshes/mesh';
|
|
3
3
|
import {EmitterShape, IParticleSystem, Particle, Plugin, random, ShapeJSON, ShapeTransform, Vector3} from 'quarks.core';
|
|
4
|
-
|
|
4
|
+
import {quarksNodeId} from './nodeId';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Emits from the surface of a Babylon mesh (Unity's Shape: Mesh, Unreal's Skeletal/Static Mesh
|
|
8
|
+
* Location): a point picked at random over the triangles, weighted by their area, moving along the
|
|
9
|
+
* triangle's normal at the particle's start speed. The mesh's vertices are read once, when it is
|
|
10
|
+
* set, in the mesh's own space, and laid in the emitter's space as they are, moved by
|
|
11
|
+
* {@link transform}; moving the mesh node afterwards does not move the shape. A shape without a
|
|
12
|
+
* mesh emits from the origin along +Z.
|
|
13
|
+
*/
|
|
5
14
|
export class MeshSurfaceEmitter implements EmitterShape {
|
|
15
|
+
/** The shape's type in effect files. */
|
|
6
16
|
type = 'mesh_surface';
|
|
7
17
|
|
|
8
18
|
private _triangleIndexToArea: number[] = [];
|
|
@@ -13,12 +23,16 @@ export class MeshSurfaceEmitter implements EmitterShape {
|
|
|
13
23
|
/** Offset, rotation and scale of the mesh within the particle system. */
|
|
14
24
|
transform?: ShapeTransform;
|
|
15
25
|
|
|
16
|
-
|
|
26
|
+
/** The mesh emitted from. Setting it reads its positions and indices; a mesh without both emits from the origin. */
|
|
27
|
+
get mesh(): Mesh | undefined {
|
|
17
28
|
return this._mesh;
|
|
18
29
|
}
|
|
19
30
|
|
|
20
31
|
set mesh(mesh: Mesh | undefined) {
|
|
21
32
|
this._mesh = mesh;
|
|
33
|
+
this._positions = undefined;
|
|
34
|
+
this._indices = undefined;
|
|
35
|
+
this._triangleIndexToArea.length = 0;
|
|
22
36
|
if (!mesh) return;
|
|
23
37
|
|
|
24
38
|
const positions = mesh.getVerticesData(VertexBuffer.PositionKind);
|
|
@@ -55,13 +69,19 @@ export class MeshSurfaceEmitter implements EmitterShape {
|
|
|
55
69
|
}
|
|
56
70
|
}
|
|
57
71
|
|
|
72
|
+
/**
|
|
73
|
+
* @param mesh The mesh to emit from.
|
|
74
|
+
* @param meshReferenceId The id of a mesh node in the effect file, kept until the loader links it.
|
|
75
|
+
* @param transform Offset, rotation and scale of the mesh within the system.
|
|
76
|
+
*/
|
|
58
77
|
constructor(mesh?: Mesh, meshReferenceId?: string, transform?: ShapeTransform) {
|
|
59
78
|
this._meshReferenceId = meshReferenceId;
|
|
60
79
|
this.transform = transform;
|
|
61
80
|
if (mesh) this.mesh = mesh;
|
|
62
81
|
}
|
|
63
82
|
|
|
64
|
-
|
|
83
|
+
/** Places a new particle on the surface and sets its velocity along the triangle's normal. */
|
|
84
|
+
initialize(p: Particle): void {
|
|
65
85
|
if (!this._positions || !this._indices) {
|
|
66
86
|
p.position.set(0, 0, 0);
|
|
67
87
|
p.velocity.set(0, 0, 1).multiplyScalar(p.startSpeed);
|
|
@@ -114,25 +134,30 @@ export class MeshSurfaceEmitter implements EmitterShape {
|
|
|
114
134
|
this.transform?.apply(p);
|
|
115
135
|
}
|
|
116
136
|
|
|
137
|
+
/** The shape in effect-file form; the mesh is written as its node id. */
|
|
117
138
|
toJSON(): ShapeJSON {
|
|
118
139
|
return {
|
|
119
140
|
type: 'mesh_surface',
|
|
120
|
-
mesh: this._mesh ? (this._mesh
|
|
141
|
+
mesh: this._mesh ? quarksNodeId(this._mesh) : (this._meshReferenceId ?? ''),
|
|
121
142
|
...ShapeTransform.toShapeJSON(this.transform),
|
|
122
143
|
};
|
|
123
144
|
}
|
|
124
145
|
|
|
146
|
+
/** Reads {@link toJSON} output; the mesh stays an id until `QuarksLoader` links it. */
|
|
125
147
|
static fromJSON(json: any): MeshSurfaceEmitter {
|
|
126
148
|
return new MeshSurfaceEmitter(undefined, json?.mesh, ShapeTransform.fromJSON(json?.transform));
|
|
127
149
|
}
|
|
128
150
|
|
|
151
|
+
/** A copy emitting from the same mesh. */
|
|
129
152
|
clone(): EmitterShape {
|
|
130
153
|
return new MeshSurfaceEmitter(this._mesh, this._meshReferenceId, this.transform?.clone());
|
|
131
154
|
}
|
|
132
155
|
|
|
156
|
+
/** Does nothing: the shape keeps no state between steps. */
|
|
133
157
|
update(_system: IParticleSystem, _delta: number): void {}
|
|
134
158
|
}
|
|
135
159
|
|
|
160
|
+
/** Registers {@link MeshSurfaceEmitter} with quarks.core's shape table; loaded when the package is imported. */
|
|
136
161
|
export const MeshSurfaceEmitterPlugin: Plugin = {
|
|
137
162
|
id: 'babylon.quarks',
|
|
138
163
|
initialize: () => {},
|