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/src/batchKey.ts
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import {Constants} from '@babylonjs/core/Engines/constants';
|
|
2
|
+
import type {Vector3} from 'quarks.core';
|
|
3
|
+
import type {VFXBatchSettings} from './BatchedRenderer';
|
|
4
|
+
import {DEFAULT_SOFT_FAR_FADE, validSoftFade, validTileCount} from './system/normalize';
|
|
5
|
+
import {
|
|
6
|
+
normalizeErosion,
|
|
7
|
+
normalizeGradientMap,
|
|
8
|
+
normalizeMapUV,
|
|
9
|
+
normalizeMeshRim,
|
|
10
|
+
normalizeUVDistortion,
|
|
11
|
+
readsUV1,
|
|
12
|
+
RenderMode,
|
|
13
|
+
StoredBatchSettings,
|
|
14
|
+
TextureUV,
|
|
15
|
+
} from './VFXBatch';
|
|
16
|
+
|
|
17
|
+
// What decides whether two systems draw from one batch.
|
|
18
|
+
//
|
|
19
|
+
// A batch is one draw call with one material, so its systems share everything the material
|
|
20
|
+
// holds. That falls into two groups, as a Unity material's shader keywords and texture slots
|
|
21
|
+
// against its MaterialPropertyBlock values, or an Unreal material against its dynamic
|
|
22
|
+
// instance's parameters:
|
|
23
|
+
//
|
|
24
|
+
// - the shader key: the render mode, the geometry, the textures, the blend and depth state,
|
|
25
|
+
// and which effects are compiled in. A change here needs a new material;
|
|
26
|
+
// - the live parameters: the numbers the effects run on, such as a soft fade distance, an
|
|
27
|
+
// erosion edge width or a rim colour. They are uniforms, so a change here is written to the
|
|
28
|
+
// material in place.
|
|
29
|
+
//
|
|
30
|
+
// Each render mode reads only part of the settings, so before two settings are compared they
|
|
31
|
+
// are reduced to a canonical form in which every field the mode ignores is at its default. Two
|
|
32
|
+
// billboards that differ only in a mesh rim then share a batch.
|
|
33
|
+
|
|
34
|
+
/** Geometry a trail batch's settings hold, since a trail draws its own strip. */
|
|
35
|
+
const NO_GEOMETRY = new Float32Array(0);
|
|
36
|
+
const NO_INDICES = new Uint16Array(0);
|
|
37
|
+
|
|
38
|
+
/** Whether `renderMode` draws camera-facing quads that take a pivot and screen-size limits. */
|
|
39
|
+
export function takesPivotAndSizeLimits(renderMode: RenderMode): boolean {
|
|
40
|
+
return (
|
|
41
|
+
renderMode === RenderMode.BillBoard ||
|
|
42
|
+
renderMode === RenderMode.StretchedBillBoard ||
|
|
43
|
+
renderMode === RenderMode.VerticalBillBoard ||
|
|
44
|
+
renderMode === RenderMode.HorizontalBillBoard
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Whether the settings draw a flipbook: more than one cell in either direction. */
|
|
49
|
+
export function tilesSheet(settings: {uTileCount: number; vTileCount: number}): boolean {
|
|
50
|
+
return settings.uTileCount > 1 || settings.vTileCount > 1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Whether the settings put a screen-size limit on the particles. */
|
|
54
|
+
export function hasSizeLimits(settings: {minParticleSize: number; maxParticleSize: number}): boolean {
|
|
55
|
+
return settings.minParticleSize > 0 || settings.maxParticleSize !== Infinity;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A batch's copy of a system's settings: every optional field defaulted, and every field the
|
|
60
|
+
* render mode ignores at its default, so that systems that draw the same share a batch whatever
|
|
61
|
+
* their unused settings say. See {@link batchSettingsEqual}.
|
|
62
|
+
*/
|
|
63
|
+
export function storedBatchSettings(settings: VFXBatchSettings): StoredBatchSettings {
|
|
64
|
+
const renderMode = settings.renderMode;
|
|
65
|
+
const isMesh = renderMode === RenderMode.Mesh;
|
|
66
|
+
const isTrail = renderMode === RenderMode.Trail;
|
|
67
|
+
const quad = takesPivotAndSizeLimits(renderMode);
|
|
68
|
+
const softParticles = settings.softParticles;
|
|
69
|
+
const transparent = settings.materialTransparent;
|
|
70
|
+
const pivot = settings.pivot;
|
|
71
|
+
const mapUV = normalizeMapUV(settings.mapUV);
|
|
72
|
+
const uvDistortion = normalizeUVDistortion(settings.uvDistortion);
|
|
73
|
+
const gradientMap = normalizeGradientMap(settings.gradientMap);
|
|
74
|
+
const erosion = normalizeErosion(settings.erosion);
|
|
75
|
+
const uTileCount = validTileCount(settings.uTileCount);
|
|
76
|
+
const vTileCount = validTileCount(settings.vTileCount);
|
|
77
|
+
const stored: StoredBatchSettings = {
|
|
78
|
+
renderMode,
|
|
79
|
+
// A trail draws its own strip and reads none of the instanced geometry.
|
|
80
|
+
instancingGeometry: isTrail ? NO_GEOMETRY : settings.instancingGeometry,
|
|
81
|
+
instancingIndices: isTrail ? NO_INDICES : settings.instancingIndices,
|
|
82
|
+
instancingUVs: isTrail ? undefined : settings.instancingUVs,
|
|
83
|
+
// Normals, and the second UV set when a texture reads it, only reach mesh particles.
|
|
84
|
+
instancingUVs2: isMesh && readsUV1({mapUV, uvDistortion, erosion}) ? settings.instancingUVs2 : undefined,
|
|
85
|
+
instancingNormals: isMesh ? settings.instancingNormals : undefined,
|
|
86
|
+
renderOrder: settings.renderOrder,
|
|
87
|
+
uTileCount,
|
|
88
|
+
vTileCount,
|
|
89
|
+
// A trail cuts its cell on the CPU; a 1×1 sheet has no second cell to blend into.
|
|
90
|
+
blendTiles: !isTrail && settings.blendTiles && tilesSheet({uTileCount, vTileCount}),
|
|
91
|
+
softParticles,
|
|
92
|
+
softNearFade: softParticles ? validSoftFade(settings.softNearFade, 0) : 0,
|
|
93
|
+
softFarFade: softParticles ? validSoftFade(settings.softFarFade, DEFAULT_SOFT_FAR_FADE) : 0,
|
|
94
|
+
// An opaque batch draws with blending off, whatever mode it would blend with.
|
|
95
|
+
materialBlendMode: transparent ? settings.materialBlendMode : Constants.ALPHA_DISABLE,
|
|
96
|
+
materialTransparent: transparent,
|
|
97
|
+
materialDepthTest: settings.materialDepthTest,
|
|
98
|
+
materialDepthWrite: settings.materialDepthWrite,
|
|
99
|
+
materialAlphaTest: settings.materialAlphaTest > 0 ? settings.materialAlphaTest : 0,
|
|
100
|
+
texture: settings.texture,
|
|
101
|
+
reflectionTexture: isMesh ? (settings.reflectionTexture ?? null) : null,
|
|
102
|
+
reflectionLevel: isMesh ? (settings.reflectionLevel ?? 1) : 1,
|
|
103
|
+
reflectionAtlas: isMesh ? (settings.reflectionAtlas ?? null) : null,
|
|
104
|
+
layerMask: settings.layerMask,
|
|
105
|
+
renderingGroupId: settings.renderingGroupId ?? 0,
|
|
106
|
+
applyFog: settings.applyFog ?? true,
|
|
107
|
+
pivot: quad && pivot && (pivot.x !== 0 || pivot.y !== 0 || pivot.z !== 0) ? pivot.clone() : undefined,
|
|
108
|
+
sortMode: settings.sortMode ?? 'none',
|
|
109
|
+
minParticleSize: quad ? Math.max(0, settings.minParticleSize ?? 0) : 0,
|
|
110
|
+
maxParticleSize: quad ? positiveOrUnlimited(settings.maxParticleSize) : Infinity,
|
|
111
|
+
lighting: isMesh ? (settings.lighting ?? 'fixed') : 'fixed',
|
|
112
|
+
rim: isMesh ? normalizeMeshRim(settings.rim) : undefined,
|
|
113
|
+
mapUV,
|
|
114
|
+
uvDistortion,
|
|
115
|
+
gradientMap,
|
|
116
|
+
erosion,
|
|
117
|
+
};
|
|
118
|
+
return stored;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** `size` when it limits something, else no limit. */
|
|
122
|
+
function positiveOrUnlimited(size: number | undefined): number {
|
|
123
|
+
return size !== undefined && size > 0 ? size : Infinity;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Whether two canonical settings compile to the same material: the same render mode, geometry,
|
|
128
|
+
* textures, blend and depth state, and the same effects. Batches with equal shader keys differ
|
|
129
|
+
* only in {@link liveParamsEqual | live parameters}.
|
|
130
|
+
*/
|
|
131
|
+
export function shaderKeyEqual(a: StoredBatchSettings, b: StoredBatchSettings): boolean {
|
|
132
|
+
return (
|
|
133
|
+
a.renderMode === b.renderMode &&
|
|
134
|
+
a.instancingGeometry === b.instancingGeometry &&
|
|
135
|
+
a.instancingIndices === b.instancingIndices &&
|
|
136
|
+
a.instancingUVs === b.instancingUVs &&
|
|
137
|
+
a.instancingUVs2 === b.instancingUVs2 &&
|
|
138
|
+
a.instancingNormals === b.instancingNormals &&
|
|
139
|
+
a.texture === b.texture &&
|
|
140
|
+
a.reflectionTexture === b.reflectionTexture &&
|
|
141
|
+
a.reflectionAtlas === b.reflectionAtlas &&
|
|
142
|
+
a.lighting === b.lighting &&
|
|
143
|
+
a.rim?.mode === b.rim?.mode &&
|
|
144
|
+
a.uTileCount === b.uTileCount &&
|
|
145
|
+
a.vTileCount === b.vTileCount &&
|
|
146
|
+
a.blendTiles === b.blendTiles &&
|
|
147
|
+
a.softParticles === b.softParticles &&
|
|
148
|
+
a.materialBlendMode === b.materialBlendMode &&
|
|
149
|
+
a.materialTransparent === b.materialTransparent &&
|
|
150
|
+
a.materialDepthTest === b.materialDepthTest &&
|
|
151
|
+
a.materialDepthWrite === b.materialDepthWrite &&
|
|
152
|
+
a.materialAlphaTest > 0 === b.materialAlphaTest > 0 &&
|
|
153
|
+
a.layerMask === b.layerMask &&
|
|
154
|
+
a.renderingGroupId === b.renderingGroupId &&
|
|
155
|
+
a.renderOrder === b.renderOrder &&
|
|
156
|
+
a.applyFog === b.applyFog &&
|
|
157
|
+
a.sortMode === b.sortMode &&
|
|
158
|
+
!a.pivot === !b.pivot &&
|
|
159
|
+
hasSizeLimits(a) === hasSizeLimits(b) &&
|
|
160
|
+
a.mapUV?.channel === b.mapUV?.channel &&
|
|
161
|
+
a.uvDistortion?.map === b.uvDistortion?.map &&
|
|
162
|
+
a.uvDistortion?.mapUV.channel === b.uvDistortion?.mapUV.channel &&
|
|
163
|
+
!a.gradientMap === !b.gradientMap &&
|
|
164
|
+
a.gradientMap?.ramp === b.gradientMap?.ramp &&
|
|
165
|
+
!a.erosion === !b.erosion &&
|
|
166
|
+
a.erosion?.mask === b.erosion?.mask &&
|
|
167
|
+
a.erosion?.maskUV.channel === b.erosion?.maskUV.channel
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Whether two canonical settings with the same shader key set the material's uniforms to the
|
|
173
|
+
* same values: the fade distances, alpha cutoff, pivot, size limits, reflection level, rim
|
|
174
|
+
* colour, each effect's parameters and each texture's layout.
|
|
175
|
+
*/
|
|
176
|
+
export function liveParamsEqual(a: StoredBatchSettings, b: StoredBatchSettings): boolean {
|
|
177
|
+
return (
|
|
178
|
+
a.softNearFade === b.softNearFade &&
|
|
179
|
+
a.softFarFade === b.softFarFade &&
|
|
180
|
+
a.materialAlphaTest === b.materialAlphaTest &&
|
|
181
|
+
a.reflectionLevel === b.reflectionLevel &&
|
|
182
|
+
pivotEqual(a.pivot, b.pivot) &&
|
|
183
|
+
a.minParticleSize === b.minParticleSize &&
|
|
184
|
+
a.maxParticleSize === b.maxParticleSize &&
|
|
185
|
+
rimParamsEqual(a.rim, b.rim) &&
|
|
186
|
+
uvLayoutEqual(a.mapUV, b.mapUV) &&
|
|
187
|
+
a.uvDistortion?.strength === b.uvDistortion?.strength &&
|
|
188
|
+
uvLayoutEqual(a.uvDistortion?.mapUV, b.uvDistortion?.mapUV) &&
|
|
189
|
+
a.gradientMap?.channel === b.gradientMap?.channel &&
|
|
190
|
+
a.erosion?.softness === b.erosion?.softness &&
|
|
191
|
+
a.erosion?.edgeWidth === b.erosion?.edgeWidth &&
|
|
192
|
+
colorEqual(a.erosion?.edgeColor, b.erosion?.edgeColor) &&
|
|
193
|
+
uvLayoutEqual(a.erosion?.maskUV, b.erosion?.maskUV)
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Whether two canonical settings draw from one batch: the same shader key and the same live parameters. */
|
|
198
|
+
export function batchSettingsEqual(a: StoredBatchSettings, b: StoredBatchSettings): boolean {
|
|
199
|
+
return shaderKeyEqual(a, b) && liveParamsEqual(a, b);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** True when both pivots put the quad in the same place; a missing pivot is zero. */
|
|
203
|
+
function pivotEqual(a: Vector3 | undefined, b: Vector3 | undefined): boolean {
|
|
204
|
+
return (a?.x ?? 0) === (b?.x ?? 0) && (a?.y ?? 0) === (b?.y ?? 0) && (a?.z ?? 0) === (b?.z ?? 0);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function rimParamsEqual(
|
|
208
|
+
a: {color: readonly number[]; power: number; strength: number} | undefined,
|
|
209
|
+
b: {color: readonly number[]; power: number; strength: number} | undefined
|
|
210
|
+
): boolean {
|
|
211
|
+
if (!a || !b) {
|
|
212
|
+
return !a === !b;
|
|
213
|
+
}
|
|
214
|
+
return a.power === b.power && a.strength === b.strength && colorEqual(a.color, b.color);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function colorEqual(a: readonly number[] | undefined, b: readonly number[] | undefined): boolean {
|
|
218
|
+
if (!a || !b) {
|
|
219
|
+
return !a === !b;
|
|
220
|
+
}
|
|
221
|
+
return a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Whether two layouts tile, offset and scroll the texture the same; the channel is part of the shader key. */
|
|
225
|
+
function uvLayoutEqual(a: TextureUV | undefined, b: TextureUV | undefined): boolean {
|
|
226
|
+
if (!a || !b) {
|
|
227
|
+
return !a === !b;
|
|
228
|
+
}
|
|
229
|
+
return (
|
|
230
|
+
a.tiling[0] === b.tiling[0] &&
|
|
231
|
+
a.tiling[1] === b.tiling[1] &&
|
|
232
|
+
a.offset[0] === b.offset[0] &&
|
|
233
|
+
a.offset[1] === b.offset[1] &&
|
|
234
|
+
a.scroll[0] === b.scroll[0] &&
|
|
235
|
+
a.scroll[1] === b.scroll[1]
|
|
236
|
+
);
|
|
237
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type {Material} from '@babylonjs/core/Materials/material';
|
|
2
|
+
import type {BaseTexture} from '@babylonjs/core/Materials/Textures/baseTexture';
|
|
3
|
+
import type {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
4
|
+
import type {Node} from '@babylonjs/core/node';
|
|
5
|
+
|
|
6
|
+
/** What an effect root owns: the textures it holds a count on, and the materials made for it alone. */
|
|
7
|
+
interface EffectResources {
|
|
8
|
+
textures: Set<BaseTexture>;
|
|
9
|
+
materials: Material[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const owned = new WeakMap<Node, EffectResources>();
|
|
13
|
+
/** How many effect roots hold each texture. */
|
|
14
|
+
const holders = new WeakMap<BaseTexture, number>();
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Records that `root` uses `textures` and owns `materials`. They are freed when the root is
|
|
18
|
+
* disposed, however that happens: {@link disposeEffect}, `root.dispose()`, a parent's dispose or
|
|
19
|
+
* the scene's.
|
|
20
|
+
*/
|
|
21
|
+
export function retainEffectResources(
|
|
22
|
+
root: TransformNode,
|
|
23
|
+
textures: Iterable<BaseTexture>,
|
|
24
|
+
materials: Material[]
|
|
25
|
+
): void {
|
|
26
|
+
if (owned.has(root)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const resources: EffectResources = {textures: new Set(textures), materials};
|
|
30
|
+
for (const texture of resources.textures) {
|
|
31
|
+
holders.set(texture, (holders.get(texture) ?? 0) + 1);
|
|
32
|
+
}
|
|
33
|
+
owned.set(root, resources);
|
|
34
|
+
root.onDisposeObservable.addOnce(() => releaseEffectResources(root));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Gives `copy` a hold on the textures the effect `source` belongs to holds, as a copy of the
|
|
39
|
+
* effect, or of a node in it, draws with them too.
|
|
40
|
+
*/
|
|
41
|
+
export function shareEffectResources(source: TransformNode, copy: TransformNode): void {
|
|
42
|
+
for (let node: Node | null = source; node !== null; node = node.parent) {
|
|
43
|
+
const resources = owned.get(node);
|
|
44
|
+
if (resources !== undefined) {
|
|
45
|
+
retainEffectResources(copy, resources.textures, []);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function releaseEffectResources(root: Node): void {
|
|
52
|
+
const resources = owned.get(root);
|
|
53
|
+
if (resources === undefined) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
owned.delete(root);
|
|
57
|
+
for (const material of resources.materials) {
|
|
58
|
+
material.dispose();
|
|
59
|
+
}
|
|
60
|
+
for (const texture of resources.textures) {
|
|
61
|
+
const count = (holders.get(texture) ?? 1) - 1;
|
|
62
|
+
if (count > 0) {
|
|
63
|
+
holders.set(texture, count);
|
|
64
|
+
} else {
|
|
65
|
+
holders.delete(texture);
|
|
66
|
+
texture.dispose();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Disposes an effect root and its nodes and systems, the materials made for it, and each texture it
|
|
73
|
+
* holds once no other effect root holds it. A root nothing was recorded for is only disposed.
|
|
74
|
+
*/
|
|
75
|
+
export function disposeEffect(root: TransformNode): void {
|
|
76
|
+
root.dispose();
|
|
77
|
+
// A root disposed earlier, before its hold was recorded, still lets go.
|
|
78
|
+
releaseEffectResources(root);
|
|
79
|
+
}
|
package/src/envAtlas.ts
CHANGED
|
@@ -2,8 +2,18 @@ import {BaseTexture} from '@babylonjs/core/Materials/Textures/baseTexture';
|
|
|
2
2
|
import {Texture} from '@babylonjs/core/Materials/Textures/texture';
|
|
3
3
|
import {Scene} from '@babylonjs/core/scene';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Atlases by cube, then by the scene they were built for: a texture belongs to one scene and one
|
|
7
|
+
* engine, so a second scene (an editor preview, a second canvas) gets an atlas of its own.
|
|
8
|
+
*/
|
|
9
|
+
const atlasesByCube = new WeakMap<BaseTexture, Map<Scene, Texture>>();
|
|
10
|
+
const pendingByCube = new WeakMap<BaseTexture, Map<Scene, Promise<Texture | null>>>();
|
|
11
|
+
/** Atlases this module built, which it disposes; an atlas given to {@link cacheEnvAtlas} stays the caller's. */
|
|
12
|
+
const builtAtlases = new WeakSet<Texture>();
|
|
13
|
+
/** Atlases whose dispose this module already listens to. */
|
|
14
|
+
const watchedAtlases = new WeakSet<Texture>();
|
|
15
|
+
/** Cubes whose dispose this module already listens to. */
|
|
16
|
+
const watchedCubes = new WeakSet<BaseTexture>();
|
|
7
17
|
|
|
8
18
|
/** Reads the six face URLs from a Babylon CubeTexture (px py pz nx ny nz). */
|
|
9
19
|
export function cubeFaceUrls(reflection: BaseTexture | null | undefined): string[] | null {
|
|
@@ -15,14 +25,85 @@ export function cubeFaceUrls(reflection: BaseTexture | null | undefined): string
|
|
|
15
25
|
return files?.length === 6 ? files.slice() : null;
|
|
16
26
|
}
|
|
17
27
|
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
return
|
|
28
|
+
/** The scene a texture belongs to, or null for a disposed texture or one with no scene. */
|
|
29
|
+
function sceneOf(texture: BaseTexture): Scene | null {
|
|
30
|
+
return (texture as {getScene?: () => Scene | null}).getScene?.() ?? null;
|
|
21
31
|
}
|
|
22
32
|
|
|
23
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Returns the atlas already built or cached for this cube in `scene`, if any.
|
|
35
|
+
*
|
|
36
|
+
* @param cube - The cube texture the atlas was made from.
|
|
37
|
+
* @param scene - The scene the atlas must belong to. Defaults to the cube's own scene.
|
|
38
|
+
*/
|
|
39
|
+
export function getCachedEnvAtlas(cube: BaseTexture, scene: Scene | null = sceneOf(cube)): Texture | null {
|
|
40
|
+
return (scene && atlasesByCube.get(cube)?.get(scene)) || null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Stores a prebuilt atlas for a cube in the atlas's scene, so systems that use the cube in that
|
|
45
|
+
* scene take it instead of building one. The atlas stays the caller's: quarks never disposes it,
|
|
46
|
+
* and drops it from the cache when it, or the cube, is disposed.
|
|
47
|
+
*
|
|
48
|
+
* @param cube - The cube texture the atlas stands in for.
|
|
49
|
+
* @param atlas - A 3×2 atlas in the layout of {@link createEnvAtlasFromFaceUrls}.
|
|
50
|
+
*/
|
|
24
51
|
export function cacheEnvAtlas(cube: BaseTexture, atlas: Texture): void {
|
|
25
|
-
|
|
52
|
+
const scene = sceneOf(atlas) ?? sceneOf(cube);
|
|
53
|
+
if (scene) {
|
|
54
|
+
store(cube, scene, atlas);
|
|
55
|
+
} else {
|
|
56
|
+
console.warn('[babylon.quarks] cacheEnvAtlas: neither the atlas nor the cube has a scene; not cached');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Drops the atlases made for `cube` from the cache and disposes the ones quarks built. Runs by
|
|
62
|
+
* itself when the cube is disposed; call it when an environment is swapped out but the cube is
|
|
63
|
+
* kept. Systems that use the cube build a new atlas the next time their material is resolved.
|
|
64
|
+
*
|
|
65
|
+
* @param cube - The cube texture whose atlases to drop.
|
|
66
|
+
* @param scene - Only the atlas for this scene. Defaults to every scene.
|
|
67
|
+
*/
|
|
68
|
+
export function disposeEnvAtlas(cube: BaseTexture, scene?: Scene): void {
|
|
69
|
+
const atlases = atlasesByCube.get(cube);
|
|
70
|
+
if (!atlases) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
for (const [atlasScene, atlas] of [...atlases]) {
|
|
74
|
+
if (scene && atlasScene !== scene) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
atlases.delete(atlasScene);
|
|
78
|
+
if (builtAtlases.has(atlas)) {
|
|
79
|
+
atlas.dispose();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function store(cube: BaseTexture, scene: Scene, atlas: Texture): void {
|
|
85
|
+
let atlases = atlasesByCube.get(cube);
|
|
86
|
+
if (!atlases) {
|
|
87
|
+
atlases = new Map();
|
|
88
|
+
atlasesByCube.set(cube, atlases);
|
|
89
|
+
}
|
|
90
|
+
atlases.set(scene, atlas);
|
|
91
|
+
// A scene disposes its textures, so this also lets go of a disposed scene.
|
|
92
|
+
if (!watchedAtlases.has(atlas) && atlas.onDisposeObservable) {
|
|
93
|
+
watchedAtlases.add(atlas);
|
|
94
|
+
atlas.onDisposeObservable.addOnce(() => {
|
|
95
|
+
if (atlasesByCube.get(cube)?.get(scene) === atlas) {
|
|
96
|
+
atlasesByCube.get(cube)!.delete(scene);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (!watchedCubes.has(cube) && cube.onDisposeObservable) {
|
|
101
|
+
watchedCubes.add(cube);
|
|
102
|
+
cube.onDisposeObservable.addOnce(() => {
|
|
103
|
+
watchedCubes.delete(cube);
|
|
104
|
+
disposeEnvAtlas(cube);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
26
107
|
}
|
|
27
108
|
|
|
28
109
|
/**
|
|
@@ -63,23 +144,30 @@ export const envAtlasBuilder = {
|
|
|
63
144
|
};
|
|
64
145
|
|
|
65
146
|
/**
|
|
66
|
-
* Ensures a 3×2 env atlas exists for a CubeTexture
|
|
67
|
-
* synchronously when available; otherwise starts (or joins) an async build and
|
|
68
|
-
*
|
|
147
|
+
* Ensures a 3×2 env atlas exists for a CubeTexture in `scene`. Returns a cached atlas
|
|
148
|
+
* synchronously when available; otherwise starts (or joins) an async build and invokes onReady
|
|
149
|
+
* when the atlas is usable. A cube disposed before its build finishes gets no atlas.
|
|
69
150
|
*/
|
|
70
151
|
export function ensureEnvAtlasFromCube(
|
|
71
152
|
cube: BaseTexture,
|
|
72
153
|
scene: Scene,
|
|
73
154
|
onReady: (atlas: Texture) => void
|
|
74
155
|
): Texture | null {
|
|
75
|
-
const cached =
|
|
156
|
+
const cached = getCachedEnvAtlas(cube, scene);
|
|
76
157
|
if (cached) {
|
|
77
158
|
onReady(cached);
|
|
78
159
|
return cached;
|
|
79
160
|
}
|
|
80
161
|
|
|
162
|
+
let pendingByScene = pendingByCube.get(cube);
|
|
163
|
+
if (!pendingByScene) {
|
|
164
|
+
pendingByScene = new Map();
|
|
165
|
+
pendingByCube.set(cube, pendingByScene);
|
|
166
|
+
}
|
|
167
|
+
const pendings = pendingByScene;
|
|
168
|
+
|
|
81
169
|
const kick = () => {
|
|
82
|
-
const existingPending =
|
|
170
|
+
const existingPending = pendings.get(scene);
|
|
83
171
|
if (existingPending) {
|
|
84
172
|
existingPending.then((atlas) => {
|
|
85
173
|
if (atlas) {
|
|
@@ -94,20 +182,34 @@ export function ensureEnvAtlasFromCube(
|
|
|
94
182
|
return;
|
|
95
183
|
}
|
|
96
184
|
|
|
185
|
+
let cubeDisposed = false;
|
|
186
|
+
// Removed once the build settles, so it does not keep the scene and onReady alive.
|
|
187
|
+
const cubeObserver = cube.onDisposeObservable?.addOnce(() => (cubeDisposed = true));
|
|
188
|
+
const settle = () => {
|
|
189
|
+
pendings.delete(scene);
|
|
190
|
+
if (cubeObserver) {
|
|
191
|
+
cube.onDisposeObservable.remove(cubeObserver);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
97
194
|
const pending = envAtlasBuilder
|
|
98
195
|
.createFromFaceUrls(urls, scene)
|
|
99
196
|
.then((atlas) => {
|
|
100
|
-
|
|
101
|
-
|
|
197
|
+
settle();
|
|
198
|
+
if (cubeDisposed || scene.isDisposed) {
|
|
199
|
+
atlas.dispose();
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
builtAtlases.add(atlas);
|
|
203
|
+
store(cube, scene, atlas);
|
|
102
204
|
onReady(atlas);
|
|
103
205
|
return atlas;
|
|
104
206
|
})
|
|
105
207
|
.catch((err) => {
|
|
106
|
-
|
|
208
|
+
settle();
|
|
107
209
|
console.warn('[babylon.quarks] Failed to build env atlas from cubemap faces', err);
|
|
108
210
|
return null;
|
|
109
211
|
});
|
|
110
|
-
|
|
212
|
+
pendings.set(scene, pending);
|
|
111
213
|
};
|
|
112
214
|
|
|
113
215
|
if (cube.isReady()) {
|
|
@@ -121,7 +223,7 @@ export function ensureEnvAtlasFromCube(
|
|
|
121
223
|
}
|
|
122
224
|
}
|
|
123
225
|
|
|
124
|
-
return
|
|
226
|
+
return getCachedEnvAtlas(cube, scene);
|
|
125
227
|
}
|
|
126
228
|
|
|
127
229
|
/** Loads one image URL as an HTMLImageElement. */
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import {loadPlugin} from 'quarks.core';
|
|
2
2
|
import {MeshSurfaceEmitterPlugin} from './MeshSurfaceEmitter';
|
|
3
|
-
import {registerShaderChunks} from './shaders';
|
|
4
3
|
|
|
5
4
|
export * from 'quarks.core';
|
|
6
|
-
export {BatchedParticleRenderer} from './BatchedParticleRenderer';
|
|
7
5
|
export {BatchedRenderer} from './BatchedRenderer';
|
|
8
6
|
export type {AdaptivePerformanceOptions, AdaptivePerformanceState, VFXBatchSettings} from './BatchedRenderer';
|
|
9
7
|
export {EffectPool} from './EffectPool';
|
|
@@ -12,11 +10,12 @@ export {
|
|
|
12
10
|
cacheEnvAtlas,
|
|
13
11
|
createEnvAtlasFromFaceUrls,
|
|
14
12
|
cubeFaceUrls,
|
|
13
|
+
disposeEnvAtlas,
|
|
15
14
|
ensureEnvAtlasFromCube,
|
|
16
15
|
getCachedEnvAtlas,
|
|
17
16
|
} from './envAtlas';
|
|
18
|
-
export * from './materials/';
|
|
19
17
|
export {MeshSurfaceEmitter, MeshSurfaceEmitterPlugin} from './MeshSurfaceEmitter';
|
|
18
|
+
export {quarksNodeId} from './nodeId';
|
|
20
19
|
export {ParticleEmitter} from './ParticleEmitter';
|
|
21
20
|
export {ParticleSystem} from './ParticleSystem';
|
|
22
21
|
export type {
|
|
@@ -29,22 +28,47 @@ export type {
|
|
|
29
28
|
ParticleSystemParameters,
|
|
30
29
|
ScalingMode,
|
|
31
30
|
} from './ParticleSystem';
|
|
32
|
-
export {QuarksLoader} from './QuarksLoader';
|
|
31
|
+
export {QuarksFormatError, QuarksLoader} from './QuarksLoader';
|
|
33
32
|
export type {
|
|
34
33
|
QuarksBatchLoadOptions,
|
|
35
34
|
QuarksJSONLoadEntry,
|
|
36
35
|
QuarksLoaderOptions,
|
|
36
|
+
QuarksLoadOptions,
|
|
37
37
|
QuarksResourceKind,
|
|
38
38
|
QuarksResourceResolver,
|
|
39
39
|
} from './QuarksLoader';
|
|
40
40
|
export {QuarksPrefab} from './QuarksPrefab';
|
|
41
41
|
export type {AnimationData, QuarksTimelineClip} from './QuarksPrefab';
|
|
42
|
+
export {QUARKS_FORMAT_VERSION, serializeEffect} from './QuarksSerializer';
|
|
43
|
+
export type {
|
|
44
|
+
QuarksEffectJSON,
|
|
45
|
+
QuarksGeometryJSON,
|
|
46
|
+
QuarksImageJSON,
|
|
47
|
+
QuarksNodeJSON,
|
|
48
|
+
QuarksTextureJSON,
|
|
49
|
+
SerializeEffectOptions,
|
|
50
|
+
} from './QuarksSerializer';
|
|
42
51
|
export {QuarksUtil} from './QuarksUtil';
|
|
43
|
-
export
|
|
44
|
-
export {
|
|
45
|
-
export {
|
|
52
|
+
export {shaderProgram} from './shaderProgram';
|
|
53
|
+
export type {ShaderContext, ShaderDepth, ShaderProgram, ShaderSettings} from './shaderProgram';
|
|
54
|
+
export type {ShaderSources} from './shaders/shaderLanguageSupport';
|
|
55
|
+
export {shaderVariants} from './shaderVariants';
|
|
56
|
+
export type {ShaderVariant} from './shaderVariants';
|
|
57
|
+
export {decodeSoftDepth, softDepthParams} from './softDepth';
|
|
58
|
+
export type {DepthTextureOptions, SoftDepthEncoding} from './softDepth';
|
|
59
|
+
export {textureSourceUrl} from './textureSource';
|
|
60
|
+
export {
|
|
61
|
+
MAX_TRAIL_TESSELLATION,
|
|
62
|
+
trailCurveTension,
|
|
63
|
+
trailLifetime,
|
|
64
|
+
trailMinVertexDistance,
|
|
65
|
+
trailTessellation,
|
|
66
|
+
trailTextureMode,
|
|
67
|
+
trailTextureScale,
|
|
68
|
+
} from './trailSettings';
|
|
46
69
|
export {
|
|
47
70
|
defaultMeshLighting,
|
|
71
|
+
EROSION_DEFAULT_SOFTNESS,
|
|
48
72
|
erosionEqual,
|
|
49
73
|
GRADIENT_MAP_WEIGHTS,
|
|
50
74
|
gradientMapEqual,
|
|
@@ -60,6 +84,7 @@ export {
|
|
|
60
84
|
RenderMode,
|
|
61
85
|
textureUVEqual,
|
|
62
86
|
textureUVToJSON,
|
|
87
|
+
UV_DISTORTION_DEFAULT_STRENGTH,
|
|
63
88
|
UV_DISTORTION_DEFAULT_UV,
|
|
64
89
|
uvDistortionEqual,
|
|
65
90
|
VFXBatch,
|
|
@@ -80,30 +105,4 @@ export type {
|
|
|
80
105
|
UVChannel,
|
|
81
106
|
} from './VFXBatch';
|
|
82
107
|
|
|
83
|
-
// Re-export quarks.core types (interfaces - type-only)
|
|
84
|
-
export type {
|
|
85
|
-
Behavior,
|
|
86
|
-
BillBoardSettings,
|
|
87
|
-
ColorGenerator,
|
|
88
|
-
EmissionState,
|
|
89
|
-
EmitterShape,
|
|
90
|
-
FunctionColorGenerator,
|
|
91
|
-
FunctionValueGenerator,
|
|
92
|
-
IEmitter,
|
|
93
|
-
IParticle,
|
|
94
|
-
IParticleSystem,
|
|
95
|
-
MeshSettings,
|
|
96
|
-
Particle,
|
|
97
|
-
ParticleSystemEvent,
|
|
98
|
-
ParticleSystemEventType,
|
|
99
|
-
RendererEmitterSettings,
|
|
100
|
-
RotationGenerator,
|
|
101
|
-
SerializationOptions,
|
|
102
|
-
StretchedBillBoardSettings,
|
|
103
|
-
TrailSettings,
|
|
104
|
-
ValueGenerator,
|
|
105
|
-
Vector3Generator,
|
|
106
|
-
} from 'quarks.core';
|
|
107
|
-
|
|
108
|
-
registerShaderChunks();
|
|
109
108
|
loadPlugin(MeshSurfaceEmitterPlugin);
|
package/src/nodeId.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {RandomGUID} from '@babylonjs/core/Misc/guid';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The id an effect file knows a node by: the uuid it was loaded under, or, for a node made in code,
|
|
5
|
+
* a GUID given to it the first time it is asked for and kept from then on. Every link written into
|
|
6
|
+
* a file (a sub emitter's target, a mesh surface shape's mesh, a prefab animation's target) and
|
|
7
|
+
* every node's own `uuid` use this, so an effect loaded, saved and loaded again keeps its links,
|
|
8
|
+
* and a node made in code never takes the id of one loaded from a file.
|
|
9
|
+
*/
|
|
10
|
+
export function quarksNodeId(node: object): string {
|
|
11
|
+
const ided = node as {_quarksUUID?: string};
|
|
12
|
+
return (ided._quarksUUID ??= RandomGUID());
|
|
13
|
+
}
|