@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.
- package/CHANGELOG.md +18 -0
- package/components.needle.json +1 -1
- package/dist/{needle-engine.bundle-CvtELXh0.js → needle-engine.bundle-D-eWNCu1.js} +15969 -15550
- package/dist/needle-engine.bundle-D3ZUII8o.min.js +1733 -0
- package/dist/needle-engine.bundle-_rOpvUGL.umd.cjs +1733 -0
- package/dist/needle-engine.d.ts +746 -156
- package/dist/needle-engine.js +529 -529
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/lib/engine/api.d.ts +5 -0
- package/lib/engine/api.js +4 -0
- package/lib/engine/api.js.map +1 -1
- package/lib/engine/codegen/register_types.js +2 -10
- package/lib/engine/codegen/register_types.js.map +1 -1
- package/lib/engine/engine_audio.d.ts +68 -0
- package/lib/engine/engine_audio.js +172 -0
- package/lib/engine/engine_audio.js.map +1 -1
- package/lib/engine/engine_components.js +1 -1
- package/lib/engine/engine_components.js.map +1 -1
- package/lib/engine/engine_context.d.ts +1 -1
- package/lib/engine/engine_context.js +2 -2
- package/lib/engine/engine_context.js.map +1 -1
- package/lib/engine/engine_disposable.d.ts +171 -0
- package/lib/engine/engine_disposable.js +136 -0
- package/lib/engine/engine_disposable.js.map +1 -0
- package/lib/engine/engine_gameobject.d.ts +1 -10
- package/lib/engine/engine_gameobject.js +22 -120
- package/lib/engine/engine_gameobject.js.map +1 -1
- package/lib/engine/engine_gltf_builtin_components.js +7 -69
- package/lib/engine/engine_gltf_builtin_components.js.map +1 -1
- package/lib/engine/engine_init.js +6 -6
- package/lib/engine/engine_init.js.map +1 -1
- package/lib/engine/engine_input.d.ts +1 -1
- package/lib/engine/engine_input.js +1 -1
- package/lib/engine/engine_input.js.map +1 -1
- package/lib/engine/engine_instantiate_resolve.d.ts +42 -0
- package/lib/engine/engine_instantiate_resolve.js +372 -0
- package/lib/engine/engine_instantiate_resolve.js.map +1 -0
- package/lib/engine/engine_license.js +1 -1
- package/lib/engine/engine_license.js.map +1 -1
- package/lib/engine/engine_mainloop_utils.js +5 -2
- package/lib/engine/engine_mainloop_utils.js.map +1 -1
- package/lib/engine/engine_networking.js +3 -1
- package/lib/engine/engine_networking.js.map +1 -1
- package/lib/engine/engine_networking_blob.js +1 -1
- package/lib/engine/engine_networking_blob.js.map +1 -1
- package/lib/engine/engine_physics_rapier.d.ts +11 -3
- package/lib/engine/engine_physics_rapier.js +88 -25
- package/lib/engine/engine_physics_rapier.js.map +1 -1
- package/lib/engine/engine_scenedata.js +2 -2
- package/lib/engine/engine_scenedata.js.map +1 -1
- package/lib/engine/engine_serialization_builtin_serializer.js +28 -5
- package/lib/engine/engine_serialization_builtin_serializer.js.map +1 -1
- package/lib/engine/engine_serialization_core.d.ts +1 -0
- package/lib/engine/engine_serialization_core.js +7 -0
- package/lib/engine/engine_serialization_core.js.map +1 -1
- package/lib/engine/engine_types.d.ts +17 -9
- package/lib/engine/engine_types.js +1 -1
- package/lib/engine/engine_types.js.map +1 -1
- package/lib/engine/engine_util_decorator.js +7 -2
- package/lib/engine/engine_util_decorator.js.map +1 -1
- package/lib/engine/engine_utils.d.ts +1 -1
- package/lib/engine/engine_utils.js +19 -5
- package/lib/engine/engine_utils.js.map +1 -1
- package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +1 -1
- package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js.map +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.js +1 -1
- package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
- package/lib/engine/webcomponents/needle-engine.d.ts +10 -4
- package/lib/engine/webcomponents/needle-engine.js +1 -1
- package/lib/engine/webcomponents/needle-engine.js.map +1 -1
- package/lib/engine/xr/NeedleXRSession.d.ts +3 -2
- package/lib/engine/xr/NeedleXRSession.js +50 -14
- package/lib/engine/xr/NeedleXRSession.js.map +1 -1
- package/lib/engine/xr/events.d.ts +1 -1
- package/lib/engine/xr/events.js.map +1 -1
- package/lib/engine-components/Animation.js +17 -16
- package/lib/engine-components/Animation.js.map +1 -1
- package/lib/engine-components/Animator.d.ts +6 -0
- package/lib/engine-components/Animator.js +17 -12
- package/lib/engine-components/Animator.js.map +1 -1
- package/lib/engine-components/AnimatorController.builder.d.ts +113 -0
- package/lib/engine-components/AnimatorController.builder.js +195 -0
- package/lib/engine-components/AnimatorController.builder.js.map +1 -0
- package/lib/engine-components/AnimatorController.d.ts +2 -119
- package/lib/engine-components/AnimatorController.js +31 -232
- package/lib/engine-components/AnimatorController.js.map +1 -1
- package/lib/engine-components/AudioSource.d.ts +19 -3
- package/lib/engine-components/AudioSource.js +121 -68
- package/lib/engine-components/AudioSource.js.map +1 -1
- package/lib/engine-components/Collider.d.ts +18 -9
- package/lib/engine-components/Collider.js +61 -14
- package/lib/engine-components/Collider.js.map +1 -1
- package/lib/engine-components/Component.d.ts +58 -6
- package/lib/engine-components/Component.js +77 -0
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/DragControls.d.ts +7 -0
- package/lib/engine-components/DragControls.js +19 -7
- package/lib/engine-components/DragControls.js.map +1 -1
- package/lib/engine-components/EventList.d.ts +31 -9
- package/lib/engine-components/EventList.js +37 -76
- package/lib/engine-components/EventList.js.map +1 -1
- package/lib/engine-components/Joints.d.ts +4 -2
- package/lib/engine-components/Joints.js +19 -3
- package/lib/engine-components/Joints.js.map +1 -1
- package/lib/engine-components/Light.js +9 -1
- package/lib/engine-components/Light.js.map +1 -1
- package/lib/engine-components/Networking.d.ts +1 -1
- package/lib/engine-components/Networking.js +1 -1
- package/lib/engine-components/OrbitControls.js +16 -11
- package/lib/engine-components/OrbitControls.js.map +1 -1
- package/lib/engine-components/RigidBody.d.ts +12 -4
- package/lib/engine-components/RigidBody.js +18 -4
- package/lib/engine-components/RigidBody.js.map +1 -1
- package/lib/engine-components/SeeThrough.js +2 -2
- package/lib/engine-components/SeeThrough.js.map +1 -1
- package/lib/engine-components/api.d.ts +1 -1
- package/lib/engine-components/api.js +1 -1
- package/lib/engine-components/api.js.map +1 -1
- package/lib/engine-components/codegen/components.d.ts +3 -9
- package/lib/engine-components/codegen/components.js +3 -9
- package/lib/engine-components/codegen/components.js.map +1 -1
- package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
- package/lib/engine-components/timeline/PlayableDirector.d.ts +16 -6
- package/lib/engine-components/timeline/PlayableDirector.js +63 -61
- package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
- package/lib/engine-components/timeline/SignalAsset.d.ts +3 -1
- package/lib/engine-components/timeline/SignalAsset.js +1 -0
- package/lib/engine-components/timeline/SignalAsset.js.map +1 -1
- package/lib/engine-components/timeline/TimelineBuilder.d.ts +247 -0
- package/lib/engine-components/timeline/TimelineBuilder.js +400 -0
- package/lib/engine-components/timeline/TimelineBuilder.js.map +1 -0
- package/lib/engine-components/timeline/TimelineModels.d.ts +2 -1
- package/lib/engine-components/timeline/TimelineModels.js +3 -0
- package/lib/engine-components/timeline/TimelineModels.js.map +1 -1
- package/lib/engine-components/timeline/TimelineTracks.d.ts +23 -0
- package/lib/engine-components/timeline/TimelineTracks.js +71 -13
- package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
- package/lib/engine-components/timeline/index.d.ts +2 -1
- package/lib/engine-components/timeline/index.js +2 -0
- package/lib/engine-components/timeline/index.js.map +1 -1
- package/lib/engine-components/ui/Canvas.d.ts +1 -1
- package/lib/engine-components/ui/Canvas.js +2 -8
- package/lib/engine-components/ui/Canvas.js.map +1 -1
- package/lib/engine-components/ui/Text.d.ts +1 -0
- package/lib/engine-components/ui/Text.js +10 -7
- package/lib/engine-components/ui/Text.js.map +1 -1
- package/lib/engine-components/web/CursorFollow.js +21 -12
- package/lib/engine-components/web/CursorFollow.js.map +1 -1
- package/lib/engine-components/webxr/WebXRImageTracking.js +4 -0
- package/lib/engine-components/webxr/WebXRImageTracking.js.map +1 -1
- package/package.json +2 -83
- package/plugins/common/worker.js +9 -4
- package/plugins/vite/asap.js +17 -8
- package/plugins/vite/dependencies.js +29 -0
- package/plugins/vite/dependency-watcher.js +2 -2
- package/plugins/vite/editor-connection.js +3 -3
- package/plugins/vite/local-files-core.js +3 -3
- package/plugins/vite/local-files-utils.d.ts +3 -1
- package/plugins/vite/local-files-utils.js +29 -5
- package/plugins/vite/reload.js +1 -1
- package/plugins/vite/server.js +2 -1
- package/src/engine/api.ts +7 -0
- package/src/engine/codegen/register_types.ts +2 -10
- package/src/engine/engine_audio.ts +184 -0
- package/src/engine/engine_components.ts +1 -1
- package/src/engine/engine_context.ts +3 -3
- package/src/engine/engine_disposable.ts +213 -0
- package/src/engine/engine_gameobject.ts +54 -159
- package/src/engine/engine_gltf_builtin_components.ts +7 -76
- package/src/engine/engine_init.ts +6 -6
- package/src/engine/engine_input.ts +1 -1
- package/src/engine/engine_instantiate_resolve.ts +407 -0
- package/src/engine/engine_license.ts +1 -1
- package/src/engine/engine_mainloop_utils.ts +5 -2
- package/src/engine/engine_networking.ts +3 -1
- package/src/engine/engine_networking_blob.ts +1 -1
- package/src/engine/engine_physics_rapier.ts +82 -27
- package/src/engine/engine_scenedata.ts +3 -3
- package/src/engine/engine_serialization_builtin_serializer.ts +32 -9
- package/src/engine/engine_serialization_core.ts +9 -0
- package/src/engine/engine_types.ts +22 -13
- package/src/engine/engine_util_decorator.ts +7 -2
- package/src/engine/engine_utils.ts +16 -5
- package/src/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +1 -1
- package/src/engine/webcomponents/needle menu/needle-menu.ts +1 -1
- package/src/engine/webcomponents/needle-engine.ts +10 -4
- package/src/engine/xr/NeedleXRSession.ts +48 -13
- package/src/engine/xr/events.ts +1 -1
- package/src/engine-components/Animation.ts +19 -16
- package/src/engine-components/Animator.ts +18 -11
- package/src/engine-components/AnimatorController.builder.ts +261 -0
- package/src/engine-components/AnimatorController.ts +19 -291
- package/src/engine-components/AudioSource.ts +130 -79
- package/src/engine-components/Collider.ts +66 -18
- package/src/engine-components/Component.ts +79 -9
- package/src/engine-components/DragControls.ts +18 -11
- package/src/engine-components/EventList.ts +45 -83
- package/src/engine-components/Joints.ts +20 -4
- package/src/engine-components/Light.ts +10 -2
- package/src/engine-components/Networking.ts +1 -1
- package/src/engine-components/OrbitControls.ts +18 -9
- package/src/engine-components/RigidBody.ts +18 -4
- package/src/engine-components/SeeThrough.ts +2 -2
- package/src/engine-components/api.ts +1 -1
- package/src/engine-components/codegen/components.ts +3 -9
- package/src/engine-components/timeline/PlayableDirector.ts +61 -64
- package/src/engine-components/timeline/SignalAsset.ts +4 -1
- package/src/engine-components/timeline/TimelineBuilder.ts +565 -0
- package/src/engine-components/timeline/TimelineModels.ts +5 -1
- package/src/engine-components/timeline/TimelineTracks.ts +74 -13
- package/src/engine-components/timeline/index.ts +2 -1
- package/src/engine-components/ui/Canvas.ts +2 -8
- package/src/engine-components/ui/Text.ts +12 -8
- package/src/engine-components/web/CursorFollow.ts +21 -13
- package/src/engine-components/webxr/WebXRImageTracking.ts +2 -0
- package/dist/needle-engine.bundle-1s2gOoKZ.min.js +0 -1732
- package/dist/needle-engine.bundle-j4nGJXCs.umd.cjs +0 -1732
- package/lib/engine-components/AvatarLoader.d.ts +0 -80
- package/lib/engine-components/AvatarLoader.js +0 -232
- package/lib/engine-components/AvatarLoader.js.map +0 -1
- package/lib/engine-components/avatar/AvatarBlink_Simple.d.ts +0 -11
- package/lib/engine-components/avatar/AvatarBlink_Simple.js +0 -77
- package/lib/engine-components/avatar/AvatarBlink_Simple.js.map +0 -1
- package/lib/engine-components/avatar/AvatarEyeLook_Rotation.d.ts +0 -14
- package/lib/engine-components/avatar/AvatarEyeLook_Rotation.js +0 -69
- package/lib/engine-components/avatar/AvatarEyeLook_Rotation.js.map +0 -1
- package/lib/engine-components/avatar/Avatar_Brain_LookAt.d.ts +0 -29
- package/lib/engine-components/avatar/Avatar_Brain_LookAt.js +0 -122
- package/lib/engine-components/avatar/Avatar_Brain_LookAt.js.map +0 -1
- package/lib/engine-components/avatar/Avatar_MouthShapes.d.ts +0 -15
- package/lib/engine-components/avatar/Avatar_MouthShapes.js +0 -80
- package/lib/engine-components/avatar/Avatar_MouthShapes.js.map +0 -1
- package/lib/engine-components/avatar/Avatar_MustacheShake.d.ts +0 -9
- package/lib/engine-components/avatar/Avatar_MustacheShake.js +0 -30
- package/lib/engine-components/avatar/Avatar_MustacheShake.js.map +0 -1
- package/src/engine-components/AvatarLoader.ts +0 -264
- package/src/engine-components/avatar/AvatarBlink_Simple.ts +0 -70
- package/src/engine-components/avatar/AvatarEyeLook_Rotation.ts +0 -64
- package/src/engine-components/avatar/Avatar_Brain_LookAt.ts +0 -140
- package/src/engine-components/avatar/Avatar_MouthShapes.ts +0 -84
- 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
|
-
|
|
348
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
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
|
-
//
|
|
985
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
mat
|
|
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.
|