@needle-tools/engine 5.1.0-alpha.3 → 5.1.0-alpha.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.
Files changed (178) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/SKILL.md +4 -1
  3. package/components.needle.json +1 -1
  4. package/dist/needle-engine.bundle-AjVIot3d.min.js +1733 -0
  5. package/dist/{needle-engine.bundle-DF01sSGQ.js → needle-engine.bundle-B7cqsI4c.js} +9149 -8867
  6. package/dist/{needle-engine.bundle-C-ixARur.umd.cjs → needle-engine.bundle-DQCuBTVp.umd.cjs} +154 -154
  7. package/dist/needle-engine.d.ts +754 -199
  8. package/dist/needle-engine.js +583 -584
  9. package/dist/needle-engine.min.js +1 -1
  10. package/dist/needle-engine.umd.cjs +1 -1
  11. package/lib/engine/api.d.ts +3 -0
  12. package/lib/engine/api.js +2 -0
  13. package/lib/engine/api.js.map +1 -1
  14. package/lib/engine/codegen/register_types.js +2 -10
  15. package/lib/engine/codegen/register_types.js.map +1 -1
  16. package/lib/engine/engine_context.js +2 -1
  17. package/lib/engine/engine_context.js.map +1 -1
  18. package/lib/engine/engine_disposable.d.ts +172 -0
  19. package/lib/engine/engine_disposable.js +136 -0
  20. package/lib/engine/engine_disposable.js.map +1 -0
  21. package/lib/engine/engine_gameobject.d.ts +1 -10
  22. package/lib/engine/engine_gameobject.js +20 -118
  23. package/lib/engine/engine_gameobject.js.map +1 -1
  24. package/lib/engine/engine_gltf_builtin_components.js +7 -69
  25. package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
  26. package/lib/engine/engine_instantiate_resolve.d.ts +42 -0
  27. package/lib/engine/engine_instantiate_resolve.js +372 -0
  28. package/lib/engine/engine_instantiate_resolve.js.map +1 -0
  29. package/lib/engine/engine_mainloop_utils.js +2 -2
  30. package/lib/engine/engine_mainloop_utils.js.map +1 -1
  31. package/lib/engine/engine_networking.d.ts +51 -37
  32. package/lib/engine/engine_networking.js +132 -82
  33. package/lib/engine/engine_networking.js.map +1 -1
  34. package/lib/engine/engine_networking.transport.websocket.d.ts +15 -0
  35. package/lib/engine/engine_networking.transport.websocket.js +38 -0
  36. package/lib/engine/engine_networking.transport.websocket.js.map +1 -0
  37. package/lib/engine/engine_networking_instantiate.js +2 -2
  38. package/lib/engine/engine_networking_instantiate.js.map +1 -1
  39. package/lib/engine/engine_networking_types.d.ts +39 -1
  40. package/lib/engine/engine_networking_types.js +7 -0
  41. package/lib/engine/engine_networking_types.js.map +1 -1
  42. package/lib/engine/engine_physics_rapier.d.ts +11 -3
  43. package/lib/engine/engine_physics_rapier.js +88 -25
  44. package/lib/engine/engine_physics_rapier.js.map +1 -1
  45. package/lib/engine/engine_serialization_builtin_serializer.js +1 -5
  46. package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
  47. package/lib/engine/engine_serialization_core.d.ts +1 -0
  48. package/lib/engine/engine_serialization_core.js +7 -0
  49. package/lib/engine/engine_serialization_core.js.map +1 -1
  50. package/lib/engine/engine_types.d.ts +19 -11
  51. package/lib/engine/engine_types.js +1 -1
  52. package/lib/engine/engine_types.js.map +1 -1
  53. package/lib/engine/engine_util_decorator.js +7 -2
  54. package/lib/engine/engine_util_decorator.js.map +1 -1
  55. package/lib/engine/engine_utils.d.ts +1 -1
  56. package/lib/engine/engine_utils.js +19 -5
  57. package/lib/engine/engine_utils.js.map +1 -1
  58. package/lib/engine-components/Animator.d.ts +6 -0
  59. package/lib/engine-components/Animator.js +17 -12
  60. package/lib/engine-components/Animator.js.map +1 -1
  61. package/lib/engine-components/AnimatorController.builder.d.ts +113 -0
  62. package/lib/engine-components/AnimatorController.builder.js +195 -0
  63. package/lib/engine-components/AnimatorController.builder.js.map +1 -0
  64. package/lib/engine-components/AnimatorController.d.ts +2 -119
  65. package/lib/engine-components/AnimatorController.js +31 -232
  66. package/lib/engine-components/AnimatorController.js.map +1 -1
  67. package/lib/engine-components/Collider.d.ts +18 -9
  68. package/lib/engine-components/Collider.js +61 -14
  69. package/lib/engine-components/Collider.js.map +1 -1
  70. package/lib/engine-components/Component.d.ts +72 -9
  71. package/lib/engine-components/Component.js +114 -10
  72. package/lib/engine-components/Component.js.map +1 -1
  73. package/lib/engine-components/DragControls.js +0 -7
  74. package/lib/engine-components/DragControls.js.map +1 -1
  75. package/lib/engine-components/EventList.d.ts +31 -9
  76. package/lib/engine-components/EventList.js +37 -76
  77. package/lib/engine-components/EventList.js.map +1 -1
  78. package/lib/engine-components/Joints.d.ts +4 -2
  79. package/lib/engine-components/Joints.js +19 -3
  80. package/lib/engine-components/Joints.js.map +1 -1
  81. package/lib/engine-components/Light.js +9 -1
  82. package/lib/engine-components/Light.js.map +1 -1
  83. package/lib/engine-components/RigidBody.d.ts +12 -4
  84. package/lib/engine-components/RigidBody.js +18 -4
  85. package/lib/engine-components/RigidBody.js.map +1 -1
  86. package/lib/engine-components/api.d.ts +1 -1
  87. package/lib/engine-components/api.js +1 -1
  88. package/lib/engine-components/api.js.map +1 -1
  89. package/lib/engine-components/codegen/components.d.ts +3 -9
  90. package/lib/engine-components/codegen/components.js +3 -9
  91. package/lib/engine-components/codegen/components.js.map +1 -1
  92. package/lib/engine-components/timeline/PlayableDirector.d.ts +16 -6
  93. package/lib/engine-components/timeline/PlayableDirector.js +70 -62
  94. package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
  95. package/lib/engine-components/timeline/SignalAsset.d.ts +3 -1
  96. package/lib/engine-components/timeline/SignalAsset.js +1 -0
  97. package/lib/engine-components/timeline/SignalAsset.js.map +1 -1
  98. package/lib/engine-components/timeline/TimelineBuilder.d.ts +247 -0
  99. package/lib/engine-components/timeline/TimelineBuilder.js +400 -0
  100. package/lib/engine-components/timeline/TimelineBuilder.js.map +1 -0
  101. package/lib/engine-components/timeline/TimelineModels.d.ts +2 -1
  102. package/lib/engine-components/timeline/TimelineModels.js +3 -0
  103. package/lib/engine-components/timeline/TimelineModels.js.map +1 -1
  104. package/lib/engine-components/timeline/TimelineTracks.d.ts +23 -0
  105. package/lib/engine-components/timeline/TimelineTracks.js +71 -13
  106. package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
  107. package/lib/engine-components/timeline/index.d.ts +2 -1
  108. package/lib/engine-components/timeline/index.js +2 -0
  109. package/lib/engine-components/timeline/index.js.map +1 -1
  110. package/package.json +2 -83
  111. package/plugins/common/license.js +5 -2
  112. package/plugins/common/worker.js +9 -4
  113. package/plugins/vite/dependencies.js +1 -11
  114. package/plugins/vite/dependency-watcher.js +2 -2
  115. package/plugins/vite/editor-connection.js +3 -3
  116. package/plugins/vite/reload.js +1 -1
  117. package/plugins/vite/server.js +2 -1
  118. package/src/engine/api.ts +4 -0
  119. package/src/engine/codegen/register_types.ts +2 -10
  120. package/src/engine/engine_context.ts +2 -1
  121. package/src/engine/engine_disposable.ts +214 -0
  122. package/src/engine/engine_gameobject.ts +52 -157
  123. package/src/engine/engine_gltf_builtin_components.ts +7 -76
  124. package/src/engine/engine_instantiate_resolve.ts +407 -0
  125. package/src/engine/engine_mainloop_utils.ts +2 -2
  126. package/src/engine/engine_networking.transport.websocket.ts +45 -0
  127. package/src/engine/engine_networking.ts +161 -137
  128. package/src/engine/engine_networking_instantiate.ts +2 -2
  129. package/src/engine/engine_networking_types.ts +41 -1
  130. package/src/engine/engine_physics_rapier.ts +82 -27
  131. package/src/engine/engine_serialization_builtin_serializer.ts +1 -6
  132. package/src/engine/engine_serialization_core.ts +9 -0
  133. package/src/engine/engine_types.ts +24 -15
  134. package/src/engine/engine_util_decorator.ts +7 -2
  135. package/src/engine/engine_utils.ts +16 -5
  136. package/src/engine-components/Animator.ts +18 -11
  137. package/src/engine-components/AnimatorController.builder.ts +261 -0
  138. package/src/engine-components/AnimatorController.ts +19 -291
  139. package/src/engine-components/Collider.ts +66 -18
  140. package/src/engine-components/Component.ts +118 -20
  141. package/src/engine-components/DragControls.ts +0 -9
  142. package/src/engine-components/EventList.ts +45 -83
  143. package/src/engine-components/Joints.ts +20 -4
  144. package/src/engine-components/Light.ts +10 -2
  145. package/src/engine-components/RigidBody.ts +18 -4
  146. package/src/engine-components/api.ts +1 -1
  147. package/src/engine-components/codegen/components.ts +3 -9
  148. package/src/engine-components/timeline/PlayableDirector.ts +67 -65
  149. package/src/engine-components/timeline/SignalAsset.ts +4 -1
  150. package/src/engine-components/timeline/TimelineBuilder.ts +564 -0
  151. package/src/engine-components/timeline/TimelineModels.ts +5 -1
  152. package/src/engine-components/timeline/TimelineTracks.ts +74 -13
  153. package/src/engine-components/timeline/index.ts +2 -1
  154. package/dist/needle-engine.bundle-CHmXdnE1.min.js +0 -1733
  155. package/lib/engine-components/AvatarLoader.d.ts +0 -80
  156. package/lib/engine-components/AvatarLoader.js +0 -232
  157. package/lib/engine-components/AvatarLoader.js.map +0 -1
  158. package/lib/engine-components/avatar/AvatarBlink_Simple.d.ts +0 -11
  159. package/lib/engine-components/avatar/AvatarBlink_Simple.js +0 -77
  160. package/lib/engine-components/avatar/AvatarBlink_Simple.js.map +0 -1
  161. package/lib/engine-components/avatar/AvatarEyeLook_Rotation.d.ts +0 -14
  162. package/lib/engine-components/avatar/AvatarEyeLook_Rotation.js +0 -69
  163. package/lib/engine-components/avatar/AvatarEyeLook_Rotation.js.map +0 -1
  164. package/lib/engine-components/avatar/Avatar_Brain_LookAt.d.ts +0 -29
  165. package/lib/engine-components/avatar/Avatar_Brain_LookAt.js +0 -122
  166. package/lib/engine-components/avatar/Avatar_Brain_LookAt.js.map +0 -1
  167. package/lib/engine-components/avatar/Avatar_MouthShapes.d.ts +0 -15
  168. package/lib/engine-components/avatar/Avatar_MouthShapes.js +0 -80
  169. package/lib/engine-components/avatar/Avatar_MouthShapes.js.map +0 -1
  170. package/lib/engine-components/avatar/Avatar_MustacheShake.d.ts +0 -9
  171. package/lib/engine-components/avatar/Avatar_MustacheShake.js +0 -30
  172. package/lib/engine-components/avatar/Avatar_MustacheShake.js.map +0 -1
  173. package/src/engine-components/AvatarLoader.ts +0 -264
  174. package/src/engine-components/avatar/AvatarBlink_Simple.ts +0 -70
  175. package/src/engine-components/avatar/AvatarEyeLook_Rotation.ts +0 -64
  176. package/src/engine-components/avatar/Avatar_Brain_LookAt.ts +0 -140
  177. package/src/engine-components/avatar/Avatar_MouthShapes.ts +0 -84
  178. package/src/engine-components/avatar/Avatar_MustacheShake.ts +0 -32
@@ -1,4 +1,4 @@
1
- import type { Ball, Collider, ColliderDesc, Cuboid, EventQueue, QueryFilterFlags, Ray, RigidBody, RigidBodyDesc, World } from '@dimforge/rapier3d-compat';
1
+ import type { Ball, Collider, ColliderDesc, Cuboid, EventQueue, ImpulseJoint, QueryFilterFlags, Ray, RigidBody, RigidBodyDesc, World } from '@dimforge/rapier3d-compat';
2
2
  import { BufferAttribute, BufferGeometry, InterleavedBufferAttribute, LineBasicMaterial, LineSegments, Matrix4, Mesh, Object3D, Quaternion, Vector3, Vector4Like } from 'three'
3
3
  import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'
4
4
 
@@ -690,12 +690,26 @@ export class RapierPhysics implements IPhysicsEngine {
690
690
  }
691
691
  }
692
692
 
693
- clearCaches() {
693
+ /** Tears down the physics world and frees all WASM resources.
694
+ * After calling this, the world will be re-created on next use. */
695
+ dispose() {
694
696
  this._meshCache.clear();
695
- if (this.eventQueue?.raw)
696
- this.eventQueue?.free();
697
- if (this.world?.bodies)
698
- this.world?.free();
697
+ this.eventQueue?.free();
698
+ this._world?.free();
699
+ // Reset initialization state so the world can be recreated
700
+ this._world = undefined;
701
+ this.eventQueue = undefined;
702
+ this.collisionHandler = undefined;
703
+ this._isInitialized = false;
704
+ this._hasCreatedWorld = false;
705
+ this._initializePromise = undefined;
706
+ this.objects.length = 0;
707
+ this.bodies.length = 0;
708
+ }
709
+
710
+ /** @deprecated Use {@link dispose} instead. */
711
+ clearCaches() {
712
+ this.dispose();
699
713
  }
700
714
 
701
715
  async addBoxCollider(collider: ICollider, size: Vector3) {
@@ -981,13 +995,17 @@ export class RapierPhysics implements IPhysicsEngine {
981
995
  }
982
996
  }
983
997
 
984
- // if we want to use explicit mass properties, we need to set the collider density to 0
985
- // otherwise rapier will compute the mass properties based on the collider shape and density
998
+ // When using explicit mass (autoMass=false), set collider mass to near-zero
999
+ // so Rapier doesn't contribute mass from the collider shape.
1000
+ // The actual mass is applied via setAdditionalMass on the rigidbody instead.
1001
+ // Note: setMass overrides any prior setDensity call (they are mutually exclusive in Rapier)
986
1002
  // https://rapier.rs/docs/user_guides/javascript/rigid_bodies#mass-properties
987
1003
  if (collider.attachedRigidbody?.autoMass === false) {
988
- desc.setDensity(.000001);
989
1004
  desc.setMass(.000001);
990
1005
  }
1006
+ else if (collider.density != null) {
1007
+ desc.setDensity(collider.density);
1008
+ }
991
1009
 
992
1010
  try {
993
1011
  const col = this.world.createCollider(desc, rigidBody);
@@ -1130,6 +1148,11 @@ export class RapierPhysics implements IPhysicsEngine {
1130
1148
  break;
1131
1149
  }
1132
1150
 
1151
+ // Update density if specified (setDensity auto-recomputes parent body mass)
1152
+ if (col.density != null) {
1153
+ collider.setDensity(col.density);
1154
+ }
1155
+
1133
1156
  if (sizeHasChanged) {
1134
1157
  const rb = col.attachedRigidbody;
1135
1158
  if (rb?.autoMass) {
@@ -1161,7 +1184,8 @@ export class RapierPhysics implements IPhysicsEngine {
1161
1184
  rigidbody.setAdditionalMass(0, false);
1162
1185
  for (let i = 0; i < rigidbody.numColliders(); i++) {
1163
1186
  const col = rigidbody.collider(i);
1164
- col.setDensity(1);
1187
+ const colliderComponent = col[$componentKey] as ICollider | null;
1188
+ col.setDensity(colliderComponent?.density ?? 1);
1165
1189
  }
1166
1190
  rigidbody.recomputeMassPropertiesFromColliders();
1167
1191
  }
@@ -1477,16 +1501,21 @@ export class RapierPhysics implements IPhysicsEngine {
1477
1501
 
1478
1502
  private static centerConnectionPos = { x: 0, y: 0, z: 0 };
1479
1503
  private static centerConnectionRot = { x: 0, y: 0, z: 0, w: 1 };
1504
+ private _jointTempMatrix = new Matrix4();
1480
1505
 
1481
1506
 
1482
-
1483
- addFixedJoint(body1: IRigidbody, body2: IRigidbody) {
1507
+ async addFixedJoint(body1: IRigidbody, body2: IRigidbody): Promise<ImpulseJoint | null> {
1508
+ if (!this._isInitialized) await this.initialize();
1484
1509
  if (!this.world) {
1485
1510
  console.error("Physics world not initialized");
1486
- return;
1511
+ return null;
1487
1512
  }
1488
1513
  const b1 = body1[$bodyKey] as RigidBody;
1489
1514
  const b2 = body2[$bodyKey] as RigidBody;
1515
+ if (!b1 || !b2) {
1516
+ console.error("Cannot create fixed joint: one or both physics bodies are not initialized");
1517
+ return null;
1518
+ }
1490
1519
 
1491
1520
  this.calculateJointRelativeMatrices(body1.gameObject, body2.gameObject, this._tempMatrix);
1492
1521
  this._tempMatrix.decompose(this._tempPosition, this._tempQuaternion, this._tempScale);
@@ -1498,41 +1527,67 @@ export class RapierPhysics implements IPhysicsEngine {
1498
1527
  const joint = this.world.createImpulseJoint(params, b1, b2, true);
1499
1528
  if (debugPhysics)
1500
1529
  console.log("ADD FIXED JOINT", joint)
1530
+ return joint;
1501
1531
  }
1502
1532
 
1503
1533
 
1504
1534
  /** The joint prevents any relative movement between two rigid-bodies, except for relative rotations along one axis. This is typically used to simulate wheels, fans, etc. They are characterized by one local anchor as well as one local axis on each rigid-body. */
1505
- addHingeJoint(body1: IRigidbody, body2: IRigidbody, anchor: { x: number, y: number, z: number }, axis: { x: number, y: number, z: number }) {
1535
+ async addHingeJoint(body1: IRigidbody, body2: IRigidbody, anchor: { x: number, y: number, z: number }, axis: { x: number, y: number, z: number }): Promise<ImpulseJoint | null> {
1536
+ if (!this._isInitialized) await this.initialize();
1506
1537
  if (!this.world) {
1507
1538
  console.error("Physics world not initialized");
1508
- return;
1539
+ return null;
1509
1540
  }
1510
1541
  const b1 = body1[$bodyKey] as RigidBody;
1511
1542
  const b2 = body2[$bodyKey] as RigidBody;
1543
+ if (!b1 || !b2) {
1544
+ console.error("Cannot create hinge joint: one or both physics bodies are not initialized");
1545
+ return null;
1546
+ }
1512
1547
 
1513
1548
  this.calculateJointRelativeMatrices(body1.gameObject, body2.gameObject, this._tempMatrix);
1514
- this._tempMatrix.decompose(this._tempPosition, this._tempQuaternion, this._tempScale);
1549
+ // Transform anchor from body1's local space to body2's local space
1550
+ const anchor2 = this._tempPosition.set(anchor.x, anchor.y, anchor.z).applyMatrix4(this._tempMatrix);
1515
1551
 
1516
- const params = MODULES.RAPIER_PHYSICS.MODULE.JointData.revolute(anchor, this._tempPosition, axis);
1552
+ const params = MODULES.RAPIER_PHYSICS.MODULE.JointData.revolute(anchor, anchor2, axis);
1517
1553
  const joint = this.world.createImpulseJoint(params, b1, b2, true);
1518
1554
  if (debugPhysics)
1519
1555
  console.log("ADD HINGE JOINT", joint)
1556
+ return joint;
1520
1557
  }
1521
1558
 
1522
1559
 
1560
+ removeJoint(joint: ImpulseJoint) {
1561
+ if (!this.world) return;
1562
+ this.world.removeImpulseJoint(joint, true);
1563
+ }
1564
+
1565
+
1566
+ /** Compute the relative transform from body1's local space to body2's local space (W2⁻¹ * W1), ignoring scale. */
1523
1567
  private calculateJointRelativeMatrices(body1: IGameObject, body2: IGameObject, mat: Matrix4) {
1524
1568
  body1.updateWorldMatrix(true, false);
1525
1569
  body2.updateWorldMatrix(true, false);
1526
- const world1 = body1.matrixWorld;
1527
- const world2 = body2.matrixWorld;
1528
- // set scale to 1
1529
- world1.elements[0] = 1;
1530
- world1.elements[5] = 1;
1531
- world1.elements[10] = 1;
1532
- world2.elements[0] = 1;
1533
- world2.elements[5] = 1;
1534
- world2.elements[10] = 1;
1535
- mat.copy(world2).premultiply(world1.invert()).invert();
1570
+
1571
+ // Work on copies to avoid mutating the actual world matrices
1572
+ mat.copy(body1.matrixWorld);
1573
+ const w2 = this._jointTempMatrix.copy(body2.matrixWorld);
1574
+
1575
+ // Strip scale by normalizing each column of the upper 3x3
1576
+ this.normalizeMatrixColumns(mat);
1577
+ this.normalizeMatrixColumns(w2);
1578
+
1579
+ // mat = W2^-1 * W1 (body1's frame in body2's local space)
1580
+ mat.premultiply(w2.invert());
1581
+ }
1582
+
1583
+ private normalizeMatrixColumns(m: Matrix4) {
1584
+ const e = m.elements;
1585
+ let len = Math.sqrt(e[0] * e[0] + e[1] * e[1] + e[2] * e[2]);
1586
+ if (len > 0) { e[0] /= len; e[1] /= len; e[2] /= len; }
1587
+ len = Math.sqrt(e[4] * e[4] + e[5] * e[5] + e[6] * e[6]);
1588
+ if (len > 0) { e[4] /= len; e[5] /= len; e[6] /= len; }
1589
+ len = Math.sqrt(e[8] * e[8] + e[9] * e[9] + e[10] * e[10]);
1590
+ if (len > 0) { e[8] /= len; e[9] /= len; e[10] /= len; }
1536
1591
  }
1537
1592
  }
1538
1593
 
@@ -124,7 +124,7 @@ class ObjectSerializer extends TypeSerializer {
124
124
  }
125
125
  if (!res) {
126
126
  if (isDevEnvironment() || debugExtension)
127
- console.warn("Could not resolve object reference", context.path, data, context.target, context.context.scene);
127
+ console.warn(`Could not resolve object reference \"${context.path}\" (guid: ${data.guid}). The referenced object may have been deleted — check if the reference is still valid in your scene.`);
128
128
  data["could_not_resolve"] = true;
129
129
  }
130
130
  else {
@@ -334,11 +334,6 @@ class EventListSerializer extends TypeSerializer {
334
334
  if (debugExtension)
335
335
  console.log(evt);
336
336
 
337
- const eventListOwner = context.target;
338
- if (eventListOwner !== undefined && context.path !== undefined) {
339
- evt.setEventTarget(context.path, eventListOwner);
340
- }
341
-
342
337
  return evt;
343
338
  }
344
339
  return undefined;
@@ -207,6 +207,15 @@ export interface ISerializable {
207
207
  onAfterDeserialize?(data: any, context: SerializationContext): void;
208
208
  };
209
209
 
210
+ export function isSerializable(obj: any): obj is ISerializable {
211
+ return obj && typeof obj === "object" && (
212
+ typeof obj.$serializedTypes === "object" ||
213
+ typeof obj.onBeforeDeserialize === "function" ||
214
+ typeof obj.onBeforeDeserializeMember === "function" ||
215
+ typeof obj.onAfterDeserializeMember === "function" ||
216
+ typeof obj.onAfterDeserialize === "function"
217
+ )
218
+ }
210
219
 
211
220
  export function serializeObject(obj: ISerializable, context: SerializationContext): object | null {
212
221
  const types = obj.$serializedTypes;
@@ -5,7 +5,6 @@ import { type GLTF as THREE_GLTF } from "three/examples/jsm/loaders/GLTFLoader.j
5
5
 
6
6
  import type { Camera as CameraComponent } from "../engine-components/api.js";
7
7
  import type { Context } from "./engine_context.js";
8
- import { InstantiateContext } from "./engine_gameobject.js";
9
8
  import { CollisionDetectionMode, type PhysicsMaterial, RigidbodyConstraints } from "./engine_physics.types.js";
10
9
  import { CircularBuffer } from "./engine_utils.js";
11
10
  import type { NeedleXRSession } from "./engine_xr.js";
@@ -196,13 +195,11 @@ export interface IComponent extends IHasGuid {
196
195
  /** @internal */
197
196
  __internalStart();
198
197
  /** @internal */
199
- __internalEnable(isAddingOrRemovingFromScene?: boolean);
198
+ __internalEnable(isHierarchyChange?: boolean);
200
199
  /** @internal */
201
- __internalDisable(isAddingOrRemovingFromScene?: boolean);
200
+ __internalDisable(isHierarchyChange?: boolean);
202
201
  /** @internal */
203
202
  __internalDestroy();
204
- /** @internal */
205
- resolveGuids?(guidsMap: GuidsMap): void;
206
203
 
207
204
  /** experimental, called when the script is registered for the first time, this is called even if the component is not enabled. */
208
205
  registering?();
@@ -240,14 +237,14 @@ export interface IComponent extends IHasGuid {
240
237
  }
241
238
 
242
239
  export function isComponent(obj: any): obj is IComponent {
243
- return obj && obj.isComponent;
240
+ return obj && typeof obj === "object" && obj.isComponent;
244
241
  }
245
242
 
246
243
  export type ICamera = CameraComponent;
247
244
 
248
245
  export type IAnimationComponent = Pick<IComponent, "gameObject"> & {
249
246
  isAnimationComponent: boolean;
250
- addClip?(clip: AnimationClip);
247
+ addClip?(clip: AnimationClip): void;
251
248
  }
252
249
 
253
250
  /** Interface for a camera controller component that can be attached to a camera to control it */
@@ -274,7 +271,10 @@ export declare interface IRenderer extends IComponent {
274
271
 
275
272
  export declare interface IEventList {
276
273
  readonly isEventList: true;
277
- __internalOnInstantiate(map: InstantiateContext): IEventList;
274
+ }
275
+
276
+ export declare interface ISignalReceiver {
277
+ readonly isSignalReceiver: true;
278
278
  }
279
279
 
280
280
  // export declare interface IPhysicsComponent extends IComponent {
@@ -302,12 +302,18 @@ export declare interface ICollider extends IComponent {
302
302
  * Default: [0]
303
303
  */
304
304
  membership?: number[];
305
- /** The collider filter indicates what groups the collider can interact with (e.g. group 3 and 4)
306
- * An `undefined` array indicates that the collider can interact with all groups
307
- * Note: Make sure to call updateProperties after having changed this property
305
+ /** The collider filter indicates what groups the collider can interact with (e.g. group 3 and 4)
306
+ * An `undefined` array indicates that the collider can interact with all groups
307
+ * Note: Make sure to call updateProperties after having changed this property
308
308
  * Default: undefined
309
309
  */
310
310
  filter?: number[];
311
+ /** The density of the collider used for automatic mass calculation.
312
+ * When the attached Rigidbody has `autoMass` enabled, the mass is computed as `density × volume`.
313
+ * Note: Make sure to call updateProperties after having changed this property
314
+ * Default: undefined (uses physics engine default of 1.0)
315
+ */
316
+ density?: number;
311
317
  }
312
318
 
313
319
  export declare interface ISphereCollider extends ICollider {
@@ -475,8 +481,10 @@ export interface IPhysicsEngine {
475
481
  postStep();
476
482
  /** Indicates whether the physics engine is currently updating */
477
483
  get isUpdating(): boolean;
478
- /** Clears all cached data (e.g., mesh data when creating scaled mesh colliders) */
479
- clearCaches();
484
+ /** Tears down the physics world and frees all resources. The world will be re-created on next use. */
485
+ dispose(): void;
486
+ /** @deprecated Use {@link dispose} instead. */
487
+ clearCaches(): void;
480
488
 
481
489
  /** Enables or disables the physics engine */
482
490
  enabled: boolean;
@@ -738,8 +746,9 @@ export interface IPhysicsEngine {
738
746
  getBody(obj: ICollider | IRigidbody): null | any;
739
747
 
740
748
  // Joints
741
- addFixedJoint(body1: IRigidbody, body2: IRigidbody);
742
- addHingeJoint(body1: IRigidbody, body2: IRigidbody, anchor: Vec3, axis: Vec3);
749
+ addFixedJoint(body1: IRigidbody, body2: IRigidbody): Promise<any> | any;
750
+ addHingeJoint(body1: IRigidbody, body2: IRigidbody, anchor: Vec3, axis: Vec3): Promise<any> | any;
751
+ removeJoint(joint: any): void;
743
752
 
744
753
  /** Enable to render collider shapes */
745
754
  debugRenderColliders: boolean;
@@ -79,8 +79,12 @@ function createPropertyWrapper(target: IComponent | any, _propertyKey: string |
79
79
  return;
80
80
  }
81
81
 
82
- // only build wrapper once per type
83
- if (this[$prop] === undefined) {
82
+ // Only install the getter/setter once per instance.
83
+ // processStart may call __internalAwake again for components that were inactive
84
+ // during processNewScripts — the base Component.__internalAwake has a __didAwake guard
85
+ // but this decorator wrapper runs before reaching it, so we need our own idempotency check.
86
+ const desc = Object.getOwnPropertyDescriptor(this, propertyKey);
87
+ if (!desc || !desc.get) {
84
88
 
85
89
  // make sure the field is initialized in a hidden property
86
90
  this[$prop] = this[propertyKey];
@@ -99,6 +103,7 @@ function createPropertyWrapper(target: IComponent | any, _propertyKey: string |
99
103
  }
100
104
 
101
105
  Object.defineProperty(this, propertyKey, {
106
+ configurable: true,
102
107
  set: function (v) {
103
108
  if (this[$isAssigningProperties] === true) {
104
109
  this[$prop] = v;
@@ -279,24 +279,35 @@ declare type deepClonePredicate = (owner: any, propertyName: string, current: an
279
279
  * return true;
280
280
  * });
281
281
  * */
282
- export function deepClone(obj: any, predicate?: deepClonePredicate): any {
282
+ export function deepClone(obj: any, predicate?: deepClonePredicate, _visited?: WeakSet<object>): any {
283
283
  if (obj !== null && obj !== undefined && typeof obj === "object") {
284
+ if (!_visited) _visited = new WeakSet();
285
+ if (_visited.has(obj)) return obj;
286
+ _visited.add(obj);
287
+
284
288
  let clone;
285
289
  if (Array.isArray(obj)) clone = [];
286
290
  else {
287
- clone = Object.create(obj);
288
- Object.assign(clone, obj);
291
+ clone = Object.create(Object.getPrototypeOf(obj));
292
+ // Copy own properties, skipping getter-only properties that can't be set
293
+ const descriptors = Object.getOwnPropertyDescriptors(obj);
294
+ for (const key in descriptors) {
295
+ const desc = descriptors[key];
296
+ if (desc.set || desc.writable !== false) {
297
+ try { clone[key] = obj[key]; }
298
+ catch { /* skip read-only properties */ }
299
+ }
300
+ }
289
301
  }
290
302
  for (const key of Object.keys(obj)) {
291
303
  const val = obj[key];
292
304
  if (predicate && !predicate(obj, key, val)) {
293
- // console.log("SKIP", val);
294
305
  clone[key] = val;
295
306
  }
296
307
  else if (val?.clone !== undefined && typeof val.clone === "function")
297
308
  clone[key] = val.clone();
298
309
  else
299
- clone[key] = deepClone(val, predicate);
310
+ clone[key] = deepClone(val, predicate, _visited);
300
311
  }
301
312
  return clone;
302
313
  }
@@ -89,6 +89,15 @@ export class Animator extends Behaviour implements IAnimationComponent {
89
89
  return true;
90
90
  }
91
91
 
92
+ /**
93
+ * The current animator mixer, used for low-level control of animations. Owned by the AnimatorController
94
+ * @returns The current AnimationMixer, or null if no controller is assigned
95
+ * @see AnimatorController.mixer
96
+ */
97
+ get mixer() {
98
+ return this._animatorController?.mixer || null;
99
+ }
100
+
92
101
  /**
93
102
  * When enabled, animation will affect the root transform position and rotation
94
103
  */
@@ -401,17 +410,15 @@ export class Animator extends Behaviour implements IAnimationComponent {
401
410
  initializeRuntimeAnimatorController(force: boolean = false) {
402
411
  const shouldRun = (force || this.runtimeAnimatorController !== this._initializeWithRuntimeAnimatorController);
403
412
  if (this.runtimeAnimatorController && shouldRun) {
404
- const clone = this.runtimeAnimatorController.clone();
405
- this._initializeWithRuntimeAnimatorController = clone;
406
- if (clone) {
407
- console.assert(this.runtimeAnimatorController !== clone);
408
- this.runtimeAnimatorController = clone;
409
- console.assert(this.runtimeAnimatorController === clone);
410
- this.runtimeAnimatorController.bind(this);
411
- this.runtimeAnimatorController.setSpeed(this._speed);
412
- this.runtimeAnimatorController.normalizedStartOffset = this._normalizedStartOffset;
413
- }
414
- else console.warn("Could not clone animator controller", this.runtimeAnimatorController);
413
+ // Create a fresh controller from the (possibly remapped) model.
414
+ // The constructor deep-clones the model, preserving Object3D refs that
415
+ // the resolve system has already remapped to cloned objects.
416
+ const controller = new AnimatorController(this.runtimeAnimatorController.model);
417
+ this.runtimeAnimatorController = controller;
418
+ this._initializeWithRuntimeAnimatorController = controller;
419
+ controller.bind(this);
420
+ controller.setSpeed(this._speed);
421
+ controller.normalizedStartOffset = this._normalizedStartOffset;
415
422
  }
416
423
  }
417
424