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.
- package/CHANGELOG.md +85 -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 +279 -53
- package/package.json +1 -1
- package/src/BatchedRenderer.ts +23 -17
- package/src/ParticleSystem.ts +240 -38
- package/src/QuarksLoader.ts +42 -8
- package/src/SpriteBatch.ts +52 -1
- package/src/TrailBatch.ts +129 -20
- package/src/VFXBatch.ts +325 -61
- package/src/index.ts +19 -2
- package/src/shaders/chunks/surface.ts +122 -42
- package/src/shaders/local_particle_physics_vert.glsl.ts +15 -0
- package/src/shaders/local_particle_physics_vert.wgsl.ts +15 -0
- package/src/shaders/particle_frag.glsl.ts +3 -0
- package/src/shaders/particle_frag.wgsl.ts +3 -0
- package/src/shaders/particle_physics_frag.glsl.ts +10 -4
- package/src/shaders/particle_physics_frag.wgsl.ts +10 -4
- package/src/shaders/particle_vert.glsl.ts +7 -0
- package/src/shaders/particle_vert.wgsl.ts +7 -0
- package/src/shaders/stretched_bb_particle_vert.glsl.ts +17 -9
- package/src/shaders/stretched_bb_particle_vert.wgsl.ts +17 -9
- package/src/shaders/trail_frag.glsl.ts +3 -0
- package/src/shaders/trail_frag.wgsl.ts +3 -0
- package/src/shaders/trail_vert.glsl.ts +8 -0
- package/src/shaders/trail_vert.wgsl.ts +8 -0
package/src/ParticleSystem.ts
CHANGED
|
@@ -57,11 +57,20 @@ import {
|
|
|
57
57
|
MeshLighting,
|
|
58
58
|
MeshRim,
|
|
59
59
|
normalizeErosion,
|
|
60
|
+
normalizeGradientMap,
|
|
61
|
+
normalizeMapUV,
|
|
60
62
|
normalizeMeshRim,
|
|
61
|
-
|
|
63
|
+
normalizeUVDistortion,
|
|
62
64
|
ParticleErosion,
|
|
65
|
+
ParticleErosionOptions,
|
|
66
|
+
ParticleGradientMap,
|
|
67
|
+
ParticleUVDistortion,
|
|
68
|
+
ParticleUVDistortionOptions,
|
|
63
69
|
RenderMode,
|
|
64
70
|
SortMode,
|
|
71
|
+
TextureUV,
|
|
72
|
+
textureUVToJSON,
|
|
73
|
+
UV_DISTORTION_DEFAULT_UV,
|
|
65
74
|
} from './VFXBatch';
|
|
66
75
|
|
|
67
76
|
export interface BurstParameters {
|
|
@@ -162,8 +171,19 @@ const DEFAULT_POSITIONS = new Float32Array([-0.5, -0.5, 0, 0.5, -0.5, 0, 0.5, 0.
|
|
|
162
171
|
const DEFAULT_UVS = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
|
|
163
172
|
|
|
164
173
|
/** Zero UVs sized to a position buffer — missing/mismatched uv attrs → GL_INVALID_OPERATION on iOS. */
|
|
174
|
+
const fallbackUVs = new WeakMap<Float32Array, Float32Array>();
|
|
175
|
+
const fallbackNormals = new WeakMap<Float32Array, Float32Array>();
|
|
176
|
+
/**
|
|
177
|
+
* Zero UVs for a geometry given without any, one array per geometry: batches
|
|
178
|
+
* are keyed by the UV array too, so systems sharing a geometry share it.
|
|
179
|
+
*/
|
|
165
180
|
function fallbackUVsForPositions(positions: Float32Array): Float32Array {
|
|
166
|
-
|
|
181
|
+
let uvs = fallbackUVs.get(positions);
|
|
182
|
+
if (uvs === undefined || uvs.length !== (positions.length / 3) * 2) {
|
|
183
|
+
uvs = new Float32Array((positions.length / 3) * 2);
|
|
184
|
+
fallbackUVs.set(positions, uvs);
|
|
185
|
+
}
|
|
186
|
+
return uvs;
|
|
167
187
|
}
|
|
168
188
|
const DEFAULT_INDICES = new Uint32Array([0, 1, 2, 0, 2, 3]);
|
|
169
189
|
|
|
@@ -177,6 +197,24 @@ function trailFlags(settings: TrailSettings | undefined): Partial<TrailSettings>
|
|
|
177
197
|
return flags;
|
|
178
198
|
}
|
|
179
199
|
|
|
200
|
+
/** A trail's side axis as JSON `[x, y, z]`, or nothing when unset. */
|
|
201
|
+
function trailSideAxisJSON(settings: TrailSettings | undefined): {sideAxis?: number[]} {
|
|
202
|
+
const axis = settings?.sideAxis;
|
|
203
|
+
return axis ? {sideAxis: [axis.x, axis.y, axis.z]} : {};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** A trail's side axis from JSON `[x, y, z]` (or an object with x, y, z), or nothing. */
|
|
207
|
+
function trailSideAxisFromJSON(value: unknown): {sideAxis?: Vector3} {
|
|
208
|
+
if (Array.isArray(value)) {
|
|
209
|
+
return {sideAxis: new Vector3(Number(value[0]) || 0, Number(value[1]) || 0, Number(value[2]) || 0)};
|
|
210
|
+
}
|
|
211
|
+
if (value && typeof value === 'object') {
|
|
212
|
+
const v = value as {x?: number; y?: number; z?: number};
|
|
213
|
+
return {sideAxis: new Vector3(v.x ?? 0, v.y ?? 0, v.z ?? 0)};
|
|
214
|
+
}
|
|
215
|
+
return {};
|
|
216
|
+
}
|
|
217
|
+
|
|
180
218
|
export interface ParticleSystemParameters {
|
|
181
219
|
autoDestroy?: boolean;
|
|
182
220
|
looping?: boolean;
|
|
@@ -200,6 +238,12 @@ export interface ParticleSystemParameters {
|
|
|
200
238
|
instancingGeometry?: Float32Array;
|
|
201
239
|
instancingIndices?: Uint32Array | Uint16Array;
|
|
202
240
|
instancingUVs?: Float32Array;
|
|
241
|
+
/**
|
|
242
|
+
* A second UV set for the mesh (glTF `TEXCOORD_1`, Unity's `uv2`), what
|
|
243
|
+
* textures on UV channel 1 read on a mesh particle; see `UVChannel`.
|
|
244
|
+
* Default none: channel 1 reads the first set.
|
|
245
|
+
*/
|
|
246
|
+
instancingUVs2?: Float32Array;
|
|
203
247
|
instancingNormals?: Float32Array;
|
|
204
248
|
renderMode?: RenderMode;
|
|
205
249
|
rendererEmitterSettings?: RendererEmitterSettings;
|
|
@@ -229,12 +273,14 @@ export interface ParticleSystemParameters {
|
|
|
229
273
|
lighting?: MeshLighting;
|
|
230
274
|
/** Rim (Fresnel) term on mesh particles, see {@link ParticleSystem.rim}. Default none. */
|
|
231
275
|
rim?: Partial<MeshRim>;
|
|
232
|
-
/**
|
|
233
|
-
|
|
276
|
+
/** The main map's layout, see {@link ParticleSystem.mapUV}. Default as drawn. */
|
|
277
|
+
mapUV?: Partial<TextureUV>;
|
|
234
278
|
/** Colour ramp for the map, see {@link ParticleSystem.gradientMap}. Default none. */
|
|
235
|
-
gradientMap?:
|
|
279
|
+
gradientMap?: Partial<ParticleGradientMap>;
|
|
236
280
|
/** Alpha erosion, see {@link ParticleSystem.erosion}. Default none. */
|
|
237
|
-
erosion?:
|
|
281
|
+
erosion?: ParticleErosionOptions;
|
|
282
|
+
/** Map UV distortion, see {@link ParticleSystem.uvDistortion}. Default none. */
|
|
283
|
+
uvDistortion?: ParticleUVDistortionOptions;
|
|
238
284
|
/** Multiplier on the time the system is advanced by, see {@link ParticleSystem.playbackSpeed}. Default 1. */
|
|
239
285
|
playbackSpeed?: number;
|
|
240
286
|
/** What the system does while off screen, see {@link CullingMode}. Default `'always'`. */
|
|
@@ -628,33 +674,39 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
628
674
|
}
|
|
629
675
|
|
|
630
676
|
/**
|
|
631
|
-
*
|
|
632
|
-
*
|
|
633
|
-
*
|
|
634
|
-
*
|
|
677
|
+
* How the main map is laid over the particle, see {@link TextureUV}: its
|
|
678
|
+
* UV channel, tiling and offset (Unity's Tiling / Offset) and a scroll on
|
|
679
|
+
* the renderer's clock (Unreal's Panner), for flowing energy, lava or a
|
|
680
|
+
* smear running along a mesh. A scrolled or tiled map should repeat. On a
|
|
681
|
+
* sprite sheet (UV channel 0) the layout applies to the sheet's UV, so a
|
|
682
|
+
* scroll or tiling there runs into the neighbouring cells. Undefined (the
|
|
683
|
+
* default), or the identity: the map as drawn. Fields left out take their
|
|
684
|
+
* defaults; assign a new object to change it. Saved with the material in
|
|
685
|
+
* the effect JSON.
|
|
635
686
|
*/
|
|
636
|
-
get
|
|
637
|
-
return this.rendererSettings.
|
|
687
|
+
get mapUV(): TextureUV | undefined {
|
|
688
|
+
return this.rendererSettings.mapUV;
|
|
638
689
|
}
|
|
639
690
|
|
|
640
|
-
set
|
|
641
|
-
this.rendererSettings.
|
|
691
|
+
set mapUV(uv: Partial<TextureUV> | undefined) {
|
|
692
|
+
this.rendererSettings.mapUV = normalizeMapUV(uv);
|
|
642
693
|
this.neededToUpdateRender = true;
|
|
643
694
|
}
|
|
644
695
|
|
|
645
696
|
/**
|
|
646
|
-
* Colour ramp the map is recoloured through (a gradient map)
|
|
647
|
-
*
|
|
648
|
-
*
|
|
649
|
-
* greyscale texture then serves every
|
|
650
|
-
*
|
|
697
|
+
* Colour ramp the map is recoloured through (a gradient map), see
|
|
698
|
+
* {@link ParticleGradientMap}: a channel of the map, or its luminance,
|
|
699
|
+
* picks a colour along U of the ramp; the particle colour still tints the
|
|
700
|
+
* result. One greyscale or channel-packed texture then serves every
|
|
701
|
+
* palette. Undefined (the default), or without a ramp: none. Assign a new
|
|
702
|
+
* object to change it. Saved with the material in the effect JSON.
|
|
651
703
|
*/
|
|
652
|
-
get gradientMap():
|
|
653
|
-
return this.rendererSettings.gradientMap
|
|
704
|
+
get gradientMap(): ParticleGradientMap | undefined {
|
|
705
|
+
return this.rendererSettings.gradientMap;
|
|
654
706
|
}
|
|
655
707
|
|
|
656
|
-
set gradientMap(
|
|
657
|
-
this.rendererSettings.gradientMap =
|
|
708
|
+
set gradientMap(gradientMap: Partial<ParticleGradientMap> | undefined) {
|
|
709
|
+
this.rendererSettings.gradientMap = normalizeGradientMap(gradientMap);
|
|
658
710
|
this.neededToUpdateRender = true;
|
|
659
711
|
}
|
|
660
712
|
|
|
@@ -671,11 +723,27 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
671
723
|
return this.rendererSettings.erosion;
|
|
672
724
|
}
|
|
673
725
|
|
|
674
|
-
set erosion(erosion:
|
|
726
|
+
set erosion(erosion: ParticleErosionOptions | undefined) {
|
|
675
727
|
this.rendererSettings.erosion = normalizeErosion(erosion);
|
|
676
728
|
this.neededToUpdateRender = true;
|
|
677
729
|
}
|
|
678
730
|
|
|
731
|
+
/**
|
|
732
|
+
* Map UV distortion, see {@link ParticleUVDistortion}: a scrolling noise
|
|
733
|
+
* texture pushes the map's UV about, for heat shimmer and licking,
|
|
734
|
+
* breathing shapes. Undefined (the default), or without a map: none.
|
|
735
|
+
* Fields left out take their defaults; assign a new object to change it.
|
|
736
|
+
* Saved with the material in the effect JSON.
|
|
737
|
+
*/
|
|
738
|
+
get uvDistortion(): ParticleUVDistortion | undefined {
|
|
739
|
+
return this.rendererSettings.uvDistortion;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
set uvDistortion(distortion: ParticleUVDistortionOptions | undefined) {
|
|
743
|
+
this.rendererSettings.uvDistortion = normalizeUVDistortion(distortion);
|
|
744
|
+
this.neededToUpdateRender = true;
|
|
745
|
+
}
|
|
746
|
+
|
|
679
747
|
/**
|
|
680
748
|
* The emitter's world scale per axis, measured with {@link motionScale}
|
|
681
749
|
* before every step; what `'hierarchy'` multiplies start speeds by. Taken
|
|
@@ -741,10 +809,53 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
741
809
|
return this.rendererSettings.instancingGeometry;
|
|
742
810
|
}
|
|
743
811
|
|
|
812
|
+
/**
|
|
813
|
+
* The mesh particles' positions. A new geometry with a different vertex
|
|
814
|
+
* count drops UV sets and normals that no longer fit it: the first UV set
|
|
815
|
+
* falls back to zeros, the second to none, the normals to +Z. Assign {@link instancingUVs} and
|
|
816
|
+
* {@link instancingUVs2} after it.
|
|
817
|
+
*/
|
|
744
818
|
set instancingGeometry(geometry: Float32Array) {
|
|
745
819
|
this.restart();
|
|
746
820
|
this.particles.length = 0;
|
|
747
|
-
this.rendererSettings
|
|
821
|
+
const settings = this.rendererSettings;
|
|
822
|
+
const uvLength = (geometry.length / 3) * 2;
|
|
823
|
+
const previous = settings.instancingGeometry;
|
|
824
|
+
settings.instancingGeometry = geometry;
|
|
825
|
+
// Sets that no longer fit, or that were the old geometry's fallbacks, move to the new one's.
|
|
826
|
+
if (settings.instancingUVs?.length !== uvLength || settings.instancingUVs === fallbackUVs.get(previous)) {
|
|
827
|
+
settings.instancingUVs = fallbackUVsForPositions(geometry);
|
|
828
|
+
}
|
|
829
|
+
if (
|
|
830
|
+
settings.instancingNormals &&
|
|
831
|
+
(settings.instancingNormals.length !== geometry.length ||
|
|
832
|
+
settings.instancingNormals === fallbackNormals.get(previous))
|
|
833
|
+
) {
|
|
834
|
+
settings.instancingNormals = ParticleSystem.createFallbackNormals(geometry);
|
|
835
|
+
}
|
|
836
|
+
if (settings.instancingUVs2 && settings.instancingUVs2.length !== uvLength) {
|
|
837
|
+
settings.instancingUVs2 = undefined;
|
|
838
|
+
}
|
|
839
|
+
this.neededToUpdateRender = true;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/** The mesh particles' first UV set, two values per vertex. */
|
|
843
|
+
get instancingUVs(): Float32Array {
|
|
844
|
+
return this.rendererSettings.instancingUVs ?? fallbackUVsForPositions(this.rendererSettings.instancingGeometry);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
set instancingUVs(uvs: Float32Array) {
|
|
848
|
+
this.rendererSettings.instancingUVs = uvs;
|
|
849
|
+
this.neededToUpdateRender = true;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/** The mesh particles' second UV set, read by textures on UV channel 1; see `UVChannel`. */
|
|
853
|
+
get instancingUVs2(): Float32Array | undefined {
|
|
854
|
+
return this.rendererSettings.instancingUVs2;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
set instancingUVs2(uvs: Float32Array | undefined) {
|
|
858
|
+
this.rendererSettings.instancingUVs2 = uvs;
|
|
748
859
|
this.neededToUpdateRender = true;
|
|
749
860
|
}
|
|
750
861
|
|
|
@@ -983,6 +1094,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
983
1094
|
instancingUVs:
|
|
984
1095
|
parameters.instancingUVs ??
|
|
985
1096
|
(parameters.instancingGeometry ? fallbackUVsForPositions(parameters.instancingGeometry) : DEFAULT_UVS),
|
|
1097
|
+
instancingUVs2: parameters.instancingUVs2,
|
|
986
1098
|
instancingNormals: parameters.instancingNormals,
|
|
987
1099
|
renderMode: parameters.renderMode ?? RenderMode.BillBoard,
|
|
988
1100
|
renderOrder: parameters.renderOrder ?? 0,
|
|
@@ -1011,9 +1123,10 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
1011
1123
|
applyFog: parameters.applyFog ?? true,
|
|
1012
1124
|
lighting: ParticleSystem.validLighting(parameters.lighting),
|
|
1013
1125
|
rim: normalizeMeshRim(parameters.rim),
|
|
1014
|
-
|
|
1015
|
-
gradientMap: parameters.gradientMap
|
|
1126
|
+
mapUV: normalizeMapUV(parameters.mapUV),
|
|
1127
|
+
gradientMap: normalizeGradientMap(parameters.gradientMap),
|
|
1016
1128
|
erosion: normalizeErosion(parameters.erosion),
|
|
1129
|
+
uvDistortion: normalizeUVDistortion(parameters.uvDistortion),
|
|
1017
1130
|
};
|
|
1018
1131
|
this.flip = ParticleSystem.nonZeroFlip(parameters.flip);
|
|
1019
1132
|
this.playbackSpeed = parameters.playbackSpeed ?? 1;
|
|
@@ -1684,6 +1797,19 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
1684
1797
|
*/
|
|
1685
1798
|
beamEnd?: BeamEnd;
|
|
1686
1799
|
|
|
1800
|
+
/**
|
|
1801
|
+
* Multiplies every particle's colour, alpha included, as it is drawn:
|
|
1802
|
+
* a colour or a fade driven from code for this instance (Unity's custom
|
|
1803
|
+
* per-instance data, Unreal's user parameters). With {@link erosion},
|
|
1804
|
+
* alpha is the erosion level, so this wipes the shape in and out. The
|
|
1805
|
+
* particles' own colours are untouched, so clearing it restores them, and
|
|
1806
|
+
* systems with different tints still batch together. Under additive
|
|
1807
|
+
* (`ALPHA_ONEONE`) or premultiplied blending, which ignore alpha, fade
|
|
1808
|
+
* with the colour instead, or with erosion on. Unset (the default): none.
|
|
1809
|
+
* Not saved in the effect JSON; copied by {@link clone}.
|
|
1810
|
+
*/
|
|
1811
|
+
tint?: Vector4;
|
|
1812
|
+
|
|
1687
1813
|
private readonly beamEndScratch = new Vector3();
|
|
1688
1814
|
|
|
1689
1815
|
/** The world position of {@link beamEnd} into `out`; false when it is unset. */
|
|
@@ -2140,6 +2266,10 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2140
2266
|
// A world-space system records world positions already.
|
|
2141
2267
|
const recordWorldSpace = trailSettings.recordWorldSpace && !this.worldSpace;
|
|
2142
2268
|
const emitterMatrix = this.emitter.matrixWorld;
|
|
2269
|
+
// A swept trail records its side as the emitter holds it, in the
|
|
2270
|
+
// space its positions are recorded in.
|
|
2271
|
+
const sideAxis = trailSettings.sideAxis;
|
|
2272
|
+
const sidesInWorld = this.worldSpace || Boolean(trailSettings.recordWorldSpace);
|
|
2143
2273
|
for (let i = 0; i < particleCount; i++) {
|
|
2144
2274
|
const trail = particles[i] as TrailParticle;
|
|
2145
2275
|
// Emptying the history lets the removal pass below take the
|
|
@@ -2159,6 +2289,25 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2159
2289
|
positions[offset + 1] = tempV.y;
|
|
2160
2290
|
positions[offset + 2] = tempV.z;
|
|
2161
2291
|
}
|
|
2292
|
+
if (sideAxis && trail.age <= trail.life) {
|
|
2293
|
+
trail.ensureSideHistory();
|
|
2294
|
+
const offset = ((trail.historyHead + trail.historyCapacity - 1) % trail.historyCapacity) * 3;
|
|
2295
|
+
const sides = trail.historySides;
|
|
2296
|
+
let x = sideAxis.x;
|
|
2297
|
+
let y = sideAxis.y;
|
|
2298
|
+
let z = sideAxis.z;
|
|
2299
|
+
if (sidesInWorld) {
|
|
2300
|
+
const m = (trail.parentMatrix ?? emitterMatrix).elements;
|
|
2301
|
+
const tx = m[0] * x + m[4] * y + m[8] * z;
|
|
2302
|
+
const ty = m[1] * x + m[5] * y + m[9] * z;
|
|
2303
|
+
z = m[2] * x + m[6] * y + m[10] * z;
|
|
2304
|
+
x = tx;
|
|
2305
|
+
y = ty;
|
|
2306
|
+
}
|
|
2307
|
+
sides[offset] = x;
|
|
2308
|
+
sides[offset + 1] = y;
|
|
2309
|
+
sides[offset + 2] = z;
|
|
2310
|
+
}
|
|
2162
2311
|
}
|
|
2163
2312
|
}
|
|
2164
2313
|
|
|
@@ -2521,6 +2670,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2521
2670
|
: new ConstantValue(30),
|
|
2522
2671
|
followLocalOrigin: trailSettings?.followLocalOrigin ?? false,
|
|
2523
2672
|
...trailFlags(trailSettings),
|
|
2673
|
+
...trailSideAxisFromJSON((trailSettings as {sideAxis?: unknown} | undefined)?.sideAxis),
|
|
2524
2674
|
};
|
|
2525
2675
|
} else if (json.renderMode === RenderMode.StretchedBillBoard) {
|
|
2526
2676
|
rendererEmitterSettings = {...(json.rendererEmitterSettings ?? {})};
|
|
@@ -2541,6 +2691,8 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2541
2691
|
const resolveTexture = (ref: unknown): BaseTexture | null =>
|
|
2542
2692
|
typeof ref === 'string' ? (meta.textures?.[ref] ?? null) : ((ref as BaseTexture | null) ?? null);
|
|
2543
2693
|
const erosionMeta = materialMeta?.erosion;
|
|
2694
|
+
const gradientMeta = materialMeta?.gradientMap;
|
|
2695
|
+
const distortionMeta = materialMeta?.uvDistortion;
|
|
2544
2696
|
const resolvedGeometryEntry =
|
|
2545
2697
|
typeof json.instancingGeometry === 'string'
|
|
2546
2698
|
? meta.geometries?.[json.instancingGeometry]
|
|
@@ -2580,6 +2732,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2580
2732
|
instancingGeometry: resolvedGeometry.positions,
|
|
2581
2733
|
instancingIndices: resolvedGeometry.indices,
|
|
2582
2734
|
instancingUVs: resolvedGeometry.uvs,
|
|
2735
|
+
instancingUVs2: resolvedGeometry.uvs2,
|
|
2583
2736
|
instancingNormals: resolvedGeometry.normals,
|
|
2584
2737
|
renderMode: json.renderMode,
|
|
2585
2738
|
rendererEmitterSettings,
|
|
@@ -2618,12 +2771,19 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2618
2771
|
flip: json.flip ? new Vector2(json.flip[0] ?? 0, json.flip[1] ?? 0) : undefined,
|
|
2619
2772
|
lighting: materialMeta?.lighting,
|
|
2620
2773
|
rim: materialMeta?.rim,
|
|
2621
|
-
|
|
2622
|
-
gradientMap:
|
|
2774
|
+
mapUV: materialMeta?.mapUV,
|
|
2775
|
+
gradientMap:
|
|
2776
|
+
gradientMeta && typeof gradientMeta === 'object'
|
|
2777
|
+
? {...gradientMeta, ramp: resolveTexture(gradientMeta.ramp)}
|
|
2778
|
+
: undefined,
|
|
2623
2779
|
erosion:
|
|
2624
2780
|
erosionMeta && typeof erosionMeta === 'object'
|
|
2625
2781
|
? {...erosionMeta, mask: resolveTexture(erosionMeta.mask)}
|
|
2626
2782
|
: undefined,
|
|
2783
|
+
uvDistortion:
|
|
2784
|
+
distortionMeta && typeof distortionMeta === 'object'
|
|
2785
|
+
? {...distortionMeta, map: resolveTexture(distortionMeta.map)}
|
|
2786
|
+
: undefined,
|
|
2627
2787
|
});
|
|
2628
2788
|
ps.behaviors = (json.behaviors ?? [])
|
|
2629
2789
|
.map((behaviorJson) => {
|
|
@@ -2642,12 +2802,15 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2642
2802
|
return `${prefix}_${ParticleSystem.serializationCounter}`;
|
|
2643
2803
|
}
|
|
2644
2804
|
|
|
2805
|
+
/** Normals facing +Z, one array per geometry so systems sharing it still batch together. */
|
|
2645
2806
|
private static createFallbackNormals(positions: Float32Array): Float32Array {
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
normals
|
|
2649
|
-
|
|
2650
|
-
|
|
2807
|
+
let normals = fallbackNormals.get(positions);
|
|
2808
|
+
if (!normals) {
|
|
2809
|
+
normals = new Float32Array(positions.length);
|
|
2810
|
+
for (let i = 2; i < normals.length; i += 3) {
|
|
2811
|
+
normals[i] = 1;
|
|
2812
|
+
}
|
|
2813
|
+
fallbackNormals.set(positions, normals);
|
|
2651
2814
|
}
|
|
2652
2815
|
return normals;
|
|
2653
2816
|
}
|
|
@@ -2658,6 +2821,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2658
2821
|
startLength: (this.rendererEmitterSettings as TrailSettings).startLength.toJSON(),
|
|
2659
2822
|
followLocalOrigin: (this.rendererEmitterSettings as TrailSettings).followLocalOrigin,
|
|
2660
2823
|
...trailFlags(this.rendererEmitterSettings as TrailSettings),
|
|
2824
|
+
...(trailSideAxisJSON(this.rendererEmitterSettings as TrailSettings) as {sideAxis?: any}),
|
|
2661
2825
|
};
|
|
2662
2826
|
}
|
|
2663
2827
|
if (this.renderMode === RenderMode.StretchedBillBoard) {
|
|
@@ -2677,6 +2841,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2677
2841
|
positions: Array.from(this.rendererSettings.instancingGeometry),
|
|
2678
2842
|
indices: Array.from(this.rendererSettings.instancingIndices),
|
|
2679
2843
|
uvs: this.rendererSettings.instancingUVs ? Array.from(this.rendererSettings.instancingUVs) : undefined,
|
|
2844
|
+
...(this.rendererSettings.instancingUVs2 ? {uvs2: Array.from(this.rendererSettings.instancingUVs2)} : {}),
|
|
2680
2845
|
normals: this.rendererSettings.instancingNormals
|
|
2681
2846
|
? Array.from(this.rendererSettings.instancingNormals)
|
|
2682
2847
|
: undefined,
|
|
@@ -2708,6 +2873,9 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2708
2873
|
return uuid;
|
|
2709
2874
|
};
|
|
2710
2875
|
const erosion = this.erosion;
|
|
2876
|
+
const distortion = this.uvDistortion;
|
|
2877
|
+
const gradientMap = this.gradientMap;
|
|
2878
|
+
const optional = <T>(key: string, value: T | undefined) => (value !== undefined ? {[key]: value} : {});
|
|
2711
2879
|
|
|
2712
2880
|
const materialUUID = ParticleSystem.nextSerializationId('quarks_material');
|
|
2713
2881
|
meta.materials[materialUUID] = {
|
|
@@ -2724,16 +2892,32 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2724
2892
|
// Written only when not the default, so existing effects serialise as before.
|
|
2725
2893
|
...(this.lighting !== 'fixed' ? {lighting: this.lighting} : {}),
|
|
2726
2894
|
...(this.rim ? {rim: {...this.rim, color: [...this.rim.color]}} : {}),
|
|
2727
|
-
...(this.
|
|
2728
|
-
...(
|
|
2895
|
+
...optional('mapUV', this.mapUV ? textureUVToJSON(this.mapUV) : undefined),
|
|
2896
|
+
...(gradientMap
|
|
2897
|
+
? {
|
|
2898
|
+
gradientMap: {
|
|
2899
|
+
ramp: textureRef(gradientMap.ramp),
|
|
2900
|
+
...(gradientMap.channel !== 'r' ? {channel: gradientMap.channel} : {}),
|
|
2901
|
+
},
|
|
2902
|
+
}
|
|
2903
|
+
: {}),
|
|
2729
2904
|
...(erosion
|
|
2730
2905
|
? {
|
|
2731
2906
|
erosion: {
|
|
2732
2907
|
mask: textureRef(erosion.mask),
|
|
2908
|
+
...optional('maskUV', textureUVToJSON(erosion.maskUV)),
|
|
2733
2909
|
softness: erosion.softness,
|
|
2734
2910
|
edgeWidth: erosion.edgeWidth,
|
|
2735
2911
|
edgeColor: [...erosion.edgeColor],
|
|
2736
|
-
|
|
2912
|
+
},
|
|
2913
|
+
}
|
|
2914
|
+
: {}),
|
|
2915
|
+
...(distortion
|
|
2916
|
+
? {
|
|
2917
|
+
uvDistortion: {
|
|
2918
|
+
map: textureRef(distortion.map),
|
|
2919
|
+
...optional('mapUV', textureUVToJSON(distortion.mapUV, UV_DISTORTION_DEFAULT_UV)),
|
|
2920
|
+
strength: distortion.strength,
|
|
2737
2921
|
},
|
|
2738
2922
|
}
|
|
2739
2923
|
: {}),
|
|
@@ -2746,6 +2930,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2746
2930
|
positions: Float32Array;
|
|
2747
2931
|
indices: Uint32Array | Uint16Array;
|
|
2748
2932
|
uvs?: Float32Array;
|
|
2933
|
+
uvs2?: Float32Array;
|
|
2749
2934
|
normals?: Float32Array;
|
|
2750
2935
|
} {
|
|
2751
2936
|
if (entry?.positions) {
|
|
@@ -2765,6 +2950,11 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2765
2950
|
? entry.uvs
|
|
2766
2951
|
: new Float32Array(entry.uvs)
|
|
2767
2952
|
: undefined,
|
|
2953
|
+
uvs2: entry.uvs2
|
|
2954
|
+
? entry.uvs2 instanceof Float32Array
|
|
2955
|
+
? entry.uvs2
|
|
2956
|
+
: new Float32Array(entry.uvs2)
|
|
2957
|
+
: undefined,
|
|
2768
2958
|
normals: entry.normals
|
|
2769
2959
|
? entry.normals instanceof Float32Array
|
|
2770
2960
|
? entry.normals
|
|
@@ -2785,6 +2975,10 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2785
2975
|
positions,
|
|
2786
2976
|
indices: ensureTriangleIndices(positions, indices),
|
|
2787
2977
|
uvs: data.attributes.uv?.array ? new Float32Array(data.attributes.uv.array) : undefined,
|
|
2978
|
+
// three.js names the second set `uv1` (r151 on), `uv2` before.
|
|
2979
|
+
uvs2: (data.attributes.uv1 ?? data.attributes.uv2)?.array
|
|
2980
|
+
? new Float32Array((data.attributes.uv1 ?? data.attributes.uv2).array)
|
|
2981
|
+
: undefined,
|
|
2788
2982
|
normals: data.attributes.normal?.array ? new Float32Array(data.attributes.normal.array) : undefined,
|
|
2789
2983
|
};
|
|
2790
2984
|
}
|
|
@@ -2847,6 +3041,9 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2847
3041
|
startLength: (this.rendererEmitterSettings as TrailSettings).startLength.clone(),
|
|
2848
3042
|
followLocalOrigin: (this.rendererEmitterSettings as TrailSettings).followLocalOrigin,
|
|
2849
3043
|
...trailFlags(this.rendererEmitterSettings as TrailSettings),
|
|
3044
|
+
...((this.rendererEmitterSettings as TrailSettings).sideAxis
|
|
3045
|
+
? {sideAxis: (this.rendererEmitterSettings as TrailSettings).sideAxis!.clone()}
|
|
3046
|
+
: {}),
|
|
2850
3047
|
};
|
|
2851
3048
|
} else if (this.renderMode === RenderMode.StretchedBillBoard) {
|
|
2852
3049
|
rendererEmitterSettings = {
|
|
@@ -2877,6 +3074,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2877
3074
|
instancingGeometry: this.rendererSettings.instancingGeometry,
|
|
2878
3075
|
instancingIndices: this.rendererSettings.instancingIndices,
|
|
2879
3076
|
instancingUVs: this.rendererSettings.instancingUVs,
|
|
3077
|
+
instancingUVs2: this.rendererSettings.instancingUVs2,
|
|
2880
3078
|
instancingNormals: this.rendererSettings.instancingNormals,
|
|
2881
3079
|
renderMode: this.renderMode,
|
|
2882
3080
|
renderOrder: this.renderOrder,
|
|
@@ -2912,9 +3110,10 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2912
3110
|
flip: this.flip,
|
|
2913
3111
|
lighting: this.lighting,
|
|
2914
3112
|
rim: this.rim,
|
|
2915
|
-
|
|
3113
|
+
mapUV: this.mapUV,
|
|
2916
3114
|
gradientMap: this.gradientMap,
|
|
2917
3115
|
erosion: this.erosion,
|
|
3116
|
+
uvDistortion: this.uvDistortion,
|
|
2918
3117
|
scene: this.emitter.getScene(),
|
|
2919
3118
|
});
|
|
2920
3119
|
// A sub emitter behavior transforms by its parent system's emitter;
|
|
@@ -2924,6 +3123,9 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2924
3123
|
behavior.particleSystem = copy;
|
|
2925
3124
|
}
|
|
2926
3125
|
}
|
|
3126
|
+
if (this.tint) {
|
|
3127
|
+
copy.tint = this.tint.clone();
|
|
3128
|
+
}
|
|
2927
3129
|
return copy;
|
|
2928
3130
|
}
|
|
2929
3131
|
}
|
package/src/QuarksLoader.ts
CHANGED
|
@@ -75,6 +75,7 @@ interface ParsedGeometry {
|
|
|
75
75
|
positions: Float32Array;
|
|
76
76
|
indices: Uint32Array | Uint16Array;
|
|
77
77
|
uvs?: Float32Array;
|
|
78
|
+
uvs2?: Float32Array;
|
|
78
79
|
normals?: Float32Array;
|
|
79
80
|
}
|
|
80
81
|
|
|
@@ -101,6 +102,7 @@ function geometryKey(geometry: ParsedGeometry): string {
|
|
|
101
102
|
geometry.positions,
|
|
102
103
|
geometry.indices,
|
|
103
104
|
geometry.uvs,
|
|
105
|
+
geometry.uvs2,
|
|
104
106
|
geometry.normals,
|
|
105
107
|
];
|
|
106
108
|
let lengths = '';
|
|
@@ -313,6 +315,7 @@ export class QuarksLoader {
|
|
|
313
315
|
positions: new Float32Array(geom.positions ?? []),
|
|
314
316
|
indices: geom.indices ? new Uint32Array(geom.indices) : new Uint32Array(0),
|
|
315
317
|
uvs: geom.uvs ? new Float32Array(geom.uvs) : undefined,
|
|
318
|
+
uvs2: geom.uvs2 ? new Float32Array(geom.uvs2) : undefined,
|
|
316
319
|
normals: geom.normals ? new Float32Array(geom.normals) : undefined,
|
|
317
320
|
});
|
|
318
321
|
}
|
|
@@ -427,7 +430,10 @@ export class QuarksLoader {
|
|
|
427
430
|
let positions = new Float32Array(0);
|
|
428
431
|
let indices: Uint32Array | Uint16Array = new Uint32Array(0);
|
|
429
432
|
let uvs: Float32Array | undefined;
|
|
433
|
+
let uvs2: Float32Array | undefined;
|
|
430
434
|
let normals: Float32Array | undefined;
|
|
435
|
+
// three.js names the second UV set `uv1` (r151 on), `uv2` before.
|
|
436
|
+
const uv2Attribute = data.attributes?.uv1 ?? data.attributes?.uv2;
|
|
431
437
|
|
|
432
438
|
if (data.interleavedBuffers && data.arrayBuffers) {
|
|
433
439
|
const buffers: {[uuid: string]: {data: Float32Array; stride: number}} = {};
|
|
@@ -453,6 +459,16 @@ export class QuarksLoader {
|
|
|
453
459
|
const buf = buffers[attr.data];
|
|
454
460
|
if (buf) uvs = this.extractInterleavedAttribute(buf.data, buf.stride, attr.offset, attr.itemSize);
|
|
455
461
|
}
|
|
462
|
+
if (uv2Attribute?.isInterleavedBufferAttribute) {
|
|
463
|
+
const buf = buffers[uv2Attribute.data];
|
|
464
|
+
if (buf)
|
|
465
|
+
uvs2 = this.extractInterleavedAttribute(
|
|
466
|
+
buf.data,
|
|
467
|
+
buf.stride,
|
|
468
|
+
uv2Attribute.offset,
|
|
469
|
+
uv2Attribute.itemSize
|
|
470
|
+
);
|
|
471
|
+
}
|
|
456
472
|
if (data.attributes.normal?.isInterleavedBufferAttribute) {
|
|
457
473
|
const attr = data.attributes.normal;
|
|
458
474
|
const buf = buffers[attr.data];
|
|
@@ -467,6 +483,9 @@ export class QuarksLoader {
|
|
|
467
483
|
if (data.attributes.uv?.array) {
|
|
468
484
|
uvs = new Float32Array(data.attributes.uv.array);
|
|
469
485
|
}
|
|
486
|
+
if (uv2Attribute?.array) {
|
|
487
|
+
uvs2 = new Float32Array(uv2Attribute.array);
|
|
488
|
+
}
|
|
470
489
|
if (data.attributes.normal?.array) {
|
|
471
490
|
normals = new Float32Array(data.attributes.normal.array);
|
|
472
491
|
}
|
|
@@ -482,7 +501,7 @@ export class QuarksLoader {
|
|
|
482
501
|
|
|
483
502
|
indices = ensureTriangleIndices(positions, indices);
|
|
484
503
|
|
|
485
|
-
return {positions, indices, uvs, normals};
|
|
504
|
+
return {positions, indices, uvs, uvs2, normals};
|
|
486
505
|
}
|
|
487
506
|
|
|
488
507
|
private extractInterleavedAttribute(buffer: Float32Array, stride: number, offset: number, itemSize: number): any {
|
|
@@ -666,7 +685,7 @@ export class QuarksLoader {
|
|
|
666
685
|
const data = await loadGeometry(geom.url);
|
|
667
686
|
// Copy arrays so a shared fetch-cache document is not aliased across
|
|
668
687
|
// effects — mutating one effect's inlined geometry must not corrupt others.
|
|
669
|
-
for (const key of ['positions', 'indices', 'uvs', 'normals']) {
|
|
688
|
+
for (const key of ['positions', 'indices', 'uvs', 'uvs2', 'normals']) {
|
|
670
689
|
const value = data?.[key];
|
|
671
690
|
if (value === undefined) {
|
|
672
691
|
continue;
|
|
@@ -824,13 +843,18 @@ export class QuarksLoader {
|
|
|
824
843
|
if (matDef.rim && typeof matDef.rim === 'object') {
|
|
825
844
|
matInfo.rim = matDef.rim;
|
|
826
845
|
}
|
|
827
|
-
// Texture effects (QuarksMaterial):
|
|
828
|
-
//
|
|
829
|
-
|
|
830
|
-
|
|
846
|
+
// Texture effects (QuarksMaterial): mapUV {channel, tiling, offset, scroll},
|
|
847
|
+
// gradientMap {ramp (texture uuid), channel},
|
|
848
|
+
// erosion {mask (texture uuid), maskUV {channel, tiling, offset, scroll}, softness, edgeWidth,
|
|
849
|
+
// edgeColor}.
|
|
850
|
+
if (matDef.mapUV && typeof matDef.mapUV === 'object') {
|
|
851
|
+
matInfo.mapUV = matDef.mapUV;
|
|
831
852
|
}
|
|
832
|
-
if (matDef.gradientMap) {
|
|
833
|
-
matInfo.gradientMap =
|
|
853
|
+
if (matDef.gradientMap && typeof matDef.gradientMap === 'object') {
|
|
854
|
+
matInfo.gradientMap = {
|
|
855
|
+
...matDef.gradientMap,
|
|
856
|
+
ramp: matDef.gradientMap.ramp ? meta.textures[matDef.gradientMap.ramp] || null : null,
|
|
857
|
+
};
|
|
834
858
|
}
|
|
835
859
|
if (matDef.erosion && typeof matDef.erosion === 'object') {
|
|
836
860
|
matInfo.erosion = {
|
|
@@ -838,6 +862,13 @@ export class QuarksLoader {
|
|
|
838
862
|
mask: matDef.erosion.mask ? meta.textures[matDef.erosion.mask] || null : null,
|
|
839
863
|
};
|
|
840
864
|
}
|
|
865
|
+
// uvDistortion {map (texture uuid), mapUV {channel, tiling, offset, scroll}, strength}.
|
|
866
|
+
if (matDef.uvDistortion && typeof matDef.uvDistortion === 'object') {
|
|
867
|
+
matInfo.uvDistortion = {
|
|
868
|
+
...matDef.uvDistortion,
|
|
869
|
+
map: matDef.uvDistortion.map ? meta.textures[matDef.uvDistortion.map] || null : null,
|
|
870
|
+
};
|
|
871
|
+
}
|
|
841
872
|
|
|
842
873
|
// QuarksMaterial (what ParticleSystem.ensureMaterialMeta emits) writes `alphaMode`
|
|
843
874
|
// directly as a Babylon alpha-mode constant; three.js materials (MeshBasicMaterial
|
|
@@ -887,6 +918,9 @@ export class QuarksLoader {
|
|
|
887
918
|
if (geometry.uvs && geometry.uvs.length > 0) {
|
|
888
919
|
vertexData.uvs = geometry.uvs;
|
|
889
920
|
}
|
|
921
|
+
if (geometry.uvs2 && geometry.uvs2.length > 0) {
|
|
922
|
+
vertexData.uvs2 = geometry.uvs2;
|
|
923
|
+
}
|
|
890
924
|
if (geometry.normals && geometry.normals.length > 0) {
|
|
891
925
|
vertexData.normals = geometry.normals;
|
|
892
926
|
}
|