babylon.quarks 0.27.0 → 0.28.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 +425 -0
- package/README.md +3 -1
- 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 +4256 -831
- package/dist/types/index.d.ts +4256 -831
- package/package.json +7 -2
- package/src/BatchedRenderer.ts +93 -78
- package/src/EffectPool.ts +91 -12
- package/src/MeshSurfaceEmitter.ts +24 -13
- package/src/ParticleEmitter.ts +31 -3
- package/src/ParticleSystem.ts +92 -25
- package/src/QuarksLoader.ts +289 -171
- package/src/QuarksPrefab.ts +301 -94
- package/src/QuarksSerializer.ts +43 -15
- package/src/QuarksUtil.ts +64 -9
- package/src/SpriteBatch.ts +21 -6
- package/src/TrailBatch.ts +12 -6
- package/src/VFXBatch.ts +70 -528
- package/src/batchKey.ts +4 -2
- package/src/batchSettings.ts +546 -0
- package/src/effectDocumentJSON.ts +261 -0
- package/src/effectResources.ts +21 -24
- package/src/envAtlas.ts +8 -2
- package/src/geometryUtil.ts +1 -1
- package/src/index.ts +39 -4
- package/src/nodeId.ts +25 -2
- package/src/shaderProgram.ts +110 -16
- package/src/shaderVariants.ts +24 -4
- package/src/shaders/chunks/blend.ts +31 -0
- package/src/shaders/chunks/fog.ts +8 -3
- package/src/shaders/chunks/soft.ts +6 -5
- package/src/shaders/chunks/surface.ts +2 -2
- package/src/shaders/particle_frag.glsl.ts +3 -1
- package/src/shaders/particle_frag.wgsl.ts +2 -0
- package/src/shaders/particle_physics_frag.glsl.ts +5 -2
- package/src/shaders/particle_physics_frag.wgsl.ts +2 -0
- package/src/shaders/trail_frag.glsl.ts +4 -1
- package/src/shaders/trail_frag.wgsl.ts +2 -0
- package/src/system/EmissionController.ts +2 -4
- package/src/system/EventDispatcher.ts +26 -10
- package/src/system/geometry.ts +22 -29
- package/src/system/materialResolve.ts +56 -7
- package/src/system/normalize.ts +6 -1
- package/src/system/serialization.ts +54 -33
- package/src/system/settingsSchema.ts +32 -10
- package/src/system/types.ts +67 -28
- package/src/trailSettings.ts +9 -6
package/src/QuarksSerializer.ts
CHANGED
|
@@ -8,10 +8,12 @@ import {Geometry} from '@babylonjs/core/Meshes/geometry';
|
|
|
8
8
|
import {InstancedMesh} from '@babylonjs/core/Meshes/instancedMesh';
|
|
9
9
|
import {Mesh} from '@babylonjs/core/Meshes/mesh';
|
|
10
10
|
import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
11
|
+
import type {GeometryEntry, QuarksMaterialJSON} from './effectDocumentJSON';
|
|
11
12
|
import {ensureTriangleIndices} from './geometryUtil';
|
|
12
13
|
import {quarksNodeId} from './nodeId';
|
|
13
14
|
import {ParticleEmitter} from './ParticleEmitter';
|
|
14
15
|
import type {BabylonMetaData, ParticleSystem, ParticleSystemJSONParameters} from './ParticleSystem';
|
|
16
|
+
import type {AnimationJSON} from './QuarksPrefab';
|
|
15
17
|
import {QuarksPrefab} from './QuarksPrefab';
|
|
16
18
|
import {textureSourceUrl} from './textureSource';
|
|
17
19
|
|
|
@@ -83,10 +85,13 @@ export interface QuarksNodeJSON {
|
|
|
83
85
|
type: 'Group' | 'ParticleEmitter' | 'Mesh' | 'QuarksPrefab' | string;
|
|
84
86
|
/** The node's name. */
|
|
85
87
|
name?: string;
|
|
86
|
-
/**
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Local transform, column-major, as Babylon's `Matrix.asArray()`. Always written; a three.js
|
|
90
|
+
* file without it places the node by `position`, `quaternion` or `rotation`, and `scale`.
|
|
91
|
+
*/
|
|
92
|
+
matrix?: number[];
|
|
93
|
+
/** Written only when the node is disabled (false); loads it disabled, with everything under it. */
|
|
94
|
+
visible?: boolean;
|
|
90
95
|
/** `Mesh`: written only when the mesh is not drawn (`isVisible`), which leaves its children drawn. */
|
|
91
96
|
isVisible?: false;
|
|
92
97
|
/** Child nodes, in order. */
|
|
@@ -95,9 +100,20 @@ export interface QuarksNodeJSON {
|
|
|
95
100
|
ps?: ParticleSystemJSONParameters;
|
|
96
101
|
/** `Mesh`: the {@link QuarksGeometryJSON} uuid. */
|
|
97
102
|
geometry?: string;
|
|
98
|
-
/** `Mesh`: the material uuid. */
|
|
99
|
-
material?: string;
|
|
100
|
-
|
|
103
|
+
/** `Mesh`: the material uuid; a three.js mesh may list several, of which the first is used. */
|
|
104
|
+
material?: string | string[];
|
|
105
|
+
/** `QuarksPrefab`: the timeline's speed, written only when not 1. */
|
|
106
|
+
timeScale?: number;
|
|
107
|
+
/** `QuarksPrefab`: the timeline's tracks. */
|
|
108
|
+
animationData?: AnimationJSON[];
|
|
109
|
+
/** three.js files without `matrix`: the position as `[x, y, z]`. */
|
|
110
|
+
position?: number[];
|
|
111
|
+
/** three.js files without `matrix`: the scale as `[x, y, z]`. */
|
|
112
|
+
scale?: number[];
|
|
113
|
+
/** three.js files without `matrix`: the rotation as a quaternion `[x, y, z, w]`. */
|
|
114
|
+
quaternion?: number[];
|
|
115
|
+
/** three.js files without `matrix` or `quaternion`: Euler angles `[x, y, z]` in radians. */
|
|
116
|
+
rotation?: number[];
|
|
101
117
|
}
|
|
102
118
|
|
|
103
119
|
/** A whole effect, as {@link serializeEffect} writes it and `QuarksLoader` reads it. */
|
|
@@ -116,7 +132,7 @@ export interface QuarksEffectJSON {
|
|
|
116
132
|
/** Meshes the nodes and particle systems use. */
|
|
117
133
|
geometries: QuarksGeometryJSON[];
|
|
118
134
|
/** Materials the particle systems and `Mesh` nodes use, referred to by uuid. */
|
|
119
|
-
materials:
|
|
135
|
+
materials: QuarksMaterialJSON[];
|
|
120
136
|
/** Textures the particle systems use. */
|
|
121
137
|
textures: QuarksTextureJSON[];
|
|
122
138
|
/** Image files the textures load. */
|
|
@@ -182,7 +198,7 @@ class EffectWriter {
|
|
|
182
198
|
readonly meta: BabylonMetaData = {geometries: {}, materials: {}, textures: {}};
|
|
183
199
|
private readonly meshGeometries = new Map<string, QuarksGeometryJSON>();
|
|
184
200
|
private readonly geometryIds = new Map<Geometry, string>();
|
|
185
|
-
private readonly meshMaterials: Array<{json:
|
|
201
|
+
private readonly meshMaterials: Array<{json: QuarksMaterialJSON; map: BaseTexture | null}> = [];
|
|
186
202
|
private readonly meshMaterialIds = new Map<StandardMaterial, string>();
|
|
187
203
|
private readonly extraTextures = new Map<BaseTexture, string>();
|
|
188
204
|
private nextId = 0;
|
|
@@ -329,7 +345,7 @@ class EffectWriter {
|
|
|
329
345
|
this.warn(`Mesh "${mesh.name}": its material's alpha and colours are not written; it loads opaque white.`);
|
|
330
346
|
}
|
|
331
347
|
// Each field only when it differs from what the loader gives a material without it.
|
|
332
|
-
const json:
|
|
348
|
+
const json: QuarksMaterialJSON = {uuid, type: 'MeshBasicMaterial'};
|
|
333
349
|
if (material.alphaMode !== Constants.ALPHA_COMBINE) json.alphaMode = material.alphaMode;
|
|
334
350
|
if (material.backFaceCulling) json.side = 0;
|
|
335
351
|
if (material.alphaCutOff !== 0) json.alphaTest = material.alphaCutOff;
|
|
@@ -398,7 +414,7 @@ class EffectWriter {
|
|
|
398
414
|
|
|
399
415
|
const materials = Object.values(this.meta.materials).map((material) => {
|
|
400
416
|
// The live Babylon material is for in-memory reuse only.
|
|
401
|
-
const {sourceMaterial: _sourceMaterial, ...serializable} = material
|
|
417
|
+
const {sourceMaterial: _sourceMaterial, ...serializable} = material;
|
|
402
418
|
return serializable;
|
|
403
419
|
});
|
|
404
420
|
|
|
@@ -409,7 +425,10 @@ class EffectWriter {
|
|
|
409
425
|
generator: this.options.generator ?? 'babylon.quarks',
|
|
410
426
|
quarksFormat: QUARKS_FORMAT_VERSION,
|
|
411
427
|
},
|
|
412
|
-
geometries: [
|
|
428
|
+
geometries: [
|
|
429
|
+
...Object.values(this.meta.geometries).filter(isQuarksGeometryJSON),
|
|
430
|
+
...this.meshGeometries.values(),
|
|
431
|
+
],
|
|
413
432
|
materials: [...materials, ...meshMaterials],
|
|
414
433
|
textures,
|
|
415
434
|
images,
|
|
@@ -418,18 +437,27 @@ class EffectWriter {
|
|
|
418
437
|
}
|
|
419
438
|
}
|
|
420
439
|
|
|
440
|
+
/** Whether a system's geometry entry is inline quarks data, the form its `toJSON` writes. */
|
|
441
|
+
function isQuarksGeometryJSON(entry: GeometryEntry): entry is QuarksGeometryJSON {
|
|
442
|
+
return 'type' in entry && entry.type === 'QuarksGeometry' && Array.isArray(entry.positions);
|
|
443
|
+
}
|
|
444
|
+
|
|
421
445
|
/** Ids a written document links to by uuid, by the kind of link. */
|
|
422
446
|
function* links(node: QuarksNodeJSON): Generator<[string, string]> {
|
|
423
|
-
const ps = node.ps
|
|
447
|
+
const ps = node.ps;
|
|
424
448
|
for (const behavior of ps?.behaviors ?? []) {
|
|
425
|
-
if (
|
|
449
|
+
if (
|
|
450
|
+
behavior.type === 'EmitSubParticleSystem' &&
|
|
451
|
+
'subParticleSystem' in behavior &&
|
|
452
|
+
typeof behavior.subParticleSystem === 'string'
|
|
453
|
+
) {
|
|
426
454
|
yield ['a sub emitter', behavior.subParticleSystem];
|
|
427
455
|
}
|
|
428
456
|
}
|
|
429
457
|
if (ps?.shape?.type === 'mesh_surface' && typeof ps.shape.mesh === 'string') {
|
|
430
458
|
yield ['a mesh surface shape', ps.shape.mesh];
|
|
431
459
|
}
|
|
432
|
-
for (const animation of
|
|
460
|
+
for (const animation of node.animationData ?? []) {
|
|
433
461
|
if (typeof animation.targetUUID === 'string') {
|
|
434
462
|
yield ['a prefab animation', animation.targetUUID];
|
|
435
463
|
}
|
package/src/QuarksUtil.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import {InstancedMesh} from '@babylonjs/core/Meshes/instancedMesh';
|
|
2
|
+
import {Mesh} from '@babylonjs/core/Meshes/mesh';
|
|
1
3
|
import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
2
4
|
import {Node} from '@babylonjs/core/node';
|
|
3
5
|
import {ConstantValue, EmitSubParticleSystem, FunctionValueGenerator, IntervalValue, ValueGenerator} from 'quarks.core';
|
|
4
6
|
import {BatchedRenderer} from './BatchedRenderer';
|
|
5
7
|
import {shareEffectResources} from './effectResources';
|
|
8
|
+
import {MeshSurfaceEmitter} from './MeshSurfaceEmitter';
|
|
6
9
|
import {ParticleEmitter} from './ParticleEmitter';
|
|
7
10
|
import {ParticleSystem} from './ParticleSystem';
|
|
8
11
|
|
|
@@ -27,13 +30,26 @@ export class QuarksUtil {
|
|
|
27
30
|
});
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Plays every system under `root`, see {@link ParticleSystem.play}: paused ones resume and
|
|
35
|
+
* finished ones, a short burst inside a longer effect included, start over.
|
|
36
|
+
*/
|
|
31
37
|
static play(root: Node): void {
|
|
32
38
|
QuarksUtil.runOnAllParticleEmitters(root, (emitter) => {
|
|
33
39
|
(emitter.system as unknown as ParticleSystem).play();
|
|
34
40
|
});
|
|
35
41
|
}
|
|
36
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Undoes {@link pause} on every system under `root`, see {@link ParticleSystem.resume}: the
|
|
45
|
+
* finished ones stay finished.
|
|
46
|
+
*/
|
|
47
|
+
static resume(root: Node): void {
|
|
48
|
+
QuarksUtil.runOnAllParticleEmitters(root, (emitter) => {
|
|
49
|
+
(emitter.system as unknown as ParticleSystem).resume();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
37
53
|
/** Clears, rewinds and pauses every system under `root`, see {@link ParticleSystem.stop}. */
|
|
38
54
|
static stop(root: Node): void {
|
|
39
55
|
QuarksUtil.runOnAllParticleEmitters(root, (emitter) => {
|
|
@@ -90,16 +106,46 @@ export class QuarksUtil {
|
|
|
90
106
|
/**
|
|
91
107
|
* A deep copy of an effect: the node tree under `root` with every particle
|
|
92
108
|
* system cloned, sub emitter links pointing at the copies, and the same
|
|
93
|
-
* transforms and enabled state.
|
|
94
|
-
*
|
|
109
|
+
* transforms and enabled state. Meshes are cloned as meshes that share
|
|
110
|
+
* the source's geometry and material, instances of a mesh in the tree as
|
|
111
|
+
* instances of its copy, and a mesh surface shape that emits from a mesh
|
|
112
|
+
* in the tree emits from its copy. An instance of a mesh outside the tree
|
|
113
|
+
* stays an instance of that mesh, and goes when it is disposed. The copy holds the
|
|
114
|
+
* source's textures and materials, so disposing the source leaves the copy
|
|
115
|
+
* whole. It is not added to any renderer. Nodes that are neither transform
|
|
116
|
+
* nodes nor emitters, such as lights and cameras, are skipped.
|
|
95
117
|
*/
|
|
96
118
|
static instantiate(root: TransformNode, name?: string): TransformNode {
|
|
97
119
|
const copies = new Map<ParticleEmitter, ParticleEmitter>();
|
|
120
|
+
const meshCopies = new Map<Node, Mesh>();
|
|
121
|
+
const inTree = new Set<Node>([root, ...root.getDescendants(false)]);
|
|
122
|
+
// A mesh is copied when first needed: where it sits in the tree, or earlier, as the source
|
|
123
|
+
// of an instance copied before it. Its children are copied as every node's are.
|
|
124
|
+
const meshCopyOf = (mesh: Mesh): Mesh => {
|
|
125
|
+
let copy = meshCopies.get(mesh);
|
|
126
|
+
if (copy === undefined) {
|
|
127
|
+
copy = mesh.clone(mesh.name, null, true);
|
|
128
|
+
copy.setEnabled(mesh.isEnabled(false));
|
|
129
|
+
meshCopies.set(mesh, copy);
|
|
130
|
+
}
|
|
131
|
+
return copy;
|
|
132
|
+
};
|
|
98
133
|
const copyNode = (node: Node, parent: TransformNode | null, copyName?: string): TransformNode | null => {
|
|
99
134
|
let copy: TransformNode;
|
|
100
135
|
if (node instanceof ParticleEmitter) {
|
|
101
136
|
copy = node.clone(copyName);
|
|
102
137
|
copies.set(node, copy as ParticleEmitter);
|
|
138
|
+
} else if (node instanceof Mesh) {
|
|
139
|
+
copy = meshCopyOf(node);
|
|
140
|
+
if (copyName !== undefined) {
|
|
141
|
+
copy.name = copyName;
|
|
142
|
+
}
|
|
143
|
+
} else if (node instanceof InstancedMesh) {
|
|
144
|
+
// An instance of a mesh in the tree becomes an instance of its copy, so the copy
|
|
145
|
+
// does not go when the source effect is disposed (a mesh disposes its instances).
|
|
146
|
+
const source = inTree.has(node.sourceMesh) ? meshCopyOf(node.sourceMesh) : undefined;
|
|
147
|
+
copy = node.clone(copyName ?? node.name, null, true, source)!;
|
|
148
|
+
copy.setEnabled(node.isEnabled(false));
|
|
103
149
|
} else if (node instanceof TransformNode) {
|
|
104
150
|
copy = new TransformNode(copyName ?? node.name, node.getScene());
|
|
105
151
|
copy.position.copyFrom(node.position);
|
|
@@ -119,11 +165,12 @@ export class QuarksUtil {
|
|
|
119
165
|
return copy;
|
|
120
166
|
};
|
|
121
167
|
const copy = copyNode(root, null, name) as TransformNode;
|
|
122
|
-
// The copy draws with the source's textures, so
|
|
168
|
+
// The copy draws with the source's textures and materials, so disposeEffect(source) keeps them.
|
|
123
169
|
shareEffectResources(root, copy);
|
|
124
|
-
// Sub emitter behaviors on the copies still target the original emitters.
|
|
125
170
|
for (const emitter of copies.values()) {
|
|
126
|
-
|
|
171
|
+
const system = emitter.system as unknown as ParticleSystem;
|
|
172
|
+
// Sub emitter behaviors on the copies still target the original emitters.
|
|
173
|
+
for (const behavior of system.behaviors) {
|
|
127
174
|
if (behavior instanceof EmitSubParticleSystem) {
|
|
128
175
|
const target = copies.get(behavior.subParticleSystem as unknown as ParticleEmitter);
|
|
129
176
|
if (target !== undefined) {
|
|
@@ -131,6 +178,14 @@ export class QuarksUtil {
|
|
|
131
178
|
}
|
|
132
179
|
}
|
|
133
180
|
}
|
|
181
|
+
// So does a mesh surface shape: it emits from the copy of its mesh when the tree has one.
|
|
182
|
+
const shape = system.emitterShape;
|
|
183
|
+
if (shape instanceof MeshSurfaceEmitter && shape.mesh !== undefined) {
|
|
184
|
+
const meshCopy = meshCopies.get(shape.mesh);
|
|
185
|
+
if (meshCopy !== undefined) {
|
|
186
|
+
shape.mesh = meshCopy;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
134
189
|
}
|
|
135
190
|
return copy;
|
|
136
191
|
}
|
|
@@ -145,7 +200,8 @@ export class QuarksUtil {
|
|
|
145
200
|
* {@link endEmit}, or when one is frozen at playback speed 0.
|
|
146
201
|
*
|
|
147
202
|
* Random values count at their maximum, so this is an upper bound; curves
|
|
148
|
-
* are sampled.
|
|
203
|
+
* are sampled. A runtime {@link ParticleSystem.timeDilation} is not counted:
|
|
204
|
+
* this is the effect's own length. Particles cut short by collisions or culling only make the
|
|
149
205
|
* real effect shorter.
|
|
150
206
|
*/
|
|
151
207
|
static getDuration(root: Node): number {
|
|
@@ -162,8 +218,7 @@ export class QuarksUtil {
|
|
|
162
218
|
continue;
|
|
163
219
|
}
|
|
164
220
|
const target = (behavior.subParticleSystem as unknown as ParticleEmitter | undefined)?.system as
|
|
165
|
-
|
|
|
166
|
-
| undefined;
|
|
221
|
+
ParticleSystem | undefined;
|
|
167
222
|
if (target !== undefined && typeof target === 'object') {
|
|
168
223
|
if (!systems.includes(target)) {
|
|
169
224
|
systems.push(target);
|
package/src/SpriteBatch.ts
CHANGED
|
@@ -37,6 +37,13 @@ export class SpriteBatch extends VFXBatch {
|
|
|
37
37
|
texture = RawTexture.CreateRGBATexture(new Uint8Array([255, 255, 255, 255]), 1, 1, scene);
|
|
38
38
|
texture.name = 'quarksMeshWhite';
|
|
39
39
|
SpriteBatch.whiteTextureByScene.set(scene, texture);
|
|
40
|
+
// Disposed from outside (a scene-wide texture sweep): the next batch makes a new one.
|
|
41
|
+
const made = texture;
|
|
42
|
+
texture.onDisposeObservable.addOnce(() => {
|
|
43
|
+
if (SpriteBatch.whiteTextureByScene.get(scene) === made) {
|
|
44
|
+
SpriteBatch.whiteTextureByScene.delete(scene);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
40
47
|
}
|
|
41
48
|
return texture;
|
|
42
49
|
}
|
|
@@ -67,14 +74,14 @@ export class SpriteBatch extends VFXBatch {
|
|
|
67
74
|
constructor(settings: VFXBatchSettings, scene: Scene, depth: DepthBinding | null = null) {
|
|
68
75
|
super(settings, scene, depth);
|
|
69
76
|
this.storeFixedLighting(defaultMeshLighting());
|
|
70
|
-
this.maxParticles =
|
|
77
|
+
this.maxParticles = this.initialCapacity;
|
|
71
78
|
this.setupBuffers();
|
|
72
79
|
this.rebuildMaterial();
|
|
73
80
|
}
|
|
74
81
|
|
|
75
82
|
/** @internal */
|
|
76
83
|
setupBuffers(): void {
|
|
77
|
-
this.mesh
|
|
84
|
+
this.mesh?.dispose();
|
|
78
85
|
this.mesh = new Mesh('spriteBatch', this.scene);
|
|
79
86
|
this.configureMesh();
|
|
80
87
|
|
|
@@ -178,11 +185,19 @@ export class SpriteBatch extends VFXBatch {
|
|
|
178
185
|
this.mesh.doNotSyncBoundingInfo = true;
|
|
179
186
|
}
|
|
180
187
|
|
|
188
|
+
/** @internal */
|
|
189
|
+
protected readonly initialCapacity = 1000;
|
|
190
|
+
|
|
181
191
|
/** @internal */
|
|
182
192
|
expandBuffers(target: number): void {
|
|
183
193
|
while (target >= this.maxParticles) {
|
|
184
194
|
this.maxParticles *= 2;
|
|
185
195
|
}
|
|
196
|
+
this.resizeBuffers();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** @internal */
|
|
200
|
+
protected resizeBuffers(): void {
|
|
186
201
|
this.disposeExpandableBuffers();
|
|
187
202
|
this.buildExpandableBuffers();
|
|
188
203
|
this.sortScratch = undefined;
|
|
@@ -393,7 +408,9 @@ export class SpriteBatch extends VFXBatch {
|
|
|
393
408
|
|
|
394
409
|
this.bindFog(mat);
|
|
395
410
|
this.bindSurface(mat);
|
|
396
|
-
|
|
411
|
+
// Billboards, beams and the like are flat sheets: both faces, whatever the setting says.
|
|
412
|
+
mat.backFaceCulling = this.settings.renderFace !== 'both';
|
|
413
|
+
mat.cullBackFaces = this.settings.renderFace !== 'back';
|
|
397
414
|
|
|
398
415
|
if (this.settings.materialTransparent) {
|
|
399
416
|
mat.alphaMode = this.settings.materialBlendMode;
|
|
@@ -532,9 +549,7 @@ export class SpriteBatch extends VFXBatch {
|
|
|
532
549
|
for (let i = 0; i < visibleSystems.length; i++) {
|
|
533
550
|
particleCount += visibleSystems[i].particleNum;
|
|
534
551
|
}
|
|
535
|
-
|
|
536
|
-
this.expandBuffers(particleCount);
|
|
537
|
-
}
|
|
552
|
+
this.fitBuffers(particleCount);
|
|
538
553
|
|
|
539
554
|
for (let s = 0; s < visibleSystems.length; s++) {
|
|
540
555
|
const system = visibleSystems[s];
|
package/src/TrailBatch.ts
CHANGED
|
@@ -121,14 +121,14 @@ export class TrailBatch extends VFXBatch {
|
|
|
121
121
|
// For a trail batch `maxParticles` counts vertices, two per drawn point: the buffers are
|
|
122
122
|
// the largest per-batch allocation in the library, so start small and let expandBuffers
|
|
123
123
|
// grow to whatever the batch actually needs.
|
|
124
|
-
this.maxParticles =
|
|
124
|
+
this.maxParticles = this.initialCapacity;
|
|
125
125
|
this.setupBuffers();
|
|
126
126
|
this.rebuildMaterial();
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
/** @internal */
|
|
130
130
|
setupBuffers(): void {
|
|
131
|
-
this.mesh
|
|
131
|
+
this.mesh?.dispose();
|
|
132
132
|
this.mesh = new Mesh('trailBatch', this.scene);
|
|
133
133
|
this.configureMesh();
|
|
134
134
|
this.buildGeometryBuffers();
|
|
@@ -210,11 +210,19 @@ export class TrailBatch extends VFXBatch {
|
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
+
/** @internal */
|
|
214
|
+
protected readonly initialCapacity = 1024;
|
|
215
|
+
|
|
213
216
|
/** Grows the buffers to hold more than `target` vertices. @internal */
|
|
214
217
|
expandBuffers(target: number): void {
|
|
215
218
|
while (target >= this.maxParticles) {
|
|
216
219
|
this.maxParticles *= 2;
|
|
217
220
|
}
|
|
221
|
+
this.resizeBuffers();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** @internal */
|
|
225
|
+
protected resizeBuffers(): void {
|
|
218
226
|
// `Geometry.setVerticesBuffer` disposes the buffer it replaces; these custom attributes
|
|
219
227
|
// are not in the geometry's map, so they are released here.
|
|
220
228
|
this.previousVB?.dispose();
|
|
@@ -518,9 +526,7 @@ export class TrailBatch extends VFXBatch {
|
|
|
518
526
|
particleCount += (historyCount * (tessellation + 1) + 1) * 2;
|
|
519
527
|
}
|
|
520
528
|
}
|
|
521
|
-
|
|
522
|
-
this.expandBuffers(particleCount);
|
|
523
|
-
}
|
|
529
|
+
this.fitBuffers(particleCount);
|
|
524
530
|
|
|
525
531
|
for (let s = 0; s < visibleSystems.length; s++) {
|
|
526
532
|
const system = visibleSystems[s];
|
|
@@ -969,7 +975,7 @@ export class TrailBatch extends VFXBatch {
|
|
|
969
975
|
private adoptMeshBounds(): void {
|
|
970
976
|
const meshBoundingInfo = this.mesh.getBoundingInfo();
|
|
971
977
|
for (const subMesh of this.mesh.subMeshes) {
|
|
972
|
-
|
|
978
|
+
subMesh.setBoundingInfo(meshBoundingInfo);
|
|
973
979
|
}
|
|
974
980
|
}
|
|
975
981
|
|