@needle-tools/engine 5.1.0-canary.deec6e4 → 5.1.0-canary.e7c2511

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 (243) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/components.needle.json +1 -1
  3. package/dist/{needle-engine.bundle-CvtELXh0.js → needle-engine.bundle-D-eWNCu1.js} +15969 -15550
  4. package/dist/needle-engine.bundle-D3ZUII8o.min.js +1733 -0
  5. package/dist/needle-engine.bundle-_rOpvUGL.umd.cjs +1733 -0
  6. package/dist/needle-engine.d.ts +746 -156
  7. package/dist/needle-engine.js +529 -529
  8. package/dist/needle-engine.min.js +1 -1
  9. package/dist/needle-engine.umd.cjs +1 -1
  10. package/lib/engine/api.d.ts +5 -0
  11. package/lib/engine/api.js +4 -0
  12. package/lib/engine/api.js.map +1 -1
  13. package/lib/engine/codegen/register_types.js +2 -10
  14. package/lib/engine/codegen/register_types.js.map +1 -1
  15. package/lib/engine/engine_audio.d.ts +68 -0
  16. package/lib/engine/engine_audio.js +172 -0
  17. package/lib/engine/engine_audio.js.map +1 -1
  18. package/lib/engine/engine_components.js +1 -1
  19. package/lib/engine/engine_components.js.map +1 -1
  20. package/lib/engine/engine_context.d.ts +1 -1
  21. package/lib/engine/engine_context.js +2 -2
  22. package/lib/engine/engine_context.js.map +1 -1
  23. package/lib/engine/engine_disposable.d.ts +171 -0
  24. package/lib/engine/engine_disposable.js +136 -0
  25. package/lib/engine/engine_disposable.js.map +1 -0
  26. package/lib/engine/engine_gameobject.d.ts +1 -10
  27. package/lib/engine/engine_gameobject.js +22 -120
  28. package/lib/engine/engine_gameobject.js.map +1 -1
  29. package/lib/engine/engine_gltf_builtin_components.js +7 -69
  30. package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
  31. package/lib/engine/engine_init.js +6 -6
  32. package/lib/engine/engine_init.js.map +1 -1
  33. package/lib/engine/engine_input.d.ts +1 -1
  34. package/lib/engine/engine_input.js +1 -1
  35. package/lib/engine/engine_input.js.map +1 -1
  36. package/lib/engine/engine_instantiate_resolve.d.ts +42 -0
  37. package/lib/engine/engine_instantiate_resolve.js +372 -0
  38. package/lib/engine/engine_instantiate_resolve.js.map +1 -0
  39. package/lib/engine/engine_license.js +1 -1
  40. package/lib/engine/engine_license.js.map +1 -1
  41. package/lib/engine/engine_mainloop_utils.js +5 -2
  42. package/lib/engine/engine_mainloop_utils.js.map +1 -1
  43. package/lib/engine/engine_networking.js +3 -1
  44. package/lib/engine/engine_networking.js.map +1 -1
  45. package/lib/engine/engine_networking_blob.js +1 -1
  46. package/lib/engine/engine_networking_blob.js.map +1 -1
  47. package/lib/engine/engine_physics_rapier.d.ts +11 -3
  48. package/lib/engine/engine_physics_rapier.js +88 -25
  49. package/lib/engine/engine_physics_rapier.js.map +1 -1
  50. package/lib/engine/engine_scenedata.js +2 -2
  51. package/lib/engine/engine_scenedata.js.map +1 -1
  52. package/lib/engine/engine_serialization_builtin_serializer.js +28 -5
  53. package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
  54. package/lib/engine/engine_serialization_core.d.ts +1 -0
  55. package/lib/engine/engine_serialization_core.js +7 -0
  56. package/lib/engine/engine_serialization_core.js.map +1 -1
  57. package/lib/engine/engine_types.d.ts +17 -9
  58. package/lib/engine/engine_types.js +1 -1
  59. package/lib/engine/engine_types.js.map +1 -1
  60. package/lib/engine/engine_util_decorator.js +7 -2
  61. package/lib/engine/engine_util_decorator.js.map +1 -1
  62. package/lib/engine/engine_utils.d.ts +1 -1
  63. package/lib/engine/engine_utils.js +19 -5
  64. package/lib/engine/engine_utils.js.map +1 -1
  65. package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +1 -1
  66. package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js.map +1 -1
  67. package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +1 -1
  68. package/lib/engine/webcomponents/needle menu/needle-menu.js +1 -1
  69. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  70. package/lib/engine/webcomponents/needle-engine.d.ts +10 -4
  71. package/lib/engine/webcomponents/needle-engine.js +1 -1
  72. package/lib/engine/webcomponents/needle-engine.js.map +1 -1
  73. package/lib/engine/xr/NeedleXRSession.d.ts +3 -2
  74. package/lib/engine/xr/NeedleXRSession.js +50 -14
  75. package/lib/engine/xr/NeedleXRSession.js.map +1 -1
  76. package/lib/engine/xr/events.d.ts +1 -1
  77. package/lib/engine/xr/events.js.map +1 -1
  78. package/lib/engine-components/Animation.js +17 -16
  79. package/lib/engine-components/Animation.js.map +1 -1
  80. package/lib/engine-components/Animator.d.ts +6 -0
  81. package/lib/engine-components/Animator.js +17 -12
  82. package/lib/engine-components/Animator.js.map +1 -1
  83. package/lib/engine-components/AnimatorController.builder.d.ts +113 -0
  84. package/lib/engine-components/AnimatorController.builder.js +195 -0
  85. package/lib/engine-components/AnimatorController.builder.js.map +1 -0
  86. package/lib/engine-components/AnimatorController.d.ts +2 -119
  87. package/lib/engine-components/AnimatorController.js +31 -232
  88. package/lib/engine-components/AnimatorController.js.map +1 -1
  89. package/lib/engine-components/AudioSource.d.ts +19 -3
  90. package/lib/engine-components/AudioSource.js +121 -68
  91. package/lib/engine-components/AudioSource.js.map +1 -1
  92. package/lib/engine-components/Collider.d.ts +18 -9
  93. package/lib/engine-components/Collider.js +61 -14
  94. package/lib/engine-components/Collider.js.map +1 -1
  95. package/lib/engine-components/Component.d.ts +58 -6
  96. package/lib/engine-components/Component.js +77 -0
  97. package/lib/engine-components/Component.js.map +1 -1
  98. package/lib/engine-components/DragControls.d.ts +7 -0
  99. package/lib/engine-components/DragControls.js +19 -7
  100. package/lib/engine-components/DragControls.js.map +1 -1
  101. package/lib/engine-components/EventList.d.ts +31 -9
  102. package/lib/engine-components/EventList.js +37 -76
  103. package/lib/engine-components/EventList.js.map +1 -1
  104. package/lib/engine-components/Joints.d.ts +4 -2
  105. package/lib/engine-components/Joints.js +19 -3
  106. package/lib/engine-components/Joints.js.map +1 -1
  107. package/lib/engine-components/Light.js +9 -1
  108. package/lib/engine-components/Light.js.map +1 -1
  109. package/lib/engine-components/Networking.d.ts +1 -1
  110. package/lib/engine-components/Networking.js +1 -1
  111. package/lib/engine-components/OrbitControls.js +16 -11
  112. package/lib/engine-components/OrbitControls.js.map +1 -1
  113. package/lib/engine-components/RigidBody.d.ts +12 -4
  114. package/lib/engine-components/RigidBody.js +18 -4
  115. package/lib/engine-components/RigidBody.js.map +1 -1
  116. package/lib/engine-components/SeeThrough.js +2 -2
  117. package/lib/engine-components/SeeThrough.js.map +1 -1
  118. package/lib/engine-components/api.d.ts +1 -1
  119. package/lib/engine-components/api.js +1 -1
  120. package/lib/engine-components/api.js.map +1 -1
  121. package/lib/engine-components/codegen/components.d.ts +3 -9
  122. package/lib/engine-components/codegen/components.js +3 -9
  123. package/lib/engine-components/codegen/components.js.map +1 -1
  124. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
  125. package/lib/engine-components/timeline/PlayableDirector.d.ts +16 -6
  126. package/lib/engine-components/timeline/PlayableDirector.js +63 -61
  127. package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
  128. package/lib/engine-components/timeline/SignalAsset.d.ts +3 -1
  129. package/lib/engine-components/timeline/SignalAsset.js +1 -0
  130. package/lib/engine-components/timeline/SignalAsset.js.map +1 -1
  131. package/lib/engine-components/timeline/TimelineBuilder.d.ts +247 -0
  132. package/lib/engine-components/timeline/TimelineBuilder.js +400 -0
  133. package/lib/engine-components/timeline/TimelineBuilder.js.map +1 -0
  134. package/lib/engine-components/timeline/TimelineModels.d.ts +2 -1
  135. package/lib/engine-components/timeline/TimelineModels.js +3 -0
  136. package/lib/engine-components/timeline/TimelineModels.js.map +1 -1
  137. package/lib/engine-components/timeline/TimelineTracks.d.ts +23 -0
  138. package/lib/engine-components/timeline/TimelineTracks.js +71 -13
  139. package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
  140. package/lib/engine-components/timeline/index.d.ts +2 -1
  141. package/lib/engine-components/timeline/index.js +2 -0
  142. package/lib/engine-components/timeline/index.js.map +1 -1
  143. package/lib/engine-components/ui/Canvas.d.ts +1 -1
  144. package/lib/engine-components/ui/Canvas.js +2 -8
  145. package/lib/engine-components/ui/Canvas.js.map +1 -1
  146. package/lib/engine-components/ui/Text.d.ts +1 -0
  147. package/lib/engine-components/ui/Text.js +10 -7
  148. package/lib/engine-components/ui/Text.js.map +1 -1
  149. package/lib/engine-components/web/CursorFollow.js +21 -12
  150. package/lib/engine-components/web/CursorFollow.js.map +1 -1
  151. package/lib/engine-components/webxr/WebXRImageTracking.js +4 -0
  152. package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
  153. package/package.json +2 -83
  154. package/plugins/common/worker.js +9 -4
  155. package/plugins/vite/asap.js +17 -8
  156. package/plugins/vite/dependencies.js +29 -0
  157. package/plugins/vite/dependency-watcher.js +2 -2
  158. package/plugins/vite/editor-connection.js +3 -3
  159. package/plugins/vite/local-files-core.js +3 -3
  160. package/plugins/vite/local-files-utils.d.ts +3 -1
  161. package/plugins/vite/local-files-utils.js +29 -5
  162. package/plugins/vite/reload.js +1 -1
  163. package/plugins/vite/server.js +2 -1
  164. package/src/engine/api.ts +7 -0
  165. package/src/engine/codegen/register_types.ts +2 -10
  166. package/src/engine/engine_audio.ts +184 -0
  167. package/src/engine/engine_components.ts +1 -1
  168. package/src/engine/engine_context.ts +3 -3
  169. package/src/engine/engine_disposable.ts +213 -0
  170. package/src/engine/engine_gameobject.ts +54 -159
  171. package/src/engine/engine_gltf_builtin_components.ts +7 -76
  172. package/src/engine/engine_init.ts +6 -6
  173. package/src/engine/engine_input.ts +1 -1
  174. package/src/engine/engine_instantiate_resolve.ts +407 -0
  175. package/src/engine/engine_license.ts +1 -1
  176. package/src/engine/engine_mainloop_utils.ts +5 -2
  177. package/src/engine/engine_networking.ts +3 -1
  178. package/src/engine/engine_networking_blob.ts +1 -1
  179. package/src/engine/engine_physics_rapier.ts +82 -27
  180. package/src/engine/engine_scenedata.ts +3 -3
  181. package/src/engine/engine_serialization_builtin_serializer.ts +32 -9
  182. package/src/engine/engine_serialization_core.ts +9 -0
  183. package/src/engine/engine_types.ts +22 -13
  184. package/src/engine/engine_util_decorator.ts +7 -2
  185. package/src/engine/engine_utils.ts +16 -5
  186. package/src/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +1 -1
  187. package/src/engine/webcomponents/needle menu/needle-menu.ts +1 -1
  188. package/src/engine/webcomponents/needle-engine.ts +10 -4
  189. package/src/engine/xr/NeedleXRSession.ts +48 -13
  190. package/src/engine/xr/events.ts +1 -1
  191. package/src/engine-components/Animation.ts +19 -16
  192. package/src/engine-components/Animator.ts +18 -11
  193. package/src/engine-components/AnimatorController.builder.ts +261 -0
  194. package/src/engine-components/AnimatorController.ts +19 -291
  195. package/src/engine-components/AudioSource.ts +130 -79
  196. package/src/engine-components/Collider.ts +66 -18
  197. package/src/engine-components/Component.ts +79 -9
  198. package/src/engine-components/DragControls.ts +18 -11
  199. package/src/engine-components/EventList.ts +45 -83
  200. package/src/engine-components/Joints.ts +20 -4
  201. package/src/engine-components/Light.ts +10 -2
  202. package/src/engine-components/Networking.ts +1 -1
  203. package/src/engine-components/OrbitControls.ts +18 -9
  204. package/src/engine-components/RigidBody.ts +18 -4
  205. package/src/engine-components/SeeThrough.ts +2 -2
  206. package/src/engine-components/api.ts +1 -1
  207. package/src/engine-components/codegen/components.ts +3 -9
  208. package/src/engine-components/timeline/PlayableDirector.ts +61 -64
  209. package/src/engine-components/timeline/SignalAsset.ts +4 -1
  210. package/src/engine-components/timeline/TimelineBuilder.ts +565 -0
  211. package/src/engine-components/timeline/TimelineModels.ts +5 -1
  212. package/src/engine-components/timeline/TimelineTracks.ts +74 -13
  213. package/src/engine-components/timeline/index.ts +2 -1
  214. package/src/engine-components/ui/Canvas.ts +2 -8
  215. package/src/engine-components/ui/Text.ts +12 -8
  216. package/src/engine-components/web/CursorFollow.ts +21 -13
  217. package/src/engine-components/webxr/WebXRImageTracking.ts +2 -0
  218. package/dist/needle-engine.bundle-1s2gOoKZ.min.js +0 -1732
  219. package/dist/needle-engine.bundle-j4nGJXCs.umd.cjs +0 -1732
  220. package/lib/engine-components/AvatarLoader.d.ts +0 -80
  221. package/lib/engine-components/AvatarLoader.js +0 -232
  222. package/lib/engine-components/AvatarLoader.js.map +0 -1
  223. package/lib/engine-components/avatar/AvatarBlink_Simple.d.ts +0 -11
  224. package/lib/engine-components/avatar/AvatarBlink_Simple.js +0 -77
  225. package/lib/engine-components/avatar/AvatarBlink_Simple.js.map +0 -1
  226. package/lib/engine-components/avatar/AvatarEyeLook_Rotation.d.ts +0 -14
  227. package/lib/engine-components/avatar/AvatarEyeLook_Rotation.js +0 -69
  228. package/lib/engine-components/avatar/AvatarEyeLook_Rotation.js.map +0 -1
  229. package/lib/engine-components/avatar/Avatar_Brain_LookAt.d.ts +0 -29
  230. package/lib/engine-components/avatar/Avatar_Brain_LookAt.js +0 -122
  231. package/lib/engine-components/avatar/Avatar_Brain_LookAt.js.map +0 -1
  232. package/lib/engine-components/avatar/Avatar_MouthShapes.d.ts +0 -15
  233. package/lib/engine-components/avatar/Avatar_MouthShapes.js +0 -80
  234. package/lib/engine-components/avatar/Avatar_MouthShapes.js.map +0 -1
  235. package/lib/engine-components/avatar/Avatar_MustacheShake.d.ts +0 -9
  236. package/lib/engine-components/avatar/Avatar_MustacheShake.js +0 -30
  237. package/lib/engine-components/avatar/Avatar_MustacheShake.js.map +0 -1
  238. package/src/engine-components/AvatarLoader.ts +0 -264
  239. package/src/engine-components/avatar/AvatarBlink_Simple.ts +0 -70
  240. package/src/engine-components/avatar/AvatarEyeLook_Rotation.ts +0 -64
  241. package/src/engine-components/avatar/Avatar_Brain_LookAt.ts +0 -140
  242. package/src/engine-components/avatar/Avatar_MouthShapes.ts +0 -84
  243. package/src/engine-components/avatar/Avatar_MustacheShake.ts +0 -32
@@ -0,0 +1,407 @@
1
+ import type { Color, Euler, Matrix2, Matrix3, Matrix4, Object3D, Quaternion, Vector2, Vector3, Vector4 } from "three";
2
+
3
+ import { InstantiateIdProvider } from "./engine_networking_instantiate.js";
4
+ import { isSerializable } from "./engine_serialization_core.js";
5
+ import { type GuidsMap, type IComponent, type UIDProvider, isComponent } from "./engine_types.js";
6
+ import { getParam } from "./engine_utils.js";
7
+
8
+ const debug = getParam("debuginstantiate");
9
+
10
+ // ————————————————————————————————————————————————————————
11
+ // Types
12
+ // ————————————————————————————————————————————————————————
13
+
14
+ export type ObjectCloneReference = {
15
+ readonly original: object;
16
+ readonly clone: object;
17
+ }
18
+
19
+ /** Maps uuid/guid → { original, clone } for Object3D and Component instances */
20
+ export type InstantiateReferenceMap = Record<string, ObjectCloneReference>;
21
+
22
+ /**
23
+ * Provides access to the instantiated object map (used by EventList etc.)
24
+ */
25
+ export type InstantiateContext = Readonly<InstantiateReferenceMap>;
26
+
27
+
28
+ // ————————————————————————————————————————————————————————
29
+ // ID Provider Cache (moved from engine_gltf_builtin_components.ts)
30
+ // ————————————————————————————————————————————————————————
31
+
32
+ /**
33
+ * Cache of id providers per component/object guid.
34
+ * Ensures deterministic guid generation regardless of scene order.
35
+ */
36
+ const idProviderCache = new Map<string, InstantiateIdProvider>();
37
+
38
+ /** Clear the id provider cache (e.g. when reloading a context) */
39
+ export function clearIdProviderCache() {
40
+ idProviderCache.clear();
41
+ }
42
+
43
+ // ————————————————————————————————————————————————————————
44
+ // Guid Generation (moved from engine_gltf_builtin_components.ts)
45
+ // ————————————————————————————————————————————————————————
46
+
47
+ export const originalComponentNameKey = Symbol("original-component-name");
48
+
49
+ // #region hierarchy guids
50
+ /**
51
+ * Recursively generates new deterministic guids for all objects and components in a hierarchy.
52
+ * Uses the idProviderCache so that the same source guid always produces the same output guid
53
+ * (needed for networking: all clients must agree on the guids of instantiated objects).
54
+ * Populates guidsMap (oldGuid → newGuid) so string references can be remapped afterwards.
55
+ */
56
+ export function generateGuidsForHierarchy(
57
+ obj: Object3D,
58
+ idProvider: UIDProvider | null,
59
+ guidsMap: GuidsMap,
60
+ ): void {
61
+ if (idProvider === null) return;
62
+ if (!obj) return;
63
+ const prev = (obj as any).guid;
64
+
65
+ // Use a cached id provider per object to ensure stable guids regardless of hierarchy order
66
+ const idProviderKey = (obj as any).guid;
67
+ if (idProviderKey?.length) {
68
+ if (!idProviderCache.has(idProviderKey)) {
69
+ if (debug) console.log("Creating InstanceIdProvider with key \"" + idProviderKey + "\" for object " + obj.name);
70
+ idProviderCache.set(idProviderKey, new InstantiateIdProvider(idProviderKey));
71
+ }
72
+ }
73
+ const objectIdProvider = idProviderKey && idProviderCache.get(idProviderKey) || idProvider;
74
+
75
+ (obj as any).guid = objectIdProvider.generateUUID();
76
+ if (prev && prev !== "invalid")
77
+ guidsMap[prev] = (obj as any).guid;
78
+
79
+ if (obj && obj.userData && obj.userData.components) {
80
+ for (const comp of obj.userData.components) {
81
+ if (comp === null) continue;
82
+
83
+ const compIdProviderKey = comp.guid;
84
+ if (compIdProviderKey) {
85
+ if (!idProviderCache.has(compIdProviderKey)) {
86
+ if (debug) console.log("Creating InstanceIdProvider with key \"" + compIdProviderKey + "\" for component " + comp[originalComponentNameKey]);
87
+ idProviderCache.set(compIdProviderKey, new InstantiateIdProvider(compIdProviderKey));
88
+ }
89
+ }
90
+ else if (debug) console.warn("Can not create IdProvider: component " + comp[originalComponentNameKey] + " has no guid", comp.guid);
91
+ const componentIdProvider = idProviderCache.get(compIdProviderKey) || idProvider;
92
+
93
+ const compPrev = comp.guid;
94
+ comp.guid = componentIdProvider.generateUUID();
95
+ if (compPrev && compPrev !== "invalid")
96
+ guidsMap[compPrev] = comp.guid;
97
+ }
98
+ }
99
+ if (obj.children) {
100
+ for (const child of obj.children) {
101
+ generateGuidsForHierarchy(child as Object3D, idProvider, guidsMap);
102
+ }
103
+ }
104
+ }
105
+
106
+ // ————————————————————————————————————————————————————————
107
+ // #region reference resolution
108
+ // ————————————————————————————————————————————————————————
109
+
110
+ /**
111
+ * The unified reference resolution function.
112
+ * Iterates all cloned components in the objectMap and remaps their properties
113
+ * to point at cloned counterparts where appropriate.
114
+ *
115
+ * Handles: Component, Object3D, Array, Map, Set, Record/plain objects,
116
+ * EventList, Vector/Color/Quaternion, and @serializable nested objects.
117
+ */
118
+ export function resolveInstanceReferences(objectMap: InstantiateReferenceMap): void {
119
+ for (const key in objectMap) {
120
+ const val = objectMap[key];
121
+ const clone = val.clone as Object3D | null;
122
+ if (!clone?.isObject3D || !clone?.userData?.components) continue;
123
+
124
+ for (let i = 0; i < clone.userData.components.length; i++) {
125
+ const component = clone.userData.components[i];
126
+ const entries = Object.entries(component);
127
+ for (const [propKey, propValue] of entries) {
128
+ if (propValue === null || propValue === undefined) continue;
129
+ // Skip primitives that can't be remapped, but allow strings for guid resolution
130
+ if (typeof propValue !== "object" && typeof propValue !== "string") continue;
131
+ const resolved = resolveValue(propKey, propValue, objectMap);
132
+ if (resolved !== undefined) {
133
+ component[propKey] = resolved;
134
+ }
135
+ }
136
+ }
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Resolves string-based guid references in all components of a hierarchy using a GuidsMap.
142
+ * Used by the glTF loading path where objects get new guids assigned and string references
143
+ * (e.g. PlayableDirector track.outputs) need to be updated.
144
+ */
145
+ export function resolveStringGuidsInHierarchy(root: Object3D, guidsMap: GuidsMap): void {
146
+ resolveStringGuidsRecursive(root, guidsMap);
147
+ }
148
+
149
+ function resolveStringGuidsRecursive(obj: Object3D, guidsMap: GuidsMap): void {
150
+ if (obj.userData?.components) {
151
+ for (const component of obj.userData.components) {
152
+ if (component === null) continue;
153
+ resolveStringGuidsInObject(component, guidsMap);
154
+ }
155
+ }
156
+ if (obj.children) {
157
+ for (const child of obj.children) {
158
+ resolveStringGuidsRecursive(child as Object3D, guidsMap);
159
+ }
160
+ }
161
+ }
162
+
163
+ function resolveStringGuidsInObject(obj: any, guidsMap: GuidsMap, visited?: WeakSet<object>): void {
164
+ if (!visited) visited = new WeakSet();
165
+ if (visited.has(obj)) return;
166
+ visited.add(obj);
167
+
168
+ for (const key of Object.keys(obj)) {
169
+ const value = obj[key];
170
+ if (value === null || value === undefined) continue;
171
+ if (typeof value === "string") {
172
+ if (guidsMap[value]) {
173
+ obj[key] = guidsMap[value];
174
+ }
175
+ }
176
+ else if (Array.isArray(value)) {
177
+ for (let i = 0; i < value.length; i++) {
178
+ if (typeof value[i] === "string" && guidsMap[value[i]]) {
179
+ value[i] = guidsMap[value[i]];
180
+ }
181
+ else if (typeof value[i] === "object" && value[i] !== null) {
182
+ resolveStringGuidsInObject(value[i], guidsMap, visited);
183
+ }
184
+ }
185
+ }
186
+ else if (typeof value === "object") {
187
+ // Skip known non-data objects
188
+ if (value.isObject3D || value.isComponent) continue;
189
+ resolveStringGuidsInObject(value, guidsMap, visited);
190
+ }
191
+ }
192
+ }
193
+
194
+ // #region resolveValue
195
+ /**
196
+ * Resolve a single value, returning the remapped value or undefined if no remap needed.
197
+ * This is the core remapping logic called recursively for nested structures.
198
+ */
199
+ export function resolveValue(key: string, value: unknown, objectMap: InstantiateReferenceMap): any | undefined {
200
+
201
+ // Handle null/undefined early to avoid unnecessary processing
202
+ if (value === undefined) return undefined;
203
+ if (value === null) return null;
204
+
205
+ // String guid resolution: if this string is a known guid/uuid in the objectMap,
206
+ // resolve it directly to the clone object. This handles e.g. PlayableDirector track.outputs.
207
+ if (typeof value === "string") {
208
+ const ref = objectMap[value];
209
+ if (ref) {
210
+ return ref.clone;
211
+ }
212
+ return undefined;
213
+ }
214
+
215
+ // Primitives: no remapping needed
216
+ if (typeof value !== "object") return undefined;
217
+
218
+ // 1. Component → find cloned counterpart by gameObject.uuid + component index
219
+ if (isComponent(value)) {
220
+ return resolveComponentReference(value, objectMap);
221
+ }
222
+
223
+ // 2. Object3D → uuid lookup, return clone if found (otherwise external, keep as-is)
224
+ if ((value as Object3D).isObject3D === true) {
225
+ if (key === "gameObject") return undefined;
226
+ const id = (value as Object3D).uuid;
227
+ const cloneRef = objectMap[id]?.clone;
228
+ if (cloneRef) {
229
+ if (debug) console.log(key, "old", value, "new", cloneRef);
230
+ return cloneRef;
231
+ }
232
+ return undefined;
233
+ }
234
+
235
+ // 3. Cloneable value types (Vector3, Quaternion, Euler, Color)
236
+ if (isCloneableValueType(value)) {
237
+ return value.clone();
238
+ }
239
+
240
+ // 4. Array → create new array, recursively resolve each element
241
+ if (Array.isArray(value)) {
242
+ return resolveArray(key, value, objectMap);
243
+ }
244
+
245
+ // 5. Map → create new Map, resolve keys and values
246
+ if (value instanceof Map) {
247
+ return resolveMap(value, objectMap);
248
+ }
249
+
250
+ // 6. Set → create new Set, resolve values
251
+ if (value instanceof Set) {
252
+ return resolveSet(value, objectMap);
253
+ }
254
+
255
+ // 7. WeakMap / WeakSet → NOT iterable, cannot remap. Keep as-is.
256
+ if (value instanceof WeakMap || value instanceof WeakSet) {
257
+ return undefined;
258
+ }
259
+
260
+ // 8. @serializable objects (incl. EventList, CallInfo) → shallow clone + recursively resolve $serializedTypes fields
261
+ if (isSerializable(value) && value.$serializedTypes) {
262
+ return resolveSerializableObject(value, objectMap);
263
+ }
264
+
265
+ // 9. Plain objects / Records → shallow clone, resolve each value
266
+ if (isPlainObject(value)) {
267
+ return resolvePlainObject(key, value, objectMap);
268
+ }
269
+
270
+ return undefined;
271
+ }
272
+
273
+ // ————————————————————————————————————————————————————————
274
+ // Internal Helpers
275
+ // ————————————————————————————————————————————————————————
276
+
277
+ function resolveComponentReference(value: IComponent, objectMap: InstantiateReferenceMap): object | undefined {
278
+ const originalGameObject = value["gameObject"] as Object3D | undefined;
279
+ if (!originalGameObject) return undefined;
280
+
281
+ const id = originalGameObject.uuid;
282
+ const newGameObject = objectMap[id]?.clone as Object3D | undefined;
283
+ if (!newGameObject) {
284
+ // Reference points to an object not in the cloned hierarchy (external)
285
+ if (debug) console.log("Component reference did not change (external)", value);
286
+ return undefined;
287
+ }
288
+
289
+ const index = originalGameObject.userData.components.indexOf(value);
290
+ if (index >= 0 && newGameObject.isObject3D) {
291
+ if (debug) console.log("Resolved component", id, "at index", index);
292
+ return newGameObject.userData.components[index];
293
+ }
294
+ else {
295
+ console.warn("Could not find component at expected index", value);
296
+ }
297
+ return undefined;
298
+ }
299
+
300
+ function resolveArray(key: string, arr: unknown[], objectMap: InstantiateReferenceMap): unknown[] {
301
+ const result: unknown[] = [];
302
+ for (let i = 0; i < arr.length; i++) {
303
+ const entry = arr[i];
304
+ if (entry === null || entry === undefined) {
305
+ result.push(entry);
306
+ continue;
307
+ }
308
+ // Skip primitives that can't be remapped (numbers, booleans)
309
+ // but allow strings through for guid resolution
310
+ if (typeof entry !== "object" && typeof entry !== "string") {
311
+ result.push(entry);
312
+ continue;
313
+ }
314
+ const resolved = resolveValue(key, entry, objectMap);
315
+ result.push(resolved !== undefined ? resolved : entry);
316
+ }
317
+ return result;
318
+ }
319
+
320
+ function resolveMap(map: Map<unknown, unknown>, objectMap: InstantiateReferenceMap): Map<any, any> {
321
+ const result = new Map();
322
+ let didChange = false;
323
+ for (const [mapKey, mapValue] of map) {
324
+ let resolvedKey = mapKey;
325
+ let resolvedValue = mapValue;
326
+
327
+ if (typeof mapKey === "object" && mapKey !== null) {
328
+ const rk = resolveValue("", mapKey, objectMap);
329
+ if (rk !== undefined) { resolvedKey = rk; didChange = true; }
330
+ }
331
+ if (typeof mapValue === "object" && mapValue !== null) {
332
+ const rv = resolveValue("", mapValue, objectMap);
333
+ if (rv !== undefined) { resolvedValue = rv; didChange = true; }
334
+ }
335
+ result.set(resolvedKey, resolvedValue);
336
+ }
337
+ return didChange ? result : result; // always return new Map to prevent shared mutation
338
+ }
339
+
340
+ function resolveSet(set: Set<unknown>, objectMap: InstantiateReferenceMap): Set<any> {
341
+ const result = new Set();
342
+ for (const entry of set) {
343
+ if (typeof entry === "object" && entry !== null) {
344
+ const resolved = resolveValue("", entry, objectMap);
345
+ result.add(resolved !== undefined ? resolved : entry);
346
+ } else {
347
+ result.add(entry);
348
+ }
349
+ }
350
+ return result;
351
+ }
352
+
353
+ function resolveSerializableObject(value: unknown, objectMap: InstantiateReferenceMap): any | undefined {
354
+ // Clone the serializable object to avoid mutating the original (which may be shared with source)
355
+ const cloned = Object.assign(Object.create(Object.getPrototypeOf(value)), value);
356
+ let didChange = false;
357
+ for (const key in cloned.$serializedTypes) {
358
+ const val = cloned[key];
359
+ if (val === null || val === undefined) continue;
360
+ if (typeof val === "object") {
361
+ if (debug) console.log("Recursively resolve references for", key, val);
362
+ const resolved = resolveValue(key, val, objectMap);
363
+ if (resolved !== undefined) {
364
+ cloned[key] = resolved;
365
+ didChange = true;
366
+ }
367
+ }
368
+ }
369
+ return didChange ? cloned : undefined;
370
+ }
371
+
372
+ function resolvePlainObject(_parentKey: string, obj: Record<string, unknown>, objectMap: InstantiateReferenceMap): Record<string, unknown> | undefined {
373
+ let didChange = false;
374
+ const clone = { ...obj };
375
+ for (const key of Object.keys(clone)) {
376
+ const val = clone[key];
377
+ if (val === null || val === undefined) continue;
378
+ // Skip primitives that can't be remapped, but allow strings for guid resolution
379
+ if (typeof val !== "object" && typeof val !== "string") continue;
380
+ const resolved = resolveValue(key, val, objectMap);
381
+ if (resolved !== undefined) {
382
+ clone[key] = resolved;
383
+ didChange = true;
384
+ }
385
+ }
386
+ return didChange ? clone : undefined;
387
+ }
388
+
389
+ function isPlainObject(obj: unknown): obj is Record<string, unknown> {
390
+ if (typeof obj !== "object" || obj === null) return false;
391
+ const proto = Object.getPrototypeOf(obj);
392
+ return proto === Object.prototype || proto === null;
393
+ }
394
+
395
+ /** Returns true if the object is a three.js value type that should be cloned (not remapped) */
396
+ function isCloneableValueType(value: object): value is { clone(): object } {
397
+ return (value as Vector2).isVector2 === true ||
398
+ (value as Vector3).isVector3 === true ||
399
+ (value as Vector4).isVector4 === true ||
400
+ (value as Quaternion).isQuaternion === true ||
401
+ (value as Euler).isEuler === true ||
402
+ (value as Color).isColor === true ||
403
+ (value as Matrix2).isMatrix2 === true ||
404
+ (value as Matrix3).isMatrix3 === true ||
405
+ (value as Matrix4).isMatrix4 === true;
406
+ }
407
+
@@ -3,9 +3,9 @@ import { ContextEvent, ContextRegistry } from "./engine_context_registry.js";
3
3
  import { onInitialized } from "./engine_lifecycle_api.js";
4
4
  import { isLocalNetwork } from "./engine_networking_utils.js";
5
5
  import { Context } from "./engine_setup.js";
6
+ import { SSR } from "./engine_ssr.js";
6
7
  import type { IContext } from "./engine_types.js";
7
8
  import { getParam } from "./engine_utils.js";
8
- import { SSR } from "./engine_ssr.js";
9
9
 
10
10
  const debug = getParam("debuglicense");
11
11
 
@@ -344,8 +344,11 @@ function updateIsActiveInHierarchyRecursiveRuntime(go: Object3D, activeInHierarc
344
344
  if (allowEventCall) {
345
345
  const components = go.userData?.components;
346
346
  if (components) {
347
- for (let ci = components.length - 1, cl = -1; ci > cl; ci--) {
348
- const comp = components[ci];
347
+ // We need to iterate on components in the original order right now to work-around UI related initialization bugs where RectTransform must be initialized before e.g. Graphic components. https://linear.app/needle/issue/NE-6986
348
+ // In the future we can reverse iterate this once the UI system has been replaced (that being said it's probably expected that component enable in the order in which they're added to an object)
349
+ const componentsCopy = [...components];
350
+ for (let ci = 0; ci < componentsCopy.length; ci++) {
351
+ const comp = componentsCopy[ci];
349
352
  if (activeInHierarchy) {
350
353
  if (comp?.enabled) {
351
354
  try { comp.__internalAwake(); }
@@ -828,7 +828,9 @@ export class NetworkConnection implements INetworkConnection {
828
828
  this._ws = undefined;
829
829
  networkingServerUrl = undefined;
830
830
 
831
- // Reset all state
831
+ // Reset all state synchronously so callers can rely on isConnected/isInRoom immediately
832
+ this.connected = false;
833
+ this._connectionId = null;
832
834
  this._currentRoomAllowEditing = true;
833
835
  this._currentRoomName = null;
834
836
  this._currentRoomViewId = null;
@@ -3,7 +3,7 @@ import { FileLoader } from "three";
3
3
  import { showBalloonWarning } from "./debug/index.js";
4
4
  import { hasCommercialLicense } from "./engine_license.js";
5
5
  import { delay } from "./engine_utils.js";
6
- import { md5Hex, md5AsBytes, sha256Base64 } from "./engine_utils_hash.js";
6
+ import { md5AsBytes, md5Hex, sha256Base64 } from "./engine_utils_hash.js";
7
7
 
8
8
 
9
9
  export namespace BlobStorage {
@@ -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
 
@@ -1,10 +1,10 @@
1
1
  import type { SceneData } from "needle-bindings";
2
2
  export type { SceneData };
3
- import type { IContext } from "./engine_types.js";
4
- import { getComponent } from "./engine_components.js";
5
- import { TypeStore } from "./engine_typestore.js";
6
3
  import { isDevEnvironment } from "./debug/index.js";
4
+ import { getComponent } from "./engine_components.js";
7
5
  import { ContextRegistry } from "./engine_context_registry.js";
6
+ import type { IContext } from "./engine_types.js";
7
+ import { TypeStore } from "./engine_typestore.js";
8
8
 
9
9
  /**
10
10
  * Quick access to the current Needle Engine context from anywhere — no need to pass `ctx` around.