@zephyr3d/scene 0.9.4 → 0.9.5

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 (102) hide show
  1. package/dist/animation/animationmask.js +224 -0
  2. package/dist/animation/animationmask.js.map +1 -0
  3. package/dist/animation/animationset.js +268 -67
  4. package/dist/animation/animationset.js.map +1 -1
  5. package/dist/animation/animationtrack.js.map +1 -1
  6. package/dist/animation/eulerrotationtrack.js +3 -0
  7. package/dist/animation/eulerrotationtrack.js.map +1 -1
  8. package/dist/animation/fixed_geometry_cache_track.js +7 -0
  9. package/dist/animation/fixed_geometry_cache_track.js.map +1 -1
  10. package/dist/animation/ik_modifier.js.map +1 -1
  11. package/dist/animation/joint_dynamics/collision.js +6 -4
  12. package/dist/animation/joint_dynamics/collision.js.map +1 -1
  13. package/dist/animation/joint_dynamics/controller.js +390 -43
  14. package/dist/animation/joint_dynamics/controller.js.map +1 -1
  15. package/dist/animation/joint_dynamics/joint_dynamics_system.js +62 -3
  16. package/dist/animation/joint_dynamics/joint_dynamics_system.js.map +1 -1
  17. package/dist/animation/joint_dynamics/solver.js +4 -3
  18. package/dist/animation/joint_dynamics/solver.js.map +1 -1
  19. package/dist/animation/joint_dynamics/types.js.map +1 -1
  20. package/dist/animation/joint_dynamics_modifier.js +2 -0
  21. package/dist/animation/joint_dynamics_modifier.js.map +1 -1
  22. package/dist/animation/morphtarget.js +3 -72
  23. package/dist/animation/morphtarget.js.map +1 -1
  24. package/dist/animation/morphtrack.js +3 -0
  25. package/dist/animation/morphtrack.js.map +1 -1
  26. package/dist/animation/pca_geometry_cache_track.js +3 -0
  27. package/dist/animation/pca_geometry_cache_track.js.map +1 -1
  28. package/dist/animation/proptrack.js +3 -0
  29. package/dist/animation/proptrack.js.map +1 -1
  30. package/dist/animation/rotationtrack.js +3 -0
  31. package/dist/animation/rotationtrack.js.map +1 -1
  32. package/dist/animation/scaletrack.js +3 -0
  33. package/dist/animation/scaletrack.js.map +1 -1
  34. package/dist/animation/skeleton.js +254 -79
  35. package/dist/animation/skeleton.js.map +1 -1
  36. package/dist/animation/skeleton_modifier.js.map +1 -1
  37. package/dist/animation/spring_modifier.js.map +1 -1
  38. package/dist/animation/translationtrack.js +3 -0
  39. package/dist/animation/translationtrack.js.map +1 -1
  40. package/dist/app/engine.js +171 -107
  41. package/dist/app/engine.js.map +1 -1
  42. package/dist/app/scriptregistry.js +251 -80
  43. package/dist/app/scriptregistry.js.map +1 -1
  44. package/dist/asset/assetmanager.js +344 -365
  45. package/dist/asset/assetmanager.js.map +1 -1
  46. package/dist/asset/loaders/gltf/gltf_loader.js +17 -14
  47. package/dist/asset/loaders/gltf/gltf_loader.js.map +1 -1
  48. package/dist/asset/loaders/loader.js +1 -6
  49. package/dist/asset/loaders/loader.js.map +1 -1
  50. package/dist/asset/loaders/zabc/zabc_loader.js.map +1 -1
  51. package/dist/asset/model.js +1000 -131
  52. package/dist/asset/model.js.map +1 -1
  53. package/dist/avatar/wardrobe.js +474 -0
  54. package/dist/avatar/wardrobe.js.map +1 -0
  55. package/dist/index.d.ts +1125 -199
  56. package/dist/index.js +3 -2
  57. package/dist/index.js.map +1 -1
  58. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js +6 -3
  59. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
  60. package/dist/material/mixins/pbr/common.js +72 -15
  61. package/dist/material/mixins/pbr/common.js.map +1 -1
  62. package/dist/material/msdf_text_sprite.js +4 -4
  63. package/dist/material/msdf_text_sprite.js.map +1 -1
  64. package/dist/material/pbrblueprint.js +102 -102
  65. package/dist/material/pbrblueprint.js.map +1 -1
  66. package/dist/render/envlight.js +54 -1
  67. package/dist/render/envlight.js.map +1 -1
  68. package/dist/render/sky.js +9 -0
  69. package/dist/render/sky.js.map +1 -1
  70. package/dist/scene/batchgroup.js +11 -13
  71. package/dist/scene/batchgroup.js.map +1 -1
  72. package/dist/scene/environment.js +15 -2
  73. package/dist/scene/environment.js.map +1 -1
  74. package/dist/scene/mesh.js +79 -16
  75. package/dist/scene/mesh.js.map +1 -1
  76. package/dist/scene/scene.js +1 -0
  77. package/dist/scene/scene.js.map +1 -1
  78. package/dist/scene/scene_node.js +203 -2
  79. package/dist/scene/scene_node.js.map +1 -1
  80. package/dist/text/font/font_asset.js.map +1 -1
  81. package/dist/text/msdf/generator.js.map +1 -1
  82. package/dist/text/msdf/shape.js +4 -1
  83. package/dist/text/msdf/shape.js.map +1 -1
  84. package/dist/text/runtime/msdf_text_atlas_manager.js +4 -1
  85. package/dist/text/runtime/msdf_text_atlas_manager.js.map +1 -1
  86. package/dist/utility/blueprint/material/inputs.js +1157 -1154
  87. package/dist/utility/blueprint/material/inputs.js.map +1 -1
  88. package/dist/utility/bounding_volume.js.map +1 -1
  89. package/dist/utility/pmrem.js +34 -8
  90. package/dist/utility/pmrem.js.map +1 -1
  91. package/dist/utility/serialization/manager.js +22 -9
  92. package/dist/utility/serialization/manager.js.map +1 -1
  93. package/dist/utility/serialization/scene/animation.js +700 -7
  94. package/dist/utility/serialization/scene/animation.js.map +1 -1
  95. package/dist/utility/serialization/scene/mesh.js +64 -4
  96. package/dist/utility/serialization/scene/mesh.js.map +1 -1
  97. package/dist/utility/serialization/scene/node.js +187 -8
  98. package/dist/utility/serialization/scene/node.js.map +1 -1
  99. package/dist/utility/serialization/types.js.map +1 -1
  100. package/dist/utility/textures/sheenlut.js +138 -0
  101. package/dist/utility/textures/sheenlut.js.map +1 -0
  102. package/package.json +2 -2
@@ -1,20 +1,26 @@
1
- import { Vector3, Quaternion, Matrix4x4, Disposable, DRef } from '@zephyr3d/base';
1
+ import { Vector3, Quaternion, Matrix4x4, Disposable, uint8ArrayToBase64, ASSERT, DRef, Vector4 } from '@zephyr3d/base';
2
+ import { getVertexFormatComponentCount } from '@zephyr3d/device';
2
3
  import { Mesh } from '../scene/mesh.js';
3
- import { SceneNode } from '../scene/scene_node.js';
4
+ import { BoundingBox } from '../utility/bounding_volume.js';
5
+ import { SceneNode, setSceneMeshAssetBinding } from '../scene/scene_node.js';
6
+ import { SkeletonRig, SkinBinding } from '../animation/skeleton.js';
4
7
  import { NodeRotationTrack } from '../animation/rotationtrack.js';
5
8
  import '../animation/eulerrotationtrack.js';
6
9
  import { NodeTranslationTrack } from '../animation/translationtrack.js';
7
10
  import { NodeScaleTrack } from '../animation/scaletrack.js';
8
- import { Skeleton } from '../animation/skeleton.js';
9
11
  import { MorphTargetTrack } from '../animation/morphtrack.js';
10
12
  import { FixedGeometryCacheTrack } from '../animation/fixed_geometry_cache_track.js';
13
+ import { getDevice } from '../app/api.js';
11
14
  import { PCAGeometryCacheTrack } from '../animation/pca_geometry_cache_track.js';
12
- import '@zephyr3d/device';
13
15
  import '../animation/joint_dynamics/collision.js';
14
16
  import '../animation/joint_dynamics/solver.js';
15
- import '../animation/joint_dynamics/joint_dynamics_system.js';
16
- import { processMorphData } from '../animation/morphtarget.js';
17
- import { MAX_MORPH_TARGETS } from '../values.js';
17
+ import { createTransformAccess, JointDynamicsSystem } from '../animation/joint_dynamics/joint_dynamics_system.js';
18
+ import { JointDynamicsModifier } from '../animation/joint_dynamics_modifier.js';
19
+ import { MAX_MORPH_TARGETS, MAX_MORPH_ATTRIBUTES } from '../values.js';
20
+ import { Primitive } from '../render/primitive.js';
21
+ import { UnlitMaterial } from '../material/unlit.js';
22
+ import { PBRSpecularGlossinessMaterial } from '../material/pbrsg.js';
23
+ import { PBRMetallicRoughnessMaterial } from '../material/pbrmr.js';
18
24
 
19
25
  /**
20
26
  * Named object interface for model loading
@@ -39,7 +45,6 @@ import { MAX_MORPH_TARGETS } from '../values.js';
39
45
  _mesh;
40
46
  _skeleton;
41
47
  _attachToSkeleton;
42
- _meshAttached;
43
48
  _matrix;
44
49
  _worldMatrix;
45
50
  _weights;
@@ -49,8 +54,9 @@ import { MAX_MORPH_TARGETS } from '../values.js';
49
54
  * Creates an instance of AssetHierarchyNode
50
55
  * @param name - Name of the node
51
56
  * @param parent - Parent of the node
52
- */ constructor(name, parent){
57
+ */ constructor(name, model, parent){
53
58
  super(name);
59
+ model.nodes.push(this);
54
60
  this._parent = null;
55
61
  this._position = Vector3.zero();
56
62
  this._rotation = Quaternion.identity();
@@ -59,7 +65,6 @@ import { MAX_MORPH_TARGETS } from '../values.js';
59
65
  this._mesh = null;
60
66
  this._skeleton = null;
61
67
  this._attachToSkeleton = null;
62
- this._meshAttached = false;
63
68
  this._matrix = null;
64
69
  this._weights = null;
65
70
  this._worldMatrix = null;
@@ -80,10 +85,9 @@ import { MAX_MORPH_TARGETS } from '../values.js';
80
85
  }
81
86
  set mesh(data) {
82
87
  this._mesh = data;
83
- this.setMeshAttached();
84
88
  }
85
89
  /** instances */ get instances() {
86
- return this._instances ?? null;
90
+ return this._instances;
87
91
  }
88
92
  /** Default morph target weights */ get weights() {
89
93
  return this._weights;
@@ -115,16 +119,22 @@ import { MAX_MORPH_TARGETS } from '../values.js';
115
119
  set scaling(val) {
116
120
  this._scaling = val;
117
121
  }
118
- /** true if the node is parent of a mesh node */ get meshAttached() {
119
- return this._meshAttached;
120
- }
121
122
  /** Children of the node */ get children() {
122
123
  return this._children;
123
124
  }
124
125
  /** The skeleton to which the node belongs if this is a joint node */ get skeletonAttached() {
125
126
  return this._attachToSkeleton;
126
127
  }
127
- /** @internal */ computeTransforms(parentTransform) {
128
+ isParentOf(child) {
129
+ while(child && child !== this){
130
+ child = child.parent;
131
+ }
132
+ return child === this;
133
+ }
134
+ getWorldPosition() {
135
+ return new Vector3(this.worldMatrix.m03, this.worldMatrix.m13, this.worldMatrix.m23);
136
+ }
137
+ computeTransforms(parentTransform) {
128
138
  this._matrix = Matrix4x4.scaling(this._scaling).rotateLeft(this._rotation).translateLeft(this._position);
129
139
  this._worldMatrix = parentTransform ? Matrix4x4.multiply(parentTransform, this._matrix) : new Matrix4x4(this._matrix);
130
140
  for (const child of this._children){
@@ -140,9 +150,6 @@ import { MAX_MORPH_TARGETS } from '../values.js';
140
150
  }
141
151
  this._children.push(child);
142
152
  child._parent = this;
143
- if (child.meshAttached) {
144
- this.setMeshAttached();
145
- }
146
153
  }
147
154
  /**
148
155
  * Removes a child of this node
@@ -165,10 +172,6 @@ import { MAX_MORPH_TARGETS } from '../values.js';
165
172
  }
166
173
  this._attachToSkeleton.add(skeleton);
167
174
  }
168
- /** @internal */ setMeshAttached() {
169
- this._meshAttached = true;
170
- this._parent?.setMeshAttached();
171
- }
172
175
  }
173
176
  /**
174
177
  * Skeleton information for model loading
@@ -203,6 +206,18 @@ import { MAX_MORPH_TARGETS } from '../values.js';
203
206
  scale: joint.scaling.clone()
204
207
  });
205
208
  }
209
+ get root() {
210
+ if (this.pivot) {
211
+ return this.pivot;
212
+ }
213
+ let root = this.joints[0];
214
+ for(let i = 1; i < this.joints.length; i++){
215
+ while(root && !root.isParentOf(this.joints[i])){
216
+ root = root.parent;
217
+ }
218
+ }
219
+ return root;
220
+ }
206
221
  }
207
222
  /**
208
223
  * Scene for model loading
@@ -221,41 +236,75 @@ import { MAX_MORPH_TARGETS } from '../values.js';
221
236
  * Model information that can be shared by multiple model nodes
222
237
  * @public
223
238
  */ class SharedModel extends Disposable {
224
- /** @internal */ _name;
225
- /** @internal */ _skeletons;
226
239
  /** @internal */ _nodes;
240
+ /** @internal */ _skeletons;
227
241
  /** @internal */ _animations;
228
242
  /** @internal */ _scenes;
229
243
  /** @internal */ _activeScene;
244
+ /** @internal */ _imageList;
245
+ /** @internal */ _primitiveList;
246
+ /** @internal */ _materialList;
247
+ /** @internal */ _springBoneColliders;
248
+ /** @internal */ _springBoneColliderGroups;
249
+ /** @internal */ _springBones;
250
+ /** @internal */ _jointDynamicsColliders;
251
+ /** @internal */ _jointDynamicsSpringBones;
252
+ /** @internal */ _morphTargetGroups;
253
+ /** @internal */ _textureMap;
254
+ /** @internal */ _primitiveMap;
230
255
  /**
231
256
  * Creates an instance of SharedModel
232
- * @param name - Name of the model
233
- */ constructor(name){
257
+ */ constructor(){
234
258
  super();
235
- this._name = name || '';
236
- this._skeletons = [];
237
259
  this._nodes = [];
260
+ this._skeletons = [];
238
261
  this._scenes = [];
239
262
  this._animations = [];
263
+ this._imageList = [];
264
+ this._primitiveList = [];
265
+ this._materialList = {};
266
+ this._springBoneColliders = [];
267
+ this._springBoneColliderGroups = [];
268
+ this._springBones = [];
269
+ this._jointDynamicsColliders = [];
270
+ this._jointDynamicsSpringBones = [];
271
+ this._morphTargetGroups = [];
240
272
  this._activeScene = -1;
241
- }
242
- /** Name of the model */ get name() {
243
- return this._name;
244
- }
245
- set name(val) {
246
- this._name = val;
273
+ this._textureMap = new Map();
274
+ this._primitiveMap = new Map();
247
275
  }
248
276
  /** All scenes that the model contains */ get scenes() {
249
277
  return this._scenes;
250
278
  }
279
+ /** All Primitives that the model contains */ get primitives() {
280
+ return this._primitiveList;
281
+ }
282
+ /** All nodes that the model contains */ get nodes() {
283
+ return this._nodes;
284
+ }
251
285
  /** All animations that the model contains */ get animations() {
252
286
  return this._animations;
253
287
  }
254
288
  /** All skeletons that the model contains */ get skeletons() {
255
289
  return this._skeletons;
256
290
  }
257
- /** All nodes that the model contains */ get nodes() {
258
- return this._nodes;
291
+ /** All SpringBone colliders that the model contains */ get springBoneColliders() {
292
+ return this._springBoneColliders;
293
+ }
294
+ /** All SpringBone collider groups that the model contains */ get springBoneColliderGroups() {
295
+ return this._springBoneColliderGroups;
296
+ }
297
+ /** All SpringBone springs that the model contains */ get springBones() {
298
+ return this._springBones;
299
+ }
300
+ /** SpringBone colliders converted to Zephyr3D JointDynamics collider format */ get jointDynamicsColliders() {
301
+ return this._jointDynamicsColliders;
302
+ }
303
+ /** SpringBone springs converted to Zephyr3D JointDynamics system format */ get jointDynamicsSpringBones() {
304
+ return this._jointDynamicsSpringBones;
305
+ }
306
+ /** Model-level morph target groups, commonly facial expressions. */ get morphTargetGroups() {
307
+ return this._morphTargetGroups;
259
308
  }
260
309
  /** The active scene of the model */ get activeScene() {
261
310
  return this._activeScene;
@@ -263,16 +312,75 @@ import { MAX_MORPH_TARGETS } from '../values.js';
263
312
  set activeScene(val) {
264
313
  this._activeScene = val;
265
314
  }
315
+ getImage(index) {
316
+ return this._imageList[index];
317
+ }
318
+ setImage(index, img) {
319
+ this._imageList[index] = img;
320
+ }
321
+ getMaterial(hash) {
322
+ return this._materialList[hash];
323
+ }
324
+ setMaterial(hash, material) {
325
+ this._materialList[hash] = material;
326
+ }
327
+ addPrimitive(prim) {
328
+ this._primitiveList.push(prim);
329
+ }
266
330
  /**
267
- * Adds a node to the scene
268
- * @param parent - Under which node the node should be added
269
- * @param index - Index of the node
270
- * @param name - Name of the node
271
- * @returns The added node
272
- */ addNode(parent, index, name) {
273
- const childNode = new AssetHierarchyNode(name, parent);
274
- this._nodes[index] = childNode;
275
- return childNode;
331
+ * Adds a model-level morph target group.
332
+ * @param group - Morph target group to add
333
+ */ addMorphTargetGroup(group) {
334
+ if (group?.name && group.bindings?.length > 0) {
335
+ this._morphTargetGroups.push(group);
336
+ }
337
+ }
338
+ /**
339
+ * Removes all model-level morph target groups.
340
+ */ clearMorphTargetGroups() {
341
+ this._morphTargetGroups = [];
342
+ }
343
+ /**
344
+ * Finds a model-level morph target group by name.
345
+ * @param name - Group name
346
+ * @returns The matching group, or null if not found
347
+ */ getMorphTargetGroup(name) {
348
+ return this._morphTargetGroups.find((group)=>group.name === name) ?? null;
349
+ }
350
+ /**
351
+ * Builds morph target groups by collecting morph target names from every mesh.
352
+ * @returns Generated morph target groups
353
+ */ buildMorphTargetGroupsByName() {
354
+ this.clearMorphTargetGroups();
355
+ const groups = new Map();
356
+ const meshes = new Set();
357
+ for (const node of this._nodes){
358
+ if (node.mesh) {
359
+ meshes.add(node.mesh);
360
+ }
361
+ }
362
+ for (const mesh of meshes){
363
+ const count = getAssetMeshMorphTargetCount(mesh);
364
+ for(let i = 0; i < count; i++){
365
+ const targetName = getAssetMeshMorphTargetName(mesh, i);
366
+ let group = groups.get(targetName);
367
+ if (!group) {
368
+ group = {
369
+ name: targetName,
370
+ bindings: []
371
+ };
372
+ groups.set(targetName, group);
373
+ }
374
+ group.bindings.push({
375
+ mesh,
376
+ targetIndex: i,
377
+ targetName,
378
+ weight: 1
379
+ });
380
+ }
381
+ }
382
+ this._morphTargetGroups = Array.from(groups.values());
383
+ return this._morphTargetGroups;
276
384
  }
277
385
  /**
278
386
  * Adds a skeleton to the scene
@@ -286,67 +394,297 @@ import { MAX_MORPH_TARGETS } from '../values.js';
286
394
  */ addAnimation(animation) {
287
395
  this._animations.push(animation);
288
396
  }
289
- createSceneNode(scene, instancing) {
397
+ static async writePrimitive(vfs, primitive, path) {
398
+ const data = {
399
+ vertices: {},
400
+ indices: primitive.indices ? uint8ArrayToBase64(new Uint8Array(primitive.indices.buffer, primitive.indices.byteOffset, primitive.indices.byteLength)) : null,
401
+ indexType: primitive.indices ? primitive.indices instanceof Uint16Array ? 'u16' : 'u32' : '',
402
+ indexCount: primitive.indexCount,
403
+ type: primitive.type,
404
+ boxMin: [
405
+ primitive.boxMin.x,
406
+ primitive.boxMin.y,
407
+ primitive.boxMin.z
408
+ ],
409
+ boxMax: [
410
+ primitive.boxMax.x,
411
+ primitive.boxMax.y,
412
+ primitive.boxMax.z
413
+ ]
414
+ };
415
+ for(const k in primitive.vertices){
416
+ const v = primitive.vertices[k];
417
+ data.vertices[k] = {
418
+ format: v.format,
419
+ data: uint8ArrayToBase64(new Uint8Array(v.data.buffer, v.data.byteOffset, v.data.byteLength))
420
+ };
421
+ }
422
+ const content = JSON.stringify({
423
+ type: 'Primitive',
424
+ data
425
+ }, null, 2);
426
+ await vfs.writeFile(path, content, {
427
+ encoding: 'utf8',
428
+ create: true
429
+ });
430
+ }
431
+ /** preprocess */ async preprocess(manager, name, destPath, srcVFS, dstVFS) {
432
+ const destName = name;
433
+ if (this._imageList.length > 0) {
434
+ console.info(`Importing ${this._imageList.length} textures`);
435
+ for(let i = 0; i < this._imageList.length; i++){
436
+ const img = this._imageList[i];
437
+ if (!img) {
438
+ continue;
439
+ }
440
+ let ext = '';
441
+ const mimeType = img.uri ? srcVFS.guessMIMEType(img.uri) : img.data ? img.mimeType : '';
442
+ if (mimeType === 'image/jpeg') {
443
+ ext = '.jpg';
444
+ } else if (mimeType === 'image/png') {
445
+ ext = '.png';
446
+ } else if (mimeType === 'image/webp') {
447
+ ext = '.webp';
448
+ } else if (mimeType === 'image/tga') {
449
+ ext = '.tga';
450
+ } else if (mimeType === 'image/vnd.radiance') {
451
+ ext = '.hdr';
452
+ } else if (mimeType === 'image/ktx') {
453
+ ext = '.ktx';
454
+ } else if (mimeType === 'image/ktx2') {
455
+ ext = '.ktx2';
456
+ } else {
457
+ continue;
458
+ }
459
+ ASSERT(!!ext, `Unknown image mime type: ${mimeType}`);
460
+ const path = dstVFS.join(destPath, `${destName}_texture_${i}${ext}`);
461
+ if (img.uri) {
462
+ img.data = new Uint8Array(await srcVFS.readFile(img.uri, {
463
+ encoding: 'binary'
464
+ }));
465
+ }
466
+ await dstVFS.writeFile(path, img.data.buffer.slice(img.data.byteOffset, img.data.byteOffset + img.data.byteLength), {
467
+ encoding: 'binary',
468
+ create: true
469
+ });
470
+ img.uri = path;
471
+ img.data = undefined;
472
+ img.mimeType = '';
473
+ }
474
+ }
475
+ const materialKeys = Object.keys(this._materialList);
476
+ if (materialKeys.length > 0) {
477
+ console.info(`Importing ${materialKeys.length} materials`);
478
+ for (const k of materialKeys){
479
+ const path = dstVFS.join(destPath, `${destName}_material_${k}.zmtl`);
480
+ const m = await this.createMaterial(manager, this._materialList[k], dstVFS);
481
+ const data = await manager.serializeObject(m);
482
+ const content = JSON.stringify({
483
+ type: 'Default',
484
+ data
485
+ }, null, 2);
486
+ await dstVFS.writeFile(path, content, {
487
+ encoding: 'utf8',
488
+ create: true
489
+ });
490
+ this._materialList[k].path = path;
491
+ m.dispose();
492
+ }
493
+ }
494
+ if (this._primitiveList.length > 0) {
495
+ console.info(`Importing ${this._primitiveList.length} meshes`);
496
+ for(let i = 0; i < this._primitiveList.length; i++){
497
+ const info = this._primitiveList[i];
498
+ if (!info) {
499
+ continue;
500
+ }
501
+ const path = dstVFS.join(destPath, `${destName}_mesh_${i}.zmsh`);
502
+ await SharedModel.writePrimitive(dstVFS, info, path);
503
+ info.path = path;
504
+ }
505
+ }
506
+ }
507
+ createJointDynamics(rootNode, nodeMap) {
508
+ if (rootNode.animationSet.rigs.length === 0) {
509
+ return;
510
+ }
511
+ for (const jd of this._jointDynamicsSpringBones){
512
+ const chains = jd.chains.map((chain)=>{
513
+ const start = nodeMap.get(chain.start);
514
+ const end = nodeMap.get(chain.end);
515
+ return {
516
+ start,
517
+ end
518
+ };
519
+ }).filter((chain)=>{
520
+ if (!chain.start || !chain.end) {
521
+ return false;
522
+ }
523
+ return true;
524
+ });
525
+ const rigs = rootNode.animationSet.rigs.filter((ref)=>{
526
+ const joints = ref.get().joints;
527
+ return chains.every((chain)=>joints.includes(chain.start));
528
+ });
529
+ if (rigs.length === 0) {
530
+ continue;
531
+ }
532
+ const colliders = jd.colliders.map((collider)=>{
533
+ const parent = nodeMap.get(collider.node);
534
+ if (!parent) {
535
+ return null;
536
+ }
537
+ const node = new SceneNode(parent.scene);
538
+ node.name = collider.name ? `${collider.name}_JointDynamicsCollider` : 'JointDynamicsCollider';
539
+ node.parent = parent;
540
+ node.position.set(collider.localPosition);
541
+ node.rotation.set(collider.localRotation);
542
+ return {
543
+ r: {
544
+ ...collider.collider
545
+ },
546
+ transform: createTransformAccess(node)
547
+ };
548
+ }).filter((collider)=>{
549
+ return !!collider;
550
+ });
551
+ const flatPlanes = jd.flatPlanes.map((flatPlane)=>{
552
+ const node = nodeMap.get(flatPlane.node);
553
+ if (!node) {
554
+ return null;
555
+ }
556
+ const up = node.worldMatrix.transformVectorAffine(flatPlane.up, new Vector3());
557
+ if (up.magnitudeSq === 0) {
558
+ return null;
559
+ }
560
+ return {
561
+ up: up.inplaceNormalize(),
562
+ position: node.worldMatrix.transformPointAffine(flatPlane.position, new Vector3())
563
+ };
564
+ }).filter((flatPlane)=>{
565
+ return !!flatPlane;
566
+ });
567
+ for (const rig of rigs){
568
+ const system = new JointDynamicsSystem({
569
+ chainConfig: {
570
+ chains,
571
+ systemRoot: rootNode
572
+ },
573
+ controllerConfig: jd.controllerConfig
574
+ }, colliders, [], flatPlanes);
575
+ const modifier = new JointDynamicsModifier(system);
576
+ rig.get().modifiers.push(modifier);
577
+ }
578
+ }
579
+ }
580
+ createMorphTargetGroups(rootNode, meshMap) {
581
+ const runtimeGroups = [];
582
+ for (const group of this._morphTargetGroups){
583
+ const bindings = [];
584
+ for (const binding of group.bindings){
585
+ const assetMesh = binding.node?.mesh ?? binding.mesh ?? null;
586
+ if (!assetMesh) {
587
+ continue;
588
+ }
589
+ for (const subMesh of assetMesh.subMeshes){
590
+ const mesh = meshMap.get(subMesh);
591
+ if (mesh) {
592
+ bindings.push({
593
+ mesh,
594
+ targetIndex: binding.targetIndex,
595
+ targetName: binding.targetName,
596
+ weight: binding.weight
597
+ });
598
+ }
599
+ }
600
+ }
601
+ if (bindings.length > 0) {
602
+ runtimeGroups.push({
603
+ name: group.name,
604
+ bindings,
605
+ isBinary: group.isBinary,
606
+ weight: this.getInitialMorphTargetGroupWeight(bindings, group.isBinary)
607
+ });
608
+ }
609
+ }
610
+ rootNode.morphTargetGroups = runtimeGroups;
611
+ }
612
+ getInitialMorphTargetGroupWeight(bindings, isBinary) {
613
+ let result = 0;
614
+ let found = false;
615
+ for (const binding of bindings){
616
+ if (!binding.weight) {
617
+ continue;
618
+ }
619
+ const targetWeight = this.getMorphTargetBindingWeight(binding);
620
+ if (targetWeight === null) {
621
+ continue;
622
+ }
623
+ const groupWeight = targetWeight / binding.weight;
624
+ if (!found || Math.abs(groupWeight) > Math.abs(result)) {
625
+ result = groupWeight;
626
+ found = true;
627
+ }
628
+ }
629
+ return isBinary ? result > 0.5 ? 1 : 0 : result;
630
+ }
631
+ getMorphTargetBindingWeight(binding) {
632
+ const targetIndex = binding.targetIndex;
633
+ if (typeof targetIndex === 'number' && targetIndex >= 0 && targetIndex < binding.mesh.getNumMorphTargets()) {
634
+ const targetName = binding.mesh.getMorphTargetName(targetIndex);
635
+ if (targetName) {
636
+ return binding.mesh.getMorphWeight(targetName);
637
+ }
638
+ }
639
+ if (binding.targetName) {
640
+ return binding.mesh.getMorphWeight(binding.targetName);
641
+ }
642
+ return null;
643
+ }
644
+ async createSceneNode(manager, scene, instancing, saveMeshes, saveSkeletons, saveAnimations, saveJointDynamics, srcVFS) {
290
645
  const group = new SceneNode(scene);
291
- group.name = this.name;
292
- const animationSet = group.animationSet;
646
+ const nodeMap = new Map();
647
+ const meshMap = new Map();
293
648
  for(let i = 0; i < this.scenes.length; i++){
294
649
  const assetScene = this.scenes[i];
295
650
  const skeletonMeshMap = new Map();
296
- const nodeMap = new Map();
297
651
  for(let k = 0; k < assetScene.rootNodes.length; k++){
298
- this.setAssetNodeToSceneNode(scene, group, assetScene.rootNodes[k], skeletonMeshMap, nodeMap, instancing);
299
- }
300
- for (const animationData of this.animations){
301
- let name = animationData.name ?? `_embbeded_animation`;
302
- if (animationSet.getAnimationClip(name)) {
303
- const baseName = name;
304
- for(let t = 1;; t++){
305
- name = `${baseName}_${t}`;
306
- if (!animationSet.getAnimationClip(name)) {
307
- break;
308
- }
652
+ await this.setAssetNodeToSceneNode(manager, scene, group, assetScene.rootNodes[k], skeletonMeshMap, nodeMap, meshMap, instancing, saveMeshes, saveSkeletons, saveAnimations, srcVFS);
653
+ }
654
+ if (saveSkeletons) {
655
+ for (const sk of this.skeletons){
656
+ if (!skeletonMeshMap.has(sk)) {
657
+ skeletonMeshMap.set(sk, {
658
+ mesh: [],
659
+ bounding: []
660
+ });
309
661
  }
310
662
  }
311
- const animation = animationSet.createAnimation(name, true);
312
- for (const track of animationData.tracks){
313
- if (track.type === 'translation') {
314
- animation.addTrack(nodeMap.get(track.node), new NodeTranslationTrack(track.interpolator, true));
315
- } else if (track.type === 'scale') {
316
- animation.addTrack(nodeMap.get(track.node), new NodeScaleTrack(track.interpolator, true));
317
- } else if (track.type === 'rotation') {
318
- animation.addTrack(nodeMap.get(track.node), new NodeRotationTrack(track.interpolator, true));
319
- } else if (track.type === 'weights') {
320
- for (const m of track.node.mesh.subMeshes){
321
- if (track.interpolator.stride > MAX_MORPH_TARGETS) {
322
- console.error(`Morph target too large: ${track.interpolator.stride}, the maximum is ${MAX_MORPH_TARGETS}`);
323
- } else {
324
- const morphTrack = new MorphTargetTrack(track.interpolator, track.defaultMorphWeights, m.targetBox, m.mesh.getBoundingVolume().toAABB(), true);
325
- animation.addTrack(m.mesh, morphTrack);
326
- }
327
- }
328
- } else if (track.type === 'geometry-cache') {
329
- const subMesh = track.node.mesh?.subMeshes[track.subMeshIndex];
330
- if (!subMesh?.mesh) {
331
- console.error(`Invalid geometry cache sub mesh: ${track.subMeshIndex}`);
332
- } else {
333
- if (track.codec === 'pca') {
334
- const pcaData = this.remapPCAGeometryCacheData(subMesh, track);
335
- animation.addTrack(subMesh.mesh, new PCAGeometryCacheTrack(pcaData, true));
336
- } else {
337
- const frames = this.remapGeometryCacheFrames(subMesh, track.frames);
338
- animation.addTrack(subMesh.mesh, new FixedGeometryCacheTrack(track.times, frames, true));
339
- }
340
- }
341
- } else {
342
- console.error(`Invalid animation track type: ${track.type}`);
663
+ const rigMap = new Map();
664
+ for (const v of skeletonMeshMap){
665
+ const sk = v[0];
666
+ const joints = sk.joints.map((val)=>{
667
+ const node = nodeMap.get(val);
668
+ node.jointTypeT = 'static';
669
+ node.jointTypeS = 'static';
670
+ node.jointTypeR = 'static';
671
+ return node;
672
+ });
673
+ const rootJoint = sk.root ? nodeMap.get(sk.root) ?? null : null;
674
+ const rigKey = SkeletonRig.getRigKey(joints, rootJoint);
675
+ let rig = rigMap.get(rigKey);
676
+ if (!rig) {
677
+ rig = new SkeletonRig(joints, sk.bindPose, {
678
+ rootJoint
679
+ });
680
+ rigMap.set(rigKey, rig);
681
+ group.animationSet.rigs.push(new DRef(rig));
343
682
  }
344
- }
345
- for (const sk of animationData.skeletons){
683
+ const binding = new SkinBinding(rig, sk.inverseBindMatrices, joints);
346
684
  const nodes = skeletonMeshMap.get(sk);
347
685
  if (nodes) {
348
- if (!nodes.skeleton) {
349
- nodes.skeleton = new Skeleton(sk.joints.map((val)=>nodeMap.get(val)), sk.inverseBindMatrices, sk.bindPose);
686
+ if (!nodes.binding) {
687
+ nodes.binding = binding;
350
688
  for(let i = 0; i < nodes.mesh.length; i++){
351
689
  const mesh = nodes.mesh[i];
352
690
  const v = {
@@ -354,22 +692,82 @@ import { MAX_MORPH_TARGETS } from '../values.js';
354
692
  blendIndices: nodes.bounding[i].rawBlendIndices,
355
693
  weights: nodes.bounding[i].rawJointWeights
356
694
  };
357
- mesh.setSkinnedBoundingInfo(nodes.skeleton.getBoundingInfo(v));
358
- mesh.skeletonName = nodes.skeleton.persistentId;
695
+ mesh.setSkinnedBoundingInfo(nodes.binding.getBoundingInfo(v));
696
+ mesh.skeletonName = nodes.binding.persistentId;
359
697
  }
360
- animationSet.skeletons.push(new DRef(nodes.skeleton));
361
698
  }
362
- animation.addSkeleton(nodes.skeleton.persistentId);
363
699
  }
700
+ group.animationSet.skeletons.push(new DRef(nodes.binding));
364
701
  }
365
702
  }
366
- }
367
- group.iterate((child)=>{
368
- if (child !== group) {
369
- child.sealed = true;
703
+ if (saveAnimations) {
704
+ for (const animationData of this.animations){
705
+ if (animationData.skeletons.length > 0 && !saveSkeletons) {
706
+ continue;
707
+ }
708
+ let name = animationData.name ?? `_embbeded_animation`;
709
+ if (group.animationSet.getAnimationClip(name)) {
710
+ const baseName = name;
711
+ for(let t = 1;; t++){
712
+ name = `${baseName}_${t}`;
713
+ if (!group.animationSet.getAnimationClip(name)) {
714
+ break;
715
+ }
716
+ }
717
+ }
718
+ const animation = group.animationSet.createAnimation(name, true);
719
+ for (const sk of animationData.skeletons){
720
+ const nodes = skeletonMeshMap.get(sk);
721
+ if (nodes) {
722
+ animation.addSkeleton(nodes.binding.rig.persistentId);
723
+ }
724
+ }
725
+ for (const track of animationData.tracks){
726
+ const target = nodeMap.get(track.node);
727
+ if (track.type === 'translation') {
728
+ animation.addTrack(target, new NodeTranslationTrack(track.interpolator, true));
729
+ target.jointTypeT = 'animated';
730
+ } else if (track.type === 'scale') {
731
+ animation.addTrack(target, new NodeScaleTrack(track.interpolator, true));
732
+ target.jointTypeS = 'animated';
733
+ } else if (track.type === 'rotation') {
734
+ animation.addTrack(target, new NodeRotationTrack(track.interpolator, true));
735
+ target.jointTypeR = 'animated';
736
+ } else if (track.type === 'weights') {
737
+ for (const m of track.node.mesh.subMeshes){
738
+ const mesh = meshMap.get(m);
739
+ if (track.interpolator.stride > MAX_MORPH_TARGETS) {
740
+ console.error(`Morph target too large: ${track.interpolator.stride}, the maximum is ${MAX_MORPH_TARGETS}`);
741
+ } else {
742
+ const morphTrack = new MorphTargetTrack(track.interpolator, track.defaultMorphWeights, m.targetBox, mesh.getBoundingVolume().toAABB(), true);
743
+ animation.addTrack(mesh, morphTrack);
744
+ }
745
+ }
746
+ } else if (track.type === 'geometry-cache') {
747
+ const subMesh = track.node.mesh?.subMeshes[track.subMeshIndex];
748
+ const mesh = subMesh ? meshMap.get(subMesh) : null;
749
+ if (!subMesh || !mesh) {
750
+ console.error(`Invalid geometry cache sub mesh: ${track.subMeshIndex}`);
751
+ } else {
752
+ if (track.codec === 'pca') {
753
+ const pcaData = this.remapPCAGeometryCacheData(subMesh, track);
754
+ animation.addTrack(mesh, new PCAGeometryCacheTrack(pcaData, true));
755
+ } else {
756
+ const frames = this.remapGeometryCacheFrames(subMesh, track.frames);
757
+ animation.addTrack(mesh, new FixedGeometryCacheTrack(track.times, frames, true));
758
+ }
759
+ }
760
+ } else {
761
+ console.error(`Invalid animation track type: ${track.type}`);
762
+ }
763
+ }
764
+ }
370
765
  }
371
- });
372
- group.sharedModel = this;
766
+ }
767
+ if (saveJointDynamics && saveSkeletons) {
768
+ this.createJointDynamics(group, nodeMap);
769
+ }
770
+ this.createMorphTargetGroups(group, meshMap);
373
771
  return group;
374
772
  }
375
773
  remapGeometryCacheFrames(subMesh, frames) {
@@ -547,51 +945,61 @@ import { MAX_MORPH_TARGETS } from '../values.js';
547
945
  }
548
946
  onDispose() {
549
947
  super.onDispose();
550
- const nodes = [
551
- ...this._nodes
552
- ];
553
- while(nodes.length > 0){
554
- const node = nodes.shift();
555
- nodes.push(...node.children);
556
- const mesh = node.mesh;
557
- if (mesh) {
558
- for (const subMesh of mesh.subMeshes){
559
- subMesh.primitive?.dispose();
560
- subMesh.material?.dispose();
561
- }
562
- }
948
+ for (const prim of this._primitiveMap.values()){
949
+ prim.dispose();
950
+ }
951
+ for (const tex of this._textureMap.values()){
952
+ tex.dispose();
563
953
  }
564
- this._nodes = [];
565
954
  this._skeletons = [];
566
955
  this._scenes = [];
567
956
  this._animations = [];
568
957
  }
569
- setAssetNodeToSceneNode(scene, parent, assetNode, skeletonMeshMap, nodeMap, instancing) {
958
+ async setAssetNodeToSceneNode(manager, scene, parent, assetNode, skeletonMeshMap, nodeMap, meshMap, instancing, saveMeshes, saveSkeletons, saveAnimations, srcVFS) {
570
959
  const node = new SceneNode(scene);
571
960
  nodeMap.set(assetNode, node);
572
961
  node.name = assetNode.name ?? '';
573
962
  node.position.set(assetNode.position);
574
963
  node.rotation.set(assetNode.rotation);
575
964
  node.scale.set(assetNode.scaling);
576
- if (assetNode.mesh) {
965
+ if (saveMeshes && assetNode.mesh) {
577
966
  const meshData = assetNode.mesh;
578
- const skeleton = assetNode.skeleton;
967
+ const skeleton = saveSkeletons ? assetNode.skeleton : null;
579
968
  for (const subMesh of meshData.subMeshes){
969
+ if (assetNode.instances.length === 0) {
970
+ assetNode.instances.push({
971
+ t: Vector3.zero(),
972
+ s: Vector3.one(),
973
+ r: Quaternion.identity()
974
+ });
975
+ }
580
976
  for (const instance of assetNode.instances){
581
977
  const meshNode = new Mesh(scene);
582
- const skinAnimation = !!skeleton;
583
- const morphAnimation = subMesh.numTargets > 0;
584
978
  meshNode.position = instance.t;
585
979
  meshNode.scale = instance.s;
586
980
  meshNode.rotation = instance.r;
587
981
  meshNode.name = subMesh.name;
588
982
  meshNode.clipTestEnabled = true;
589
983
  meshNode.showState = 'inherit';
590
- meshNode.primitive = subMesh.primitive.get();
591
- meshNode.material = instancing && !skinAnimation && !morphAnimation ? subMesh.material.get().createInstance() : subMesh.material.get().clone();
984
+ if (!this._primitiveMap.get(subMesh.primitive)) {
985
+ this._primitiveMap.set(subMesh.primitive, new DRef(await this.createPrimitive(manager, subMesh.primitive, srcVFS) ?? undefined));
986
+ }
987
+ meshNode.primitive = this._primitiveMap.get(subMesh.primitive).get();
988
+ if (!subMesh.material.material) {
989
+ subMesh.material.material = new DRef(await this.createMaterial(manager, subMesh.material, srcVFS) ?? undefined);
990
+ }
991
+ meshNode.material = subMesh.material.material?.get() ?? null;
992
+ if (instancing) {
993
+ meshNode.material = meshNode.material.createInstance();
994
+ }
592
995
  meshNode.parent = node;
593
- subMesh.mesh = meshNode;
594
- processMorphData(subMesh, meshData.morphWeights);
996
+ meshMap.set(subMesh, meshNode);
997
+ setSceneMeshAssetBinding(meshNode, {
998
+ node: assetNode,
999
+ mesh: meshData,
1000
+ subMesh
1001
+ });
1002
+ processMorphData(subMesh, meshNode, assetNode.weights ?? meshData.morphWeights, meshData.morphNames);
595
1003
  if (skeleton) {
596
1004
  if (!skeletonMeshMap.has(skeleton)) {
597
1005
  skeletonMeshMap.set(skeleton, {
@@ -612,9 +1020,470 @@ import { MAX_MORPH_TARGETS } from '../values.js';
612
1020
  }
613
1021
  node.parent = parent;
614
1022
  for (const child of assetNode.children){
615
- this.setAssetNodeToSceneNode(scene, node, child, skeletonMeshMap, nodeMap, instancing);
1023
+ await this.setAssetNodeToSceneNode(manager, scene, node, child, skeletonMeshMap, nodeMap, meshMap, instancing, saveMeshes, saveSkeletons, saveAnimations, srcVFS);
1024
+ }
1025
+ return node;
1026
+ }
1027
+ async image2Texture(manager, info, srcVFS) {
1028
+ if (info.image.uri) {
1029
+ const texture = await manager.fetchTexture(info.image.uri, {
1030
+ linearColorSpace: !info.sRGB,
1031
+ overrideVFS: srcVFS
1032
+ });
1033
+ texture.name = info.image.uri;
1034
+ return texture;
1035
+ } else if (info.image.data && info.image.mimeType) {
1036
+ const texture = await manager.loadTextureFromBuffer(info.image.data, info.image.mimeType, !!info.sRGB);
1037
+ return texture;
1038
+ }
1039
+ return null;
1040
+ }
1041
+ async createTexture(manager, info, srcVFS) {
1042
+ try {
1043
+ let textureRef = this._textureMap.get(info);
1044
+ if (!textureRef) {
1045
+ textureRef = new DRef(await this.image2Texture(manager, info, srcVFS));
1046
+ this._textureMap.set(info, textureRef);
1047
+ }
1048
+ const sampler = getDevice().createSampler({
1049
+ addressU: info.sampler.wrapS,
1050
+ addressV: info.sampler.wrapT,
1051
+ magFilter: info.sampler.magFilter,
1052
+ minFilter: info.sampler.minFilter,
1053
+ mipFilter: info.sampler.mipFilter
1054
+ });
1055
+ const transform = info.transform;
1056
+ const texCoord = info.texCoord;
1057
+ return {
1058
+ texture: textureRef.get(),
1059
+ sampler,
1060
+ texCoord,
1061
+ transform
1062
+ };
1063
+ } catch (err) {
1064
+ console.error(`Load asset texture failed: ${err}`);
1065
+ return null;
1066
+ }
1067
+ }
1068
+ async createPrimitive(manager, info, srcVFS) {
1069
+ if (info.path) {
1070
+ return manager.fetchPrimitive(info.path, {
1071
+ overrideVFS: srcVFS
1072
+ });
1073
+ }
1074
+ const primitive = new Primitive();
1075
+ for(const k in info.vertices){
1076
+ const v = info.vertices[k];
1077
+ primitive.createAndSetVertexBuffer(v.format, v.data);
1078
+ }
1079
+ if (info.indices) {
1080
+ primitive.createAndSetIndexBuffer(info.indices);
1081
+ }
1082
+ primitive.primitiveType = info.type;
1083
+ primitive.indexCount = info.indexCount;
1084
+ primitive.setBoundingVolume(new BoundingBox(info.boxMin, info.boxMax));
1085
+ return primitive;
1086
+ }
1087
+ async createMaterial(manager, assetMaterial, srcVFS) {
1088
+ if (assetMaterial.path) {
1089
+ return manager.fetchMaterial(assetMaterial.path, {
1090
+ overrideVFS: srcVFS
1091
+ });
1092
+ }
1093
+ const infoMap = new Map();
1094
+ const that = this;
1095
+ async function getTextureInfo(info) {
1096
+ let t = infoMap.get(info);
1097
+ if (t === undefined) {
1098
+ t = await that.createTexture(manager, info, srcVFS);
1099
+ infoMap.set(info, t);
1100
+ }
1101
+ return t;
1102
+ }
1103
+ if (assetMaterial.type === 'unlit') {
1104
+ const unlitAssetMaterial = assetMaterial;
1105
+ const unlitMaterial = new UnlitMaterial();
1106
+ unlitMaterial.albedoColor = unlitAssetMaterial.diffuse ?? Vector4.one();
1107
+ if (unlitAssetMaterial.diffuseMap) {
1108
+ const info = await getTextureInfo(unlitAssetMaterial.diffuseMap);
1109
+ if (info) {
1110
+ unlitMaterial.albedoTexture = info.texture;
1111
+ unlitMaterial.albedoTextureSampler = info.sampler;
1112
+ unlitMaterial.albedoTexCoordIndex = info.texCoord;
1113
+ unlitMaterial.albedoTexCoordMatrix = info.transform;
1114
+ }
1115
+ }
1116
+ unlitMaterial.vertexColor = unlitAssetMaterial.common.vertexColor;
1117
+ if (assetMaterial.common.alphaMode === 'blend') {
1118
+ unlitMaterial.blendMode = 'blend';
1119
+ } else if (assetMaterial.common.alphaMode === 'mask') {
1120
+ unlitMaterial.alphaCutoff = assetMaterial.common.alphaCutoff;
1121
+ }
1122
+ if (assetMaterial.common.doubleSided) {
1123
+ unlitMaterial.cullMode = 'none';
1124
+ }
1125
+ return unlitMaterial;
1126
+ } else if (assetMaterial.type === 'pbrSpecularGlossiness') {
1127
+ const assetPBRMaterial = assetMaterial;
1128
+ const pbrMaterial = new PBRSpecularGlossinessMaterial();
1129
+ pbrMaterial.ior = assetPBRMaterial.ior;
1130
+ pbrMaterial.albedoColor = assetPBRMaterial.diffuse;
1131
+ pbrMaterial.specularFactor = new Vector3(assetPBRMaterial.specular.x, assetPBRMaterial.specular.y, assetPBRMaterial.specular.z);
1132
+ pbrMaterial.glossinessFactor = assetPBRMaterial.glossness;
1133
+ if (assetPBRMaterial.diffuseMap) {
1134
+ const info = await getTextureInfo(assetPBRMaterial.diffuseMap);
1135
+ if (info) {
1136
+ pbrMaterial.albedoTexture = info.texture;
1137
+ pbrMaterial.albedoTextureSampler = info.sampler;
1138
+ pbrMaterial.albedoTexCoordIndex = info.texCoord;
1139
+ pbrMaterial.albedoTexCoordMatrix = info.transform;
1140
+ }
1141
+ }
1142
+ if (assetPBRMaterial.common.normalMap) {
1143
+ const info = await getTextureInfo(assetPBRMaterial.common.normalMap);
1144
+ if (info) {
1145
+ pbrMaterial.normalTexture = info.texture;
1146
+ pbrMaterial.normalTextureSampler = info.sampler;
1147
+ pbrMaterial.normalTexCoordIndex = info.texCoord;
1148
+ pbrMaterial.normalTexCoordMatrix = info.transform;
1149
+ }
1150
+ }
1151
+ pbrMaterial.normalScale = assetPBRMaterial.common.bumpScale;
1152
+ if (assetPBRMaterial.common.emissiveMap) {
1153
+ const info = await getTextureInfo(assetPBRMaterial.common.emissiveMap);
1154
+ if (info) {
1155
+ pbrMaterial.emissiveTexture = info.texture;
1156
+ pbrMaterial.emissiveTextureSampler = info.sampler;
1157
+ pbrMaterial.emissiveTexCoordIndex = info.texCoord;
1158
+ pbrMaterial.emissiveTexCoordMatrix = info.transform;
1159
+ }
1160
+ }
1161
+ pbrMaterial.emissiveColor = assetPBRMaterial.common.emissiveColor;
1162
+ pbrMaterial.emissiveStrength = assetPBRMaterial.common.emissiveStrength;
1163
+ if (assetPBRMaterial.common.occlusionMap) {
1164
+ const info = await getTextureInfo(assetPBRMaterial.common.occlusionMap);
1165
+ if (info) {
1166
+ pbrMaterial.occlusionTexture = info.texture;
1167
+ pbrMaterial.occlusionTextureSampler = info.sampler;
1168
+ pbrMaterial.occlusionTexCoordIndex = info.texCoord;
1169
+ pbrMaterial.occlusionTexCoordMatrix = info.transform;
1170
+ }
1171
+ }
1172
+ pbrMaterial.occlusionStrength = assetPBRMaterial.common.occlusionStrength;
1173
+ if (assetPBRMaterial.specularGlossnessMap) {
1174
+ const info = await getTextureInfo(assetPBRMaterial.specularGlossnessMap);
1175
+ if (info) {
1176
+ pbrMaterial.specularTexture = info.texture;
1177
+ pbrMaterial.specularTextureSampler = info.sampler;
1178
+ pbrMaterial.specularTexCoordIndex = info.texCoord;
1179
+ pbrMaterial.specularTexCoordMatrix = info.transform;
1180
+ }
1181
+ }
1182
+ pbrMaterial.vertexTangent = assetPBRMaterial.common.useTangent;
1183
+ pbrMaterial.vertexColor = assetPBRMaterial.common.vertexColor;
1184
+ if (assetPBRMaterial.common.alphaMode === 'blend') {
1185
+ pbrMaterial.blendMode = 'blend';
1186
+ } else if (assetPBRMaterial.common.alphaMode === 'mask') {
1187
+ pbrMaterial.alphaCutoff = assetPBRMaterial.common.alphaCutoff;
1188
+ }
1189
+ if (assetPBRMaterial.common.doubleSided) {
1190
+ pbrMaterial.cullMode = 'none';
1191
+ }
1192
+ pbrMaterial.vertexNormal = !!assetMaterial.common.vertexNormal;
1193
+ return pbrMaterial;
1194
+ } else if (assetMaterial.type === 'pbrMetallicRoughness') {
1195
+ const assetPBRMaterial = assetMaterial;
1196
+ const pbrMaterial = new PBRMetallicRoughnessMaterial();
1197
+ pbrMaterial.ior = assetPBRMaterial.ior;
1198
+ pbrMaterial.albedoColor = assetPBRMaterial.diffuse;
1199
+ pbrMaterial.metallic = assetPBRMaterial.metallic;
1200
+ pbrMaterial.roughness = assetPBRMaterial.roughness;
1201
+ if (assetPBRMaterial.diffuseMap) {
1202
+ const info = await getTextureInfo(assetPBRMaterial.diffuseMap);
1203
+ if (info) {
1204
+ pbrMaterial.albedoTexture = info.texture;
1205
+ pbrMaterial.albedoTextureSampler = info.sampler;
1206
+ pbrMaterial.albedoTexCoordIndex = info.texCoord;
1207
+ pbrMaterial.albedoTexCoordMatrix = info.transform;
1208
+ }
1209
+ }
1210
+ if (assetPBRMaterial.common.normalMap) {
1211
+ const info = await getTextureInfo(assetPBRMaterial.common.normalMap);
1212
+ if (info) {
1213
+ pbrMaterial.normalTexture = info.texture;
1214
+ pbrMaterial.normalTextureSampler = info.sampler;
1215
+ pbrMaterial.normalTexCoordIndex = info.texCoord;
1216
+ pbrMaterial.normalTexCoordMatrix = info.transform;
1217
+ }
1218
+ }
1219
+ pbrMaterial.normalScale = assetPBRMaterial.common.bumpScale;
1220
+ if (assetPBRMaterial.common.emissiveMap) {
1221
+ const info = await getTextureInfo(assetPBRMaterial.common.emissiveMap);
1222
+ if (info) {
1223
+ pbrMaterial.emissiveTexture = info.texture;
1224
+ pbrMaterial.emissiveTextureSampler = info.sampler;
1225
+ pbrMaterial.emissiveTexCoordIndex = info.texCoord;
1226
+ pbrMaterial.emissiveTexCoordMatrix = info.transform;
1227
+ }
1228
+ }
1229
+ pbrMaterial.emissiveColor = assetPBRMaterial.common.emissiveColor;
1230
+ pbrMaterial.emissiveStrength = assetPBRMaterial.common.emissiveStrength;
1231
+ if (assetPBRMaterial.common.occlusionMap) {
1232
+ const info = await getTextureInfo(assetPBRMaterial.common.occlusionMap);
1233
+ if (info) {
1234
+ pbrMaterial.occlusionTexture = info.texture;
1235
+ pbrMaterial.occlusionTextureSampler = info.sampler;
1236
+ pbrMaterial.occlusionTexCoordIndex = info.texCoord;
1237
+ pbrMaterial.occlusionTexCoordMatrix = info.transform;
1238
+ }
1239
+ pbrMaterial.occlusionStrength = assetPBRMaterial.common.occlusionStrength;
1240
+ }
1241
+ if (assetPBRMaterial.metallicMap) {
1242
+ const info = await getTextureInfo(assetPBRMaterial.metallicMap);
1243
+ if (info) {
1244
+ pbrMaterial.metallicRoughnessTexture = info.texture;
1245
+ pbrMaterial.metallicRoughnessTextureSampler = info.sampler;
1246
+ pbrMaterial.metallicRoughnessTexCoordIndex = info.texCoord;
1247
+ pbrMaterial.metallicRoughnessTexCoordMatrix = info.transform;
1248
+ }
1249
+ }
1250
+ pbrMaterial.specularFactor = assetPBRMaterial.specularFactor;
1251
+ if (assetPBRMaterial.specularMap) {
1252
+ const info = await getTextureInfo(assetPBRMaterial.specularMap);
1253
+ if (info) {
1254
+ pbrMaterial.specularTexture = info.texture;
1255
+ pbrMaterial.specularTextureSampler = info.sampler;
1256
+ pbrMaterial.specularTexCoordIndex = info.texCoord;
1257
+ pbrMaterial.specularTexCoordMatrix = info.transform;
1258
+ }
1259
+ }
1260
+ if (assetPBRMaterial.specularColorMap) {
1261
+ const info = await getTextureInfo(assetPBRMaterial.specularColorMap);
1262
+ if (info) {
1263
+ pbrMaterial.specularColorTexture = info.texture;
1264
+ pbrMaterial.specularColorTextureSampler = info.sampler;
1265
+ pbrMaterial.specularColorTexCoordIndex = info.texCoord;
1266
+ pbrMaterial.specularColorTexCoordMatrix = info.transform;
1267
+ }
1268
+ }
1269
+ if (assetPBRMaterial.sheen) {
1270
+ const sheen = assetPBRMaterial.sheen;
1271
+ pbrMaterial.sheen = true;
1272
+ pbrMaterial.sheenColorFactor = sheen.sheenColorFactor;
1273
+ pbrMaterial.sheenRoughnessFactor = sheen.sheenRoughnessFactor;
1274
+ if (sheen.sheenColorMap) {
1275
+ const info = await getTextureInfo(sheen.sheenColorMap);
1276
+ if (info) {
1277
+ pbrMaterial.sheenColorTexture = info.texture;
1278
+ pbrMaterial.sheenColorTextureSampler = info.sampler;
1279
+ pbrMaterial.sheenColorTexCoordIndex = info.texCoord;
1280
+ pbrMaterial.sheenColorTexCoordMatrix = info.transform;
1281
+ }
1282
+ }
1283
+ if (sheen.sheenRoughnessMap) {
1284
+ const info = await getTextureInfo(sheen.sheenRoughnessMap);
1285
+ if (info) {
1286
+ pbrMaterial.sheenRoughnessTexture = info.texture;
1287
+ pbrMaterial.sheenRoughnessTextureSampler = info.sampler;
1288
+ pbrMaterial.sheenRoughnessTexCoordIndex = info.texCoord;
1289
+ pbrMaterial.sheenRoughnessTexCoordMatrix = info.transform;
1290
+ }
1291
+ }
1292
+ }
1293
+ if (assetPBRMaterial.iridescence) {
1294
+ const iridescence = assetPBRMaterial.iridescence;
1295
+ pbrMaterial.iridescence = true;
1296
+ pbrMaterial.iridescenceFactor = iridescence.iridescenceFactor;
1297
+ pbrMaterial.iridescenceIor = iridescence.iridescenceIor;
1298
+ if (iridescence.iridescenceMap) {
1299
+ const info = await getTextureInfo(iridescence.iridescenceMap);
1300
+ if (info) {
1301
+ pbrMaterial.iridescenceTexture = info.texture;
1302
+ pbrMaterial.iridescenceTextureSampler = info.sampler;
1303
+ pbrMaterial.iridescenceTexCoordIndex = info.texCoord;
1304
+ pbrMaterial.iridescenceTexCoordMatrix = info.transform;
1305
+ }
1306
+ }
1307
+ pbrMaterial.iridescenceThicknessMin = iridescence.iridescenceThicknessMinimum;
1308
+ pbrMaterial.iridescenceThicknessMax = iridescence.iridescenceThicknessMaximum;
1309
+ if (iridescence.iridescenceThicknessMap) {
1310
+ const info = await getTextureInfo(iridescence.iridescenceThicknessMap);
1311
+ if (info) {
1312
+ pbrMaterial.iridescenceThicknessTexture = info.texture;
1313
+ pbrMaterial.iridescenceThicknessTextureSampler = info.sampler;
1314
+ pbrMaterial.iridescenceThicknessTexCoordIndex = info.texCoord;
1315
+ pbrMaterial.iridescenceThicknessTexCoordMatrix = info.transform;
1316
+ }
1317
+ }
1318
+ }
1319
+ if (assetPBRMaterial.transmission) {
1320
+ const transmission = assetPBRMaterial.transmission;
1321
+ pbrMaterial.transmission = true;
1322
+ pbrMaterial.transmissionFactor = transmission.transmissionFactor;
1323
+ if (transmission.transmissionMap) {
1324
+ const info = await getTextureInfo(transmission.transmissionMap);
1325
+ if (info) {
1326
+ pbrMaterial.transmissionTexture = info.texture;
1327
+ pbrMaterial.transmissionTextureSampler = info.sampler;
1328
+ pbrMaterial.transmissionTexCoordIndex = info.texCoord;
1329
+ pbrMaterial.transmissionTexCoordMatrix = info.transform;
1330
+ }
1331
+ }
1332
+ pbrMaterial.thicknessFactor = transmission.thicknessFactor;
1333
+ if (transmission.thicknessMap) {
1334
+ const info = await getTextureInfo(transmission.thicknessMap);
1335
+ if (info) {
1336
+ pbrMaterial.thicknessTexture = info.texture;
1337
+ pbrMaterial.thicknessTextureSampler = info.sampler;
1338
+ pbrMaterial.thicknessTexCoordIndex = info.texCoord;
1339
+ pbrMaterial.thicknessTexCoordMatrix = info.transform;
1340
+ }
1341
+ }
1342
+ pbrMaterial.attenuationDistance = transmission.attenuationDistance;
1343
+ pbrMaterial.attenuationColor = transmission.attenuationColor;
1344
+ }
1345
+ if (assetPBRMaterial.clearcoat) {
1346
+ const cc = assetPBRMaterial.clearcoat;
1347
+ pbrMaterial.clearcoat = true;
1348
+ pbrMaterial.clearcoatIntensity = cc.clearCoatFactor;
1349
+ pbrMaterial.clearcoatRoughnessFactor = cc.clearCoatRoughnessFactor;
1350
+ if (cc.clearCoatIntensityMap) {
1351
+ const info = await getTextureInfo(cc.clearCoatIntensityMap);
1352
+ if (info) {
1353
+ pbrMaterial.clearcoatIntensityTexture = info.texture;
1354
+ pbrMaterial.clearcoatIntensityTextureSampler = info.sampler;
1355
+ pbrMaterial.clearcoatIntensityTexCoordIndex = info.texCoord;
1356
+ pbrMaterial.clearcoatIntensityTexCoordMatrix = info.transform;
1357
+ }
1358
+ }
1359
+ if (cc.clearCoatRoughnessMap) {
1360
+ const info = await getTextureInfo(cc.clearCoatRoughnessMap);
1361
+ if (info) {
1362
+ pbrMaterial.clearcoatRoughnessTexture = info.texture;
1363
+ pbrMaterial.clearcoatRoughnessTextureSampler = info.sampler;
1364
+ pbrMaterial.clearcoatRoughnessTexCoordIndex = info.texCoord;
1365
+ pbrMaterial.clearcoatRoughnessTexCoordMatrix = info.transform;
1366
+ }
1367
+ }
1368
+ if (cc.clearCoatNormalMap) {
1369
+ const info = await getTextureInfo(cc.clearCoatNormalMap);
1370
+ if (info) {
1371
+ pbrMaterial.clearcoatNormalTexture = info.texture;
1372
+ pbrMaterial.clearcoatNormalTextureSampler = info.sampler;
1373
+ pbrMaterial.clearcoatNormalTexCoordIndex = info.texCoord;
1374
+ pbrMaterial.clearcoatNormalTexCoordMatrix = info.transform;
1375
+ }
1376
+ }
1377
+ }
1378
+ pbrMaterial.vertexTangent = assetPBRMaterial.common.useTangent;
1379
+ pbrMaterial.vertexColor = assetPBRMaterial.common.vertexColor;
1380
+ if (assetPBRMaterial.common.alphaMode === 'blend') {
1381
+ pbrMaterial.blendMode = 'blend';
1382
+ } else if (assetPBRMaterial.common.alphaMode === 'mask') {
1383
+ pbrMaterial.alphaCutoff = assetPBRMaterial.common.alphaCutoff;
1384
+ }
1385
+ if (assetPBRMaterial.common.doubleSided) {
1386
+ pbrMaterial.cullMode = 'none';
1387
+ }
1388
+ pbrMaterial.vertexNormal = !!assetMaterial.common.vertexNormal;
1389
+ return pbrMaterial;
1390
+ }
1391
+ return null;
1392
+ }
1393
+ }
1394
+ /** @internal */ function processMorphData(subMesh, mesh, morphWeights, morphNames) {
1395
+ const device = getDevice();
1396
+ const numTargets = subMesh.numTargets;
1397
+ if (numTargets === 0) {
1398
+ return;
1399
+ }
1400
+ const attributes = Object.getOwnPropertyNames(subMesh.targets);
1401
+ const positionInfo = subMesh.primitive.vertices['position'];
1402
+ const numVertices = positionInfo ? positionInfo.data.length / getVertexFormatComponentCount(positionInfo.format) >> 0 : 0;
1403
+ const weightsAndOffsets = new Float32Array(4 + MAX_MORPH_TARGETS + MAX_MORPH_ATTRIBUTES);
1404
+ for(let i = 0; i < numTargets; i++){
1405
+ weightsAndOffsets[4 + i] = morphWeights?.[i] ?? 0;
1406
+ }
1407
+ const textureSize = Math.ceil(Math.sqrt(numVertices * attributes.length * numTargets));
1408
+ if (textureSize > device.getDeviceCaps().textureCaps.maxTextureSize) {
1409
+ // TODO: reduce morph attributes
1410
+ throw new Error(`Morph target data too large`);
1411
+ }
1412
+ weightsAndOffsets[0] = textureSize;
1413
+ weightsAndOffsets[1] = textureSize;
1414
+ weightsAndOffsets[2] = numVertices;
1415
+ weightsAndOffsets[3] = numTargets;
1416
+ let offset = 0;
1417
+ const textureData = new Float32Array(textureSize * textureSize * 4);
1418
+ for(let attrib = 0; attrib < MAX_MORPH_ATTRIBUTES; attrib++){
1419
+ const index = attributes.indexOf(String(attrib));
1420
+ if (index < 0) {
1421
+ weightsAndOffsets[4 + MAX_MORPH_TARGETS + attrib] = -1;
1422
+ continue;
1423
+ }
1424
+ weightsAndOffsets[4 + MAX_MORPH_TARGETS + attrib] = offset >> 2;
1425
+ const info = subMesh.targets[attrib];
1426
+ if (info.data.length !== numTargets) {
1427
+ console.error(`Invalid morph target data`);
1428
+ return;
1429
+ }
1430
+ for(let t = 0; t < numTargets; t++){
1431
+ const data = info.data[t];
1432
+ for(let i = 0; i < numVertices; i++){
1433
+ for(let j = 0; j < 4; j++){
1434
+ textureData[offset++] = j < info.numComponents ? data[i * info.numComponents + j] : 1;
1435
+ }
1436
+ }
616
1437
  }
617
1438
  }
1439
+ const morphBoundingBox = new BoundingBox();
1440
+ calculateMorphBoundingBox(morphBoundingBox, subMesh.targetBox, weightsAndOffsets.subarray(4, 4 + MAX_MORPH_TARGETS), numTargets);
1441
+ const meshAABB = mesh.getBoundingVolume().toAABB();
1442
+ morphBoundingBox.minPoint.addBy(meshAABB.minPoint);
1443
+ morphBoundingBox.maxPoint.addBy(meshAABB.maxPoint);
1444
+ const names = {};
1445
+ for(let i = 0; i < numTargets; i++){
1446
+ const name = morphNames?.[i] ?? `Target${i}`;
1447
+ names[name] = i;
1448
+ }
1449
+ mesh.setMorphData({
1450
+ width: textureSize,
1451
+ height: textureSize,
1452
+ data: textureData
1453
+ });
1454
+ mesh.setMorphInfo({
1455
+ data: weightsAndOffsets,
1456
+ names
1457
+ });
1458
+ mesh.setMorphBoundingInfo({
1459
+ targetBoxes: subMesh.targetBox,
1460
+ originBox: new BoundingBox(meshAABB)
1461
+ });
1462
+ mesh.setAnimatedBoundingBox(morphBoundingBox);
1463
+ }
1464
+ /** @internal */ function getAssetMeshMorphTargetCount(mesh) {
1465
+ let count = 0;
1466
+ for (const subMesh of mesh.subMeshes){
1467
+ count = Math.max(count, subMesh.numTargets);
1468
+ }
1469
+ return count;
1470
+ }
1471
+ /** @internal */ function getAssetMeshMorphTargetName(mesh, index) {
1472
+ return mesh.morphNames?.[index] ?? `Target${index}`;
1473
+ }
1474
+ /** @internal */ function calculateMorphBoundingBox(morphBoundingBox, keyframeBoundingBox, weights, numTargets) {
1475
+ morphBoundingBox.minPoint.setXYZ(0, 0, 0);
1476
+ morphBoundingBox.maxPoint.setXYZ(0, 0, 0);
1477
+ for(let i = 0; i < numTargets; i++){
1478
+ const weight = weights[i];
1479
+ const keyframeBox = keyframeBoundingBox[i];
1480
+ morphBoundingBox.minPoint.x += keyframeBox.minPoint.x * weight;
1481
+ morphBoundingBox.minPoint.y += keyframeBox.minPoint.y * weight;
1482
+ morphBoundingBox.minPoint.z += keyframeBox.minPoint.z * weight;
1483
+ morphBoundingBox.maxPoint.x += keyframeBox.maxPoint.x * weight;
1484
+ morphBoundingBox.maxPoint.y += keyframeBox.maxPoint.y * weight;
1485
+ morphBoundingBox.maxPoint.z += keyframeBox.maxPoint.z * weight;
1486
+ }
618
1487
  }
619
1488
 
620
1489
  export { AssetHierarchyNode, AssetScene, AssetSkeleton, NamedObject, SharedModel };