babylon.quarks 0.17.3 → 0.17.6

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.
Files changed (77) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/LICENSE +21 -21
  3. package/README.md +165 -33
  4. package/dist/babylon.quarks.cjs +1 -1
  5. package/dist/babylon.quarks.cjs.map +1 -1
  6. package/dist/babylon.quarks.esm.js +1 -1
  7. package/dist/babylon.quarks.esm.js.map +1 -1
  8. package/dist/babylon.quarks.umd.min.js +2 -0
  9. package/dist/babylon.quarks.umd.min.js.map +1 -0
  10. package/dist/types/index.d.ts +2829 -19
  11. package/package.json +62 -60
  12. package/src/BatchedParticleRenderer.ts +3 -3
  13. package/src/BatchedRenderer.ts +228 -227
  14. package/src/MeshSurfaceEmitter.ts +137 -137
  15. package/src/ParticleEmitter.ts +86 -86
  16. package/src/ParticleSystem.ts +1322 -1274
  17. package/src/QuarksLoader.ts +529 -507
  18. package/src/QuarksPrefab.ts +307 -307
  19. package/src/QuarksUtil.ts +66 -66
  20. package/src/SpriteBatch.ts +403 -404
  21. package/src/TrailBatch.ts +420 -419
  22. package/src/VFXBatch.ts +108 -108
  23. package/src/geometryUtil.ts +15 -0
  24. package/src/index.ts +59 -52
  25. package/src/materials/ParticleMaterials.ts +23 -23
  26. package/src/materials/index.ts +1 -1
  27. package/src/shaders/chunks/index.ts +1 -1
  28. package/src/shaders/chunks/register-shader-chunks.ts +32 -32
  29. package/src/shaders/chunks/soft_fragment.glsl.ts +13 -13
  30. package/src/shaders/chunks/soft_pars_fragment.glsl.ts +20 -20
  31. package/src/shaders/chunks/soft_pars_vertex.glsl.ts +6 -6
  32. package/src/shaders/chunks/soft_vertex.glsl.ts +6 -6
  33. package/src/shaders/chunks/tile_fragment.glsl.ts +9 -9
  34. package/src/shaders/chunks/tile_pars_fragment.glsl.ts +9 -9
  35. package/src/shaders/chunks/tile_pars_vertex.glsl.ts +28 -28
  36. package/src/shaders/chunks/tile_vertex.glsl.ts +18 -18
  37. package/src/shaders/index.ts +9 -9
  38. package/src/shaders/local_particle_physics_vert.glsl.ts +79 -79
  39. package/src/shaders/local_particle_vert.glsl.ts +50 -50
  40. package/src/shaders/particle_frag.glsl.ts +61 -61
  41. package/src/shaders/particle_physics_frag.glsl.ts +67 -67
  42. package/src/shaders/particle_vert.glsl.ts +85 -85
  43. package/src/shaders/stretched_bb_particle_vert.glsl.ts +74 -74
  44. package/src/shaders/trail_frag.glsl.ts +21 -21
  45. package/src/shaders/trail_vert.glsl.ts +58 -58
  46. package/dist/types/BatchedParticleRenderer.d.ts +0 -2
  47. package/dist/types/BatchedRenderer.d.ts +0 -40
  48. package/dist/types/MeshSurfaceEmitter.d.ts +0 -19
  49. package/dist/types/ParticleEmitter.d.ts +0 -16
  50. package/dist/types/ParticleSystem.d.ts +0 -226
  51. package/dist/types/QuarksLoader.d.ts +0 -28
  52. package/dist/types/QuarksPrefab.d.ts +0 -48
  53. package/dist/types/QuarksUtil.d.ts +0 -14
  54. package/dist/types/SpriteBatch.d.ts +0 -32
  55. package/dist/types/TrailBatch.d.ts +0 -27
  56. package/dist/types/VFXBatch.d.ts +0 -51
  57. package/dist/types/materials/ParticleMaterials.d.ts +0 -12
  58. package/dist/types/materials/index.d.ts +0 -1
  59. package/dist/types/shaders/chunks/index.d.ts +0 -1
  60. package/dist/types/shaders/chunks/register-shader-chunks.d.ts +0 -3
  61. package/dist/types/shaders/chunks/soft_fragment.glsl.d.ts +0 -2
  62. package/dist/types/shaders/chunks/soft_pars_fragment.glsl.d.ts +0 -2
  63. package/dist/types/shaders/chunks/soft_pars_vertex.glsl.d.ts +0 -2
  64. package/dist/types/shaders/chunks/soft_vertex.glsl.d.ts +0 -2
  65. package/dist/types/shaders/chunks/tile_fragment.glsl.d.ts +0 -2
  66. package/dist/types/shaders/chunks/tile_pars_fragment.glsl.d.ts +0 -2
  67. package/dist/types/shaders/chunks/tile_pars_vertex.glsl.d.ts +0 -2
  68. package/dist/types/shaders/chunks/tile_vertex.glsl.d.ts +0 -2
  69. package/dist/types/shaders/index.d.ts +0 -9
  70. package/dist/types/shaders/local_particle_physics_vert.glsl.d.ts +0 -2
  71. package/dist/types/shaders/local_particle_vert.glsl.d.ts +0 -2
  72. package/dist/types/shaders/particle_frag.glsl.d.ts +0 -2
  73. package/dist/types/shaders/particle_physics_frag.glsl.d.ts +0 -2
  74. package/dist/types/shaders/particle_vert.glsl.d.ts +0 -2
  75. package/dist/types/shaders/stretched_bb_particle_vert.glsl.d.ts +0 -2
  76. package/dist/types/shaders/trail_frag.glsl.d.ts +0 -2
  77. package/dist/types/shaders/trail_vert.glsl.d.ts +0 -2
package/src/TrailBatch.ts CHANGED
@@ -1,419 +1,420 @@
1
- import {Mesh} from '@babylonjs/core/Meshes/mesh';
2
- import {VertexBuffer} from '@babylonjs/core/Buffers/buffer';
3
- import {Effect} from '@babylonjs/core/Materials/effect';
4
- import {ShaderMaterial} from '@babylonjs/core/Materials/shaderMaterial';
5
- import {Scene} from '@babylonjs/core/scene';
6
- import {BoundingInfo} from '@babylonjs/core/Culling/boundingInfo';
7
- import {Vector3 as BVector3} from '@babylonjs/core/Maths/math.vector';
8
- import {Vector2 as BVector2} from '@babylonjs/core/Maths/math.vector';
9
- import {Constants} from '@babylonjs/core/Engines/constants';
10
- import {Matrix4, Quaternion, RecordState, TrailParticle, Vector3} from 'quarks.core';
11
- import {VFXBatch, RenderMode} from './VFXBatch';
12
- import {VFXBatchSettings} from './BatchedRenderer';
13
- import trail_vert from './shaders/trail_vert.glsl';
14
- import trail_frag from './shaders/trail_frag.glsl';
15
-
16
- export class TrailBatch extends VFXBatch {
17
- private positionBuffer!: Float32Array;
18
- private previousBuffer!: Float32Array;
19
- private nextBuffer!: Float32Array;
20
- private uvBuffer!: Float32Array;
21
- private sideBuffer!: Float32Array;
22
- private widthBuffer!: Float32Array;
23
- private colorBuffer!: Float32Array;
24
- private indexBuffer!: Uint32Array;
25
- private previousVB!: VertexBuffer;
26
- private nextVB!: VertexBuffer;
27
- private sideVB!: VertexBuffer;
28
- private widthVB!: VertexBuffer;
29
-
30
- private getTrailHistoryCount(particle: TrailParticle): number {
31
- const trailAny = particle as any;
32
- const fastHistoryCount = trailAny._trailHistoryCount;
33
- if (typeof fastHistoryCount === 'number') {
34
- return fastHistoryCount;
35
- }
36
- return particle.previous.length;
37
- }
38
-
39
- constructor(settings: VFXBatchSettings, scene: Scene) {
40
- super(settings, scene);
41
- this.maxParticles = 10000;
42
- this.setupBuffers();
43
- this.rebuildMaterial();
44
- this.mesh.setEnabled(false);
45
- }
46
-
47
- setupBuffers(): void {
48
- this.mesh.dispose();
49
- this.mesh = new Mesh('trailBatch', this.scene);
50
- this.mesh.alwaysSelectAsActiveMesh = true;
51
-
52
- this.positionBuffer = new Float32Array(this.maxParticles * 6);
53
- this.previousBuffer = new Float32Array(this.maxParticles * 6);
54
- this.nextBuffer = new Float32Array(this.maxParticles * 6);
55
- this.uvBuffer = new Float32Array(this.maxParticles * 4);
56
- this.sideBuffer = new Float32Array(this.maxParticles * 2);
57
- this.widthBuffer = new Float32Array(this.maxParticles * 2);
58
- this.colorBuffer = new Float32Array(this.maxParticles * 8);
59
- this.indexBuffer = new Uint32Array(this.maxParticles * 6);
60
-
61
- // Initialize with full-capacity buffers (updatable) and a dummy triangle
62
- this.positionBuffer[0] = 0; this.positionBuffer[1] = 0; this.positionBuffer[2] = 0;
63
- this.positionBuffer[3] = 1; this.positionBuffer[4] = 0; this.positionBuffer[5] = 0;
64
- this.positionBuffer[6] = 0; this.positionBuffer[7] = 1; this.positionBuffer[8] = 0;
65
- this.positionBuffer[9] = 1; this.positionBuffer[10] = 1; this.positionBuffer[11] = 0;
66
- this.indexBuffer[0] = 0; this.indexBuffer[1] = 1; this.indexBuffer[2] = 2;
67
- this.indexBuffer[3] = 1; this.indexBuffer[4] = 3; this.indexBuffer[5] = 2;
68
-
69
- // Set standard vertex data with full buffer capacity (updatable)
70
- this.mesh.setVerticesData(VertexBuffer.PositionKind, this.positionBuffer, true);
71
- this.mesh.setVerticesData(VertexBuffer.UVKind, this.uvBuffer, true);
72
- this.mesh.setVerticesData(VertexBuffer.ColorKind, this.colorBuffer, true, 4);
73
- this.mesh.setIndices(this.indexBuffer.subarray(0, 6), null, true);
74
- const engine = this.scene.getEngine();
75
- this.previousVB = new VertexBuffer(engine, this.previousBuffer, 'previous', true, false, 3, false);
76
- this.nextVB = new VertexBuffer(engine, this.nextBuffer, 'next', true, false, 3, false);
77
- this.sideVB = new VertexBuffer(engine, this.sideBuffer, 'side', true, false, 1, false);
78
- this.widthVB = new VertexBuffer(engine, this.widthBuffer, 'width', true, false, 1, false);
79
- this.mesh.setVerticesBuffer(this.previousVB);
80
- this.mesh.setVerticesBuffer(this.nextVB);
81
- this.mesh.setVerticesBuffer(this.sideVB);
82
- this.mesh.setVerticesBuffer(this.widthVB);
83
-
84
- // Disable bounding info recomputation
85
- this.mesh.doNotSyncBoundingInfo = true;
86
- const min = new BVector3(-10000, -10000, -10000);
87
- const max = new BVector3(10000, 10000, 10000);
88
- this.mesh.setBoundingInfo(new BoundingInfo(min, max));
89
- if (this.mesh.subMeshes) {
90
- for (const sub of this.mesh.subMeshes) {
91
- (sub as any)._boundingInfo = this.mesh.getBoundingInfo();
92
- }
93
- }
94
-
95
- }
96
-
97
- expandBuffers(target: number): void {
98
- while (target >= this.maxParticles) {
99
- this.maxParticles *= 2;
100
- }
101
- this.setupBuffers();
102
- this.rebuildMaterial();
103
- }
104
-
105
- rebuildMaterial(): void {
106
- const shaderName = `quarksTrail_${Date.now()}`;
107
- const defines: string[] = [];
108
-
109
- if (this.settings.texture) {
110
- defines.push('USE_MAP');
111
- }
112
-
113
- Effect.ShadersStore[shaderName + 'VertexShader'] = trail_vert;
114
- Effect.ShadersStore[shaderName + 'FragmentShader'] = trail_frag;
115
-
116
- const attributes = ['position', 'previous', 'next', 'side', 'width', 'uv', 'color'];
117
- const uniforms = ['world', 'view', 'projection', 'worldViewProjection', 'lineWidth', 'resolution', 'sizeAttenuation'];
118
- const samplers: string[] = [];
119
-
120
- if (this.settings.texture) {
121
- samplers.push('map');
122
- }
123
-
124
- const mat = new ShaderMaterial(shaderName, this.scene,
125
- {vertex: shaderName, fragment: shaderName},
126
- {
127
- attributes,
128
- uniforms,
129
- samplers,
130
- defines,
131
- needAlphaBlending: this.settings.materialTransparent,
132
- }
133
- );
134
-
135
- if (this.settings.texture) {
136
- mat.setTexture('map', this.settings.texture);
137
- }
138
- const engine = this.scene.getEngine();
139
- mat.setFloat('lineWidth', 1);
140
- mat.setVector2('resolution', new BVector2(engine.getRenderWidth(), engine.getRenderHeight()));
141
- mat.setFloat('sizeAttenuation', 1);
142
- mat.onBindObservable.add(() => {
143
- const renderEngine = this.scene.getEngine();
144
- mat.setVector2('resolution', new BVector2(renderEngine.getRenderWidth(), renderEngine.getRenderHeight()));
145
- });
146
-
147
- mat.backFaceCulling = false;
148
-
149
- if (this.settings.materialTransparent) {
150
- mat.alphaMode = this.settings.materialBlendMode;
151
- } else {
152
- mat.alphaMode = Constants.ALPHA_DISABLE;
153
- }
154
- mat.needDepthPrePass = this.settings.materialDepthWrite;
155
- mat.forceDepthWrite = this.settings.materialDepthWrite;
156
- mat.disableDepthWrite = !this.settings.materialDepthWrite;
157
-
158
- this.mesh.material = mat;
159
- }
160
-
161
- private vector_ = new Vector3();
162
- private vector2_ = new Vector3();
163
- private vector3_ = new Vector3();
164
- private quaternion_ = new Quaternion();
165
-
166
- update(): void {
167
- let index = 0;
168
- let triangles = 0;
169
-
170
- let particleCount = 0;
171
- const visibleSystems = this.getVisibleSystems();
172
- for (const system of visibleSystems) {
173
- for (let j = 0; j < system.particleNum; j++) {
174
- particleCount += this.getTrailHistoryCount(system.particles[j] as TrailParticle) * 2;
175
- }
176
- }
177
- if (particleCount > this.maxParticles) {
178
- this.expandBuffers(particleCount);
179
- }
180
-
181
- for (const system of visibleSystems) {
182
- // Ensure world matrix is up to date
183
- if ((system.emitter as any).computeWorldMatrix) {
184
- (system.emitter as any).computeWorldMatrix(true);
185
- }
186
- const rotation = this.quaternion_;
187
- const translation = this.vector2_;
188
- const scale = this.vector3_;
189
- system.emitter.matrixWorld.decompose(translation, rotation, scale);
190
-
191
- const particles = system.particles;
192
- const particleNum = system.particleNum;
193
- const uTileCount = this.settings.uTileCount;
194
- const vTileCount = this.settings.vTileCount;
195
- const tileWidth = 1 / uTileCount;
196
- const tileHeight = 1 / vTileCount;
197
- const systemWorldSpace = system.worldSpace;
198
- const objectScale = (Math.abs(scale.x) + Math.abs(scale.y) + Math.abs(scale.z)) / 3;
199
-
200
- for (let j = 0; j < particleNum; j++) {
201
- const particle = particles[j] as TrailParticle;
202
- const particleAny = particle as any;
203
- const fastHistoryCount = particleAny._trailHistoryCount as number | undefined;
204
- const fastHistoryCapacity = particleAny._trailHistoryCapacity as number | undefined;
205
- const fastHistoryHead = particleAny._trailHistoryHead as number | undefined;
206
- const fastHistoryPositions = particleAny._trailHistoryPositions as Float32Array | undefined;
207
- const fastHistorySizes = particleAny._trailHistorySizes as Float32Array | undefined;
208
- const fastHistoryColors = particleAny._trailHistoryColors as Float32Array | undefined;
209
- const hasFastHistory =
210
- typeof fastHistoryCount === 'number' &&
211
- fastHistoryCount > 0 &&
212
- typeof fastHistoryCapacity === 'number' &&
213
- typeof fastHistoryHead === 'number' &&
214
- fastHistoryPositions instanceof Float32Array &&
215
- fastHistorySizes instanceof Float32Array &&
216
- fastHistoryColors instanceof Float32Array;
217
- const historyCapacity = hasFastHistory ? (fastHistoryCapacity as number) : 0;
218
- const historyHead = hasFastHistory ? (fastHistoryHead as number) : 0;
219
- const historyPositions = fastHistoryPositions as Float32Array;
220
- const historySizes = fastHistorySizes as Float32Array;
221
- const historyColors = fastHistoryColors as Float32Array;
222
- const particleHistory = particle.previous;
223
- const particleHistoryLength = hasFastHistory ? fastHistoryCount : particleHistory.length;
224
- if (particleHistoryLength === 0) {
225
- continue;
226
- }
227
- const col = particle.uvTile % vTileCount;
228
- const row = Math.floor(particle.uvTile / vTileCount + 0.001);
229
- const particleMatrix = particle.parentMatrix as unknown as Matrix4 | undefined;
230
- const matrixToUse = particleMatrix ?? system.emitter.matrixWorld;
231
-
232
- let previousNode = particleHistory.head;
233
- let currentNode = previousNode;
234
- let nextNode = currentNode?.next ?? currentNode;
235
- const oldestFastHistoryIndex = hasFastHistory
236
- ? (historyHead - particleHistoryLength + historyCapacity) % historyCapacity
237
- : 0;
238
-
239
- for (let i = 0; i < particleHistoryLength; i++, index += 2) {
240
- let currentX: number;
241
- let currentY: number;
242
- let currentZ: number;
243
- let currentSize: number;
244
- let currentColorX: number;
245
- let currentColorY: number;
246
- let currentColorZ: number;
247
- let currentColorW: number;
248
- let previousX: number;
249
- let previousY: number;
250
- let previousZ: number;
251
- let nextX: number;
252
- let nextY: number;
253
- let nextZ: number;
254
-
255
- if (hasFastHistory) {
256
- const currentHistoryIndex = (oldestFastHistoryIndex + i) % historyCapacity;
257
- const previousHistoryIndex = i === 0 ? currentHistoryIndex : (oldestFastHistoryIndex + i - 1) % historyCapacity;
258
- const nextHistoryIndex =
259
- i + 1 < particleHistoryLength
260
- ? (oldestFastHistoryIndex + i + 1) % historyCapacity
261
- : currentHistoryIndex;
262
-
263
- const currentPosIndex = currentHistoryIndex * 3;
264
- const previousPosIndex = previousHistoryIndex * 3;
265
- const nextPosIndex = nextHistoryIndex * 3;
266
-
267
- currentX = historyPositions[currentPosIndex];
268
- currentY = historyPositions[currentPosIndex + 1];
269
- currentZ = historyPositions[currentPosIndex + 2];
270
- previousX = historyPositions[previousPosIndex];
271
- previousY = historyPositions[previousPosIndex + 1];
272
- previousZ = historyPositions[previousPosIndex + 2];
273
- nextX = historyPositions[nextPosIndex];
274
- nextY = historyPositions[nextPosIndex + 1];
275
- nextZ = historyPositions[nextPosIndex + 2];
276
-
277
- currentSize = historySizes[currentHistoryIndex];
278
- const currentColorIndex = currentHistoryIndex * 4;
279
- currentColorX = historyColors[currentColorIndex];
280
- currentColorY = historyColors[currentColorIndex + 1];
281
- currentColorZ = historyColors[currentColorIndex + 2];
282
- currentColorW = historyColors[currentColorIndex + 3];
283
- } else {
284
- const previous = previousNode!.data as RecordState;
285
- const current = currentNode!.data as RecordState;
286
- const next = nextNode!.data as RecordState;
287
- currentX = current.position.x;
288
- currentY = current.position.y;
289
- currentZ = current.position.z;
290
- previousX = previous.position.x;
291
- previousY = previous.position.y;
292
- previousZ = previous.position.z;
293
- nextX = next.position.x;
294
- nextY = next.position.y;
295
- nextZ = next.position.z;
296
- currentSize = current.size;
297
- currentColorX = current.color.x;
298
- currentColorY = current.color.y;
299
- currentColorZ = current.color.z;
300
- currentColorW = current.color.w;
301
- }
302
-
303
- if (!systemWorldSpace) {
304
- this.vector_.set(currentX, currentY, currentZ).applyMatrix4(matrixToUse);
305
- currentX = this.vector_.x;
306
- currentY = this.vector_.y;
307
- currentZ = this.vector_.z;
308
- this.vector_.set(previousX, previousY, previousZ).applyMatrix4(matrixToUse);
309
- previousX = this.vector_.x;
310
- previousY = this.vector_.y;
311
- previousZ = this.vector_.z;
312
- this.vector_.set(nextX, nextY, nextZ).applyMatrix4(matrixToUse);
313
- nextX = this.vector_.x;
314
- nextY = this.vector_.y;
315
- nextZ = this.vector_.z;
316
- }
317
-
318
- const pi = index * 3;
319
- this.positionBuffer[pi] = currentX;
320
- this.positionBuffer[pi + 1] = currentY;
321
- this.positionBuffer[pi + 2] = currentZ;
322
- this.positionBuffer[pi + 3] = currentX;
323
- this.positionBuffer[pi + 4] = currentY;
324
- this.positionBuffer[pi + 5] = currentZ;
325
-
326
- this.previousBuffer[pi] = previousX;
327
- this.previousBuffer[pi + 1] = previousY;
328
- this.previousBuffer[pi + 2] = previousZ;
329
- this.previousBuffer[pi + 3] = previousX;
330
- this.previousBuffer[pi + 4] = previousY;
331
- this.previousBuffer[pi + 5] = previousZ;
332
-
333
- this.nextBuffer[pi] = nextX;
334
- this.nextBuffer[pi + 1] = nextY;
335
- this.nextBuffer[pi + 2] = nextZ;
336
- this.nextBuffer[pi + 3] = nextX;
337
- this.nextBuffer[pi + 4] = nextY;
338
- this.nextBuffer[pi + 5] = nextZ;
339
-
340
- this.sideBuffer[index] = 1;
341
- this.sideBuffer[index + 1] = -1;
342
-
343
- if (systemWorldSpace || particle.parentMatrix) {
344
- this.widthBuffer[index] = currentSize;
345
- this.widthBuffer[index + 1] = currentSize;
346
- } else {
347
- this.widthBuffer[index] = currentSize * objectScale;
348
- this.widthBuffer[index + 1] = currentSize * objectScale;
349
- }
350
-
351
- const ui = index * 2;
352
- this.uvBuffer[ui] = (i / particleHistoryLength + col) * tileWidth;
353
- this.uvBuffer[ui + 1] = (vTileCount - row - 1) * tileHeight;
354
- this.uvBuffer[ui + 2] = (i / particleHistoryLength + col) * tileWidth;
355
- this.uvBuffer[ui + 3] = (vTileCount - row) * tileHeight;
356
-
357
- const cci = index * 4;
358
- this.colorBuffer[cci] = currentColorX;
359
- this.colorBuffer[cci + 1] = currentColorY;
360
- this.colorBuffer[cci + 2] = currentColorZ;
361
- this.colorBuffer[cci + 3] = currentColorW;
362
- this.colorBuffer[cci + 4] = currentColorX;
363
- this.colorBuffer[cci + 5] = currentColorY;
364
- this.colorBuffer[cci + 6] = currentColorZ;
365
- this.colorBuffer[cci + 7] = currentColorW;
366
-
367
- if (i + 1 < particleHistoryLength) {
368
- this.indexBuffer[triangles * 3] = index;
369
- this.indexBuffer[triangles * 3 + 1] = index + 1;
370
- this.indexBuffer[triangles * 3 + 2] = index + 2;
371
- triangles++;
372
- this.indexBuffer[triangles * 3] = index + 2;
373
- this.indexBuffer[triangles * 3 + 1] = index + 1;
374
- this.indexBuffer[triangles * 3 + 2] = index + 3;
375
- triangles++;
376
- }
377
-
378
- if (!hasFastHistory) {
379
- previousNode = currentNode;
380
- currentNode = nextNode;
381
- nextNode = nextNode?.next ?? nextNode;
382
- }
383
- }
384
- }
385
- }
386
-
387
- if (index > 0 && triangles > 0) {
388
- this.mesh.updateVerticesData(VertexBuffer.PositionKind, this.positionBuffer);
389
- this.mesh.updateVerticesData(VertexBuffer.UVKind, this.uvBuffer);
390
- this.mesh.updateIndices(this.indexBuffer.subarray(0, triangles * 3));
391
- this.previousVB.update(this.previousBuffer);
392
- this.nextVB.update(this.nextBuffer);
393
- this.sideVB.update(this.sideBuffer);
394
- this.widthVB.update(this.widthBuffer);
395
-
396
- const colorVB = this.mesh.getVertexBuffer(VertexBuffer.ColorKind);
397
- if (colorVB) colorVB.update(this.colorBuffer);
398
-
399
- this.mesh.setEnabled(true);
400
- if (this.mesh.subMeshes && this.mesh.subMeshes.length > 0) {
401
- const meshBoundingInfo = this.mesh.getBoundingInfo();
402
- for (const subMesh of this.mesh.subMeshes) {
403
- (subMesh as any)._boundingInfo = meshBoundingInfo;
404
- }
405
- this.mesh.subMeshes[0].indexCount = triangles * 3;
406
- this.mesh.subMeshes[0].verticesCount = index;
407
- }
408
- } else {
409
- this.mesh.setEnabled(false);
410
- }
411
- }
412
-
413
- dispose(): void {
414
- if (this.mesh.material) {
415
- this.mesh.material.dispose();
416
- }
417
- super.dispose();
418
- }
419
- }
1
+ import {Mesh} from '@babylonjs/core/Meshes/mesh';
2
+ import {VertexBuffer} from '@babylonjs/core/Buffers/buffer';
3
+ import {Effect} from '@babylonjs/core/Materials/effect';
4
+ import {ShaderMaterial} from '@babylonjs/core/Materials/shaderMaterial';
5
+ import {Scene} from '@babylonjs/core/scene';
6
+ import {BoundingInfo} from '@babylonjs/core/Culling/boundingInfo';
7
+ import {Vector3 as BVector3} from '@babylonjs/core/Maths/math.vector';
8
+ import {Vector2 as BVector2} from '@babylonjs/core/Maths/math.vector';
9
+ import {Constants} from '@babylonjs/core/Engines/constants';
10
+ import {Matrix4, Quaternion, RecordState, TrailParticle, Vector3} from 'quarks.core';
11
+ import {VFXBatch, RenderMode} from './VFXBatch';
12
+ import {VFXBatchSettings} from './BatchedRenderer';
13
+ import trail_vert from './shaders/trail_vert.glsl';
14
+ import trail_frag from './shaders/trail_frag.glsl';
15
+
16
+ export class TrailBatch extends VFXBatch {
17
+ private positionBuffer!: Float32Array;
18
+ private previousBuffer!: Float32Array;
19
+ private nextBuffer!: Float32Array;
20
+ private uvBuffer!: Float32Array;
21
+ private sideBuffer!: Float32Array;
22
+ private widthBuffer!: Float32Array;
23
+ private colorBuffer!: Float32Array;
24
+ private indexBuffer!: Uint32Array;
25
+ private previousVB!: VertexBuffer;
26
+ private nextVB!: VertexBuffer;
27
+ private sideVB!: VertexBuffer;
28
+ private widthVB!: VertexBuffer;
29
+
30
+ private getTrailHistoryCount(particle: TrailParticle): number {
31
+ const trailAny = particle as any;
32
+ const fastHistoryCount = trailAny._trailHistoryCount;
33
+ if (typeof fastHistoryCount === 'number') {
34
+ return fastHistoryCount;
35
+ }
36
+ return particle.previous.length;
37
+ }
38
+
39
+ constructor(settings: VFXBatchSettings, scene: Scene) {
40
+ super(settings, scene);
41
+ this.maxParticles = 10000;
42
+ this.setupBuffers();
43
+ this.rebuildMaterial();
44
+ this.mesh.setEnabled(false);
45
+ }
46
+
47
+ setupBuffers(): void {
48
+ this.mesh.dispose();
49
+ this.mesh = new Mesh('trailBatch', this.scene);
50
+ this.mesh.alwaysSelectAsActiveMesh = true;
51
+ this.buildGeometryBuffers();
52
+ }
53
+
54
+ private buildGeometryBuffers(): void {
55
+ this.positionBuffer = new Float32Array(this.maxParticles * 6);
56
+ this.previousBuffer = new Float32Array(this.maxParticles * 6);
57
+ this.nextBuffer = new Float32Array(this.maxParticles * 6);
58
+ this.uvBuffer = new Float32Array(this.maxParticles * 4);
59
+ this.sideBuffer = new Float32Array(this.maxParticles * 2);
60
+ this.widthBuffer = new Float32Array(this.maxParticles * 2);
61
+ this.colorBuffer = new Float32Array(this.maxParticles * 8);
62
+ this.indexBuffer = new Uint32Array(this.maxParticles * 6);
63
+
64
+ // Initialize with full-capacity buffers (updatable) and a dummy triangle
65
+ this.positionBuffer[0] = 0; this.positionBuffer[1] = 0; this.positionBuffer[2] = 0;
66
+ this.positionBuffer[3] = 1; this.positionBuffer[4] = 0; this.positionBuffer[5] = 0;
67
+ this.positionBuffer[6] = 0; this.positionBuffer[7] = 1; this.positionBuffer[8] = 0;
68
+ this.positionBuffer[9] = 1; this.positionBuffer[10] = 1; this.positionBuffer[11] = 0;
69
+ this.indexBuffer[0] = 0; this.indexBuffer[1] = 1; this.indexBuffer[2] = 2;
70
+ this.indexBuffer[3] = 1; this.indexBuffer[4] = 3; this.indexBuffer[5] = 2;
71
+
72
+ // Set standard vertex data with full buffer capacity (updatable)
73
+ this.mesh.setVerticesData(VertexBuffer.PositionKind, this.positionBuffer, true);
74
+ this.mesh.setVerticesData(VertexBuffer.UVKind, this.uvBuffer, true);
75
+ this.mesh.setVerticesData(VertexBuffer.ColorKind, this.colorBuffer, true, 4);
76
+ this.mesh.setIndices(this.indexBuffer.subarray(0, 6), null, true);
77
+ const engine = this.scene.getEngine();
78
+ this.previousVB = new VertexBuffer(engine, this.previousBuffer, 'previous', true, false, 3, false);
79
+ this.nextVB = new VertexBuffer(engine, this.nextBuffer, 'next', true, false, 3, false);
80
+ this.sideVB = new VertexBuffer(engine, this.sideBuffer, 'side', true, false, 1, false);
81
+ this.widthVB = new VertexBuffer(engine, this.widthBuffer, 'width', true, false, 1, false);
82
+ this.mesh.setVerticesBuffer(this.previousVB);
83
+ this.mesh.setVerticesBuffer(this.nextVB);
84
+ this.mesh.setVerticesBuffer(this.sideVB);
85
+ this.mesh.setVerticesBuffer(this.widthVB);
86
+
87
+ // Disable bounding info recomputation
88
+ this.mesh.doNotSyncBoundingInfo = true;
89
+ const min = new BVector3(-10000, -10000, -10000);
90
+ const max = new BVector3(10000, 10000, 10000);
91
+ this.mesh.setBoundingInfo(new BoundingInfo(min, max));
92
+ if (this.mesh.subMeshes) {
93
+ for (const sub of this.mesh.subMeshes) {
94
+ (sub as any)._boundingInfo = this.mesh.getBoundingInfo();
95
+ }
96
+ }
97
+ }
98
+
99
+ expandBuffers(target: number): void {
100
+ while (target >= this.maxParticles) {
101
+ this.maxParticles *= 2;
102
+ }
103
+ this.buildGeometryBuffers();
104
+ }
105
+
106
+ rebuildMaterial(): void {
107
+ const shaderName = `quarksTrail_${Date.now()}`;
108
+ const defines: string[] = [];
109
+
110
+ if (this.settings.texture) {
111
+ defines.push('USE_MAP');
112
+ }
113
+
114
+ Effect.ShadersStore[shaderName + 'VertexShader'] = trail_vert;
115
+ Effect.ShadersStore[shaderName + 'FragmentShader'] = trail_frag;
116
+
117
+ const attributes = ['position', 'previous', 'next', 'side', 'width', 'uv', 'color'];
118
+ const uniforms = ['world', 'view', 'projection', 'worldViewProjection', 'lineWidth', 'resolution', 'sizeAttenuation'];
119
+ const samplers: string[] = [];
120
+
121
+ if (this.settings.texture) {
122
+ samplers.push('map');
123
+ }
124
+
125
+ const mat = new ShaderMaterial(shaderName, this.scene,
126
+ {vertex: shaderName, fragment: shaderName},
127
+ {
128
+ attributes,
129
+ uniforms,
130
+ samplers,
131
+ defines,
132
+ needAlphaBlending: this.settings.materialTransparent,
133
+ }
134
+ );
135
+
136
+ if (this.settings.texture) {
137
+ mat.setTexture('map', this.settings.texture);
138
+ }
139
+ const engine = this.scene.getEngine();
140
+ mat.setFloat('lineWidth', 1);
141
+ mat.setVector2('resolution', new BVector2(engine.getRenderWidth(), engine.getRenderHeight()));
142
+ mat.setFloat('sizeAttenuation', 1);
143
+ mat.onBindObservable.add(() => {
144
+ const renderEngine = this.scene.getEngine();
145
+ mat.setVector2('resolution', new BVector2(renderEngine.getRenderWidth(), renderEngine.getRenderHeight()));
146
+ });
147
+
148
+ mat.backFaceCulling = false;
149
+
150
+ if (this.settings.materialTransparent) {
151
+ mat.alphaMode = this.settings.materialBlendMode;
152
+ } else {
153
+ mat.alphaMode = Constants.ALPHA_DISABLE;
154
+ }
155
+ mat.needDepthPrePass = this.settings.materialDepthWrite;
156
+ mat.forceDepthWrite = this.settings.materialDepthWrite;
157
+ mat.disableDepthWrite = !this.settings.materialDepthWrite;
158
+
159
+ this.mesh.material = mat;
160
+ }
161
+
162
+ private vector_ = new Vector3();
163
+ private vector2_ = new Vector3();
164
+ private vector3_ = new Vector3();
165
+ private quaternion_ = new Quaternion();
166
+
167
+ update(): void {
168
+ let index = 0;
169
+ let triangles = 0;
170
+
171
+ let particleCount = 0;
172
+ const visibleSystems = this.getVisibleSystems();
173
+ for (const system of visibleSystems) {
174
+ for (let j = 0; j < system.particleNum; j++) {
175
+ particleCount += this.getTrailHistoryCount(system.particles[j] as TrailParticle) * 2;
176
+ }
177
+ }
178
+ if (particleCount > this.maxParticles) {
179
+ this.expandBuffers(particleCount);
180
+ }
181
+
182
+ for (const system of visibleSystems) {
183
+ // Ensure world matrix is up to date
184
+ if ((system.emitter as any).computeWorldMatrix) {
185
+ (system.emitter as any).computeWorldMatrix(true);
186
+ }
187
+ const rotation = this.quaternion_;
188
+ const translation = this.vector2_;
189
+ const scale = this.vector3_;
190
+ system.emitter.matrixWorld.decompose(translation, rotation, scale);
191
+
192
+ const particles = system.particles;
193
+ const particleNum = system.particleNum;
194
+ const uTileCount = this.settings.uTileCount;
195
+ const vTileCount = this.settings.vTileCount;
196
+ const tileWidth = 1 / uTileCount;
197
+ const tileHeight = 1 / vTileCount;
198
+ const systemWorldSpace = system.worldSpace;
199
+ const objectScale = (Math.abs(scale.x) + Math.abs(scale.y) + Math.abs(scale.z)) / 3;
200
+
201
+ for (let j = 0; j < particleNum; j++) {
202
+ const particle = particles[j] as TrailParticle;
203
+ const particleAny = particle as any;
204
+ const fastHistoryCount = particleAny._trailHistoryCount as number | undefined;
205
+ const fastHistoryCapacity = particleAny._trailHistoryCapacity as number | undefined;
206
+ const fastHistoryHead = particleAny._trailHistoryHead as number | undefined;
207
+ const fastHistoryPositions = particleAny._trailHistoryPositions as Float32Array | undefined;
208
+ const fastHistorySizes = particleAny._trailHistorySizes as Float32Array | undefined;
209
+ const fastHistoryColors = particleAny._trailHistoryColors as Float32Array | undefined;
210
+ const hasFastHistory =
211
+ typeof fastHistoryCount === 'number' &&
212
+ fastHistoryCount > 0 &&
213
+ typeof fastHistoryCapacity === 'number' &&
214
+ typeof fastHistoryHead === 'number' &&
215
+ fastHistoryPositions instanceof Float32Array &&
216
+ fastHistorySizes instanceof Float32Array &&
217
+ fastHistoryColors instanceof Float32Array;
218
+ const historyCapacity = hasFastHistory ? (fastHistoryCapacity as number) : 0;
219
+ const historyHead = hasFastHistory ? (fastHistoryHead as number) : 0;
220
+ const historyPositions = fastHistoryPositions as Float32Array;
221
+ const historySizes = fastHistorySizes as Float32Array;
222
+ const historyColors = fastHistoryColors as Float32Array;
223
+ const particleHistory = particle.previous;
224
+ const particleHistoryLength = hasFastHistory ? fastHistoryCount : particleHistory.length;
225
+ if (particleHistoryLength === 0) {
226
+ continue;
227
+ }
228
+ const col = particle.uvTile % vTileCount;
229
+ const row = Math.floor(particle.uvTile / vTileCount + 0.001);
230
+ const particleMatrix = particle.parentMatrix as unknown as Matrix4 | undefined;
231
+ const matrixToUse = particleMatrix ?? system.emitter.matrixWorld;
232
+
233
+ let previousNode = particleHistory.head;
234
+ let currentNode = previousNode;
235
+ let nextNode = currentNode?.next ?? currentNode;
236
+ const oldestFastHistoryIndex = hasFastHistory
237
+ ? (historyHead - particleHistoryLength + historyCapacity) % historyCapacity
238
+ : 0;
239
+
240
+ for (let i = 0; i < particleHistoryLength; i++, index += 2) {
241
+ let currentX: number;
242
+ let currentY: number;
243
+ let currentZ: number;
244
+ let currentSize: number;
245
+ let currentColorX: number;
246
+ let currentColorY: number;
247
+ let currentColorZ: number;
248
+ let currentColorW: number;
249
+ let previousX: number;
250
+ let previousY: number;
251
+ let previousZ: number;
252
+ let nextX: number;
253
+ let nextY: number;
254
+ let nextZ: number;
255
+
256
+ if (hasFastHistory) {
257
+ const currentHistoryIndex = (oldestFastHistoryIndex + i) % historyCapacity;
258
+ const previousHistoryIndex = i === 0 ? currentHistoryIndex : (oldestFastHistoryIndex + i - 1) % historyCapacity;
259
+ const nextHistoryIndex =
260
+ i + 1 < particleHistoryLength
261
+ ? (oldestFastHistoryIndex + i + 1) % historyCapacity
262
+ : currentHistoryIndex;
263
+
264
+ const currentPosIndex = currentHistoryIndex * 3;
265
+ const previousPosIndex = previousHistoryIndex * 3;
266
+ const nextPosIndex = nextHistoryIndex * 3;
267
+
268
+ currentX = historyPositions[currentPosIndex];
269
+ currentY = historyPositions[currentPosIndex + 1];
270
+ currentZ = historyPositions[currentPosIndex + 2];
271
+ previousX = historyPositions[previousPosIndex];
272
+ previousY = historyPositions[previousPosIndex + 1];
273
+ previousZ = historyPositions[previousPosIndex + 2];
274
+ nextX = historyPositions[nextPosIndex];
275
+ nextY = historyPositions[nextPosIndex + 1];
276
+ nextZ = historyPositions[nextPosIndex + 2];
277
+
278
+ currentSize = historySizes[currentHistoryIndex];
279
+ const currentColorIndex = currentHistoryIndex * 4;
280
+ currentColorX = historyColors[currentColorIndex];
281
+ currentColorY = historyColors[currentColorIndex + 1];
282
+ currentColorZ = historyColors[currentColorIndex + 2];
283
+ currentColorW = historyColors[currentColorIndex + 3];
284
+ } else {
285
+ const previous = previousNode!.data as RecordState;
286
+ const current = currentNode!.data as RecordState;
287
+ const next = nextNode!.data as RecordState;
288
+ currentX = current.position.x;
289
+ currentY = current.position.y;
290
+ currentZ = current.position.z;
291
+ previousX = previous.position.x;
292
+ previousY = previous.position.y;
293
+ previousZ = previous.position.z;
294
+ nextX = next.position.x;
295
+ nextY = next.position.y;
296
+ nextZ = next.position.z;
297
+ currentSize = current.size;
298
+ currentColorX = current.color.x;
299
+ currentColorY = current.color.y;
300
+ currentColorZ = current.color.z;
301
+ currentColorW = current.color.w;
302
+ }
303
+
304
+ if (!systemWorldSpace) {
305
+ this.vector_.set(currentX, currentY, currentZ).applyMatrix4(matrixToUse);
306
+ currentX = this.vector_.x;
307
+ currentY = this.vector_.y;
308
+ currentZ = this.vector_.z;
309
+ this.vector_.set(previousX, previousY, previousZ).applyMatrix4(matrixToUse);
310
+ previousX = this.vector_.x;
311
+ previousY = this.vector_.y;
312
+ previousZ = this.vector_.z;
313
+ this.vector_.set(nextX, nextY, nextZ).applyMatrix4(matrixToUse);
314
+ nextX = this.vector_.x;
315
+ nextY = this.vector_.y;
316
+ nextZ = this.vector_.z;
317
+ }
318
+
319
+ const pi = index * 3;
320
+ this.positionBuffer[pi] = currentX;
321
+ this.positionBuffer[pi + 1] = currentY;
322
+ this.positionBuffer[pi + 2] = currentZ;
323
+ this.positionBuffer[pi + 3] = currentX;
324
+ this.positionBuffer[pi + 4] = currentY;
325
+ this.positionBuffer[pi + 5] = currentZ;
326
+
327
+ this.previousBuffer[pi] = previousX;
328
+ this.previousBuffer[pi + 1] = previousY;
329
+ this.previousBuffer[pi + 2] = previousZ;
330
+ this.previousBuffer[pi + 3] = previousX;
331
+ this.previousBuffer[pi + 4] = previousY;
332
+ this.previousBuffer[pi + 5] = previousZ;
333
+
334
+ this.nextBuffer[pi] = nextX;
335
+ this.nextBuffer[pi + 1] = nextY;
336
+ this.nextBuffer[pi + 2] = nextZ;
337
+ this.nextBuffer[pi + 3] = nextX;
338
+ this.nextBuffer[pi + 4] = nextY;
339
+ this.nextBuffer[pi + 5] = nextZ;
340
+
341
+ this.sideBuffer[index] = 1;
342
+ this.sideBuffer[index + 1] = -1;
343
+
344
+ if (systemWorldSpace || particle.parentMatrix) {
345
+ this.widthBuffer[index] = currentSize;
346
+ this.widthBuffer[index + 1] = currentSize;
347
+ } else {
348
+ this.widthBuffer[index] = currentSize * objectScale;
349
+ this.widthBuffer[index + 1] = currentSize * objectScale;
350
+ }
351
+
352
+ const ui = index * 2;
353
+ this.uvBuffer[ui] = (i / particleHistoryLength + col) * tileWidth;
354
+ this.uvBuffer[ui + 1] = (vTileCount - row - 1) * tileHeight;
355
+ this.uvBuffer[ui + 2] = (i / particleHistoryLength + col) * tileWidth;
356
+ this.uvBuffer[ui + 3] = (vTileCount - row) * tileHeight;
357
+
358
+ const cci = index * 4;
359
+ this.colorBuffer[cci] = currentColorX;
360
+ this.colorBuffer[cci + 1] = currentColorY;
361
+ this.colorBuffer[cci + 2] = currentColorZ;
362
+ this.colorBuffer[cci + 3] = currentColorW;
363
+ this.colorBuffer[cci + 4] = currentColorX;
364
+ this.colorBuffer[cci + 5] = currentColorY;
365
+ this.colorBuffer[cci + 6] = currentColorZ;
366
+ this.colorBuffer[cci + 7] = currentColorW;
367
+
368
+ if (i + 1 < particleHistoryLength) {
369
+ this.indexBuffer[triangles * 3] = index;
370
+ this.indexBuffer[triangles * 3 + 1] = index + 1;
371
+ this.indexBuffer[triangles * 3 + 2] = index + 2;
372
+ triangles++;
373
+ this.indexBuffer[triangles * 3] = index + 2;
374
+ this.indexBuffer[triangles * 3 + 1] = index + 1;
375
+ this.indexBuffer[triangles * 3 + 2] = index + 3;
376
+ triangles++;
377
+ }
378
+
379
+ if (!hasFastHistory) {
380
+ previousNode = currentNode;
381
+ currentNode = nextNode;
382
+ nextNode = nextNode?.next ?? nextNode;
383
+ }
384
+ }
385
+ }
386
+ }
387
+
388
+ if (index > 0 && triangles > 0) {
389
+ this.mesh.updateVerticesData(VertexBuffer.PositionKind, this.positionBuffer);
390
+ this.mesh.updateVerticesData(VertexBuffer.UVKind, this.uvBuffer);
391
+ this.mesh.updateIndices(this.indexBuffer.subarray(0, triangles * 3));
392
+ this.previousVB.update(this.previousBuffer);
393
+ this.nextVB.update(this.nextBuffer);
394
+ this.sideVB.update(this.sideBuffer);
395
+ this.widthVB.update(this.widthBuffer);
396
+
397
+ const colorVB = this.mesh.getVertexBuffer(VertexBuffer.ColorKind);
398
+ if (colorVB) colorVB.update(this.colorBuffer);
399
+
400
+ this.mesh.setEnabled(true);
401
+ if (this.mesh.subMeshes && this.mesh.subMeshes.length > 0) {
402
+ const meshBoundingInfo = this.mesh.getBoundingInfo();
403
+ for (const subMesh of this.mesh.subMeshes) {
404
+ (subMesh as any)._boundingInfo = meshBoundingInfo;
405
+ }
406
+ this.mesh.subMeshes[0].indexCount = triangles * 3;
407
+ this.mesh.subMeshes[0].verticesCount = index;
408
+ }
409
+ } else {
410
+ this.mesh.setEnabled(false);
411
+ }
412
+ }
413
+
414
+ dispose(): void {
415
+ if (this.mesh.material) {
416
+ this.mesh.material.dispose();
417
+ }
418
+ super.dispose();
419
+ }
420
+ }