babylon.quarks 0.24.1 → 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 +124 -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 +501 -9
- package/package.json +1 -1
- package/src/BatchedRenderer.ts +34 -1
- package/src/ParticleSystem.ts +465 -13
- package/src/QuarksLoader.ts +50 -2
- package/src/QuarksUtil.ts +97 -1
- package/src/SpriteBatch.ts +101 -10
- package/src/TrailBatch.ts +132 -20
- package/src/VFXBatch.ts +465 -4
- package/src/index.ts +38 -2
- package/src/shaders/chunks/surface.ts +268 -0
- 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 +6 -11
- package/src/shaders/particle_frag.wgsl.ts +6 -11
- package/src/shaders/particle_physics_frag.glsl.ts +13 -15
- package/src/shaders/particle_physics_frag.wgsl.ts +13 -15
- 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 +27 -7
- package/src/shaders/stretched_bb_particle_vert.wgsl.ts +32 -9
- package/src/shaders/trail_frag.glsl.ts +6 -7
- package/src/shaders/trail_frag.wgsl.ts +6 -7
- package/src/shaders/trail_vert.glsl.ts +8 -0
- package/src/shaders/trail_vert.wgsl.ts +8 -0
package/src/ParticleSystem.ts
CHANGED
|
@@ -53,7 +53,25 @@ import {BatchedRenderer, VFXBatchSettings} from './BatchedRenderer';
|
|
|
53
53
|
import {ensureEnvAtlasFromCube, getCachedEnvAtlas} from './envAtlas';
|
|
54
54
|
import {ensureTriangleIndices} from './geometryUtil';
|
|
55
55
|
import {ParticleEmitter} from './ParticleEmitter';
|
|
56
|
-
import {
|
|
56
|
+
import {
|
|
57
|
+
MeshLighting,
|
|
58
|
+
MeshRim,
|
|
59
|
+
normalizeErosion,
|
|
60
|
+
normalizeGradientMap,
|
|
61
|
+
normalizeMapUV,
|
|
62
|
+
normalizeMeshRim,
|
|
63
|
+
normalizeUVDistortion,
|
|
64
|
+
ParticleErosion,
|
|
65
|
+
ParticleErosionOptions,
|
|
66
|
+
ParticleGradientMap,
|
|
67
|
+
ParticleUVDistortion,
|
|
68
|
+
ParticleUVDistortionOptions,
|
|
69
|
+
RenderMode,
|
|
70
|
+
SortMode,
|
|
71
|
+
TextureUV,
|
|
72
|
+
textureUVToJSON,
|
|
73
|
+
UV_DISTORTION_DEFAULT_UV,
|
|
74
|
+
} from './VFXBatch';
|
|
57
75
|
|
|
58
76
|
export interface BurstParameters {
|
|
59
77
|
time: number;
|
|
@@ -81,6 +99,27 @@ export interface BurstParameters {
|
|
|
81
99
|
*/
|
|
82
100
|
export type CullingMode = 'always' | 'pause' | 'pauseAndCatchUp';
|
|
83
101
|
|
|
102
|
+
/**
|
|
103
|
+
* What the emitter's world scale does to a world-space system (Unity's
|
|
104
|
+
* Scaling Mode, as far as it differs here). A local-space system lives in its
|
|
105
|
+
* emitter's space, so its scale reaches everything either way.
|
|
106
|
+
*
|
|
107
|
+
* - `'shape'` (default): the scale sizes the emission shape and the
|
|
108
|
+
* particles; speeds, forces and speed limits stay in world units, so a
|
|
109
|
+
* scaled-down effect moves as far as the full-size one.
|
|
110
|
+
* - `'hierarchy'`: the scale also multiplies start speeds (per axis),
|
|
111
|
+
* `ForceOverLife` and `LimitSpeedOverLife` (by the mean of the axes), so the
|
|
112
|
+
* whole effect, motion included, scales like a model would.
|
|
113
|
+
*/
|
|
114
|
+
export type ScalingMode = 'shape' | 'hierarchy';
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Where a {@link RenderMode.Beam} system's quads end: a point in world space,
|
|
118
|
+
* or anything with a world position, such as a Babylon `TransformNode`, read
|
|
119
|
+
* every frame.
|
|
120
|
+
*/
|
|
121
|
+
export type BeamEnd = {x: number; y: number; z: number} | {getAbsolutePosition(): {x: number; y: number; z: number}};
|
|
122
|
+
|
|
84
123
|
/** Particle backed by a {@link ParticleStore} row, as this system always creates. */
|
|
85
124
|
type StoreBackedParticle = Particle & {storeIndex: number; setStoreIndex(index: number): void};
|
|
86
125
|
|
|
@@ -132,8 +171,19 @@ const DEFAULT_POSITIONS = new Float32Array([-0.5, -0.5, 0, 0.5, -0.5, 0, 0.5, 0.
|
|
|
132
171
|
const DEFAULT_UVS = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
|
|
133
172
|
|
|
134
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
|
+
*/
|
|
135
180
|
function fallbackUVsForPositions(positions: Float32Array): Float32Array {
|
|
136
|
-
|
|
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;
|
|
137
187
|
}
|
|
138
188
|
const DEFAULT_INDICES = new Uint32Array([0, 1, 2, 0, 2, 3]);
|
|
139
189
|
|
|
@@ -147,6 +197,24 @@ function trailFlags(settings: TrailSettings | undefined): Partial<TrailSettings>
|
|
|
147
197
|
return flags;
|
|
148
198
|
}
|
|
149
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
|
+
|
|
150
218
|
export interface ParticleSystemParameters {
|
|
151
219
|
autoDestroy?: boolean;
|
|
152
220
|
looping?: boolean;
|
|
@@ -170,6 +238,12 @@ export interface ParticleSystemParameters {
|
|
|
170
238
|
instancingGeometry?: Float32Array;
|
|
171
239
|
instancingIndices?: Uint32Array | Uint16Array;
|
|
172
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;
|
|
173
247
|
instancingNormals?: Float32Array;
|
|
174
248
|
renderMode?: RenderMode;
|
|
175
249
|
rendererEmitterSettings?: RendererEmitterSettings;
|
|
@@ -199,10 +273,20 @@ export interface ParticleSystemParameters {
|
|
|
199
273
|
lighting?: MeshLighting;
|
|
200
274
|
/** Rim (Fresnel) term on mesh particles, see {@link ParticleSystem.rim}. Default none. */
|
|
201
275
|
rim?: Partial<MeshRim>;
|
|
276
|
+
/** The main map's layout, see {@link ParticleSystem.mapUV}. Default as drawn. */
|
|
277
|
+
mapUV?: Partial<TextureUV>;
|
|
278
|
+
/** Colour ramp for the map, see {@link ParticleSystem.gradientMap}. Default none. */
|
|
279
|
+
gradientMap?: Partial<ParticleGradientMap>;
|
|
280
|
+
/** Alpha erosion, see {@link ParticleSystem.erosion}. Default none. */
|
|
281
|
+
erosion?: ParticleErosionOptions;
|
|
282
|
+
/** Map UV distortion, see {@link ParticleSystem.uvDistortion}. Default none. */
|
|
283
|
+
uvDistortion?: ParticleUVDistortionOptions;
|
|
202
284
|
/** Multiplier on the time the system is advanced by, see {@link ParticleSystem.playbackSpeed}. Default 1. */
|
|
203
285
|
playbackSpeed?: number;
|
|
204
286
|
/** What the system does while off screen, see {@link CullingMode}. Default `'always'`. */
|
|
205
287
|
cullingMode?: CullingMode;
|
|
288
|
+
/** What the emitter's scale does to a world-space system, see {@link ScalingMode}. Default `'shape'`. */
|
|
289
|
+
scalingMode?: ScalingMode;
|
|
206
290
|
/** Seed for this system's own random generator, see {@link ParticleSystem.seed}. Default none. */
|
|
207
291
|
seed?: number;
|
|
208
292
|
/** Draw order within the batch, see {@link ParticleSystem.sortMode}. Default `'none'`. */
|
|
@@ -258,6 +342,8 @@ export interface ParticleSystemJSONParameters {
|
|
|
258
342
|
playbackSpeed?: number;
|
|
259
343
|
/** Written only when not `'always'`. */
|
|
260
344
|
cullingMode?: CullingMode;
|
|
345
|
+
/** Written only when not `'shape'`. */
|
|
346
|
+
scalingMode?: ScalingMode;
|
|
261
347
|
/** Written only when set. */
|
|
262
348
|
seed?: number;
|
|
263
349
|
/** Written only when not `'none'`. */
|
|
@@ -587,6 +673,99 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
587
673
|
this.neededToUpdateRender = true;
|
|
588
674
|
}
|
|
589
675
|
|
|
676
|
+
/**
|
|
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.
|
|
686
|
+
*/
|
|
687
|
+
get mapUV(): TextureUV | undefined {
|
|
688
|
+
return this.rendererSettings.mapUV;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
set mapUV(uv: Partial<TextureUV> | undefined) {
|
|
692
|
+
this.rendererSettings.mapUV = normalizeMapUV(uv);
|
|
693
|
+
this.neededToUpdateRender = true;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
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.
|
|
703
|
+
*/
|
|
704
|
+
get gradientMap(): ParticleGradientMap | undefined {
|
|
705
|
+
return this.rendererSettings.gradientMap;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
set gradientMap(gradientMap: Partial<ParticleGradientMap> | undefined) {
|
|
709
|
+
this.rendererSettings.gradientMap = normalizeGradientMap(gradientMap);
|
|
710
|
+
this.neededToUpdateRender = true;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Alpha erosion, see {@link ParticleErosion}: the particle's alpha decides
|
|
715
|
+
* how much of the shape is left rather than how transparent it is, so a
|
|
716
|
+
* `ColorOverLife` alpha curve dissolves the shape through a noise mask or
|
|
717
|
+
* wipes it through a gradient one — and an effect told to stop can fade
|
|
718
|
+
* out that way. Undefined (the default): alpha is plain transparency.
|
|
719
|
+
* Fields left out take their defaults; assign a new object to change it.
|
|
720
|
+
* Saved with the material in the effect JSON.
|
|
721
|
+
*/
|
|
722
|
+
get erosion(): ParticleErosion | undefined {
|
|
723
|
+
return this.rendererSettings.erosion;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
set erosion(erosion: ParticleErosionOptions | undefined) {
|
|
727
|
+
this.rendererSettings.erosion = normalizeErosion(erosion);
|
|
728
|
+
this.neededToUpdateRender = true;
|
|
729
|
+
}
|
|
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
|
+
|
|
747
|
+
/**
|
|
748
|
+
* The emitter's world scale per axis, measured with {@link motionScale}
|
|
749
|
+
* before every step; what `'hierarchy'` multiplies start speeds by. Taken
|
|
750
|
+
* from the system's own emitter rather than the emission matrix, whose
|
|
751
|
+
* columns carry a parent particle's velocity for a sub emitter.
|
|
752
|
+
*/
|
|
753
|
+
private readonly hierarchyScale = new Vector3(1, 1, 1);
|
|
754
|
+
|
|
755
|
+
/** The mean world scale of the emitter for a world-space `'hierarchy'` system, else 1. */
|
|
756
|
+
private measureMotionScale(): number {
|
|
757
|
+
if (this.scalingMode !== 'hierarchy' || !this.worldSpace) {
|
|
758
|
+
this.hierarchyScale.set(1, 1, 1);
|
|
759
|
+
return 1;
|
|
760
|
+
}
|
|
761
|
+
const e = this.emitter.matrixWorld.elements;
|
|
762
|
+
const sx = Math.sqrt(e[0] * e[0] + e[1] * e[1] + e[2] * e[2]);
|
|
763
|
+
const sy = Math.sqrt(e[4] * e[4] + e[5] * e[5] + e[6] * e[6]);
|
|
764
|
+
const sz = Math.sqrt(e[8] * e[8] + e[9] * e[9] + e[10] * e[10]);
|
|
765
|
+
this.hierarchyScale.set(sx, sy, sz);
|
|
766
|
+
return (sx + sy + sz) / 3;
|
|
767
|
+
}
|
|
768
|
+
|
|
590
769
|
/** `lighting` if it is one of the modes, else the default. */
|
|
591
770
|
private static validLighting(lighting: unknown): MeshLighting {
|
|
592
771
|
return lighting === 'unlit' || lighting === 'scene' ? lighting : 'fixed';
|
|
@@ -630,10 +809,53 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
630
809
|
return this.rendererSettings.instancingGeometry;
|
|
631
810
|
}
|
|
632
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
|
+
*/
|
|
633
818
|
set instancingGeometry(geometry: Float32Array) {
|
|
634
819
|
this.restart();
|
|
635
820
|
this.particles.length = 0;
|
|
636
|
-
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;
|
|
637
859
|
this.neededToUpdateRender = true;
|
|
638
860
|
}
|
|
639
861
|
|
|
@@ -674,6 +896,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
674
896
|
case RenderMode.BillBoard:
|
|
675
897
|
case RenderMode.VerticalBillBoard:
|
|
676
898
|
case RenderMode.HorizontalBillBoard:
|
|
899
|
+
case RenderMode.Beam:
|
|
677
900
|
this.rendererEmitterSettings = {};
|
|
678
901
|
break;
|
|
679
902
|
}
|
|
@@ -730,9 +953,28 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
730
953
|
*/
|
|
731
954
|
playbackSpeed = 1;
|
|
732
955
|
|
|
956
|
+
/**
|
|
957
|
+
* Runtime multiplier on how many particles the system emits: bursts,
|
|
958
|
+
* emission over time and over distance alike. Unlike
|
|
959
|
+
* {@link setQualityFactor}, which only ever thins an effect, it may go
|
|
960
|
+
* above 1 to make a stronger version of the same effect. The two multiply.
|
|
961
|
+
* 1 by default; not saved in the effect JSON.
|
|
962
|
+
*/
|
|
963
|
+
emissionMultiplier = 1;
|
|
964
|
+
|
|
733
965
|
/** What this system does while off screen, see {@link CullingMode}. `'always'` by default. */
|
|
734
966
|
cullingMode: CullingMode = 'always';
|
|
735
967
|
|
|
968
|
+
/** What the emitter's world scale does to a world-space system, see {@link ScalingMode}. `'shape'` by default. */
|
|
969
|
+
scalingMode: ScalingMode = 'shape';
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* What world-space forces and speed limits are multiplied by this step:
|
|
973
|
+
* the mean of the emitter's world scale axes in `'hierarchy'` mode, 1
|
|
974
|
+
* otherwise. Updated before every simulation step.
|
|
975
|
+
*/
|
|
976
|
+
motionScale = 1;
|
|
977
|
+
|
|
736
978
|
/**
|
|
737
979
|
* Whether each particle starts turned along the direction its shape sends
|
|
738
980
|
* it (Unity's Align To Direction): a mesh particle's +Z is pointed along
|
|
@@ -852,6 +1094,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
852
1094
|
instancingUVs:
|
|
853
1095
|
parameters.instancingUVs ??
|
|
854
1096
|
(parameters.instancingGeometry ? fallbackUVsForPositions(parameters.instancingGeometry) : DEFAULT_UVS),
|
|
1097
|
+
instancingUVs2: parameters.instancingUVs2,
|
|
855
1098
|
instancingNormals: parameters.instancingNormals,
|
|
856
1099
|
renderMode: parameters.renderMode ?? RenderMode.BillBoard,
|
|
857
1100
|
renderOrder: parameters.renderOrder ?? 0,
|
|
@@ -880,10 +1123,15 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
880
1123
|
applyFog: parameters.applyFog ?? true,
|
|
881
1124
|
lighting: ParticleSystem.validLighting(parameters.lighting),
|
|
882
1125
|
rim: normalizeMeshRim(parameters.rim),
|
|
1126
|
+
mapUV: normalizeMapUV(parameters.mapUV),
|
|
1127
|
+
gradientMap: normalizeGradientMap(parameters.gradientMap),
|
|
1128
|
+
erosion: normalizeErosion(parameters.erosion),
|
|
1129
|
+
uvDistortion: normalizeUVDistortion(parameters.uvDistortion),
|
|
883
1130
|
};
|
|
884
1131
|
this.flip = ParticleSystem.nonZeroFlip(parameters.flip);
|
|
885
1132
|
this.playbackSpeed = parameters.playbackSpeed ?? 1;
|
|
886
1133
|
this.cullingMode = parameters.cullingMode ?? 'always';
|
|
1134
|
+
this.scalingMode = parameters.scalingMode === 'hierarchy' ? 'hierarchy' : 'shape';
|
|
887
1135
|
this.alignToDirection = parameters.alignToDirection === true;
|
|
888
1136
|
this.seed = parameters.seed;
|
|
889
1137
|
if (this.rendererSettings.renderMode === RenderMode.Mesh && !this.rendererSettings.instancingNormals) {
|
|
@@ -1062,6 +1310,12 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
1062
1310
|
this.qualityFactor = Math.max(0.1, Math.min(1, qualityFactor));
|
|
1063
1311
|
}
|
|
1064
1312
|
|
|
1313
|
+
/** Quality factor times {@link emissionMultiplier}: what emission counts are scaled by. */
|
|
1314
|
+
private emissionFactor(): number {
|
|
1315
|
+
const multiplier = this.emissionMultiplier;
|
|
1316
|
+
return multiplier >= 0 ? this.qualityFactor * multiplier : this.qualityFactor;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1065
1319
|
/**
|
|
1066
1320
|
* Appends one particle to the pool, giving it the next row of the store.
|
|
1067
1321
|
*
|
|
@@ -1212,7 +1466,12 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
1212
1466
|
particle.position.applyMatrix4(matrix);
|
|
1213
1467
|
particle.startSize.multiply(scale).abs();
|
|
1214
1468
|
particle.size.copy(particle.startSize);
|
|
1215
|
-
|
|
1469
|
+
if (this.scalingMode === 'hierarchy') {
|
|
1470
|
+
// Scaled per axis by the emitter's world scale, then turned: the speed scales with it.
|
|
1471
|
+
particle.velocity.multiply(this.hierarchyScale).applyQuaternion(tempQ);
|
|
1472
|
+
} else {
|
|
1473
|
+
particle.velocity.multiply(scale).applyMatrix3(this.normalMatrix);
|
|
1474
|
+
}
|
|
1216
1475
|
if (particle.rotation && particle.rotation instanceof Quaternion) {
|
|
1217
1476
|
particle.rotation.multiplyQuaternions(tempQ, particle.rotation);
|
|
1218
1477
|
}
|
|
@@ -1514,6 +1773,76 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
1514
1773
|
* frame behind the display.
|
|
1515
1774
|
*/
|
|
1516
1775
|
getWorldBounds(min: Vector3, max: Vector3): boolean {
|
|
1776
|
+
if (!this.measureWorldBounds(min, max)) {
|
|
1777
|
+
return false;
|
|
1778
|
+
}
|
|
1779
|
+
if (this.rendererSettings.renderMode === RenderMode.Beam) {
|
|
1780
|
+
// The quads reach the beam's end, which moves on its own. Without
|
|
1781
|
+
// one a beam draws nothing beyond its particles.
|
|
1782
|
+
const end = this.beamEndScratch;
|
|
1783
|
+
if (!this.getBeamEnd(end)) {
|
|
1784
|
+
return true;
|
|
1785
|
+
}
|
|
1786
|
+
const pad = this.boundsInWorld ? this.boundsPadding : this.boundsPadding * this.emitterMaxScale();
|
|
1787
|
+
min.set(Math.min(min.x, end.x - pad), Math.min(min.y, end.y - pad), Math.min(min.z, end.z - pad));
|
|
1788
|
+
max.set(Math.max(max.x, end.x + pad), Math.max(max.y, end.y + pad), Math.max(max.z, end.z + pad));
|
|
1789
|
+
}
|
|
1790
|
+
return true;
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
/**
|
|
1794
|
+
* Where a {@link RenderMode.Beam} system's quads end, see {@link BeamEnd}.
|
|
1795
|
+
* Unset (the default), a beam has no length and draws nothing. Not saved
|
|
1796
|
+
* in the effect JSON: it is where this instance is aimed at runtime.
|
|
1797
|
+
*/
|
|
1798
|
+
beamEnd?: BeamEnd;
|
|
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
|
+
|
|
1813
|
+
private readonly beamEndScratch = new Vector3();
|
|
1814
|
+
|
|
1815
|
+
/** The world position of {@link beamEnd} into `out`; false when it is unset. */
|
|
1816
|
+
getBeamEnd(out: Vector3): boolean {
|
|
1817
|
+
const end = this.beamEnd;
|
|
1818
|
+
if (end === undefined) {
|
|
1819
|
+
return false;
|
|
1820
|
+
}
|
|
1821
|
+
const node = end as {getAbsolutePosition?: () => {x: number; y: number; z: number}};
|
|
1822
|
+
if (typeof node.getAbsolutePosition === 'function') {
|
|
1823
|
+
const position = node.getAbsolutePosition();
|
|
1824
|
+
out.set(position.x, position.y, position.z);
|
|
1825
|
+
} else {
|
|
1826
|
+
// Any point: quarks' Vector3, or a Babylon one passed from plain JavaScript.
|
|
1827
|
+
const point = end as {x: number; y: number; z: number};
|
|
1828
|
+
out.set(point.x, point.y, point.z);
|
|
1829
|
+
}
|
|
1830
|
+
return true;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
/** The largest axis of the emitter's world scale. */
|
|
1834
|
+
private emitterMaxScale(): number {
|
|
1835
|
+
const e = this.emitter.matrixWorld.elements;
|
|
1836
|
+
return Math.sqrt(
|
|
1837
|
+
Math.max(
|
|
1838
|
+
e[0] * e[0] + e[1] * e[1] + e[2] * e[2],
|
|
1839
|
+
e[4] * e[4] + e[5] * e[5] + e[6] * e[6],
|
|
1840
|
+
e[8] * e[8] + e[9] * e[9] + e[10] * e[10]
|
|
1841
|
+
)
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
private measureWorldBounds(min: Vector3, max: Vector3): boolean {
|
|
1517
1846
|
if (!this.boundsValid) {
|
|
1518
1847
|
return false;
|
|
1519
1848
|
}
|
|
@@ -1689,6 +2018,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
1689
2018
|
}
|
|
1690
2019
|
|
|
1691
2020
|
private stepOnce(delta: number): void {
|
|
2021
|
+
this.motionScale = this.measureMotionScale();
|
|
1692
2022
|
if (!this.onlyUsedByOther) {
|
|
1693
2023
|
this.emit(delta, this.emissionState, this.emitter.matrixWorld);
|
|
1694
2024
|
}
|
|
@@ -1936,6 +2266,10 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
1936
2266
|
// A world-space system records world positions already.
|
|
1937
2267
|
const recordWorldSpace = trailSettings.recordWorldSpace && !this.worldSpace;
|
|
1938
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);
|
|
1939
2273
|
for (let i = 0; i < particleCount; i++) {
|
|
1940
2274
|
const trail = particles[i] as TrailParticle;
|
|
1941
2275
|
// Emptying the history lets the removal pass below take the
|
|
@@ -1955,6 +2289,25 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
1955
2289
|
positions[offset + 1] = tempV.y;
|
|
1956
2290
|
positions[offset + 2] = tempV.z;
|
|
1957
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
|
+
}
|
|
1958
2311
|
}
|
|
1959
2312
|
}
|
|
1960
2313
|
|
|
@@ -2060,8 +2413,9 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2060
2413
|
return;
|
|
2061
2414
|
}
|
|
2062
2415
|
const rawCount = burst.count.genValue(this.memory, this.time);
|
|
2063
|
-
const
|
|
2064
|
-
|
|
2416
|
+
const factor = this.emissionFactor();
|
|
2417
|
+
// Rounded at random, so a fraction is not always lost: three at 1.5 is four or five, on average 4.5.
|
|
2418
|
+
const count = Math.abs(factor - 1) < 0.001 ? rawCount : Math.floor(rawCount * factor + random());
|
|
2065
2419
|
emissionState.isBursting = true;
|
|
2066
2420
|
emissionState.burstParticleCount = count;
|
|
2067
2421
|
this.spawn(count, emissionState, emitterMatrix);
|
|
@@ -2126,6 +2480,12 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2126
2480
|
}
|
|
2127
2481
|
|
|
2128
2482
|
emit(delta: number, emissionState: EmissionState, emitterMatrix: Matrix4) {
|
|
2483
|
+
// Ended before this step (endEmit, or a past step's end of duration):
|
|
2484
|
+
// no more bursts either. A looping system would otherwise keep firing
|
|
2485
|
+
// them every loop after endEmit and never finish. Only the system's
|
|
2486
|
+
// own emission: a sub emitter's bursts belong to its parent particles,
|
|
2487
|
+
// which still go off as they die after the effect was told to end.
|
|
2488
|
+
const endedBefore = this.emitEnded && emissionState === this.emissionState;
|
|
2129
2489
|
if (emissionState.time > this.duration) {
|
|
2130
2490
|
if (this.looping) {
|
|
2131
2491
|
emissionState.time -= this.duration;
|
|
@@ -2149,9 +2509,9 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2149
2509
|
this.normalMatrix.getNormalMatrix(emitterMatrix);
|
|
2150
2510
|
}
|
|
2151
2511
|
const emissionBurstCount = this.emissionBursts.length;
|
|
2152
|
-
const qualityFactor = this.
|
|
2512
|
+
const qualityFactor = this.emissionFactor();
|
|
2153
2513
|
|
|
2154
|
-
if (emissionBurstCount > 0) {
|
|
2514
|
+
if (emissionBurstCount > 0 && !endedBefore) {
|
|
2155
2515
|
this.emitBursts(emissionState, emitterMatrix);
|
|
2156
2516
|
}
|
|
2157
2517
|
|
|
@@ -2265,6 +2625,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2265
2625
|
...(this.applyFog ? {} : {applyFog: false}),
|
|
2266
2626
|
...(this.playbackSpeed !== 1 ? {playbackSpeed: this.playbackSpeed} : {}),
|
|
2267
2627
|
...(this.cullingMode !== 'always' ? {cullingMode: this.cullingMode} : {}),
|
|
2628
|
+
...(this.scalingMode !== 'shape' ? {scalingMode: this.scalingMode} : {}),
|
|
2268
2629
|
...(this.seedValue !== undefined ? {seed: this.seedValue} : {}),
|
|
2269
2630
|
...(this.sortMode !== 'none' ? {sortMode: this.sortMode} : {}),
|
|
2270
2631
|
...(this.alignToDirection ? {alignToDirection: true} : {}),
|
|
@@ -2309,6 +2670,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2309
2670
|
: new ConstantValue(30),
|
|
2310
2671
|
followLocalOrigin: trailSettings?.followLocalOrigin ?? false,
|
|
2311
2672
|
...trailFlags(trailSettings),
|
|
2673
|
+
...trailSideAxisFromJSON((trailSettings as {sideAxis?: unknown} | undefined)?.sideAxis),
|
|
2312
2674
|
};
|
|
2313
2675
|
} else if (json.renderMode === RenderMode.StretchedBillBoard) {
|
|
2314
2676
|
rendererEmitterSettings = {...(json.rendererEmitterSettings ?? {})};
|
|
@@ -2325,6 +2687,12 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2325
2687
|
typeof materialTextureRef === 'string'
|
|
2326
2688
|
? (meta.textures?.[materialTextureRef] ?? null)
|
|
2327
2689
|
: (materialTextureRef ?? null);
|
|
2690
|
+
// Texture effects reference textures by uuid in a serialised meta, or hold them once a loader resolved them.
|
|
2691
|
+
const resolveTexture = (ref: unknown): BaseTexture | null =>
|
|
2692
|
+
typeof ref === 'string' ? (meta.textures?.[ref] ?? null) : ((ref as BaseTexture | null) ?? null);
|
|
2693
|
+
const erosionMeta = materialMeta?.erosion;
|
|
2694
|
+
const gradientMeta = materialMeta?.gradientMap;
|
|
2695
|
+
const distortionMeta = materialMeta?.uvDistortion;
|
|
2328
2696
|
const resolvedGeometryEntry =
|
|
2329
2697
|
typeof json.instancingGeometry === 'string'
|
|
2330
2698
|
? meta.geometries?.[json.instancingGeometry]
|
|
@@ -2364,6 +2732,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2364
2732
|
instancingGeometry: resolvedGeometry.positions,
|
|
2365
2733
|
instancingIndices: resolvedGeometry.indices,
|
|
2366
2734
|
instancingUVs: resolvedGeometry.uvs,
|
|
2735
|
+
instancingUVs2: resolvedGeometry.uvs2,
|
|
2367
2736
|
instancingNormals: resolvedGeometry.normals,
|
|
2368
2737
|
renderMode: json.renderMode,
|
|
2369
2738
|
rendererEmitterSettings,
|
|
@@ -2372,6 +2741,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2372
2741
|
applyFog: json.applyFog !== false,
|
|
2373
2742
|
playbackSpeed: typeof json.playbackSpeed === 'number' ? json.playbackSpeed : undefined,
|
|
2374
2743
|
cullingMode: ParticleSystem.validCullingMode(json.cullingMode),
|
|
2744
|
+
scalingMode: json.scalingMode === 'hierarchy' ? 'hierarchy' : 'shape',
|
|
2375
2745
|
seed: typeof json.seed === 'number' ? json.seed : undefined,
|
|
2376
2746
|
sortMode: ParticleSystem.validSortMode(json.sortMode),
|
|
2377
2747
|
alignToDirection: json.alignToDirection === true,
|
|
@@ -2401,6 +2771,19 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2401
2771
|
flip: json.flip ? new Vector2(json.flip[0] ?? 0, json.flip[1] ?? 0) : undefined,
|
|
2402
2772
|
lighting: materialMeta?.lighting,
|
|
2403
2773
|
rim: materialMeta?.rim,
|
|
2774
|
+
mapUV: materialMeta?.mapUV,
|
|
2775
|
+
gradientMap:
|
|
2776
|
+
gradientMeta && typeof gradientMeta === 'object'
|
|
2777
|
+
? {...gradientMeta, ramp: resolveTexture(gradientMeta.ramp)}
|
|
2778
|
+
: undefined,
|
|
2779
|
+
erosion:
|
|
2780
|
+
erosionMeta && typeof erosionMeta === 'object'
|
|
2781
|
+
? {...erosionMeta, mask: resolveTexture(erosionMeta.mask)}
|
|
2782
|
+
: undefined,
|
|
2783
|
+
uvDistortion:
|
|
2784
|
+
distortionMeta && typeof distortionMeta === 'object'
|
|
2785
|
+
? {...distortionMeta, map: resolveTexture(distortionMeta.map)}
|
|
2786
|
+
: undefined,
|
|
2404
2787
|
});
|
|
2405
2788
|
ps.behaviors = (json.behaviors ?? [])
|
|
2406
2789
|
.map((behaviorJson) => {
|
|
@@ -2419,12 +2802,15 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2419
2802
|
return `${prefix}_${ParticleSystem.serializationCounter}`;
|
|
2420
2803
|
}
|
|
2421
2804
|
|
|
2805
|
+
/** Normals facing +Z, one array per geometry so systems sharing it still batch together. */
|
|
2422
2806
|
private static createFallbackNormals(positions: Float32Array): Float32Array {
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
normals
|
|
2426
|
-
|
|
2427
|
-
|
|
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);
|
|
2428
2814
|
}
|
|
2429
2815
|
return normals;
|
|
2430
2816
|
}
|
|
@@ -2435,6 +2821,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2435
2821
|
startLength: (this.rendererEmitterSettings as TrailSettings).startLength.toJSON(),
|
|
2436
2822
|
followLocalOrigin: (this.rendererEmitterSettings as TrailSettings).followLocalOrigin,
|
|
2437
2823
|
...trailFlags(this.rendererEmitterSettings as TrailSettings),
|
|
2824
|
+
...(trailSideAxisJSON(this.rendererEmitterSettings as TrailSettings) as {sideAxis?: any}),
|
|
2438
2825
|
};
|
|
2439
2826
|
}
|
|
2440
2827
|
if (this.renderMode === RenderMode.StretchedBillBoard) {
|
|
@@ -2454,6 +2841,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2454
2841
|
positions: Array.from(this.rendererSettings.instancingGeometry),
|
|
2455
2842
|
indices: Array.from(this.rendererSettings.instancingIndices),
|
|
2456
2843
|
uvs: this.rendererSettings.instancingUVs ? Array.from(this.rendererSettings.instancingUVs) : undefined,
|
|
2844
|
+
...(this.rendererSettings.instancingUVs2 ? {uvs2: Array.from(this.rendererSettings.instancingUVs2)} : {}),
|
|
2457
2845
|
normals: this.rendererSettings.instancingNormals
|
|
2458
2846
|
? Array.from(this.rendererSettings.instancingNormals)
|
|
2459
2847
|
: undefined,
|
|
@@ -2476,6 +2864,19 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2476
2864
|
meta.textures[reflectionAtlasUUID] = reflectionAtlas;
|
|
2477
2865
|
}
|
|
2478
2866
|
|
|
2867
|
+
const textureRef = (value: BaseTexture | null | undefined): string | undefined => {
|
|
2868
|
+
if (!(value instanceof Texture)) {
|
|
2869
|
+
return undefined;
|
|
2870
|
+
}
|
|
2871
|
+
const uuid = ParticleSystem.nextSerializationId('quarks_texture');
|
|
2872
|
+
meta.textures[uuid] = value;
|
|
2873
|
+
return uuid;
|
|
2874
|
+
};
|
|
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} : {});
|
|
2879
|
+
|
|
2479
2880
|
const materialUUID = ParticleSystem.nextSerializationId('quarks_material');
|
|
2480
2881
|
meta.materials[materialUUID] = {
|
|
2481
2882
|
uuid: materialUUID,
|
|
@@ -2491,6 +2892,35 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2491
2892
|
// Written only when not the default, so existing effects serialise as before.
|
|
2492
2893
|
...(this.lighting !== 'fixed' ? {lighting: this.lighting} : {}),
|
|
2493
2894
|
...(this.rim ? {rim: {...this.rim, color: [...this.rim.color]}} : {}),
|
|
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
|
+
: {}),
|
|
2904
|
+
...(erosion
|
|
2905
|
+
? {
|
|
2906
|
+
erosion: {
|
|
2907
|
+
mask: textureRef(erosion.mask),
|
|
2908
|
+
...optional('maskUV', textureUVToJSON(erosion.maskUV)),
|
|
2909
|
+
softness: erosion.softness,
|
|
2910
|
+
edgeWidth: erosion.edgeWidth,
|
|
2911
|
+
edgeColor: [...erosion.edgeColor],
|
|
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,
|
|
2921
|
+
},
|
|
2922
|
+
}
|
|
2923
|
+
: {}),
|
|
2494
2924
|
sourceMaterial: this.materialRef ?? undefined,
|
|
2495
2925
|
};
|
|
2496
2926
|
return materialUUID;
|
|
@@ -2500,6 +2930,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2500
2930
|
positions: Float32Array;
|
|
2501
2931
|
indices: Uint32Array | Uint16Array;
|
|
2502
2932
|
uvs?: Float32Array;
|
|
2933
|
+
uvs2?: Float32Array;
|
|
2503
2934
|
normals?: Float32Array;
|
|
2504
2935
|
} {
|
|
2505
2936
|
if (entry?.positions) {
|
|
@@ -2519,6 +2950,11 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2519
2950
|
? entry.uvs
|
|
2520
2951
|
: new Float32Array(entry.uvs)
|
|
2521
2952
|
: undefined,
|
|
2953
|
+
uvs2: entry.uvs2
|
|
2954
|
+
? entry.uvs2 instanceof Float32Array
|
|
2955
|
+
? entry.uvs2
|
|
2956
|
+
: new Float32Array(entry.uvs2)
|
|
2957
|
+
: undefined,
|
|
2522
2958
|
normals: entry.normals
|
|
2523
2959
|
? entry.normals instanceof Float32Array
|
|
2524
2960
|
? entry.normals
|
|
@@ -2539,6 +2975,10 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2539
2975
|
positions,
|
|
2540
2976
|
indices: ensureTriangleIndices(positions, indices),
|
|
2541
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,
|
|
2542
2982
|
normals: data.attributes.normal?.array ? new Float32Array(data.attributes.normal.array) : undefined,
|
|
2543
2983
|
};
|
|
2544
2984
|
}
|
|
@@ -2601,6 +3041,9 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2601
3041
|
startLength: (this.rendererEmitterSettings as TrailSettings).startLength.clone(),
|
|
2602
3042
|
followLocalOrigin: (this.rendererEmitterSettings as TrailSettings).followLocalOrigin,
|
|
2603
3043
|
...trailFlags(this.rendererEmitterSettings as TrailSettings),
|
|
3044
|
+
...((this.rendererEmitterSettings as TrailSettings).sideAxis
|
|
3045
|
+
? {sideAxis: (this.rendererEmitterSettings as TrailSettings).sideAxis!.clone()}
|
|
3046
|
+
: {}),
|
|
2604
3047
|
};
|
|
2605
3048
|
} else if (this.renderMode === RenderMode.StretchedBillBoard) {
|
|
2606
3049
|
rendererEmitterSettings = {
|
|
@@ -2631,6 +3074,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2631
3074
|
instancingGeometry: this.rendererSettings.instancingGeometry,
|
|
2632
3075
|
instancingIndices: this.rendererSettings.instancingIndices,
|
|
2633
3076
|
instancingUVs: this.rendererSettings.instancingUVs,
|
|
3077
|
+
instancingUVs2: this.rendererSettings.instancingUVs2,
|
|
2634
3078
|
instancingNormals: this.rendererSettings.instancingNormals,
|
|
2635
3079
|
renderMode: this.renderMode,
|
|
2636
3080
|
renderOrder: this.renderOrder,
|
|
@@ -2638,6 +3082,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2638
3082
|
applyFog: this.applyFog,
|
|
2639
3083
|
playbackSpeed: this.playbackSpeed,
|
|
2640
3084
|
cullingMode: this.cullingMode,
|
|
3085
|
+
scalingMode: this.scalingMode,
|
|
2641
3086
|
seed: this.seedValue,
|
|
2642
3087
|
sortMode: this.sortMode,
|
|
2643
3088
|
alignToDirection: this.alignToDirection,
|
|
@@ -2665,6 +3110,10 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2665
3110
|
flip: this.flip,
|
|
2666
3111
|
lighting: this.lighting,
|
|
2667
3112
|
rim: this.rim,
|
|
3113
|
+
mapUV: this.mapUV,
|
|
3114
|
+
gradientMap: this.gradientMap,
|
|
3115
|
+
erosion: this.erosion,
|
|
3116
|
+
uvDistortion: this.uvDistortion,
|
|
2668
3117
|
scene: this.emitter.getScene(),
|
|
2669
3118
|
});
|
|
2670
3119
|
// A sub emitter behavior transforms by its parent system's emitter;
|
|
@@ -2674,6 +3123,9 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
2674
3123
|
behavior.particleSystem = copy;
|
|
2675
3124
|
}
|
|
2676
3125
|
}
|
|
3126
|
+
if (this.tint) {
|
|
3127
|
+
copy.tint = this.tint.clone();
|
|
3128
|
+
}
|
|
2677
3129
|
return copy;
|
|
2678
3130
|
}
|
|
2679
3131
|
}
|