babylon.quarks 0.17.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.
Files changed (72) hide show
  1. package/LICENSE +21 -0
  2. package/dist/babylon.quarks.cjs +2 -0
  3. package/dist/babylon.quarks.cjs.map +1 -0
  4. package/dist/babylon.quarks.esm.js +2 -0
  5. package/dist/babylon.quarks.esm.js.map +1 -0
  6. package/dist/types/BatchedParticleRenderer.d.ts +2 -0
  7. package/dist/types/BatchedRenderer.d.ts +40 -0
  8. package/dist/types/MeshSurfaceEmitter.d.ts +19 -0
  9. package/dist/types/ParticleEmitter.d.ts +16 -0
  10. package/dist/types/ParticleSystem.d.ts +226 -0
  11. package/dist/types/QuarksLoader.d.ts +28 -0
  12. package/dist/types/QuarksPrefab.d.ts +48 -0
  13. package/dist/types/QuarksUtil.d.ts +14 -0
  14. package/dist/types/SpriteBatch.d.ts +32 -0
  15. package/dist/types/TrailBatch.d.ts +27 -0
  16. package/dist/types/VFXBatch.d.ts +51 -0
  17. package/dist/types/index.d.ts +19 -0
  18. package/dist/types/materials/ParticleMaterials.d.ts +12 -0
  19. package/dist/types/materials/index.d.ts +1 -0
  20. package/dist/types/shaders/chunks/index.d.ts +1 -0
  21. package/dist/types/shaders/chunks/register-shader-chunks.d.ts +3 -0
  22. package/dist/types/shaders/chunks/soft_fragment.glsl.d.ts +2 -0
  23. package/dist/types/shaders/chunks/soft_pars_fragment.glsl.d.ts +2 -0
  24. package/dist/types/shaders/chunks/soft_pars_vertex.glsl.d.ts +2 -0
  25. package/dist/types/shaders/chunks/soft_vertex.glsl.d.ts +2 -0
  26. package/dist/types/shaders/chunks/tile_fragment.glsl.d.ts +2 -0
  27. package/dist/types/shaders/chunks/tile_pars_fragment.glsl.d.ts +2 -0
  28. package/dist/types/shaders/chunks/tile_pars_vertex.glsl.d.ts +2 -0
  29. package/dist/types/shaders/chunks/tile_vertex.glsl.d.ts +2 -0
  30. package/dist/types/shaders/index.d.ts +9 -0
  31. package/dist/types/shaders/local_particle_physics_vert.glsl.d.ts +2 -0
  32. package/dist/types/shaders/local_particle_vert.glsl.d.ts +2 -0
  33. package/dist/types/shaders/particle_frag.glsl.d.ts +2 -0
  34. package/dist/types/shaders/particle_physics_frag.glsl.d.ts +2 -0
  35. package/dist/types/shaders/particle_vert.glsl.d.ts +2 -0
  36. package/dist/types/shaders/stretched_bb_particle_vert.glsl.d.ts +2 -0
  37. package/dist/types/shaders/trail_frag.glsl.d.ts +2 -0
  38. package/dist/types/shaders/trail_vert.glsl.d.ts +2 -0
  39. package/package.json +58 -0
  40. package/src/BatchedParticleRenderer.ts +3 -0
  41. package/src/BatchedRenderer.ts +135 -0
  42. package/src/MeshSurfaceEmitter.ts +137 -0
  43. package/src/ParticleEmitter.ts +86 -0
  44. package/src/ParticleSystem.ts +1162 -0
  45. package/src/QuarksLoader.ts +503 -0
  46. package/src/QuarksPrefab.ts +307 -0
  47. package/src/QuarksUtil.ts +66 -0
  48. package/src/SpriteBatch.ts +391 -0
  49. package/src/TrailBatch.ts +345 -0
  50. package/src/VFXBatch.ts +102 -0
  51. package/src/index.ts +54 -0
  52. package/src/materials/ParticleMaterials.ts +23 -0
  53. package/src/materials/index.ts +1 -0
  54. package/src/shaders/chunks/index.ts +1 -0
  55. package/src/shaders/chunks/register-shader-chunks.ts +32 -0
  56. package/src/shaders/chunks/soft_fragment.glsl.ts +13 -0
  57. package/src/shaders/chunks/soft_pars_fragment.glsl.ts +20 -0
  58. package/src/shaders/chunks/soft_pars_vertex.glsl.ts +6 -0
  59. package/src/shaders/chunks/soft_vertex.glsl.ts +6 -0
  60. package/src/shaders/chunks/tile_fragment.glsl.ts +9 -0
  61. package/src/shaders/chunks/tile_pars_fragment.glsl.ts +9 -0
  62. package/src/shaders/chunks/tile_pars_vertex.glsl.ts +28 -0
  63. package/src/shaders/chunks/tile_vertex.glsl.ts +18 -0
  64. package/src/shaders/index.ts +9 -0
  65. package/src/shaders/local_particle_physics_vert.glsl.ts +66 -0
  66. package/src/shaders/local_particle_vert.glsl.ts +50 -0
  67. package/src/shaders/particle_frag.glsl.ts +61 -0
  68. package/src/shaders/particle_physics_frag.glsl.ts +67 -0
  69. package/src/shaders/particle_vert.glsl.ts +85 -0
  70. package/src/shaders/stretched_bb_particle_vert.glsl.ts +74 -0
  71. package/src/shaders/trail_frag.glsl.ts +21 -0
  72. package/src/shaders/trail_vert.glsl.ts +58 -0
@@ -0,0 +1,503 @@
1
+ import {Scene} from '@babylonjs/core/scene';
2
+ import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
3
+ import {Mesh} from '@babylonjs/core/Meshes/mesh';
4
+ import {VertexData} from '@babylonjs/core/Meshes/mesh.vertexData';
5
+ import {Texture} from '@babylonjs/core/Materials/Textures/texture';
6
+ import {StandardMaterial} from '@babylonjs/core/Materials/standardMaterial';
7
+ import {Constants} from '@babylonjs/core/Engines/constants';
8
+ import {Matrix, Quaternion, Vector3} from '@babylonjs/core/Maths/math.vector';
9
+ import {
10
+ Behavior,
11
+ EmitSubParticleSystem,
12
+ } from 'quarks.core';
13
+ import {ParticleSystem} from './ParticleSystem';
14
+ import {ParticleEmitter} from './ParticleEmitter';
15
+ import {QuarksPrefab} from './QuarksPrefab';
16
+
17
+ export interface QuarksLoaderOptions {
18
+ baseUrl?: string;
19
+ }
20
+
21
+ interface ParsedGeometry {
22
+ positions: Float32Array;
23
+ indices: Uint32Array | Uint16Array;
24
+ uvs?: Float32Array;
25
+ normals?: Float32Array;
26
+ }
27
+
28
+ interface LoadedMeta {
29
+ textures: {[uuid: string]: Texture | null};
30
+ geometries: {[uuid: string]: ParsedGeometry};
31
+ materials: {[uuid: string]: any};
32
+ }
33
+
34
+ export class QuarksLoader {
35
+ private scene: Scene;
36
+ private options: QuarksLoaderOptions;
37
+
38
+ constructor(scene: Scene, options: QuarksLoaderOptions = {}) {
39
+ this.scene = scene;
40
+ this.options = options;
41
+ }
42
+
43
+ async load(url: string): Promise<TransformNode> {
44
+ const baseUrl = this.options.baseUrl || url.substring(0, url.lastIndexOf('/') + 1);
45
+ const response = await fetch(url);
46
+ const json = await response.json();
47
+ return this.parse(json, baseUrl);
48
+ }
49
+
50
+ parse(json: any, baseUrl: string = ''): TransformNode {
51
+ const meta: LoadedMeta = {
52
+ textures: {},
53
+ geometries: {},
54
+ materials: {},
55
+ };
56
+
57
+ if (json.geometries) {
58
+ this.parseGeometries(json.geometries, meta);
59
+ }
60
+ if (json.images) {
61
+ this.parseImages(json.images, baseUrl, meta);
62
+ }
63
+ if (json.textures) {
64
+ this.parseTextures(json.textures, meta);
65
+ }
66
+ if (json.materials) {
67
+ this.parseMaterials(json.materials, meta);
68
+ }
69
+
70
+ const root = this.parseObject(json.object, meta);
71
+ this.linkReferences(root);
72
+ return root;
73
+ }
74
+
75
+ private parseGeometries(geometries: any[], meta: LoadedMeta): void {
76
+ for (const geom of geometries) {
77
+ if (geom.type === 'PlaneGeometry') {
78
+ const w = geom.width || 1;
79
+ const h = geom.height || 1;
80
+ const hw = w / 2;
81
+ const hh = h / 2;
82
+ meta.geometries[geom.uuid] = {
83
+ positions: new Float32Array([-hw, -hh, 0, hw, -hh, 0, hw, hh, 0, -hw, hh, 0]),
84
+ indices: new Uint32Array([0, 1, 2, 0, 2, 3]),
85
+ uvs: new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]),
86
+ normals: new Float32Array([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]),
87
+ };
88
+ } else if (geom.type === 'SphereGeometry') {
89
+ meta.geometries[geom.uuid] = this.parseSphereGeometry(geom);
90
+ } else if (geom.type === 'BufferGeometry' && geom.data) {
91
+ const parsed = this.parseBufferGeometry(geom.data);
92
+ meta.geometries[geom.uuid] = parsed;
93
+ }
94
+ }
95
+ }
96
+
97
+ private parseSphereGeometry(geom: any): ParsedGeometry {
98
+ const radius = geom.radius ?? 0.5;
99
+ const widthSegments = Math.max(3, Math.floor(geom.widthSegments ?? 16));
100
+ const heightSegments = Math.max(2, Math.floor(geom.heightSegments ?? 8));
101
+ const phiStart = geom.phiStart ?? 0;
102
+ const phiLength = geom.phiLength ?? Math.PI * 2;
103
+ const thetaStart = geom.thetaStart ?? 0;
104
+ const thetaLength = geom.thetaLength ?? Math.PI;
105
+
106
+ const positions: number[] = [];
107
+ const normals: number[] = [];
108
+ const uvs: number[] = [];
109
+ const indices: number[] = [];
110
+ const grid: number[][] = [];
111
+
112
+ let index = 0;
113
+
114
+ for (let iy = 0; iy <= heightSegments; iy++) {
115
+ const verticesRow: number[] = [];
116
+ const v = iy / heightSegments;
117
+ const theta = thetaStart + v * thetaLength;
118
+
119
+ for (let ix = 0; ix <= widthSegments; ix++) {
120
+ const u = ix / widthSegments;
121
+ const phi = phiStart + u * phiLength;
122
+
123
+ const x = -radius * Math.cos(phi) * Math.sin(theta);
124
+ const y = radius * Math.cos(theta);
125
+ const z = radius * Math.sin(phi) * Math.sin(theta);
126
+
127
+ positions.push(x, y, z);
128
+ const length = Math.hypot(x, y, z) || 1;
129
+ normals.push(x / length, y / length, z / length);
130
+ uvs.push(u, 1 - v);
131
+
132
+ verticesRow.push(index++);
133
+ }
134
+
135
+ grid.push(verticesRow);
136
+ }
137
+
138
+ const thetaEnd = thetaStart + thetaLength;
139
+ const isTopFull = thetaStart <= 0;
140
+ const isBottomFull = thetaEnd >= Math.PI;
141
+
142
+ for (let iy = 0; iy < heightSegments; iy++) {
143
+ for (let ix = 0; ix < widthSegments; ix++) {
144
+ const a = grid[iy][ix + 1];
145
+ const b = grid[iy][ix];
146
+ const c = grid[iy + 1][ix];
147
+ const d = grid[iy + 1][ix + 1];
148
+
149
+ if (iy !== 0 || !isTopFull) {
150
+ indices.push(a, b, d);
151
+ }
152
+ if (iy !== heightSegments - 1 || !isBottomFull) {
153
+ indices.push(b, c, d);
154
+ }
155
+ }
156
+ }
157
+
158
+ const typedIndices = positions.length / 3 > 65535 ? new Uint32Array(indices) : new Uint16Array(indices);
159
+ return {
160
+ positions: new Float32Array(positions),
161
+ indices: typedIndices,
162
+ uvs: new Float32Array(uvs),
163
+ normals: new Float32Array(normals),
164
+ };
165
+ }
166
+
167
+ private parseBufferGeometry(data: any): ParsedGeometry {
168
+ let positions = new Float32Array(0);
169
+ let indices: Uint32Array | Uint16Array = new Uint32Array(0);
170
+ let uvs: Float32Array | undefined;
171
+ let normals: Float32Array | undefined;
172
+
173
+ if (data.interleavedBuffers && data.arrayBuffers) {
174
+ const buffers: {[uuid: string]: {data: Float32Array; stride: number}} = {};
175
+ for (const [uuid, ibDef] of Object.entries(data.interleavedBuffers) as any[]) {
176
+ const arrayBuffer = data.arrayBuffers[ibDef.buffer];
177
+ if (arrayBuffer) {
178
+ const floatArray = new Float32Array(
179
+ Array.isArray(arrayBuffer) ? this.int32ArrayToFloat32(arrayBuffer) : arrayBuffer
180
+ );
181
+ buffers[uuid] = {data: floatArray, stride: ibDef.stride};
182
+ }
183
+ }
184
+
185
+ if (data.attributes) {
186
+ if (data.attributes.position?.isInterleavedBufferAttribute) {
187
+ const attr = data.attributes.position;
188
+ const buf = buffers[attr.data];
189
+ if (buf) positions = this.extractInterleavedAttribute(buf.data, buf.stride, attr.offset, attr.itemSize);
190
+ }
191
+ if (data.attributes.uv?.isInterleavedBufferAttribute) {
192
+ const attr = data.attributes.uv;
193
+ const buf = buffers[attr.data];
194
+ if (buf) uvs = this.extractInterleavedAttribute(buf.data, buf.stride, attr.offset, attr.itemSize);
195
+ }
196
+ if (data.attributes.normal?.isInterleavedBufferAttribute) {
197
+ const attr = data.attributes.normal;
198
+ const buf = buffers[attr.data];
199
+ if (buf) normals = this.extractInterleavedAttribute(buf.data, buf.stride, attr.offset, attr.itemSize);
200
+ }
201
+ }
202
+ } else if (data.attributes) {
203
+ if (data.attributes.position?.array) {
204
+ positions = new Float32Array(data.attributes.position.array);
205
+ }
206
+ if (data.attributes.uv?.array) {
207
+ uvs = new Float32Array(data.attributes.uv.array);
208
+ }
209
+ if (data.attributes.normal?.array) {
210
+ normals = new Float32Array(data.attributes.normal.array);
211
+ }
212
+ }
213
+
214
+ if (data.index) {
215
+ if (data.index.type === 'Uint16Array') {
216
+ indices = new Uint16Array(data.index.array);
217
+ } else {
218
+ indices = new Uint32Array(data.index.array);
219
+ }
220
+ }
221
+
222
+ return {positions, indices, uvs, normals};
223
+ }
224
+
225
+ private extractInterleavedAttribute(buffer: Float32Array, stride: number, offset: number, itemSize: number): any {
226
+ const count = Math.floor(buffer.length / stride);
227
+ const result = new Float32Array(count * itemSize);
228
+ for (let i = 0; i < count; i++) {
229
+ for (let j = 0; j < itemSize; j++) {
230
+ result[i * itemSize + j] = buffer[i * stride + offset + j];
231
+ }
232
+ }
233
+ return result;
234
+ }
235
+
236
+ private int32ArrayToFloat32(intArray: number[]): Float32Array {
237
+ const buffer = new ArrayBuffer(intArray.length * 4);
238
+ const int32View = new Int32Array(buffer);
239
+ for (let i = 0; i < intArray.length; i++) {
240
+ int32View[i] = intArray[i];
241
+ }
242
+ return new Float32Array(buffer) as any;
243
+ }
244
+
245
+ private parseImages(images: any[], baseUrl: string, _meta: LoadedMeta): void {
246
+ (this as any)._images = {};
247
+ for (const img of images) {
248
+ (this as any)._images[img.uuid] = img.url ? baseUrl + img.url : null;
249
+ }
250
+ }
251
+
252
+ private parseTextures(textures: any[], meta: LoadedMeta): void {
253
+ const images = (this as any)._images || {};
254
+ for (const texDef of textures) {
255
+ const imageUrl = images[texDef.image];
256
+ if (imageUrl) {
257
+ const texture = new Texture(imageUrl, this.scene);
258
+ if (Array.isArray(texDef.wrap)) {
259
+ texture.wrapU = this.mapWrapMode(texDef.wrap[0]);
260
+ texture.wrapV = this.mapWrapMode(texDef.wrap[1] ?? texDef.wrap[0]);
261
+ }
262
+ if (Array.isArray(texDef.repeat)) {
263
+ texture.uScale = texDef.repeat[0] ?? 1;
264
+ texture.vScale = texDef.repeat[1] ?? 1;
265
+ }
266
+ if (Array.isArray(texDef.offset)) {
267
+ texture.uOffset = texDef.offset[0] ?? 0;
268
+ texture.vOffset = texDef.offset[1] ?? 0;
269
+ }
270
+ if (typeof texDef.rotation === 'number') {
271
+ texture.wAng = texDef.rotation;
272
+ }
273
+ meta.textures[texDef.uuid] = texture;
274
+ } else {
275
+ meta.textures[texDef.uuid] = null;
276
+ }
277
+ }
278
+ }
279
+
280
+ private mapWrapMode(wrapMode?: number): number {
281
+ switch (wrapMode) {
282
+ case 1001: // ClampToEdgeWrapping (three.js)
283
+ return Texture.CLAMP_ADDRESSMODE;
284
+ case 1002: // MirroredRepeatWrapping (three.js)
285
+ return Texture.MIRROR_ADDRESSMODE;
286
+ case 1000: // RepeatWrapping (three.js)
287
+ default:
288
+ return Texture.WRAP_ADDRESSMODE;
289
+ }
290
+ }
291
+
292
+ private parseMaterials(materials: any[], meta: LoadedMeta): void {
293
+ for (const matDef of materials) {
294
+ const matInfo: any = {
295
+ uuid: matDef.uuid,
296
+ transparent: matDef.transparent ?? true,
297
+ blending: matDef.blending ?? 1,
298
+ side: matDef.side ?? 2,
299
+ depthTest: matDef.depthTest ?? true,
300
+ depthWrite: matDef.depthWrite ?? false,
301
+ alphaTest: matDef.alphaTest ?? 0,
302
+ texture: null,
303
+ };
304
+
305
+ if (matDef.map) {
306
+ matInfo.texture = meta.textures[matDef.map] || null;
307
+ }
308
+
309
+ const blendingToAlphaMode: {[key: number]: number} = {
310
+ 1: Constants.ALPHA_COMBINE,
311
+ 2: Constants.ALPHA_ADD,
312
+ 3: Constants.ALPHA_SUBTRACT,
313
+ 4: Constants.ALPHA_MULTIPLY,
314
+ };
315
+ matInfo.alphaMode = blendingToAlphaMode[matDef.blending] || Constants.ALPHA_COMBINE;
316
+
317
+ meta.materials[matDef.uuid] = matInfo;
318
+ }
319
+ }
320
+
321
+ private getGeometry(meta: LoadedMeta, geometryId: string | undefined): ParsedGeometry | undefined {
322
+ if (!geometryId) return undefined;
323
+ return meta.geometries[geometryId];
324
+ }
325
+
326
+ private getMaterial(meta: LoadedMeta, materialId: string | string[] | undefined): any | undefined {
327
+ if (!materialId) return undefined;
328
+ const selectedId = Array.isArray(materialId) ? materialId[0] : materialId;
329
+ return meta.materials[selectedId];
330
+ }
331
+
332
+ private createPlaceholderNode(type: string, name?: string): TransformNode {
333
+ const node = new TransformNode(name || type || 'node', this.scene);
334
+ (node as any).quarksOriginalType = type;
335
+ return node;
336
+ }
337
+
338
+ private createMeshNode(data: any, meta: LoadedMeta): Mesh {
339
+ const mesh = new Mesh(data.name || data.type || 'mesh', this.scene);
340
+ const geometry = this.getGeometry(meta, data.geometry);
341
+ if (geometry && geometry.positions.length > 0 && geometry.indices.length > 0) {
342
+ const vertexData = new VertexData();
343
+ vertexData.positions = Array.from(geometry.positions);
344
+ vertexData.indices = Array.from(geometry.indices);
345
+ if (geometry.uvs && geometry.uvs.length > 0) {
346
+ vertexData.uvs = Array.from(geometry.uvs);
347
+ }
348
+ if (geometry.normals && geometry.normals.length > 0) {
349
+ vertexData.normals = Array.from(geometry.normals);
350
+ }
351
+ vertexData.applyToMesh(mesh, true);
352
+ }
353
+
354
+ const materialInfo = this.getMaterial(meta, data.material);
355
+ if (materialInfo) {
356
+ const material = new StandardMaterial(`quarks_loader_material_${materialInfo.uuid}`, this.scene);
357
+ material.alphaMode = materialInfo.alphaMode ?? Constants.ALPHA_COMBINE;
358
+ material.backFaceCulling = materialInfo.side !== 2;
359
+ material.alphaCutOff = materialInfo.alphaTest ?? 0;
360
+ material.disableDepthWrite = !(materialInfo.depthWrite ?? false);
361
+ if (materialInfo.texture) {
362
+ material.diffuseTexture = materialInfo.texture;
363
+ material.opacityTexture = materialInfo.texture;
364
+ }
365
+ mesh.material = material;
366
+ }
367
+ return mesh;
368
+ }
369
+
370
+ private parseObject(data: any, meta: LoadedMeta): TransformNode {
371
+ let node: TransformNode;
372
+ switch (data.type) {
373
+ case 'QuarksPrefab':
374
+ node = QuarksPrefab.fromJSON(data, this.scene);
375
+ break;
376
+ case 'ParticleEmitter':
377
+ if (data.ps) {
378
+ const ps = this.parseParticleSystem(data.ps, meta);
379
+ node = ps.emitter;
380
+ break;
381
+ }
382
+ node = this.createPlaceholderNode(data.type, data.name);
383
+ break;
384
+ case 'Mesh':
385
+ case 'SkinnedMesh':
386
+ case 'InstancedMesh':
387
+ case 'BatchedMesh':
388
+ node = this.createMeshNode(data, meta);
389
+ break;
390
+ case 'Scene':
391
+ case 'Group':
392
+ case 'Object3D':
393
+ case 'PerspectiveCamera':
394
+ case 'OrthographicCamera':
395
+ case 'AmbientLight':
396
+ case 'DirectionalLight':
397
+ case 'PointLight':
398
+ case 'RectAreaLight':
399
+ case 'SpotLight':
400
+ case 'HemisphereLight':
401
+ case 'LightProbe':
402
+ case 'LOD':
403
+ case 'Line':
404
+ case 'LineSegments':
405
+ case 'LineLoop':
406
+ case 'PointCloud':
407
+ case 'Points':
408
+ case 'Sprite':
409
+ case 'Bone':
410
+ node = this.createPlaceholderNode(data.type, data.name);
411
+ break;
412
+ default:
413
+ node = new TransformNode(data.name || data.type || 'node', this.scene);
414
+ }
415
+
416
+ if (data.uuid) (node as any)._quarksUUID = data.uuid;
417
+ if (data.name) node.name = data.name;
418
+
419
+ if (data.matrix) {
420
+ const matrix = Matrix.FromArray(data.matrix);
421
+ const scaling = new Vector3(1, 1, 1);
422
+ const rotation = Quaternion.Identity();
423
+ const translation = new Vector3(0, 0, 0);
424
+ matrix.decompose(scaling, rotation, translation);
425
+ node.position.copyFrom(translation);
426
+ node.scaling.copyFrom(scaling);
427
+ node.rotationQuaternion = rotation;
428
+ } else {
429
+ if (data.position) node.position.set(data.position[0], data.position[1], data.position[2]);
430
+ if (data.scale) node.scaling.set(data.scale[0], data.scale[1], data.scale[2]);
431
+ if (data.quaternion) {
432
+ node.rotationQuaternion = new Quaternion(data.quaternion[0], data.quaternion[1], data.quaternion[2], data.quaternion[3]);
433
+ } else if (data.rotation) {
434
+ node.rotation.set(data.rotation[0], data.rotation[1], data.rotation[2]);
435
+ }
436
+ }
437
+
438
+ if (data.visible !== undefined) node.setEnabled(data.visible);
439
+
440
+ if (data.children) {
441
+ for (const childData of data.children) {
442
+ const child = this.parseObject(childData, meta);
443
+ child.parent = node;
444
+ }
445
+ }
446
+
447
+ return node;
448
+ }
449
+
450
+ private parseParticleSystem(json: any, meta: LoadedMeta): ParticleSystem {
451
+ const dependencies: {[uuid: string]: Behavior} = {};
452
+ const ps = ParticleSystem.fromJSON(json, meta as any, dependencies, this.scene);
453
+ (ps as any)._meshSurfaceReferenceUUID = json?.shape?.type === 'mesh_surface' ? json?.shape?.mesh : undefined;
454
+ return ps;
455
+ }
456
+
457
+ private linkReferences(root: TransformNode): void {
458
+ const nodesMap: {[uuid: string]: TransformNode} = {};
459
+
460
+ const traverse = (node: TransformNode) => {
461
+ if ((node as any)._quarksUUID) {
462
+ nodesMap[(node as any)._quarksUUID] = node;
463
+ }
464
+ for (const child of node.getChildren()) {
465
+ if (child instanceof TransformNode) {
466
+ traverse(child);
467
+ }
468
+ }
469
+ };
470
+ traverse(root);
471
+
472
+ const linkNode = (node: TransformNode) => {
473
+ if (node instanceof QuarksPrefab) {
474
+ node.resolveReferences(root);
475
+ }
476
+ if (node instanceof ParticleEmitter) {
477
+ const system = node.system as ParticleSystem;
478
+ const meshSurfaceUUID = (system as any)._meshSurfaceReferenceUUID;
479
+ if (meshSurfaceUUID && (system.emitterShape as any).type === 'mesh_surface') {
480
+ const targetNode = nodesMap[meshSurfaceUUID];
481
+ if (targetNode instanceof Mesh) {
482
+ (system.emitterShape as any).mesh = targetNode;
483
+ }
484
+ }
485
+ for (let i = 0; i < system.behaviors.length; i++) {
486
+ if (system.behaviors[i] instanceof EmitSubParticleSystem) {
487
+ const subEmitter = system.behaviors[i] as EmitSubParticleSystem;
488
+ const targetUUID = subEmitter.subParticleSystem as any;
489
+ if (typeof targetUUID === 'string' && nodesMap[targetUUID]) {
490
+ (subEmitter as any).subParticleSystem = nodesMap[targetUUID];
491
+ }
492
+ }
493
+ }
494
+ }
495
+ for (const child of node.getChildren()) {
496
+ if (child instanceof TransformNode) {
497
+ linkNode(child);
498
+ }
499
+ }
500
+ };
501
+ linkNode(root);
502
+ }
503
+ }