babylon.quarks 0.25.0 → 0.26.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.
@@ -15,6 +15,7 @@ import {
15
15
  SpriteParticle,
16
16
  StretchedBillBoardSettings,
17
17
  Vector3,
18
+ Vector4,
18
19
  } from 'quarks.core';
19
20
  import {VFXBatchSettings} from './BatchedRenderer';
20
21
  import {gather, sortByKey, SortScratch} from './instanceSort';
@@ -91,6 +92,9 @@ export class SpriteBatch extends VFXBatch {
91
92
  } else {
92
93
  vertexData.uvs = new Float32Array(vertexCount * 2);
93
94
  }
95
+ if (this.meshUV2()) {
96
+ vertexData.uvs2 = this.settings.instancingUVs2!;
97
+ }
94
98
  if (this.settings.instancingNormals) {
95
99
  vertexData.normals = this.settings.instancingNormals;
96
100
  }
@@ -99,6 +103,33 @@ export class SpriteBatch extends VFXBatch {
99
103
  this.buildExpandableBuffers();
100
104
  }
101
105
 
106
+ /**
107
+ * True when a mesh batch reads UV channel 1 and has a second UV set to
108
+ * read it from, two values per vertex. A set of any other length is left
109
+ * out with a warning, and channel 1 then reads the first set.
110
+ */
111
+ private warnedUV2 = false;
112
+
113
+ private meshUV2(): boolean {
114
+ const uvs2 = this.settings.instancingUVs2;
115
+ if (this.settings.renderMode !== RenderMode.Mesh || !uvs2 || !this.usesUV1()) {
116
+ return false;
117
+ }
118
+ const expected = (this.settings.instancingGeometry.length / 3) * 2;
119
+ if (uvs2.length !== expected) {
120
+ if (this.warnedUV2) {
121
+ return false;
122
+ }
123
+ this.warnedUV2 = true;
124
+ console.warn(
125
+ `[babylon.quarks] instancingUVs2 has ${uvs2.length} values for ${expected / 2} vertices ` +
126
+ '(two per vertex expected); UV channel 1 reads the first UV set instead.'
127
+ );
128
+ return false;
129
+ }
130
+ return true;
131
+ }
132
+
102
133
  /** Releases the instance vertex buffers so a resize does not leak GPU memory. */
103
134
  private disposeExpandableBuffers(): void {
104
135
  this.offsetVB?.dispose();
@@ -343,7 +374,9 @@ export class SpriteBatch extends VFXBatch {
343
374
  const rim = isMesh ? this.settings.rim : undefined;
344
375
  if (rim) {
345
376
  defines.push('MESH_RIM');
346
- if (rim.mode === 'mix') {
377
+ if (rim.mode === 'fade') {
378
+ defines.push('MESH_RIM_FADE');
379
+ } else if (rim.mode === 'mix') {
347
380
  defines.push('MESH_RIM_MIX');
348
381
  }
349
382
  }
@@ -357,6 +390,10 @@ export class SpriteBatch extends VFXBatch {
357
390
  if (this.settings.renderMode === RenderMode.Mesh) {
358
391
  attributes.push('normal');
359
392
  }
393
+ if (this.usesUV1() && this.meshUV2()) {
394
+ defines.push('MESH_UV2');
395
+ attributes.push('uv2');
396
+ }
360
397
  if (SpriteBatch.drawsAlongVelocity(this.settings.renderMode)) {
361
398
  attributes.push('velocity');
362
399
  }
@@ -913,6 +950,10 @@ export class SpriteBatch extends VFXBatch {
913
950
  }
914
951
  }
915
952
 
953
+ const tint = (system as ParticleSystem).tint;
954
+ if (tint !== undefined && index > firstIndex) {
955
+ SpriteBatch.applyTint(this.colorBuffer, firstIndex * 4, index - firstIndex, tint);
956
+ }
916
957
  if (flipsSprites && system.flip !== undefined) {
917
958
  if (store !== undefined) {
918
959
  SpriteBatch.applyFlip(this.sizeBuffer, firstIndex * 3, store.flip, particleNum);
@@ -1020,6 +1061,16 @@ export class SpriteBatch extends VFXBatch {
1020
1061
  );
1021
1062
  }
1022
1063
 
1064
+ /** Multiplies `count` RGBA colours starting at `base` by a system's {@link ParticleSystem.tint}. */
1065
+ private static applyTint(colors: Float32Array, base: number, count: number, tint: Vector4): void {
1066
+ for (let j = 0, o = base; j < count; j++, o += 4) {
1067
+ colors[o] *= tint.x;
1068
+ colors[o + 1] *= tint.y;
1069
+ colors[o + 2] *= tint.z;
1070
+ colors[o + 3] *= tint.w;
1071
+ }
1072
+ }
1073
+
1023
1074
  /**
1024
1075
  * Negates the size of each flipped particle along the axes it flips on,
1025
1076
  * starting at `base` in `sizes`. The billboard shader scales the quad by
package/src/TrailBatch.ts CHANGED
@@ -19,6 +19,8 @@ export class TrailBatch extends VFXBatch {
19
19
  private previousBuffer!: Float32Array;
20
20
  private nextBuffer!: Float32Array;
21
21
  private uvBuffer!: Float32Array;
22
+ /** UV channel 1: along the trail and across it, without the sheet's cell; only when a texture reads it. */
23
+ private uv1Buffer: Float32Array | null = null;
22
24
  private sideBuffer!: Float32Array;
23
25
  private widthBuffer!: Float32Array;
24
26
  private colorBuffer!: Float32Array;
@@ -52,6 +54,7 @@ export class TrailBatch extends VFXBatch {
52
54
  this.previousBuffer = new Float32Array(this.maxParticles * 6);
53
55
  this.nextBuffer = new Float32Array(this.maxParticles * 6);
54
56
  this.uvBuffer = new Float32Array(this.maxParticles * 4);
57
+ this.uv1Buffer = this.usesUV1() ? new Float32Array(this.maxParticles * 4) : null;
55
58
  this.sideBuffer = new Float32Array(this.maxParticles * 2);
56
59
  this.widthBuffer = new Float32Array(this.maxParticles * 2);
57
60
  this.colorBuffer = new Float32Array(this.maxParticles * 8);
@@ -86,6 +89,9 @@ export class TrailBatch extends VFXBatch {
86
89
  // Set standard vertex data with full buffer capacity (updatable)
87
90
  this.mesh.setVerticesData(VertexBuffer.PositionKind, this.positionBuffer, true);
88
91
  this.mesh.setVerticesData(VertexBuffer.UVKind, this.uvBuffer, true);
92
+ if (this.uv1Buffer) {
93
+ this.mesh.setVerticesData(VertexBuffer.UV2Kind, this.uv1Buffer, true);
94
+ }
89
95
  this.mesh.setVerticesData(VertexBuffer.ColorKind, this.colorBuffer, true, 4);
90
96
  // The whole array, not just the dummy triangle: this call is what sizes
91
97
  // the GPU index buffer, and updateIndices later writes into it without
@@ -150,6 +156,9 @@ export class TrailBatch extends VFXBatch {
150
156
  );
151
157
 
152
158
  const attributes = ['position', 'previous', 'next', 'side', 'width', 'uv', 'color'];
159
+ if (this.uv1Buffer) {
160
+ attributes.push('uv2');
161
+ }
153
162
  const uniforms = [
154
163
  'world',
155
164
  'view',
@@ -231,6 +240,30 @@ export class TrailBatch extends VFXBatch {
231
240
  * frame costs a matrix compose per system; checking the transform cache
232
241
  * first makes a still emitter free.
233
242
  */
243
+ /**
244
+ * Writes a swept ribbon's vertex pair at `pi`: the sample at (x, y, z)
245
+ * pushed `half` widths along its side (sx, sy, sz) one way for the first
246
+ * vertex and the other way for the second.
247
+ */
248
+ private writeSweptPair(
249
+ pi: number,
250
+ x: number,
251
+ y: number,
252
+ z: number,
253
+ sx: number,
254
+ sy: number,
255
+ sz: number,
256
+ half: number
257
+ ): void {
258
+ const positions = this.positionBuffer;
259
+ positions[pi] = x + sx * half;
260
+ positions[pi + 1] = y + sy * half;
261
+ positions[pi + 2] = z + sz * half;
262
+ positions[pi + 3] = x - sx * half;
263
+ positions[pi + 4] = y - sy * half;
264
+ positions[pi + 5] = z - sz * half;
265
+ }
266
+
234
267
  private static ensureWorldMatrix(system: IParticleSystem): void {
235
268
  const emitter = system.emitter as {
236
269
  computeWorldMatrix?: (force: boolean) => void;
@@ -296,10 +329,15 @@ export class TrailBatch extends VFXBatch {
296
329
  // rather than by the particle's velocity, so it is left alone.
297
330
  const trailSettings = system.rendererEmitterSettings as TrailSettings;
298
331
  const uvFromHead = Boolean(trailSettings.uvFromHead);
332
+ const uv1 = this.uv1Buffer;
299
333
  const colorFromParticle = Boolean(trailSettings.colorFromParticle);
300
334
  // Recorded samples already sit in world space; the live tip still comes
301
335
  // from the particle's own (local) position.
302
336
  const samplesInWorld = systemWorldSpace || Boolean(trailSettings.recordWorldSpace);
337
+ // A swept trail spans each sample's recorded side instead of facing
338
+ // the camera: its vertices are laid out here, and a zero width
339
+ // keeps the shader from pushing them.
340
+ const sideAxis = trailSettings.sideAxis;
303
341
  const residual = trailSettings.followLocalOrigin ? 0 : (system.simulationResidual ?? 0);
304
342
  // Measured from the step the particle just took, not from its
305
343
  // velocity: a trail pulled along by an orbit or by a plugin's own
@@ -315,6 +353,8 @@ export class TrailBatch extends VFXBatch {
315
353
  const historyCapacity = particle.historyCapacity;
316
354
  const historyPositions = particle.historyPositions;
317
355
  const historySizes = particle.historySizes;
356
+ const historySides = particle.historySides;
357
+ const swept = sideAxis !== undefined && historySides.length === historyCapacity * 3;
318
358
  // Every sample takes its color from one place: its own recorded slot, or
319
359
  // the particle's current row in the store.
320
360
  const colors = colorFromParticle ? particle.store.color : particle.historyColors;
@@ -394,12 +434,27 @@ export class TrailBatch extends VFXBatch {
394
434
  const currentColorW = colors[currentColorIndex + 3];
395
435
 
396
436
  const pi = index * 3;
397
- this.positionBuffer[pi] = currentX;
398
- this.positionBuffer[pi + 1] = currentY;
399
- this.positionBuffer[pi + 2] = currentZ;
400
- this.positionBuffer[pi + 3] = currentX;
401
- this.positionBuffer[pi + 4] = currentY;
402
- this.positionBuffer[pi + 5] = currentZ;
437
+ if (swept) {
438
+ const si = currentSlot * 3;
439
+ let sx = historySides[si];
440
+ let sy = historySides[si + 1];
441
+ let sz = historySides[si + 2];
442
+ if (!samplesInWorld) {
443
+ const tx = m00 * sx + m10 * sy + m20 * sz;
444
+ const ty = m01 * sx + m11 * sy + m21 * sz;
445
+ sz = m02 * sx + m12 * sy + m22 * sz;
446
+ sx = tx;
447
+ sy = ty;
448
+ }
449
+ this.writeSweptPair(pi, currentX, currentY, currentZ, sx, sy, sz, currentSize * 0.5);
450
+ } else {
451
+ this.positionBuffer[pi] = currentX;
452
+ this.positionBuffer[pi + 1] = currentY;
453
+ this.positionBuffer[pi + 2] = currentZ;
454
+ this.positionBuffer[pi + 3] = currentX;
455
+ this.positionBuffer[pi + 4] = currentY;
456
+ this.positionBuffer[pi + 5] = currentZ;
457
+ }
403
458
 
404
459
  this.previousBuffer[pi] = previousX;
405
460
  this.previousBuffer[pi + 1] = previousY;
@@ -415,7 +470,10 @@ export class TrailBatch extends VFXBatch {
415
470
  this.nextBuffer[pi + 4] = nextY;
416
471
  this.nextBuffer[pi + 5] = nextZ;
417
472
 
418
- if (systemWorldSpace || particle.parentMatrix) {
473
+ if (swept) {
474
+ this.widthBuffer[index] = 0;
475
+ this.widthBuffer[index + 1] = 0;
476
+ } else if (systemWorldSpace || particle.parentMatrix) {
419
477
  this.widthBuffer[index] = currentSize;
420
478
  this.widthBuffer[index + 1] = currentSize;
421
479
  } else {
@@ -430,6 +488,13 @@ export class TrailBatch extends VFXBatch {
430
488
  this.uvBuffer[ui + 1] = (vTileCount - row - 1) * tileHeight;
431
489
  this.uvBuffer[ui + 2] = u;
432
490
  this.uvBuffer[ui + 3] = (vTileCount - row) * tileHeight;
491
+ if (uv1) {
492
+ const u1 = uvFromHead ? 1 - along : along;
493
+ uv1[ui] = u1;
494
+ uv1[ui + 1] = 0;
495
+ uv1[ui + 2] = u1;
496
+ uv1[ui + 3] = 1;
497
+ }
433
498
 
434
499
  const cci = index * 4;
435
500
  this.colorBuffer[cci] = currentColorX;
@@ -470,9 +535,12 @@ export class TrailBatch extends VFXBatch {
470
535
  let tipY = position.y + (position.y - previousHead.y) * stepFraction;
471
536
  let tipZ = position.z + (position.z - previousHead.z) * stepFraction;
472
537
  const headIndex = index - 2;
473
- const headX = this.positionBuffer[headIndex * 3];
474
- const headY = this.positionBuffer[headIndex * 3 + 1];
475
- const headZ = this.positionBuffer[headIndex * 3 + 2];
538
+ // The pair's midpoint: a swept pair lies to either side of its sample.
539
+ const headX = (this.positionBuffer[headIndex * 3] + this.positionBuffer[headIndex * 3 + 3]) * 0.5;
540
+ const headY =
541
+ (this.positionBuffer[headIndex * 3 + 1] + this.positionBuffer[headIndex * 3 + 4]) * 0.5;
542
+ const headZ =
543
+ (this.positionBuffer[headIndex * 3 + 2] + this.positionBuffer[headIndex * 3 + 5]) * 0.5;
476
544
 
477
545
  if (!systemWorldSpace) {
478
546
  const w = 1 / (m03 * tipX + m13 * tipY + m23 * tipZ + m33);
@@ -484,12 +552,30 @@ export class TrailBatch extends VFXBatch {
484
552
  }
485
553
 
486
554
  const tipPi = index * 3;
487
- this.positionBuffer[tipPi] = tipX;
488
- this.positionBuffer[tipPi + 1] = tipY;
489
- this.positionBuffer[tipPi + 2] = tipZ;
490
- this.positionBuffer[tipPi + 3] = tipX;
491
- this.positionBuffer[tipPi + 4] = tipY;
492
- this.positionBuffer[tipPi + 5] = tipZ;
555
+ const tipSize = particle.size.x;
556
+ if (swept) {
557
+ // The side as the emitter holds it now.
558
+ const ax = sideAxis!.x;
559
+ const ay = sideAxis!.y;
560
+ const az = sideAxis!.z;
561
+ this.writeSweptPair(
562
+ tipPi,
563
+ tipX,
564
+ tipY,
565
+ tipZ,
566
+ m00 * ax + m10 * ay + m20 * az,
567
+ m01 * ax + m11 * ay + m21 * az,
568
+ m02 * ax + m12 * ay + m22 * az,
569
+ tipSize * 0.5
570
+ );
571
+ } else {
572
+ this.positionBuffer[tipPi] = tipX;
573
+ this.positionBuffer[tipPi + 1] = tipY;
574
+ this.positionBuffer[tipPi + 2] = tipZ;
575
+ this.positionBuffer[tipPi + 3] = tipX;
576
+ this.positionBuffer[tipPi + 4] = tipY;
577
+ this.positionBuffer[tipPi + 5] = tipZ;
578
+ }
493
579
 
494
580
  this.previousBuffer[tipPi] = headX;
495
581
  this.previousBuffer[tipPi + 1] = headY;
@@ -516,8 +602,10 @@ export class TrailBatch extends VFXBatch {
516
602
  this.nextBuffer[headPi + 4] = tipY;
517
603
  this.nextBuffer[headPi + 5] = tipZ;
518
604
 
519
- const tipSize = particle.size.x;
520
- if (systemWorldSpace || particle.parentMatrix) {
605
+ if (swept) {
606
+ this.widthBuffer[index] = 0;
607
+ this.widthBuffer[index + 1] = 0;
608
+ } else if (systemWorldSpace || particle.parentMatrix) {
521
609
  this.widthBuffer[index] = tipSize;
522
610
  this.widthBuffer[index + 1] = tipSize;
523
611
  } else {
@@ -531,6 +619,13 @@ export class TrailBatch extends VFXBatch {
531
619
  this.uvBuffer[tipUi + 1] = (vTileCount - row - 1) * tileHeight;
532
620
  this.uvBuffer[tipUi + 2] = tipU;
533
621
  this.uvBuffer[tipUi + 3] = (vTileCount - row) * tileHeight;
622
+ if (uv1) {
623
+ const tipU1 = uvFromHead ? 0 : 1;
624
+ uv1[tipUi] = tipU1;
625
+ uv1[tipUi + 1] = 0;
626
+ uv1[tipUi + 2] = tipU1;
627
+ uv1[tipUi + 3] = 1;
628
+ }
534
629
 
535
630
  const tipColor = particle.color;
536
631
  const tipCi = index * 4;
@@ -555,6 +650,17 @@ export class TrailBatch extends VFXBatch {
555
650
  index += 2;
556
651
  }
557
652
  }
653
+ const tint = (system as ParticleSystem).tint;
654
+ if (tint !== undefined) {
655
+ // As SpriteBatch does: the system's tint over its ribbons' vertices.
656
+ const colors = this.colorBuffer;
657
+ for (let o = firstVertex * 4, end = index * 4; o < end; o += 4) {
658
+ colors[o] *= tint.x;
659
+ colors[o + 1] *= tint.y;
660
+ colors[o + 2] *= tint.z;
661
+ colors[o + 3] *= tint.w;
662
+ }
663
+ }
558
664
  this.measureSystemBounds(system as ParticleSystem, firstVertex, index);
559
665
  }
560
666
 
@@ -578,8 +684,8 @@ export class TrailBatch extends VFXBatch {
578
684
  let maxX = -Infinity;
579
685
  let maxY = -Infinity;
580
686
  let maxZ = -Infinity;
581
- // Each sample writes its position twice (both ribbon edges); one of the pair is enough.
582
- for (let o = fromVertex * 3, end = toVertex * 3; o < end; o += 6) {
687
+ // Both of each pair: a swept ribbon's edges lie apart, a facing one's coincide.
688
+ for (let o = fromVertex * 3, end = toVertex * 3; o < end; o += 3) {
583
689
  const x = positions[o];
584
690
  const y = positions[o + 1];
585
691
  const z = positions[o + 2];
@@ -614,6 +720,9 @@ export class TrailBatch extends VFXBatch {
614
720
  // the high-water mark of every system in the batch.
615
721
  this.mesh.updateVerticesData(VertexBuffer.PositionKind, this.positionBuffer.subarray(0, index * 3));
616
722
  this.mesh.updateVerticesData(VertexBuffer.UVKind, this.uvBuffer.subarray(0, index * 2));
723
+ if (this.uv1Buffer) {
724
+ this.mesh.updateVerticesData(VertexBuffer.UV2Kind, this.uv1Buffer.subarray(0, index * 2));
725
+ }
617
726
  // Straight to the GPU buffer. Going through `mesh.updateIndices` with a
618
727
  // range whose length changes every frame makes Babylon copy the
619
728
  // indices, rebuild the sub mesh and recompute its bounds from the