babylon.quarks 0.17.2 → 0.17.4
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/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/types/BatchedRenderer.d.ts +19 -0
- package/dist/types/ParticleSystem.d.ts +8 -0
- package/dist/types/SpriteBatch.d.ts +1 -0
- package/dist/types/TrailBatch.d.ts +1 -0
- package/dist/types/geometryUtil.d.ts +2 -0
- package/dist/types/shaders/local_particle_physics_vert.glsl.d.ts +1 -1
- package/package.json +2 -1
- package/src/BatchedRenderer.ts +94 -2
- package/src/ParticleSystem.ts +182 -59
- package/src/QuarksLoader.ts +3 -0
- package/src/SpriteBatch.ts +25 -12
- package/src/TrailBatch.ts +167 -93
- package/src/VFXBatch.ts +7 -1
- package/src/geometryUtil.ts +15 -0
- package/src/index.ts +0 -2
|
@@ -25,16 +25,35 @@ export interface VFXBatchSettings {
|
|
|
25
25
|
texture: Texture | null;
|
|
26
26
|
layerMask: number;
|
|
27
27
|
}
|
|
28
|
+
export interface AdaptivePerformanceOptions {
|
|
29
|
+
targetFrameMs: number;
|
|
30
|
+
minQuality: number;
|
|
31
|
+
maxQuality: number;
|
|
32
|
+
decreaseStep: number;
|
|
33
|
+
increaseStep: number;
|
|
34
|
+
}
|
|
35
|
+
export interface AdaptivePerformanceState extends AdaptivePerformanceOptions {
|
|
36
|
+
enabled: boolean;
|
|
37
|
+
currentQuality: number;
|
|
38
|
+
lastFrameCpuMs: number;
|
|
39
|
+
}
|
|
28
40
|
export declare class BatchedRenderer extends TransformNode {
|
|
29
41
|
batches: Array<VFXBatch>;
|
|
30
42
|
systemToBatchIndex: Map<IParticleSystem, number>;
|
|
31
43
|
depthTexture: BaseTexture | null;
|
|
44
|
+
private adaptivePerformanceState;
|
|
45
|
+
private lastAppliedQuality;
|
|
32
46
|
constructor(name: string, scene: Scene);
|
|
47
|
+
private static clamp;
|
|
33
48
|
private static equals;
|
|
34
49
|
addSystem(system: IParticleSystem): void;
|
|
35
50
|
deleteSystem(system: IParticleSystem): void;
|
|
36
51
|
updateSystem(system: IParticleSystem): void;
|
|
37
52
|
setDepthTexture(depthTexture: BaseTexture | null): void;
|
|
53
|
+
configureAdaptivePerformance(options?: Partial<AdaptivePerformanceOptions>): void;
|
|
54
|
+
disableAdaptivePerformance(resetQuality?: boolean): void;
|
|
55
|
+
getAdaptivePerformanceState(): AdaptivePerformanceState;
|
|
56
|
+
private applyAdaptiveQuality;
|
|
38
57
|
update(delta: number): void;
|
|
39
58
|
dispose(): void;
|
|
40
59
|
}
|
|
@@ -162,6 +162,8 @@ export declare class ParticleSystem implements IParticleSystem {
|
|
|
162
162
|
private listeners;
|
|
163
163
|
private readonly layerMaskProxy;
|
|
164
164
|
private materialRef;
|
|
165
|
+
private qualityFactor;
|
|
166
|
+
private useFastTrailHistory;
|
|
165
167
|
/** @internal **/
|
|
166
168
|
_renderer?: BatchedRenderer;
|
|
167
169
|
set time(time: number);
|
|
@@ -198,6 +200,12 @@ export declare class ParticleSystem implements IParticleSystem {
|
|
|
198
200
|
pause(): void;
|
|
199
201
|
play(): void;
|
|
200
202
|
stop(): void;
|
|
203
|
+
private refreshTrailHistoryMode;
|
|
204
|
+
private ensureTrailHistoryCapacity;
|
|
205
|
+
private resetTrailHistory;
|
|
206
|
+
private getTrailHistoryCount;
|
|
207
|
+
private updateFastTrailHistory;
|
|
208
|
+
setQualityFactor(qualityFactor: number): void;
|
|
201
209
|
private spawn;
|
|
202
210
|
endEmit(): void;
|
|
203
211
|
dispose(): void;
|
|
@@ -14,6 +14,7 @@ export declare class TrailBatch extends VFXBatch {
|
|
|
14
14
|
private nextVB;
|
|
15
15
|
private sideVB;
|
|
16
16
|
private widthVB;
|
|
17
|
+
private getTrailHistoryCount;
|
|
17
18
|
constructor(settings: VFXBatchSettings, scene: Scene);
|
|
18
19
|
setupBuffers(): void;
|
|
19
20
|
expandBuffers(target: number): void;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: "\nattribute vec3 position;\nattribute vec2 uv;\nattribute vec3 normal;\nattribute vec3 offset;\nattribute vec4 color;\nattribute vec3 size;\nattribute vec4 rotation;\nattribute float uvTile;\n\nuniform mat4 world;\nuniform mat4 view;\nuniform mat4 projection;\n\n#ifdef UV_TILE\nuniform float tileCountX;\nuniform float tileCountY;\n#endif\n\nvarying vec2 vUV;\nvarying vec4 vColor;\nvarying vec3 vNormal;\nvarying vec3 vWorldPos;\n\n#ifdef SOFT_PARTICLES\nvarying vec4 projPosition;\nvarying float linearDepth;\n#endif\n\nvec3 applyQuaternion(vec3 v, vec4 q) {\n vec3 qVec = q.xyz;\n float qW = q.w;\n vec3 t = 2.0 * cross(qVec, v);\n return v + qW * t + cross(qVec, t);\n}\n\nvoid main() {\n vec3 scaledPosition = position * size;\n vec3 rotatedPosition = applyQuaternion(scaledPosition, rotation);\n vec3 rotatedNormal = normalize(applyQuaternion(normal, rotation));\n vec3 localPos = rotatedPosition + offset;\n\n vec4 worldPos = world * vec4(localPos, 1.0);\n vec4 viewPos = view * worldPos;\n gl_Position = projection * viewPos;\n\n vWorldPos = worldPos.xyz;\n vNormal = normalize((world * vec4(rotatedNormal, 0.0)).xyz);\n\n#ifdef SOFT_PARTICLES\n projPosition = gl_Position;\n linearDepth = -viewPos.z;\n#endif\n\n#ifdef UV_TILE\n vec2 tc = vec2(tileCountX, tileCountY);\n float tileU = mod(
|
|
1
|
+
declare const _default: "\nattribute vec3 position;\nattribute vec2 uv;\nattribute vec3 normal;\nattribute vec3 offset;\nattribute vec4 color;\nattribute vec3 size;\nattribute vec4 rotation;\nattribute float uvTile;\n\nuniform mat4 world;\nuniform mat4 view;\nuniform mat4 projection;\n\n#ifdef UV_TILE\nuniform float tileCountX;\nuniform float tileCountY;\n#endif\n\nvarying vec2 vUV;\nvarying vec4 vColor;\nvarying vec3 vNormal;\nvarying vec3 vWorldPos;\n\n#ifdef TILE_BLEND\nvarying vec2 vUV2;\nvarying float vTileBlend;\n#endif\n\n#ifdef SOFT_PARTICLES\nvarying vec4 projPosition;\nvarying float linearDepth;\n#endif\n\nvec3 applyQuaternion(vec3 v, vec4 q) {\n vec3 qVec = q.xyz;\n float qW = q.w;\n vec3 t = 2.0 * cross(qVec, v);\n return v + qW * t + cross(qVec, t);\n}\n\nvoid main() {\n vec3 scaledPosition = position * size;\n vec3 rotatedPosition = applyQuaternion(scaledPosition, rotation);\n vec3 rotatedNormal = normalize(applyQuaternion(normal, rotation));\n vec3 localPos = rotatedPosition + offset;\n\n vec4 worldPos = world * vec4(localPos, 1.0);\n vec4 viewPos = view * worldPos;\n gl_Position = projection * viewPos;\n\n vWorldPos = worldPos.xyz;\n vNormal = normalize((world * vec4(rotatedNormal, 0.0)).xyz);\n\n#ifdef SOFT_PARTICLES\n projPosition = gl_Position;\n linearDepth = -viewPos.z;\n#endif\n\n#ifdef UV_TILE\n vec2 tc = vec2(tileCountX, tileCountY);\n float baseTile = floor(uvTile);\n float tileU = mod(baseTile, tc.x) / tc.x;\n float tileV = 1.0 - floor(baseTile / tc.x) / tc.y - 1.0 / tc.y;\n vUV = uv / tc + vec2(tileU, tileV);\n #ifdef TILE_BLEND\n float nextTile = ceil(uvTile);\n float nextU = mod(nextTile, tc.x) / tc.x;\n float nextV = 1.0 - floor(nextTile / tc.x) / tc.y - 1.0 / tc.y;\n vUV2 = uv / tc + vec2(nextU, nextV);\n vTileBlend = fract(uvTile);\n #endif\n#else\n vUV = uv;\n#endif\n\n vColor = color;\n}\n";
|
|
2
2
|
export default _default;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "babylon.quarks",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.4",
|
|
4
4
|
"description": "A High-Performance Particle System for Babylon.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./dist/types/index.d.ts",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"build:js": "cross-env NODE_ENV=production rollup -c",
|
|
26
26
|
"build": "npm run clean && npm run build:types && npm run build:js",
|
|
27
27
|
"test": "jest",
|
|
28
|
+
"test:coverage": "jest --coverage",
|
|
28
29
|
"prettier": "prettier --write --ignore-unknown **/*.ts"
|
|
29
30
|
},
|
|
30
31
|
"repository": {
|
package/src/BatchedRenderer.ts
CHANGED
|
@@ -30,15 +30,44 @@ export interface VFXBatchSettings {
|
|
|
30
30
|
layerMask: number;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
export interface AdaptivePerformanceOptions {
|
|
34
|
+
targetFrameMs: number;
|
|
35
|
+
minQuality: number;
|
|
36
|
+
maxQuality: number;
|
|
37
|
+
decreaseStep: number;
|
|
38
|
+
increaseStep: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface AdaptivePerformanceState extends AdaptivePerformanceOptions {
|
|
42
|
+
enabled: boolean;
|
|
43
|
+
currentQuality: number;
|
|
44
|
+
lastFrameCpuMs: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
33
47
|
export class BatchedRenderer extends TransformNode {
|
|
34
48
|
batches: Array<VFXBatch> = [];
|
|
35
49
|
systemToBatchIndex: Map<IParticleSystem, number> = new Map<IParticleSystem, number>();
|
|
36
50
|
depthTexture: BaseTexture | null = null;
|
|
51
|
+
private adaptivePerformanceState: AdaptivePerformanceState = {
|
|
52
|
+
enabled: false,
|
|
53
|
+
targetFrameMs: 16.7,
|
|
54
|
+
minQuality: 0.35,
|
|
55
|
+
maxQuality: 1,
|
|
56
|
+
decreaseStep: 0.08,
|
|
57
|
+
increaseStep: 0.02,
|
|
58
|
+
currentQuality: 1,
|
|
59
|
+
lastFrameCpuMs: 0,
|
|
60
|
+
};
|
|
61
|
+
private lastAppliedQuality = Number.NaN;
|
|
37
62
|
|
|
38
63
|
constructor(name: string, scene: Scene) {
|
|
39
64
|
super(name, scene);
|
|
40
65
|
}
|
|
41
66
|
|
|
67
|
+
private static clamp(value: number, min: number, max: number): number {
|
|
68
|
+
return Math.max(min, Math.min(max, value));
|
|
69
|
+
}
|
|
70
|
+
|
|
42
71
|
private static equals(a: StoredBatchSettings, b: VFXBatchSettings): boolean {
|
|
43
72
|
return (
|
|
44
73
|
a.materialBlendMode === b.materialBlendMode &&
|
|
@@ -62,6 +91,9 @@ export class BatchedRenderer extends TransformNode {
|
|
|
62
91
|
|
|
63
92
|
addSystem(system: IParticleSystem) {
|
|
64
93
|
(system as unknown as ParticleSystem)._renderer = this;
|
|
94
|
+
if (this.adaptivePerformanceState.enabled) {
|
|
95
|
+
(system as any).setQualityFactor?.(this.adaptivePerformanceState.currentQuality);
|
|
96
|
+
}
|
|
65
97
|
const settings = (system as unknown as ParticleSystem).getRendererSettings();
|
|
66
98
|
for (let i = 0; i < this.batches.length; i++) {
|
|
67
99
|
if (BatchedRenderer.equals(this.batches[i].settings, settings)) {
|
|
@@ -115,13 +147,73 @@ export class BatchedRenderer extends TransformNode {
|
|
|
115
147
|
}
|
|
116
148
|
}
|
|
117
149
|
|
|
150
|
+
configureAdaptivePerformance(options: Partial<AdaptivePerformanceOptions> = {}) {
|
|
151
|
+
const state = this.adaptivePerformanceState;
|
|
152
|
+
const minQuality = BatchedRenderer.clamp(options.minQuality ?? state.minQuality, 0.05, 1);
|
|
153
|
+
const maxQuality = BatchedRenderer.clamp(options.maxQuality ?? state.maxQuality, minQuality, 1);
|
|
154
|
+
state.targetFrameMs = Math.max(options.targetFrameMs ?? state.targetFrameMs, 0.1);
|
|
155
|
+
state.minQuality = minQuality;
|
|
156
|
+
state.maxQuality = maxQuality;
|
|
157
|
+
state.decreaseStep = Math.max(options.decreaseStep ?? state.decreaseStep, 0.001);
|
|
158
|
+
state.increaseStep = Math.max(options.increaseStep ?? state.increaseStep, 0.001);
|
|
159
|
+
state.currentQuality = BatchedRenderer.clamp(state.currentQuality, state.minQuality, state.maxQuality);
|
|
160
|
+
state.enabled = true;
|
|
161
|
+
this.lastAppliedQuality = Number.NaN;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
disableAdaptivePerformance(resetQuality = true) {
|
|
165
|
+
this.adaptivePerformanceState.enabled = false;
|
|
166
|
+
if (resetQuality) {
|
|
167
|
+
this.adaptivePerformanceState.currentQuality = this.adaptivePerformanceState.maxQuality;
|
|
168
|
+
this.applyAdaptiveQuality();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
getAdaptivePerformanceState(): AdaptivePerformanceState {
|
|
173
|
+
return {...this.adaptivePerformanceState};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private applyAdaptiveQuality() {
|
|
177
|
+
const quality = this.adaptivePerformanceState.currentQuality;
|
|
178
|
+
if (quality === this.lastAppliedQuality) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
for (const ps of this.systemToBatchIndex.keys()) {
|
|
182
|
+
(ps as any).setQualityFactor?.(quality);
|
|
183
|
+
}
|
|
184
|
+
this.lastAppliedQuality = quality;
|
|
185
|
+
}
|
|
186
|
+
|
|
118
187
|
update(delta: number) {
|
|
119
|
-
|
|
188
|
+
const adaptiveState = this.adaptivePerformanceState;
|
|
189
|
+
const frameStart = adaptiveState.enabled ? performance.now() : 0;
|
|
190
|
+
if (adaptiveState.enabled) {
|
|
191
|
+
this.applyAdaptiveQuality();
|
|
192
|
+
}
|
|
193
|
+
for (const ps of this.systemToBatchIndex.keys()) {
|
|
120
194
|
(ps as any).update(delta);
|
|
121
|
-
}
|
|
195
|
+
}
|
|
122
196
|
for (let i = 0; i < this.batches.length; i++) {
|
|
123
197
|
this.batches[i].update();
|
|
124
198
|
}
|
|
199
|
+
if (!adaptiveState.enabled) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
adaptiveState.lastFrameCpuMs = performance.now() - frameStart;
|
|
204
|
+
if (adaptiveState.lastFrameCpuMs > adaptiveState.targetFrameMs) {
|
|
205
|
+
adaptiveState.currentQuality = BatchedRenderer.clamp(
|
|
206
|
+
adaptiveState.currentQuality - adaptiveState.decreaseStep,
|
|
207
|
+
adaptiveState.minQuality,
|
|
208
|
+
adaptiveState.maxQuality
|
|
209
|
+
);
|
|
210
|
+
} else {
|
|
211
|
+
adaptiveState.currentQuality = BatchedRenderer.clamp(
|
|
212
|
+
adaptiveState.currentQuality + adaptiveState.increaseStep,
|
|
213
|
+
adaptiveState.minQuality,
|
|
214
|
+
adaptiveState.maxQuality
|
|
215
|
+
);
|
|
216
|
+
}
|
|
125
217
|
}
|
|
126
218
|
|
|
127
219
|
dispose(): void {
|
package/src/ParticleSystem.ts
CHANGED
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
import {ParticleEmitter} from './ParticleEmitter';
|
|
42
42
|
import {RenderMode} from './VFXBatch';
|
|
43
43
|
import {BatchedRenderer, VFXBatchSettings} from './BatchedRenderer';
|
|
44
|
+
import {ensureTriangleIndices} from './geometryUtil';
|
|
44
45
|
|
|
45
46
|
export interface BurstParameters {
|
|
46
47
|
time: number;
|
|
@@ -206,6 +207,8 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
206
207
|
private listeners: {[event: string]: Array<(event: ParticleSystemEvent) => void>} = {};
|
|
207
208
|
private readonly layerMaskProxy: {mask: number};
|
|
208
209
|
private materialRef: any = null;
|
|
210
|
+
private qualityFactor = 1;
|
|
211
|
+
private useFastTrailHistory = true;
|
|
209
212
|
/** @internal **/
|
|
210
213
|
_renderer?: BatchedRenderer;
|
|
211
214
|
|
|
@@ -456,6 +459,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
456
459
|
|
|
457
460
|
this.emissionBursts.forEach((burst) => burst.count.startGen(this.memory));
|
|
458
461
|
this.emissionOverDistance.startGen(this.memory);
|
|
462
|
+
this.refreshTrailHistoryMode();
|
|
459
463
|
|
|
460
464
|
this.emitEnded = false;
|
|
461
465
|
this.markForDestroy = false;
|
|
@@ -474,6 +478,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
474
478
|
layerMask?: number;
|
|
475
479
|
} = {}
|
|
476
480
|
) {
|
|
481
|
+
/* istanbul ignore if -- rendererSettings is always assigned in the constructor before any code path can call this private method */
|
|
477
482
|
if (!this.rendererSettings) {
|
|
478
483
|
return;
|
|
479
484
|
}
|
|
@@ -534,17 +539,93 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
534
539
|
play() { this.paused = false; }
|
|
535
540
|
stop() { this.restart(); this.pause(); }
|
|
536
541
|
|
|
542
|
+
private refreshTrailHistoryMode() {
|
|
543
|
+
this.useFastTrailHistory = !this.behaviors.some((behavior) => (behavior as any).type === 'WidthOverLength');
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
private ensureTrailHistoryCapacity(particle: TrailParticle, capacity: number) {
|
|
547
|
+
const trailAny = particle as any;
|
|
548
|
+
if (trailAny._trailHistoryCapacity === capacity && trailAny._trailHistoryPositions) {
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
trailAny._trailHistoryCapacity = capacity;
|
|
552
|
+
trailAny._trailHistoryPositions = new Float32Array(capacity * 3);
|
|
553
|
+
trailAny._trailHistorySizes = new Float32Array(capacity);
|
|
554
|
+
trailAny._trailHistoryColors = new Float32Array(capacity * 4);
|
|
555
|
+
trailAny._trailHistoryHead = 0;
|
|
556
|
+
trailAny._trailHistoryCount = 0;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
private resetTrailHistory(particle: TrailParticle) {
|
|
560
|
+
const trailAny = particle as any;
|
|
561
|
+
trailAny._trailHistoryHead = 0;
|
|
562
|
+
trailAny._trailHistoryCount = 0;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
private getTrailHistoryCount(particle: TrailParticle): number {
|
|
566
|
+
const trailAny = particle as any;
|
|
567
|
+
const fastHistoryCount = trailAny._trailHistoryCount;
|
|
568
|
+
if (typeof fastHistoryCount === 'number') {
|
|
569
|
+
return fastHistoryCount;
|
|
570
|
+
}
|
|
571
|
+
return particle.previous.length;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
private updateFastTrailHistory(particle: TrailParticle) {
|
|
575
|
+
const trailAny = particle as any;
|
|
576
|
+
const capacity = Math.max(1, Math.ceil(particle.length));
|
|
577
|
+
this.ensureTrailHistoryCapacity(particle, capacity);
|
|
578
|
+
let head = trailAny._trailHistoryHead as number;
|
|
579
|
+
let count = trailAny._trailHistoryCount as number;
|
|
580
|
+
const positions = trailAny._trailHistoryPositions as Float32Array;
|
|
581
|
+
const sizes = trailAny._trailHistorySizes as Float32Array;
|
|
582
|
+
const colors = trailAny._trailHistoryColors as Float32Array;
|
|
583
|
+
|
|
584
|
+
if (particle.age <= particle.life) {
|
|
585
|
+
const posIndex = head * 3;
|
|
586
|
+
positions[posIndex] = particle.position.x;
|
|
587
|
+
positions[posIndex + 1] = particle.position.y;
|
|
588
|
+
positions[posIndex + 2] = particle.position.z;
|
|
589
|
+
sizes[head] = particle.size.x;
|
|
590
|
+
const colorIndex = head * 4;
|
|
591
|
+
colors[colorIndex] = particle.color.x;
|
|
592
|
+
colors[colorIndex + 1] = particle.color.y;
|
|
593
|
+
colors[colorIndex + 2] = particle.color.z;
|
|
594
|
+
colors[colorIndex + 3] = particle.color.w;
|
|
595
|
+
head = (head + 1) % capacity;
|
|
596
|
+
if (count < capacity) {
|
|
597
|
+
count++;
|
|
598
|
+
}
|
|
599
|
+
} else if (count > 0) {
|
|
600
|
+
count--;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
trailAny._trailHistoryHead = head;
|
|
604
|
+
trailAny._trailHistoryCount = count;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
setQualityFactor(qualityFactor: number) {
|
|
608
|
+
this.qualityFactor = Math.max(0.1, Math.min(1, qualityFactor));
|
|
609
|
+
}
|
|
610
|
+
|
|
537
611
|
private spawn(count: number, emissionState: EmissionState, matrix: Matrix4) {
|
|
538
612
|
tempQ.setFromRotationMatrix(matrix);
|
|
539
613
|
const translation = tempV;
|
|
540
614
|
const scale = tempV2;
|
|
541
615
|
matrix.decompose(translation, tempQ, scale);
|
|
616
|
+
const behaviors = this.behaviors;
|
|
617
|
+
const behaviorCount = behaviors.length;
|
|
618
|
+
const renderMode = this.rendererSettings.renderMode;
|
|
619
|
+
const isTrailMode = renderMode === RenderMode.Trail;
|
|
620
|
+
const timeRatio = emissionState.time / this.duration;
|
|
621
|
+
const trailSettings = isTrailMode ? (this.rendererEmitterSettings as TrailSettings) : undefined;
|
|
622
|
+
const followLocalOrigin = Boolean(trailSettings?.followLocalOrigin);
|
|
542
623
|
|
|
543
624
|
for (let i = 0; i < count; i++) {
|
|
544
625
|
emissionState.burstParticleIndex = i;
|
|
545
626
|
this.particleNum++;
|
|
546
|
-
|
|
547
|
-
if (
|
|
627
|
+
if (this.particles.length < this.particleNum) {
|
|
628
|
+
if (isTrailMode) {
|
|
548
629
|
this.particles.push(new TrailParticle());
|
|
549
630
|
} else {
|
|
550
631
|
this.particles.push(new SpriteParticle());
|
|
@@ -557,15 +638,15 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
557
638
|
this.startColor.genColor(particle.memory, particle.startColor, this.emissionState.time);
|
|
558
639
|
particle.color.copy(particle.startColor);
|
|
559
640
|
this.startSpeed.startGen(particle.memory);
|
|
560
|
-
particle.startSpeed = this.startSpeed.genValue(particle.memory,
|
|
641
|
+
particle.startSpeed = this.startSpeed.genValue(particle.memory, timeRatio);
|
|
561
642
|
this.startLife.startGen(particle.memory);
|
|
562
|
-
particle.life = this.startLife.genValue(particle.memory,
|
|
643
|
+
particle.life = this.startLife.genValue(particle.memory, timeRatio);
|
|
563
644
|
particle.age = 0;
|
|
564
645
|
this.startSize.startGen(particle.memory);
|
|
565
646
|
if (this.startSize.type === 'vec3function') {
|
|
566
|
-
(this.startSize as Vector3Generator).genValue(particle.memory, particle.startSize,
|
|
647
|
+
(this.startSize as Vector3Generator).genValue(particle.memory, particle.startSize, timeRatio);
|
|
567
648
|
} else {
|
|
568
|
-
const size = (this.startSize as FunctionValueGenerator).genValue(particle.memory,
|
|
649
|
+
const size = (this.startSize as FunctionValueGenerator).genValue(particle.memory, timeRatio);
|
|
569
650
|
particle.startSize.set(size, size, size);
|
|
570
651
|
}
|
|
571
652
|
this.startTileIndex.startGen(particle.memory);
|
|
@@ -581,33 +662,40 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
581
662
|
) {
|
|
582
663
|
const sprite = particle as SpriteParticle;
|
|
583
664
|
this.startRotation.startGen(particle.memory);
|
|
584
|
-
if (
|
|
665
|
+
if (renderMode === RenderMode.Mesh) {
|
|
585
666
|
if (!(sprite.rotation instanceof Quaternion)) {
|
|
586
667
|
sprite.rotation = new Quaternion();
|
|
587
668
|
}
|
|
588
669
|
if (this.startRotation.type === 'rotation') {
|
|
589
|
-
this.startRotation.genValue(particle.memory, sprite.rotation as Quaternion, 1,
|
|
670
|
+
this.startRotation.genValue(particle.memory, sprite.rotation as Quaternion, 1, timeRatio);
|
|
590
671
|
} else {
|
|
591
|
-
(sprite.rotation as Quaternion).setFromAxisAngle(UP, this.startRotation.genValue(sprite.memory,
|
|
672
|
+
(sprite.rotation as Quaternion).setFromAxisAngle(UP, this.startRotation.genValue(sprite.memory, timeRatio));
|
|
592
673
|
}
|
|
593
674
|
} else {
|
|
594
675
|
if (this.startRotation.type === 'rotation') {
|
|
595
676
|
sprite.rotation = 0;
|
|
596
677
|
} else {
|
|
597
|
-
sprite.rotation = this.startRotation.genValue(sprite.memory,
|
|
678
|
+
sprite.rotation = this.startRotation.genValue(sprite.memory, timeRatio);
|
|
598
679
|
}
|
|
599
680
|
}
|
|
600
|
-
} else if (
|
|
681
|
+
} else if (isTrailMode && trailSettings) {
|
|
601
682
|
const trail = particle as TrailParticle;
|
|
602
|
-
|
|
603
|
-
trail.length =
|
|
683
|
+
trailSettings.startLength.startGen(trail.memory);
|
|
684
|
+
trail.length = trailSettings.startLength.genValue(trail.memory, timeRatio);
|
|
685
|
+
if (this.useFastTrailHistory) {
|
|
686
|
+
this.ensureTrailHistoryCapacity(trail, Math.max(1, Math.ceil(trail.length)));
|
|
687
|
+
this.resetTrailHistory(trail);
|
|
688
|
+
}
|
|
604
689
|
}
|
|
605
690
|
|
|
606
691
|
this.emitterShape.initialize(particle, emissionState);
|
|
607
692
|
|
|
608
|
-
if (
|
|
693
|
+
if (isTrailMode && followLocalOrigin) {
|
|
609
694
|
const trail = particle as TrailParticle;
|
|
610
|
-
|
|
695
|
+
if (!trail.localPosition) {
|
|
696
|
+
trail.localPosition = new Vector3();
|
|
697
|
+
}
|
|
698
|
+
trail.localPosition.copy(trail.position);
|
|
611
699
|
}
|
|
612
700
|
|
|
613
701
|
if (this.worldSpace) {
|
|
@@ -624,8 +712,8 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
624
712
|
}
|
|
625
713
|
}
|
|
626
714
|
|
|
627
|
-
for (let j = 0; j <
|
|
628
|
-
|
|
715
|
+
for (let j = 0; j < behaviorCount; j++) {
|
|
716
|
+
behaviors[j].initialize(particle, this);
|
|
629
717
|
}
|
|
630
718
|
}
|
|
631
719
|
}
|
|
@@ -697,49 +785,66 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
697
785
|
this.emit(delta, this.emissionState, this.emitter.matrixWorld);
|
|
698
786
|
}
|
|
699
787
|
|
|
788
|
+
const particles = this.particles;
|
|
789
|
+
const behaviorCount = this.behaviors.length;
|
|
790
|
+
const particleCount = this.particleNum;
|
|
700
791
|
this.emitterShape.update(this, delta);
|
|
701
|
-
for (let j = 0; j <
|
|
702
|
-
this.behaviors[j]
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
792
|
+
for (let j = 0; j < behaviorCount; j++) {
|
|
793
|
+
const behavior = this.behaviors[j];
|
|
794
|
+
behavior.frameUpdate(delta);
|
|
795
|
+
for (let i = 0; i < particleCount; i++) {
|
|
796
|
+
const particle = particles[i];
|
|
797
|
+
if (!particle.died) {
|
|
798
|
+
behavior.update(particle, delta);
|
|
706
799
|
}
|
|
707
800
|
}
|
|
708
801
|
}
|
|
709
802
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
803
|
+
const followLocalOrigin =
|
|
804
|
+
this.rendererSettings.renderMode === RenderMode.Trail &&
|
|
805
|
+
(this.rendererEmitterSettings as TrailSettings).followLocalOrigin;
|
|
806
|
+
const emitterMatrix = this.emitter.matrixWorld;
|
|
807
|
+
for (let i = 0; i < particleCount; i++) {
|
|
808
|
+
const particle = particles[i];
|
|
809
|
+
if (followLocalOrigin && (particle as TrailParticle).localPosition) {
|
|
810
|
+
particle.position.copy((particle as TrailParticle).localPosition!);
|
|
811
|
+
if (particle.parentMatrix) {
|
|
812
|
+
particle.position.applyMatrix4(particle.parentMatrix);
|
|
718
813
|
} else {
|
|
719
|
-
|
|
814
|
+
particle.position.applyMatrix4(emitterMatrix);
|
|
720
815
|
}
|
|
721
816
|
} else {
|
|
722
|
-
|
|
817
|
+
particle.position.addScaledVector(particle.velocity, delta * particle.speedModifier);
|
|
723
818
|
}
|
|
724
|
-
|
|
819
|
+
particle.age += delta;
|
|
725
820
|
}
|
|
726
821
|
|
|
727
822
|
if (this.rendererSettings.renderMode === RenderMode.Trail) {
|
|
728
|
-
for (let i = 0; i <
|
|
729
|
-
|
|
823
|
+
for (let i = 0; i < particleCount; i++) {
|
|
824
|
+
const trailParticle = particles[i] as TrailParticle;
|
|
825
|
+
if (this.useFastTrailHistory) {
|
|
826
|
+
this.updateFastTrailHistory(trailParticle);
|
|
827
|
+
} else {
|
|
828
|
+
trailParticle.update();
|
|
829
|
+
}
|
|
730
830
|
}
|
|
731
831
|
}
|
|
732
832
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
this.
|
|
833
|
+
let liveParticleCount = this.particleNum;
|
|
834
|
+
for (let i = 0; i < liveParticleCount; i++) {
|
|
835
|
+
const particle = particles[i];
|
|
836
|
+
if (
|
|
837
|
+
particle.died &&
|
|
838
|
+
(!(particle instanceof TrailParticle) || this.getTrailHistoryCount(particle as TrailParticle) === 0)
|
|
839
|
+
) {
|
|
840
|
+
particles[i] = particles[liveParticleCount - 1];
|
|
841
|
+
particles[liveParticleCount - 1] = particle;
|
|
842
|
+
liveParticleCount--;
|
|
739
843
|
i--;
|
|
740
844
|
this.fire({type: 'particleDied', particleSystem: this, particle: particle});
|
|
741
845
|
}
|
|
742
846
|
}
|
|
847
|
+
this.particleNum = liveParticleCount;
|
|
743
848
|
}
|
|
744
849
|
|
|
745
850
|
emit(delta: number, emissionState: EmissionState, emitterMatrix: Matrix4) {
|
|
@@ -756,17 +861,22 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
756
861
|
}
|
|
757
862
|
|
|
758
863
|
this.normalMatrix.getNormalMatrix(emitterMatrix);
|
|
864
|
+
const emissionBursts = this.emissionBursts;
|
|
865
|
+
const emissionBurstCount = emissionBursts.length;
|
|
866
|
+
const qualityFactor = this.qualityFactor;
|
|
759
867
|
|
|
760
868
|
const totalSpawn = Math.ceil(emissionState.waitEmiting);
|
|
761
869
|
this.spawn(totalSpawn, emissionState, emitterMatrix);
|
|
762
870
|
emissionState.waitEmiting -= totalSpawn;
|
|
763
871
|
|
|
764
872
|
while (
|
|
765
|
-
emissionState.burstIndex <
|
|
766
|
-
|
|
873
|
+
emissionState.burstIndex < emissionBurstCount &&
|
|
874
|
+
emissionBursts[emissionState.burstIndex].time <= emissionState.time
|
|
767
875
|
) {
|
|
768
|
-
|
|
769
|
-
|
|
876
|
+
const burst = emissionBursts[emissionState.burstIndex];
|
|
877
|
+
if (Math.random() < burst.probability) {
|
|
878
|
+
const rawCount = burst.count.genValue(this.memory, this.time);
|
|
879
|
+
const count = qualityFactor >= 0.999 ? rawCount : Math.floor(rawCount * qualityFactor);
|
|
770
880
|
emissionState.isBursting = true;
|
|
771
881
|
emissionState.burstParticleCount = count;
|
|
772
882
|
this.spawn(count, emissionState, emitterMatrix);
|
|
@@ -776,12 +886,14 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
776
886
|
}
|
|
777
887
|
|
|
778
888
|
if (!this.emitEnded) {
|
|
779
|
-
|
|
889
|
+
const timeRatio = emissionState.time / this.duration;
|
|
890
|
+
emissionState.waitEmiting += delta * this.emissionOverTime.genValue(this.memory, timeRatio) * qualityFactor;
|
|
780
891
|
|
|
781
|
-
|
|
892
|
+
const previousWorldPos = emissionState.previousWorldPos;
|
|
893
|
+
if (previousWorldPos !== undefined) {
|
|
782
894
|
this.temp.set(emitterMatrix.elements[12], emitterMatrix.elements[13], emitterMatrix.elements[14]);
|
|
783
|
-
emissionState.travelDistance +=
|
|
784
|
-
const emitPerMeter = this.emissionOverDistance.genValue(this.memory,
|
|
895
|
+
emissionState.travelDistance += previousWorldPos.distanceTo(this.temp);
|
|
896
|
+
const emitPerMeter = this.emissionOverDistance.genValue(this.memory, timeRatio) * qualityFactor;
|
|
785
897
|
if (emissionState.travelDistance * emitPerMeter > 0) {
|
|
786
898
|
const count = Math.floor(emissionState.travelDistance * emitPerMeter);
|
|
787
899
|
emissionState.travelDistance -= count / emitPerMeter;
|
|
@@ -1031,13 +1143,18 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
1031
1143
|
uvs?: Float32Array;
|
|
1032
1144
|
normals?: Float32Array;
|
|
1033
1145
|
} {
|
|
1034
|
-
if (entry?.positions
|
|
1146
|
+
if (entry?.positions) {
|
|
1147
|
+
const positions =
|
|
1148
|
+
entry.positions instanceof Float32Array ? entry.positions : new Float32Array(entry.positions);
|
|
1149
|
+
const rawIndices =
|
|
1150
|
+
entry.indices instanceof Uint16Array || entry.indices instanceof Uint32Array
|
|
1151
|
+
? entry.indices
|
|
1152
|
+
: entry.indices
|
|
1153
|
+
? new Uint32Array(entry.indices)
|
|
1154
|
+
: undefined;
|
|
1035
1155
|
return {
|
|
1036
|
-
positions
|
|
1037
|
-
indices:
|
|
1038
|
-
entry.indices instanceof Uint16Array || entry.indices instanceof Uint32Array
|
|
1039
|
-
? entry.indices
|
|
1040
|
-
: new Uint32Array(entry.indices),
|
|
1156
|
+
positions,
|
|
1157
|
+
indices: ensureTriangleIndices(positions, rawIndices),
|
|
1041
1158
|
uvs: entry.uvs
|
|
1042
1159
|
? entry.uvs instanceof Float32Array
|
|
1043
1160
|
? entry.uvs
|
|
@@ -1052,11 +1169,16 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
1052
1169
|
}
|
|
1053
1170
|
|
|
1054
1171
|
const data = entry?.data ?? entry;
|
|
1055
|
-
if (data?.attributes?.position?.array
|
|
1056
|
-
const
|
|
1172
|
+
if (data?.attributes?.position?.array) {
|
|
1173
|
+
const positions = new Float32Array(data.attributes.position.array);
|
|
1174
|
+
let indices: Uint32Array | Uint16Array | undefined;
|
|
1175
|
+
if (data.index?.array) {
|
|
1176
|
+
const indexType = data.index.type === 'Uint16Array' ? Uint16Array : Uint32Array;
|
|
1177
|
+
indices = new indexType(data.index.array);
|
|
1178
|
+
}
|
|
1057
1179
|
return {
|
|
1058
|
-
positions
|
|
1059
|
-
indices:
|
|
1180
|
+
positions,
|
|
1181
|
+
indices: ensureTriangleIndices(positions, indices),
|
|
1060
1182
|
uvs: data.attributes.uv?.array ? new Float32Array(data.attributes.uv.array) : undefined,
|
|
1061
1183
|
normals: data.attributes.normal?.array ? new Float32Array(data.attributes.normal.array) : undefined,
|
|
1062
1184
|
};
|
|
@@ -1071,6 +1193,7 @@ export class ParticleSystem implements IParticleSystem {
|
|
|
1071
1193
|
|
|
1072
1194
|
addBehavior(behavior: Behavior) {
|
|
1073
1195
|
this.behaviors.push(behavior);
|
|
1196
|
+
this.refreshTrailHistoryMode();
|
|
1074
1197
|
}
|
|
1075
1198
|
|
|
1076
1199
|
getRendererSettings(): VFXBatchSettings {
|
package/src/QuarksLoader.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import {ParticleSystem} from './ParticleSystem';
|
|
14
14
|
import {ParticleEmitter} from './ParticleEmitter';
|
|
15
15
|
import {QuarksPrefab} from './QuarksPrefab';
|
|
16
|
+
import {ensureTriangleIndices} from './geometryUtil';
|
|
16
17
|
|
|
17
18
|
export interface QuarksLoaderOptions {
|
|
18
19
|
baseUrl?: string;
|
|
@@ -219,6 +220,8 @@ export class QuarksLoader {
|
|
|
219
220
|
}
|
|
220
221
|
}
|
|
221
222
|
|
|
223
|
+
indices = ensureTriangleIndices(positions, indices);
|
|
224
|
+
|
|
222
225
|
return {positions, indices, uvs, normals};
|
|
223
226
|
}
|
|
224
227
|
|