babylon.quarks 0.20.0 → 0.23.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 +105 -0
- package/README.md +2 -0
- 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.ts +523 -43
- package/package.json +1 -1
- package/src/BatchedRenderer.ts +59 -2
- package/src/ParticleSystem.ts +388 -59
- package/src/QuarksLoader.ts +381 -56
- package/src/SpriteBatch.ts +388 -111
- package/src/TrailBatch.ts +79 -63
- package/src/VFXBatch.ts +101 -0
- package/src/index.ts +9 -3
- package/src/shaders/particle_physics_frag.glsl.ts +26 -1
- package/src/shaders/particle_physics_frag.wgsl.ts +26 -1
package/src/SpriteBatch.ts
CHANGED
|
@@ -28,7 +28,7 @@ import particle_vert_wgsl from './shaders/particle_vert.wgsl';
|
|
|
28
28
|
import {registerShaders, shaderLanguageFor, ShaderSources} from './shaders/shaderLanguageSupport';
|
|
29
29
|
import stretched_bb_particle_vert from './shaders/stretched_bb_particle_vert.glsl';
|
|
30
30
|
import stretched_bb_particle_vert_wgsl from './shaders/stretched_bb_particle_vert.wgsl';
|
|
31
|
-
import {RenderMode, VFXBatch} from './VFXBatch';
|
|
31
|
+
import {defaultMeshLighting, MeshLightingSettings, RenderMode, VFXBatch} from './VFXBatch';
|
|
32
32
|
|
|
33
33
|
export class SpriteBatch extends VFXBatch {
|
|
34
34
|
private static whiteTextureByScene = new WeakMap<Scene, RawTexture>();
|
|
@@ -52,6 +52,10 @@ export class SpriteBatch extends VFXBatch {
|
|
|
52
52
|
|
|
53
53
|
/** Concretely typed handle on the batch material, see syncStretchedSpeedFactor. */
|
|
54
54
|
private shaderMaterial: ShaderMaterial | null = null;
|
|
55
|
+
/** The `'fixed'` mesh light, as shader uniforms; see applyMeshLighting. */
|
|
56
|
+
private readonly fixedLightDirection = new BVector3();
|
|
57
|
+
private readonly fixedLightColor = new BVector3();
|
|
58
|
+
private readonly fixedAmbientColor = new BVector3();
|
|
55
59
|
|
|
56
60
|
private offsetVB!: VertexBuffer;
|
|
57
61
|
private rotationVB!: VertexBuffer;
|
|
@@ -62,6 +66,7 @@ export class SpriteBatch extends VFXBatch {
|
|
|
62
66
|
|
|
63
67
|
constructor(settings: VFXBatchSettings, scene: Scene) {
|
|
64
68
|
super(settings, scene);
|
|
69
|
+
this.storeFixedLighting(defaultMeshLighting());
|
|
65
70
|
this.maxParticles = 1000;
|
|
66
71
|
this.setupBuffers();
|
|
67
72
|
this.rebuildMaterial();
|
|
@@ -213,6 +218,20 @@ export class SpriteBatch extends VFXBatch {
|
|
|
213
218
|
if (pivot) {
|
|
214
219
|
defines.push('PIVOT');
|
|
215
220
|
}
|
|
221
|
+
const isMesh = this.settings.renderMode === RenderMode.Mesh;
|
|
222
|
+
const lighting = this.settings.lighting ?? 'fixed';
|
|
223
|
+
const lit = isMesh && lighting !== 'unlit';
|
|
224
|
+
if (isMesh && !lit) {
|
|
225
|
+
defines.push('MESH_UNLIT');
|
|
226
|
+
}
|
|
227
|
+
const rim = isMesh ? this.settings.rim : undefined;
|
|
228
|
+
if (rim) {
|
|
229
|
+
defines.push('MESH_RIM');
|
|
230
|
+
if (rim.mode === 'mix') {
|
|
231
|
+
defines.push('MESH_RIM_MIX');
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const needsEyePosition = useEnvAtlas || !!rim;
|
|
216
235
|
|
|
217
236
|
const shaderLanguage = shaderLanguageFor(this.scene.getEngine());
|
|
218
237
|
registerShaders(shaderName, vertexShader, fragmentShader, shaderLanguage);
|
|
@@ -237,9 +256,15 @@ export class SpriteBatch extends VFXBatch {
|
|
|
237
256
|
}
|
|
238
257
|
if (useEnvAtlas) {
|
|
239
258
|
samplers.push('envAtlas');
|
|
240
|
-
uniforms.push('eyePosition');
|
|
241
259
|
uniforms.push('reflectionLevel');
|
|
242
260
|
}
|
|
261
|
+
if (needsEyePosition) {
|
|
262
|
+
uniforms.push('eyePosition');
|
|
263
|
+
}
|
|
264
|
+
if (rim) {
|
|
265
|
+
uniforms.push('rimColor');
|
|
266
|
+
uniforms.push('rimParams');
|
|
267
|
+
}
|
|
243
268
|
if (this.settings.renderMode === RenderMode.StretchedBillBoard) {
|
|
244
269
|
uniforms.push('speedFactor');
|
|
245
270
|
}
|
|
@@ -254,7 +279,7 @@ export class SpriteBatch extends VFXBatch {
|
|
|
254
279
|
if (pivot) {
|
|
255
280
|
uniforms.push('pivot');
|
|
256
281
|
}
|
|
257
|
-
if (
|
|
282
|
+
if (lit) {
|
|
258
283
|
uniforms.push('lightDirection');
|
|
259
284
|
uniforms.push('lightColor');
|
|
260
285
|
uniforms.push('ambientColor');
|
|
@@ -280,7 +305,14 @@ export class SpriteBatch extends VFXBatch {
|
|
|
280
305
|
if (useEnvAtlas && atlas) {
|
|
281
306
|
mat.setTexture('envAtlas', atlas);
|
|
282
307
|
mat.setFloat('reflectionLevel', this.settings.reflectionLevel);
|
|
308
|
+
}
|
|
309
|
+
if (rim) {
|
|
310
|
+
mat.setVector3('rimColor', new BVector3(rim.color[0], rim.color[1], rim.color[2]));
|
|
311
|
+
mat.setVector2('rimParams', new BVector2(rim.power, rim.strength));
|
|
312
|
+
}
|
|
313
|
+
if (needsEyePosition) {
|
|
283
314
|
const eyePosition = new BVector3();
|
|
315
|
+
mat.setVector3('eyePosition', eyePosition);
|
|
284
316
|
mat.onBindObservable.add(() => {
|
|
285
317
|
const camera = this.scene.activeCamera;
|
|
286
318
|
if (!camera) {
|
|
@@ -322,10 +354,22 @@ export class SpriteBatch extends VFXBatch {
|
|
|
322
354
|
if (pivot) {
|
|
323
355
|
mat.setVector3('pivot', new BVector3(pivot.x, pivot.y, pivot.z));
|
|
324
356
|
}
|
|
325
|
-
if (
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
357
|
+
if (lit) {
|
|
358
|
+
this.writeFixedLighting(mat);
|
|
359
|
+
if (lighting === 'scene') {
|
|
360
|
+
const direction = new BVector3();
|
|
361
|
+
const color = new BVector3();
|
|
362
|
+
const ambient = new BVector3();
|
|
363
|
+
mat.onBindObservable.add(() => {
|
|
364
|
+
if (SpriteBatch.readSceneLight(this.scene, direction, color, ambient)) {
|
|
365
|
+
mat.setVector3('lightDirection', direction);
|
|
366
|
+
mat.setVector3('lightColor', color);
|
|
367
|
+
mat.setVector3('ambientColor', ambient);
|
|
368
|
+
} else {
|
|
369
|
+
this.writeFixedLighting(mat);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
}
|
|
329
373
|
}
|
|
330
374
|
|
|
331
375
|
mat.backFaceCulling = false;
|
|
@@ -343,6 +387,64 @@ export class SpriteBatch extends VFXBatch {
|
|
|
343
387
|
this.shaderMaterial = mat;
|
|
344
388
|
}
|
|
345
389
|
|
|
390
|
+
applyMeshLighting(lighting: MeshLightingSettings): void {
|
|
391
|
+
this.storeFixedLighting(lighting);
|
|
392
|
+
const material = this.shaderMaterial;
|
|
393
|
+
if (material !== null && this.settings.renderMode === RenderMode.Mesh && this.settings.lighting !== 'unlit') {
|
|
394
|
+
this.writeFixedLighting(material);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
private storeFixedLighting(lighting: MeshLightingSettings): void {
|
|
399
|
+
this.fixedLightDirection.copyFrom(lighting.direction);
|
|
400
|
+
this.fixedLightColor.set(lighting.color.r, lighting.color.g, lighting.color.b);
|
|
401
|
+
this.fixedAmbientColor.set(lighting.ambient.r, lighting.ambient.g, lighting.ambient.b);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
private writeFixedLighting(material: ShaderMaterial): void {
|
|
405
|
+
material.setVector3('lightDirection', this.fixedLightDirection);
|
|
406
|
+
material.setVector3('lightColor', this.fixedLightColor);
|
|
407
|
+
material.setVector3('ambientColor', this.fixedAmbientColor);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Reads the scene's key light for `'scene'` mesh lighting: the first
|
|
412
|
+
* enabled directional or hemispheric light, its diffuse colour times its
|
|
413
|
+
* intensity, plus the scene's ambient colour. A hemispheric light lights
|
|
414
|
+
* from its `direction` (which points at the sky) and adds its ground colour
|
|
415
|
+
* to the ambient, which approximates its sky-to-ground blend with the one
|
|
416
|
+
* directional term the shader has. False when there is no such light.
|
|
417
|
+
*/
|
|
418
|
+
static readSceneLight(scene: Scene, direction: BVector3, color: BVector3, ambient: BVector3): boolean {
|
|
419
|
+
const lights = scene.lights;
|
|
420
|
+
for (let i = 0; i < lights.length; i++) {
|
|
421
|
+
const light = lights[i];
|
|
422
|
+
const kind = light.getClassName();
|
|
423
|
+
if ((kind !== 'DirectionalLight' && kind !== 'HemisphericLight') || !light.isEnabled()) {
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
const intensity = light.intensity;
|
|
427
|
+
const diffuse = light.diffuse;
|
|
428
|
+
const sceneAmbient = scene.ambientColor;
|
|
429
|
+
color.set(diffuse.r * intensity, diffuse.g * intensity, diffuse.b * intensity);
|
|
430
|
+
ambient.set(sceneAmbient.r, sceneAmbient.g, sceneAmbient.b);
|
|
431
|
+
const lightDirection = (light as unknown as {direction: BVector3}).direction;
|
|
432
|
+
if (kind === 'DirectionalLight') {
|
|
433
|
+
direction.copyFrom(lightDirection);
|
|
434
|
+
} else {
|
|
435
|
+
direction.copyFrom(lightDirection).scaleInPlace(-1);
|
|
436
|
+
const ground = (light as unknown as {groundColor?: {r: number; g: number; b: number}}).groundColor;
|
|
437
|
+
if (ground) {
|
|
438
|
+
ambient.x += ground.r * intensity;
|
|
439
|
+
ambient.y += ground.g * intensity;
|
|
440
|
+
ambient.z += ground.b * intensity;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
|
|
346
448
|
private vector_ = new Vector3();
|
|
347
449
|
private vector2_ = new Vector3();
|
|
348
450
|
private vector3_ = new Vector3();
|
|
@@ -438,7 +540,9 @@ export class SpriteBatch extends VFXBatch {
|
|
|
438
540
|
const residual = system.simulationResidual ?? 0;
|
|
439
541
|
const stepFraction = residual === 0 ? 0 : residual / (system.simulationStep ?? residual);
|
|
440
542
|
if (copiedColor) {
|
|
441
|
-
|
|
543
|
+
// An aliased previous column means nothing changed since the
|
|
544
|
+
// step; the copy is then the carried value.
|
|
545
|
+
if (stepFraction !== 0 && particleNum > 0 && store!.previousColor !== store!.color) {
|
|
442
546
|
SpriteBatch.continueColor(this.colorBuffer, index * 4, store!, particleNum, stepFraction);
|
|
443
547
|
} else {
|
|
444
548
|
this.colorBuffer.set(store!.color.subarray(0, particleNum * 4), index * 4);
|
|
@@ -451,7 +555,7 @@ export class SpriteBatch extends VFXBatch {
|
|
|
451
555
|
} else {
|
|
452
556
|
this.offsetBuffer.set(store.position.subarray(0, particleNum * 3), index * 3);
|
|
453
557
|
}
|
|
454
|
-
if (stepFraction !== 0 && particleNum > 0) {
|
|
558
|
+
if (stepFraction !== 0 && particleNum > 0 && store.previousSize !== store.size) {
|
|
455
559
|
SpriteBatch.continueVector3(
|
|
456
560
|
this.sizeBuffer,
|
|
457
561
|
index * 3,
|
|
@@ -500,7 +604,7 @@ export class SpriteBatch extends VFXBatch {
|
|
|
500
604
|
offsets[o + 2] = (m02 * px + m12 * py + m22 * pz + m32) * w;
|
|
501
605
|
}
|
|
502
606
|
const sizes = this.sizeBuffer;
|
|
503
|
-
if (stepFraction !== 0) {
|
|
607
|
+
if (stepFraction !== 0 && store!.previousSize !== store!.size) {
|
|
504
608
|
SpriteBatch.continueVector3(
|
|
505
609
|
sizes,
|
|
506
610
|
base,
|
|
@@ -521,122 +625,172 @@ export class SpriteBatch extends VFXBatch {
|
|
|
521
625
|
}
|
|
522
626
|
|
|
523
627
|
const firstIndex = index;
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
628
|
+
// Every particle of a system that emits for itself shares the emitter's transform and,
|
|
629
|
+
// outside mesh mode, rotates by an angle: its scalar attributes are then store columns
|
|
630
|
+
// and go to the buffers as ranges. Only meshes (quaternion rotations) and particles that
|
|
631
|
+
// carry their own parent transform still need to be visited one by one.
|
|
632
|
+
if (sharedTransform && !isMeshRender) {
|
|
633
|
+
const columns = store!;
|
|
634
|
+
this.uvTileBuffer.set(columns.uvTile.subarray(0, particleNum), firstIndex);
|
|
635
|
+
const rotations = this.rotationBuffer;
|
|
636
|
+
rotations.set(columns.rotation.subarray(0, particleNum), firstIndex);
|
|
637
|
+
if (residual !== 0) {
|
|
638
|
+
// Carry the spin on by the time since the last step, as the
|
|
639
|
+
// per-particle path does.
|
|
640
|
+
const rates = columns.angularVelocity;
|
|
641
|
+
for (let j = 0; j < particleNum; j++) {
|
|
642
|
+
rotations[firstIndex + j] += rates[j] * residual;
|
|
537
643
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
644
|
+
}
|
|
645
|
+
if (isStretchedRender && this.velocityBuffer) {
|
|
646
|
+
SpriteBatch.writeVelocities(
|
|
647
|
+
this.velocityBuffer,
|
|
648
|
+
firstIndex * 4,
|
|
649
|
+
columns,
|
|
650
|
+
particleNum,
|
|
651
|
+
stepFraction,
|
|
652
|
+
systemWorldSpace ? null : this.rotationMat_,
|
|
653
|
+
speedFactor,
|
|
654
|
+
lengthFactor
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
index += particleNum;
|
|
658
|
+
} else if (sharedTransform && store!.rotationQuaternion.length > 0) {
|
|
659
|
+
// Meshes rotate by quaternion, kept in the store too: turn them in
|
|
660
|
+
// bulk, by the emitter's rotation when the system is local.
|
|
661
|
+
const columns = store!;
|
|
662
|
+
this.uvTileBuffer.set(columns.uvTile.subarray(0, particleNum), firstIndex);
|
|
663
|
+
SpriteBatch.writeQuaternions(
|
|
664
|
+
this.rotationBuffer,
|
|
665
|
+
firstIndex * 4,
|
|
666
|
+
columns,
|
|
667
|
+
particleNum,
|
|
668
|
+
stepFraction,
|
|
669
|
+
systemWorldSpace ? null : rotation
|
|
670
|
+
);
|
|
671
|
+
index += particleNum;
|
|
672
|
+
} else {
|
|
673
|
+
for (let j = 0; j < particleNum; j++, index++) {
|
|
674
|
+
const particle = particles[j] as SpriteParticle;
|
|
675
|
+
|
|
676
|
+
if (isMeshRender) {
|
|
677
|
+
// Turning behaviors leave the last step's turn on the
|
|
678
|
+
// particle; a fraction of it carries the mesh the rest of
|
|
679
|
+
// the way to now.
|
|
680
|
+
let own = particle.rotation as Quaternion;
|
|
681
|
+
const step = particle.angularVelocity;
|
|
682
|
+
if (stepFraction !== 0 && step instanceof Quaternion) {
|
|
683
|
+
own = this.quaternion4_
|
|
684
|
+
.copy(own)
|
|
685
|
+
.multiply(SpriteBatch.partialTurn(step, stepFraction, this.quaternion5_));
|
|
686
|
+
}
|
|
687
|
+
let q: Quaternion;
|
|
688
|
+
if (systemWorldSpace) {
|
|
689
|
+
q = own;
|
|
545
690
|
} else {
|
|
546
|
-
parentQ
|
|
691
|
+
let parentQ: Quaternion;
|
|
692
|
+
if (particle.parentMatrix) {
|
|
693
|
+
parentQ = this.quaternion3_.setFromRotationMatrix(particle.parentMatrix);
|
|
694
|
+
} else {
|
|
695
|
+
parentQ = rotation;
|
|
696
|
+
}
|
|
697
|
+
q = this.quaternion_;
|
|
698
|
+
q.copy(parentQ).multiply(own);
|
|
547
699
|
}
|
|
548
|
-
|
|
549
|
-
q.
|
|
700
|
+
const ri = index * 4;
|
|
701
|
+
this.rotationBuffer[ri] = q.x;
|
|
702
|
+
this.rotationBuffer[ri + 1] = q.y;
|
|
703
|
+
this.rotationBuffer[ri + 2] = q.z;
|
|
704
|
+
this.rotationBuffer[ri + 3] = q.w;
|
|
705
|
+
} else {
|
|
706
|
+
const spin = particle.angularVelocity;
|
|
707
|
+
this.rotationBuffer[index] =
|
|
708
|
+
(particle.rotation as number) + (typeof spin === 'number' ? spin * residual : 0);
|
|
550
709
|
}
|
|
551
|
-
const ri = index * 4;
|
|
552
|
-
this.rotationBuffer[ri] = q.x;
|
|
553
|
-
this.rotationBuffer[ri + 1] = q.y;
|
|
554
|
-
this.rotationBuffer[ri + 2] = q.z;
|
|
555
|
-
this.rotationBuffer[ri + 3] = q.w;
|
|
556
|
-
} else {
|
|
557
|
-
const spin = particle.angularVelocity;
|
|
558
|
-
this.rotationBuffer[index] =
|
|
559
|
-
(particle.rotation as number) + (typeof spin === 'number' ? spin * residual : 0);
|
|
560
|
-
}
|
|
561
710
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
711
|
+
if (!copiedPositionAndSize) {
|
|
712
|
+
const position = particle.position;
|
|
713
|
+
const previous = particle.previousPosition;
|
|
714
|
+
let px = position.x + (position.x - previous.x) * stepFraction;
|
|
715
|
+
let py = position.y + (position.y - previous.y) * stepFraction;
|
|
716
|
+
let pz = position.z + (position.z - previous.z) * stepFraction;
|
|
717
|
+
if (!systemWorldSpace) {
|
|
718
|
+
// Inlined point transform: this is the default (local space)
|
|
719
|
+
// path and runs for every particle every frame.
|
|
720
|
+
const me = (particle.parentMatrix ?? emitterMatrix).elements;
|
|
721
|
+
const w = 1 / (me[3] * px + me[7] * py + me[11] * pz + me[15]);
|
|
722
|
+
const tx = (me[0] * px + me[4] * py + me[8] * pz + me[12]) * w;
|
|
723
|
+
const ty = (me[1] * px + me[5] * py + me[9] * pz + me[13]) * w;
|
|
724
|
+
pz = (me[2] * px + me[6] * py + me[10] * pz + me[14]) * w;
|
|
725
|
+
px = tx;
|
|
726
|
+
py = ty;
|
|
727
|
+
}
|
|
579
728
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
729
|
+
const oi = index * 3;
|
|
730
|
+
this.offsetBuffer[oi] = px;
|
|
731
|
+
this.offsetBuffer[oi + 1] = py;
|
|
732
|
+
this.offsetBuffer[oi + 2] = pz;
|
|
733
|
+
|
|
734
|
+
const size = particle.size;
|
|
735
|
+
const si = index * 3;
|
|
736
|
+
// Particle size is already in world units when the system is in world
|
|
737
|
+
// space or the particle carries its own parent transform.
|
|
738
|
+
if (systemWorldSpace || particle.parentMatrix) {
|
|
739
|
+
this.sizeBuffer[si] = size.x;
|
|
740
|
+
this.sizeBuffer[si + 1] = size.y;
|
|
741
|
+
this.sizeBuffer[si + 2] = size.z;
|
|
742
|
+
} else {
|
|
743
|
+
this.sizeBuffer[si] = size.x * absScaleX;
|
|
744
|
+
this.sizeBuffer[si + 1] = size.y * absScaleY;
|
|
745
|
+
this.sizeBuffer[si + 2] = size.z * absScaleZ;
|
|
746
|
+
}
|
|
597
747
|
}
|
|
598
|
-
}
|
|
599
748
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
749
|
+
if (!copiedColor) {
|
|
750
|
+
const color = particle.color;
|
|
751
|
+
const ci = index * 4;
|
|
752
|
+
this.colorBuffer[ci] = color.x;
|
|
753
|
+
this.colorBuffer[ci + 1] = color.y;
|
|
754
|
+
this.colorBuffer[ci + 2] = color.z;
|
|
755
|
+
this.colorBuffer[ci + 3] = color.w;
|
|
756
|
+
}
|
|
608
757
|
|
|
609
|
-
|
|
758
|
+
this.uvTileBuffer[index] = particle.uvTile;
|
|
610
759
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
760
|
+
if (isStretchedRender && this.velocityBuffer) {
|
|
761
|
+
// The streak points along the velocity and is as long as it,
|
|
762
|
+
// so a velocity frozen between steps makes the sprite's
|
|
763
|
+
// outline pop at the step rate while its position glides.
|
|
764
|
+
const velocity = particle.velocity;
|
|
765
|
+
const previousVelocity = particle.previousVelocity;
|
|
766
|
+
const vx = velocity.x + (velocity.x - previousVelocity.x) * stepFraction;
|
|
767
|
+
const vy = velocity.y + (velocity.y - previousVelocity.y) * stepFraction;
|
|
768
|
+
const vz = velocity.z + (velocity.z - previousVelocity.z) * stepFraction;
|
|
769
|
+
let vel: Vector3 = this.vector_;
|
|
770
|
+
vel.set(vx, vy, vz);
|
|
771
|
+
if (!systemWorldSpace) {
|
|
772
|
+
if (particle.parentMatrix) {
|
|
773
|
+
this.rotationMat2_.setFromMatrix4(particle.parentMatrix);
|
|
774
|
+
vel.applyMatrix3(this.rotationMat2_);
|
|
775
|
+
} else {
|
|
776
|
+
vel.applyMatrix3(this.rotationMat_);
|
|
777
|
+
}
|
|
628
778
|
}
|
|
779
|
+
const vi = index * 4;
|
|
780
|
+
this.velocityBuffer[vi] = vel.x * speedFactor;
|
|
781
|
+
this.velocityBuffer[vi + 1] = vel.y * speedFactor;
|
|
782
|
+
this.velocityBuffer[vi + 2] = vel.z * speedFactor;
|
|
783
|
+
this.velocityBuffer[vi + 3] = lengthFactor;
|
|
629
784
|
}
|
|
630
|
-
const vi = index * 4;
|
|
631
|
-
this.velocityBuffer[vi] = vel.x * speedFactor;
|
|
632
|
-
this.velocityBuffer[vi + 1] = vel.y * speedFactor;
|
|
633
|
-
this.velocityBuffer[vi + 2] = vel.z * speedFactor;
|
|
634
|
-
this.velocityBuffer[vi + 3] = lengthFactor;
|
|
635
785
|
}
|
|
636
786
|
}
|
|
637
787
|
|
|
638
788
|
if (flipsSprites && system.flip !== undefined) {
|
|
639
|
-
|
|
789
|
+
if (store !== undefined) {
|
|
790
|
+
SpriteBatch.applyFlip(this.sizeBuffer, firstIndex * 3, store.flip, particleNum);
|
|
791
|
+
} else {
|
|
792
|
+
SpriteBatch.applyFlipFromParticles(this.sizeBuffer, firstIndex * 3, particles, particleNum);
|
|
793
|
+
}
|
|
640
794
|
}
|
|
641
795
|
}
|
|
642
796
|
|
|
@@ -709,7 +863,23 @@ export class SpriteBatch extends VFXBatch {
|
|
|
709
863
|
* quad being centred — the way Unity's renderer Flip mirrors U and V. The
|
|
710
864
|
* stretched shader reads the sign and mirrors the texture coordinates.
|
|
711
865
|
*/
|
|
712
|
-
private static applyFlip(sizes: Float32Array, base: number,
|
|
866
|
+
private static applyFlip(sizes: Float32Array, base: number, flips: Uint8Array, count: number): void {
|
|
867
|
+
for (let j = 0, o = base; j < count; j++, o += 3) {
|
|
868
|
+
const flip = flips[j];
|
|
869
|
+
if (flip !== 0) {
|
|
870
|
+
if (flip & 1) sizes[o] = -sizes[o];
|
|
871
|
+
if (flip & 2) sizes[o + 1] = -sizes[o + 1];
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/** The same for a system without a store, read off the particles. */
|
|
877
|
+
private static applyFlipFromParticles(
|
|
878
|
+
sizes: Float32Array,
|
|
879
|
+
base: number,
|
|
880
|
+
particles: IParticle[],
|
|
881
|
+
count: number
|
|
882
|
+
): void {
|
|
713
883
|
for (let j = 0, o = base; j < count; j++, o += 3) {
|
|
714
884
|
const flip = (particles[j] as SpriteParticle).flip;
|
|
715
885
|
if (flip !== 0) {
|
|
@@ -719,6 +889,113 @@ export class SpriteBatch extends VFXBatch {
|
|
|
719
889
|
}
|
|
720
890
|
}
|
|
721
891
|
|
|
892
|
+
/**
|
|
893
|
+
* Writes the mesh rotations for rows `[0, count)` of a store: each
|
|
894
|
+
* particle's quaternion, carried on by `fraction` of its last turn (see
|
|
895
|
+
* {@link partialTurn}) and, for a local-space system, turned by the
|
|
896
|
+
* emitter's rotation `parent`.
|
|
897
|
+
*/
|
|
898
|
+
private static writeQuaternions(
|
|
899
|
+
target: Float32Array,
|
|
900
|
+
base: number,
|
|
901
|
+
store: ParticleStore,
|
|
902
|
+
count: number,
|
|
903
|
+
fraction: number,
|
|
904
|
+
parent: Quaternion | null
|
|
905
|
+
): void {
|
|
906
|
+
const rotations = store.rotationQuaternion;
|
|
907
|
+
const turns = store.angularVelocityQuaternion;
|
|
908
|
+
const hasParent = parent !== null;
|
|
909
|
+
const px = hasParent ? parent.x : 0;
|
|
910
|
+
const py = hasParent ? parent.y : 0;
|
|
911
|
+
const pz = hasParent ? parent.z : 0;
|
|
912
|
+
const pw = hasParent ? parent.w : 1;
|
|
913
|
+
for (let i = 0, o = 0, t = base; i < count; i++, o += 4, t += 4) {
|
|
914
|
+
let x = rotations[o];
|
|
915
|
+
let y = rotations[o + 1];
|
|
916
|
+
let z = rotations[o + 2];
|
|
917
|
+
let w = rotations[o + 3];
|
|
918
|
+
if (fraction !== 0) {
|
|
919
|
+
// The fraction of the step's turn, as a normalised lerp from no
|
|
920
|
+
// rotation, then own = own * turn.
|
|
921
|
+
const sx = turns[o] * fraction;
|
|
922
|
+
const sy = turns[o + 1] * fraction;
|
|
923
|
+
const sz = turns[o + 2] * fraction;
|
|
924
|
+
const sw = 1 + (turns[o + 3] - 1) * fraction;
|
|
925
|
+
const inverseLength = 1 / Math.sqrt(sx * sx + sy * sy + sz * sz + sw * sw);
|
|
926
|
+
const tx = sx * inverseLength;
|
|
927
|
+
const ty = sy * inverseLength;
|
|
928
|
+
const tz = sz * inverseLength;
|
|
929
|
+
const tw = sw * inverseLength;
|
|
930
|
+
const nx = x * tw + w * tx + y * tz - z * ty;
|
|
931
|
+
const ny = y * tw + w * ty + z * tx - x * tz;
|
|
932
|
+
const nz = z * tw + w * tz + x * ty - y * tx;
|
|
933
|
+
w = w * tw - x * tx - y * ty - z * tz;
|
|
934
|
+
x = nx;
|
|
935
|
+
y = ny;
|
|
936
|
+
z = nz;
|
|
937
|
+
}
|
|
938
|
+
if (hasParent) {
|
|
939
|
+
// q = parent * own
|
|
940
|
+
const nx = px * w + pw * x + py * z - pz * y;
|
|
941
|
+
const ny = py * w + pw * y + pz * x - px * z;
|
|
942
|
+
const nz = pz * w + pw * z + px * y - py * x;
|
|
943
|
+
w = pw * w - px * x - py * y - pz * z;
|
|
944
|
+
x = nx;
|
|
945
|
+
y = ny;
|
|
946
|
+
z = nz;
|
|
947
|
+
}
|
|
948
|
+
target[t] = x;
|
|
949
|
+
target[t + 1] = y;
|
|
950
|
+
target[t + 2] = z;
|
|
951
|
+
target[t + 3] = w;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/**
|
|
956
|
+
* Writes the stretched billboards' velocity attribute for rows `[0, count)`
|
|
957
|
+
* of a store: the velocity carried on by `fraction` of the last step (see
|
|
958
|
+
* {@link extrapolate}), turned into world space by `rotation` when the
|
|
959
|
+
* system simulates locally, scaled by the speed factor, with the length
|
|
960
|
+
* factor in `w`.
|
|
961
|
+
*/
|
|
962
|
+
private static writeVelocities(
|
|
963
|
+
target: Float32Array,
|
|
964
|
+
base: number,
|
|
965
|
+
store: ParticleStore,
|
|
966
|
+
count: number,
|
|
967
|
+
fraction: number,
|
|
968
|
+
rotation: Matrix3 | null,
|
|
969
|
+
speedFactor: number,
|
|
970
|
+
lengthFactor: number
|
|
971
|
+
): void {
|
|
972
|
+
const velocities = store.velocity;
|
|
973
|
+
// Empty until the first stretched step has run; the velocity then has no history yet.
|
|
974
|
+
const previous = store.previousVelocity.length >= count * 3 ? store.previousVelocity : velocities;
|
|
975
|
+
const e = rotation !== null ? rotation.elements : null;
|
|
976
|
+
for (let i = 0, o = 0, t = base; i < count; i++, o += 3, t += 4) {
|
|
977
|
+
let vx = velocities[o];
|
|
978
|
+
let vy = velocities[o + 1];
|
|
979
|
+
let vz = velocities[o + 2];
|
|
980
|
+
if (fraction !== 0) {
|
|
981
|
+
vx += (vx - previous[o]) * fraction;
|
|
982
|
+
vy += (vy - previous[o + 1]) * fraction;
|
|
983
|
+
vz += (vz - previous[o + 2]) * fraction;
|
|
984
|
+
}
|
|
985
|
+
if (e !== null) {
|
|
986
|
+
const x = e[0] * vx + e[3] * vy + e[6] * vz;
|
|
987
|
+
const y = e[1] * vx + e[4] * vy + e[7] * vz;
|
|
988
|
+
vz = e[2] * vx + e[5] * vy + e[8] * vz;
|
|
989
|
+
vx = x;
|
|
990
|
+
vy = y;
|
|
991
|
+
}
|
|
992
|
+
target[t] = vx * speedFactor;
|
|
993
|
+
target[t + 1] = vy * speedFactor;
|
|
994
|
+
target[t + 2] = vz * speedFactor;
|
|
995
|
+
target[t + 3] = lengthFactor;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
722
999
|
/**
|
|
723
1000
|
* The fraction `f` of a turn, as a normalised lerp from no rotation toward
|
|
724
1001
|
* `step`, written into `out`.
|