@needle-tools/engine 5.0.2 → 5.1.0-canary.87c4c44

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 (165) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +6 -7
  3. package/SKILL.md +39 -21
  4. package/components.needle.json +1 -1
  5. package/dist/needle-engine.bundle-BlVD1I5a.min.js +1656 -0
  6. package/dist/{needle-engine.bundle-BoTyA-Le.js → needle-engine.bundle-CFOipCo_.js} +8519 -7920
  7. package/dist/needle-engine.bundle-DX2Y-SQF.umd.cjs +1656 -0
  8. package/dist/needle-engine.d.ts +628 -61
  9. package/dist/needle-engine.js +575 -565
  10. package/dist/needle-engine.min.js +1 -1
  11. package/dist/needle-engine.umd.cjs +1 -1
  12. package/dist/{vendor-vHLk8sXu.js → vendor-CAcsI0eU.js} +116 -115
  13. package/dist/{vendor-CntUvmJu.umd.cjs → vendor-CEM38hLE.umd.cjs} +2 -2
  14. package/dist/{vendor-DPbfJJ4d.min.js → vendor-HRlxIBga.min.js} +2 -2
  15. package/lib/engine/api.d.ts +2 -0
  16. package/lib/engine/api.js +2 -0
  17. package/lib/engine/api.js.map +1 -1
  18. package/lib/engine/engine_addressables.js +5 -1
  19. package/lib/engine/engine_addressables.js.map +1 -1
  20. package/lib/engine/engine_animation.d.ts +14 -7
  21. package/lib/engine/engine_animation.js +49 -9
  22. package/lib/engine/engine_animation.js.map +1 -1
  23. package/lib/engine/engine_components.js +33 -4
  24. package/lib/engine/engine_components.js.map +1 -1
  25. package/lib/engine/engine_context.d.ts +7 -2
  26. package/lib/engine/engine_context.js +10 -2
  27. package/lib/engine/engine_context.js.map +1 -1
  28. package/lib/engine/engine_gameobject.d.ts +4 -0
  29. package/lib/engine/engine_gameobject.js.map +1 -1
  30. package/lib/engine/engine_init.js +2 -0
  31. package/lib/engine/engine_init.js.map +1 -1
  32. package/lib/engine/engine_input.js +4 -1
  33. package/lib/engine/engine_input.js.map +1 -1
  34. package/lib/engine/engine_materialpropertyblock.js +1 -20
  35. package/lib/engine/engine_materialpropertyblock.js.map +1 -1
  36. package/lib/engine/engine_networking.d.ts +11 -8
  37. package/lib/engine/engine_networking.js +43 -26
  38. package/lib/engine/engine_networking.js.map +1 -1
  39. package/lib/engine/engine_networking_instantiate.d.ts +100 -5
  40. package/lib/engine/engine_networking_instantiate.js +150 -16
  41. package/lib/engine/engine_networking_instantiate.js.map +1 -1
  42. package/lib/engine/engine_networking_prefabs.d.ts +59 -0
  43. package/lib/engine/engine_networking_prefabs.js +67 -0
  44. package/lib/engine/engine_networking_prefabs.js.map +1 -0
  45. package/lib/engine/postprocessing/api.d.ts +2 -0
  46. package/lib/engine/postprocessing/api.js +2 -0
  47. package/lib/engine/postprocessing/api.js.map +1 -0
  48. package/lib/engine/postprocessing/index.d.ts +2 -0
  49. package/lib/engine/postprocessing/index.js +2 -0
  50. package/lib/engine/postprocessing/index.js.map +1 -0
  51. package/lib/engine/postprocessing/postprocessing.d.ts +83 -0
  52. package/lib/engine/postprocessing/postprocessing.js +280 -0
  53. package/lib/engine/postprocessing/postprocessing.js.map +1 -0
  54. package/lib/engine/postprocessing/types.d.ts +39 -0
  55. package/lib/engine/postprocessing/types.js +2 -0
  56. package/lib/engine/postprocessing/types.js.map +1 -0
  57. package/lib/engine/webcomponents/WebXRButtons.js +17 -3
  58. package/lib/engine/webcomponents/WebXRButtons.js.map +1 -1
  59. package/lib/engine/webcomponents/icons.js +3 -1
  60. package/lib/engine/webcomponents/icons.js.map +1 -1
  61. package/lib/engine/xr/NeedleXRSession.d.ts +1 -0
  62. package/lib/engine/xr/NeedleXRSession.js +43 -10
  63. package/lib/engine/xr/NeedleXRSession.js.map +1 -1
  64. package/lib/engine/xr/init.d.ts +4 -0
  65. package/lib/engine/xr/init.js +49 -0
  66. package/lib/engine/xr/init.js.map +1 -0
  67. package/lib/engine-components/AnimationUtils.d.ts +4 -1
  68. package/lib/engine-components/AnimationUtils.js +7 -19
  69. package/lib/engine-components/AnimationUtils.js.map +1 -1
  70. package/lib/engine-components/AnimatorController.d.ts +135 -2
  71. package/lib/engine-components/AnimatorController.js +216 -13
  72. package/lib/engine-components/AnimatorController.js.map +1 -1
  73. package/lib/engine-components/OrbitControls.d.ts +4 -0
  74. package/lib/engine-components/OrbitControls.js +5 -1
  75. package/lib/engine-components/OrbitControls.js.map +1 -1
  76. package/lib/engine-components/SeeThrough.d.ts +0 -2
  77. package/lib/engine-components/SeeThrough.js +0 -89
  78. package/lib/engine-components/SeeThrough.js.map +1 -1
  79. package/lib/engine-components/SyncedRoom.d.ts +4 -0
  80. package/lib/engine-components/SyncedRoom.js +23 -8
  81. package/lib/engine-components/SyncedRoom.js.map +1 -1
  82. package/lib/engine-components/SyncedTransform.js +5 -5
  83. package/lib/engine-components/SyncedTransform.js.map +1 -1
  84. package/lib/engine-components/Voip.d.ts +46 -0
  85. package/lib/engine-components/Voip.js +126 -2
  86. package/lib/engine-components/Voip.js.map +1 -1
  87. package/lib/engine-components/api.d.ts +1 -0
  88. package/lib/engine-components/api.js +1 -0
  89. package/lib/engine-components/api.js.map +1 -1
  90. package/lib/engine-components/postprocessing/Effects/Tonemapping.d.ts +5 -2
  91. package/lib/engine-components/postprocessing/Effects/Tonemapping.js +11 -18
  92. package/lib/engine-components/postprocessing/Effects/Tonemapping.js.map +1 -1
  93. package/lib/engine-components/postprocessing/PostProcessingEffect.d.ts +3 -4
  94. package/lib/engine-components/postprocessing/PostProcessingEffect.js +6 -15
  95. package/lib/engine-components/postprocessing/PostProcessingEffect.js.map +1 -1
  96. package/lib/engine-components/postprocessing/PostProcessingHandler.d.ts +2 -1
  97. package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
  98. package/lib/engine-components/postprocessing/Volume.d.ts +18 -11
  99. package/lib/engine-components/postprocessing/Volume.js +61 -140
  100. package/lib/engine-components/postprocessing/Volume.js.map +1 -1
  101. package/lib/engine-components/postprocessing/index.d.ts +1 -0
  102. package/lib/engine-components/postprocessing/index.js +1 -0
  103. package/lib/engine-components/postprocessing/index.js.map +1 -1
  104. package/lib/engine-components/postprocessing/utils.d.ts +2 -0
  105. package/lib/engine-components/postprocessing/utils.js +2 -0
  106. package/lib/engine-components/postprocessing/utils.js.map +1 -1
  107. package/lib/engine-components/ui/Canvas.js +2 -2
  108. package/lib/engine-components/ui/Canvas.js.map +1 -1
  109. package/lib/engine-components/ui/Graphic.d.ts +3 -3
  110. package/lib/engine-components/ui/Graphic.js +6 -2
  111. package/lib/engine-components/ui/Graphic.js.map +1 -1
  112. package/lib/engine-components/ui/Text.d.ts +64 -11
  113. package/lib/engine-components/ui/Text.js +154 -45
  114. package/lib/engine-components/ui/Text.js.map +1 -1
  115. package/lib/engine-components/ui/index.d.ts +1 -0
  116. package/lib/engine-components/ui/index.js +1 -0
  117. package/lib/engine-components/ui/index.js.map +1 -1
  118. package/lib/engine-components-experimental/networking/PlayerSync.d.ts +25 -3
  119. package/lib/engine-components-experimental/networking/PlayerSync.js +60 -11
  120. package/lib/engine-components-experimental/networking/PlayerSync.js.map +1 -1
  121. package/package.json +6 -5
  122. package/plugins/vite/ai.d.ts +11 -10
  123. package/plugins/vite/ai.js +305 -31
  124. package/plugins/vite/dependencies.js +5 -0
  125. package/src/engine/api.ts +3 -0
  126. package/src/engine/engine_addressables.ts +4 -1
  127. package/src/engine/engine_animation.ts +47 -9
  128. package/src/engine/engine_components.ts +36 -7
  129. package/src/engine/engine_context.ts +11 -2
  130. package/src/engine/engine_gameobject.ts +5 -0
  131. package/src/engine/engine_init.ts +2 -0
  132. package/src/engine/engine_input.ts +2 -1
  133. package/src/engine/engine_materialpropertyblock.ts +1 -20
  134. package/src/engine/engine_networking.ts +46 -23
  135. package/src/engine/engine_networking_instantiate.ts +160 -18
  136. package/src/engine/engine_networking_prefabs.ts +80 -0
  137. package/src/engine/postprocessing/api.ts +2 -0
  138. package/src/engine/postprocessing/index.ts +2 -0
  139. package/src/engine/postprocessing/postprocessing.ts +322 -0
  140. package/src/engine/postprocessing/types.ts +43 -0
  141. package/src/engine/webcomponents/WebXRButtons.ts +21 -4
  142. package/src/engine/webcomponents/icons.ts +5 -3
  143. package/src/engine/xr/NeedleXRSession.ts +50 -15
  144. package/src/engine/xr/init.ts +56 -0
  145. package/src/engine-components/AnimationUtils.ts +7 -17
  146. package/src/engine-components/AnimatorController.ts +288 -18
  147. package/src/engine-components/OrbitControls.ts +5 -1
  148. package/src/engine-components/SeeThrough.ts +0 -116
  149. package/src/engine-components/SyncedRoom.ts +28 -9
  150. package/src/engine-components/SyncedTransform.ts +5 -5
  151. package/src/engine-components/Voip.ts +129 -2
  152. package/src/engine-components/api.ts +1 -0
  153. package/src/engine-components/postprocessing/Effects/Tonemapping.ts +16 -24
  154. package/src/engine-components/postprocessing/PostProcessingEffect.ts +9 -16
  155. package/src/engine-components/postprocessing/PostProcessingHandler.ts +2 -1
  156. package/src/engine-components/postprocessing/Volume.ts +72 -163
  157. package/src/engine-components/postprocessing/index.ts +1 -0
  158. package/src/engine-components/postprocessing/utils.ts +2 -0
  159. package/src/engine-components/ui/Canvas.ts +2 -2
  160. package/src/engine-components/ui/Graphic.ts +7 -3
  161. package/src/engine-components/ui/Text.ts +170 -52
  162. package/src/engine-components/ui/index.ts +2 -1
  163. package/src/engine-components-experimental/networking/PlayerSync.ts +64 -11
  164. package/dist/needle-engine.bundle-B3ywqx5o.min.js +0 -1654
  165. package/dist/needle-engine.bundle-CzOPcOui.umd.cjs +0 -1654
@@ -2,9 +2,10 @@ import { AnimationAction, AnimationClip, AnimationMixer, KeyframeTrack, Object3D
2
2
 
3
3
  import { isDevEnvironment } from "./debug/index.js";
4
4
  import type { Context } from "./engine_context.js";
5
- import { GLTF, IAnimationComponent, Model } from "./engine_types.js";
5
+ import { IAnimationComponent, Model } from "./engine_types.js";
6
6
  import { TypeStore } from "./engine_typestore.js";
7
7
 
8
+ // #region Registry
8
9
  /**
9
10
  * Registry for animation related data. Use {@link registerAnimationMixer} to register an animation mixer instance.
10
11
  * Can be accessed from {@link Context.animations} and is used internally e.g. when exporting GLTF files.
@@ -52,10 +53,11 @@ export class AnimationsRegistry {
52
53
  }
53
54
 
54
55
 
56
+ // #region Animation Utils
55
57
  /**
56
58
  * Utility class for working with animations.
57
59
  */
58
- export class AnimationUtils {
60
+ export namespace AnimationUtils {
59
61
 
60
62
  /**
61
63
  * Tests if the root object of an AnimationAction can be animated. Objects where matrixAutoUpdate or matrixWorldAutoUpdate is set to false may not animate correctly.
@@ -63,7 +65,7 @@ export class AnimationUtils {
63
65
  * @param allowLog Whether to allow logging warnings. Default is false, which only allows logging in development environments.
64
66
  * @returns True if the root object can be animated, false otherwise
65
67
  */
66
- static testIfRootCanAnimate(action: AnimationAction, allowLog?: boolean): boolean {
68
+ export function testIfRootCanAnimate(action: AnimationAction, allowLog?: boolean): boolean {
67
69
  const root = action.getRoot();
68
70
 
69
71
  if (root && (root.userData.static || root.matrixAutoUpdate === false || root.matrixWorldAutoUpdate === false)) {
@@ -79,13 +81,15 @@ export class AnimationUtils {
79
81
  * @param mixer The animation mixer to get the actions from
80
82
  * @returns The actions or null if the mixer is invalid
81
83
  */
82
- static tryGetActionsFromMixer(mixer: AnimationMixer): Array<AnimationAction> | null {
84
+
85
+ export function tryGetActionsFromMixer(mixer: AnimationMixer): Array<AnimationAction> | null {
83
86
  const actions = mixer["_actions"] as Array<AnimationAction>;
84
87
  if (!actions) return null;
85
88
  return actions;
86
89
  }
87
90
 
88
- static tryGetAnimationClipsFromObjectHierarchy(obj: Object3D, target?: Array<AnimationClip>): Array<AnimationClip> {
91
+
92
+ export function tryGetAnimationClipsFromObjectHierarchy(obj: Object3D, target?: Array<AnimationClip>): Array<AnimationClip> {
89
93
  if (!target) target = new Array<AnimationClip>();
90
94
 
91
95
  if (!obj) {
@@ -96,18 +100,49 @@ export class AnimationUtils {
96
100
  }
97
101
  if (obj.children) {
98
102
  for (const child of obj.children) {
99
- this.tryGetAnimationClipsFromObjectHierarchy(child, target);
103
+ tryGetAnimationClipsFromObjectHierarchy(child, target);
100
104
  }
101
105
  }
102
106
  return target;
103
107
  }
104
108
 
109
+
110
+ const $objectAnimationKey = Symbol("objectIsAnimatedData");
111
+
112
+ /** Internal method - This marks an object as being animated. Make sure to always call isAnimated=false if you stop animating the object
113
+ * @param obj The object to mark
114
+ * @param isAnimated Whether the object is animated or not
115
+ */
116
+ export function setObjectAnimated(obj: Object3D, animatedBy: object, isAnimated: boolean) {
117
+ if (!obj) return;
118
+ if (obj[$objectAnimationKey] === undefined) {
119
+ if (!isAnimated) return;
120
+ obj[$objectAnimationKey] = new Set<object>();
121
+ }
122
+
123
+ const set = obj[$objectAnimationKey] as Set<object>;
124
+ if (isAnimated) {
125
+ set.add(animatedBy);
126
+ }
127
+ else if (set.has(animatedBy))
128
+ set.delete(animatedBy);
129
+ }
130
+
131
+ /** Get is the object is currently animated. Currently used by the Animator to check if a timeline animationtrack is actively animating an object */
132
+ export function getObjectAnimated(obj: Object3D): boolean {
133
+ if (!obj) return false;
134
+ const set = obj[$objectAnimationKey] as Set<object>;
135
+ return set !== undefined && set.size > 0;
136
+ }
137
+
138
+ // #region Autoplay
105
139
  /**
106
140
  * Assigns animations from a GLTF file to the objects in the scene.
107
141
  * This method will look for objects in the scene that have animations and assign them to the correct objects.
108
142
  * @param file The GLTF file to assign the animations from
109
143
  */
110
- static autoplayAnimations(file: Object3D | Pick<Model, "animations" | "scene">): Array<IAnimationComponent> | null {
144
+
145
+ export function autoplayAnimations(file: Object3D | Pick<Model, "animations" | "scene">): Array<IAnimationComponent> | null {
111
146
  if (!file || !file.animations) {
112
147
  console.debug("No animations found in file");
113
148
  return null;
@@ -171,11 +206,14 @@ export class AnimationUtils {
171
206
  }
172
207
 
173
208
 
174
- static emptyClip(): AnimationClip {
209
+ // #region Create Clips
210
+
211
+ export function emptyClip(): AnimationClip {
175
212
  return new AnimationClip("empty", 0, []);
176
213
  }
177
214
 
178
- static createScaleClip(options?: ScaleClipOptions): AnimationClip {
215
+
216
+ export function createScaleClip(options?: ScaleClipOptions): AnimationClip {
179
217
  const duration = options?.duration ?? 0.3;
180
218
 
181
219
  let baseScale: Vector3Like = { x: 1, y: 1, z: 1 };
@@ -1,4 +1,5 @@
1
1
  import { Object3D, Scene } from "three";
2
+ import { v5 } from 'uuid';
2
3
 
3
4
  import { ComponentLifecycleEvents } from "./engine_components_internal.js";
4
5
  import { activeInHierarchyFieldName } from "./engine_constants.js";
@@ -6,8 +7,35 @@ import { removeScriptFromContext, updateActiveInHierarchyWithoutEventCall } from
6
7
  import { InstantiateIdProvider } from "./engine_networking_instantiate.js";
7
8
  import { Context, registerComponent } from "./engine_setup.js";
8
9
  import type { ComponentInit, Constructor, ConstructorConcrete, IComponent, IGameObject } from "./engine_types.js";
10
+ import { $componentName } from "./engine_types.js";
9
11
  import { getParam } from "./engine_utils.js";
10
12
  import { apply } from "./js-extensions/index.js";
13
+ import { TypeStore } from "./engine_typestore.js";
14
+
15
+ const COMPONENT_GUID_NAMESPACE = 'eff8ba80-635d-11ec-90d6-0242ac120003';
16
+
17
+ /**
18
+ * Generates a deterministic guid for a component based on the object's guid and the component type.
19
+ * If the object has a guid, the component guid is derived from `objectGuid:TypeName:index`
20
+ * using UUID v5 hashing. This ensures the same component type added to the same object on
21
+ * different clients gets the same guid — critical for networked components like SyncedTransform.
22
+ * Falls back to the shared counter-based IdProvider if the object has no guid.
23
+ */
24
+ function generateDeterministicComponentGuid(obj: Object3D, component: IComponent): string {
25
+ const objectGuid = obj["guid"];
26
+ if (objectGuid) {
27
+ const typeName = component[$componentName] ?? component.constructor?.name ?? "Component";
28
+ // Count existing components of same type for uniqueness (e.g. multiple of the same component type)
29
+ const existingComponents = getComponents(obj, component.constructor as any);
30
+ let sameTypeCount = 0;
31
+ for (const c of existingComponents) {
32
+ if (c !== component) sameTypeCount++;
33
+ }
34
+ const key = `${objectGuid}:${typeName}:${sameTypeCount}`;
35
+ return v5(key, COMPONENT_GUID_NAMESPACE);
36
+ }
37
+ return getIdProvider().generateUUID();
38
+ }
11
39
 
12
40
 
13
41
  const debug = getParam("debuggetcomponent");
@@ -57,9 +85,8 @@ export function addNewComponent<T extends IComponent>(obj: Object3D, componentIn
57
85
  if (!obj.userData.components) obj.userData.components = [];
58
86
  obj.userData.components.push(componentInstance);
59
87
  componentInstance.gameObject = obj as IGameObject;
60
- // TODO: currently add component does not ensure a new component instance has a guid
61
88
  if (componentInstance.guid === undefined || componentInstance.guid === "invalid") {
62
- componentInstance.guid = getIdProvider().generateUUID();
89
+ componentInstance.guid = generateDeterministicComponentGuid(obj, componentInstance);
63
90
  }
64
91
  apply(obj);
65
92
  // register the component - make sure to provide the component instance context (if assigned)
@@ -109,9 +136,9 @@ export function addComponent<T extends IComponent>(obj: Object3D, componentInsta
109
136
  // componentInstance.__internalEnable();
110
137
  // componentInstance.transform = obj;
111
138
  if (componentInstance.guid === undefined || componentInstance.guid === "invalid") {
112
- componentInstance.guid = getIdProvider().generateUUID();
139
+ componentInstance.guid = generateDeterministicComponentGuid(obj, componentInstance);
113
140
  }
114
- if(init) componentInstance._internalInit(init);
141
+ if (init) componentInstance._internalInit(init);
115
142
  // Register the component - make sure to provide the component instance context (if assigned)
116
143
  registerComponent(componentInstance, componentInstance.context);
117
144
  return componentInstance;
@@ -144,10 +171,12 @@ function onGetComponent<T>(obj: Object3D | null | undefined, componentType: Cons
144
171
  }
145
172
  if (!(obj?.userData?.components)) return null;
146
173
  if (typeof componentType === "string") {
147
- if (!didWarnAboutComponentAccess) {
174
+ const type = TypeStore.get(componentType);
175
+ if (!didWarnAboutComponentAccess && !type) {
148
176
  didWarnAboutComponentAccess = true;
149
177
  console.warn(`Accessing components by name is not supported.\nPlease use the component type instead. This may keep working in local development but it will fail when bundling your application.\n\nYou can import other modules your main module to get access to types\nor if you use npmdefs you can make types available globally using globalThis:\nhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis`, componentType);
150
178
  }
179
+ if (type) componentType = type as Constructor<T>;
151
180
  }
152
181
 
153
182
  if (debugEnabled())
@@ -222,7 +251,7 @@ export function getComponents<T extends IComponent>(obj: Object3D, componentType
222
251
  * ```
223
252
  */
224
253
  export function getComponentInChildren<T extends IComponent>(obj: Object3D, componentType: Constructor<T>, includeInactive: boolean = false): T | null {
225
- if (includeInactive === false && obj[activeInHierarchyFieldName] === false) return null;
254
+ if (includeInactive === false && obj[activeInHierarchyFieldName] === false) return null;
226
255
  const res = getComponent(obj, componentType) as IComponent | null;
227
256
  if (includeInactive === false && (res?.enabled === false || res?.activeAndEnabled === false)) return null;
228
257
  if (res) return res as T;
@@ -335,7 +364,7 @@ export function findObjectOfType<T extends IComponent>(type: Constructor<T>, con
335
364
  if (!scene) return null;
336
365
 
337
366
  const res = getComponentInChildren(scene, type, includeInactive);
338
- if(res) return res;
367
+ if (res) return res;
339
368
  return null;
340
369
  }
341
370
 
@@ -43,6 +43,7 @@ import { patchTonemapping } from './engine_tonemapping.js';
43
43
  import type { CoroutineData, ICamera, IComponent, IContext, ILight, LoadedModel, Model, SourceIdentifier, Vec2 } from "./engine_types.js";
44
44
  import { deepClone, delay, DeviceUtilities, getParam } from './engine_utils.js';
45
45
  import type { INeedleXRSessionEventReceiver, NeedleXRSession } from './engine_xr.js';
46
+ import { PostProcessing } from './postprocessing/index.js';
46
47
  import { NeedleMenu } from './webcomponents/needle menu/needle-menu.js';
47
48
  import type { NeedleEngineWebComponent } from './webcomponents/needle-engine.js';
48
49
 
@@ -387,9 +388,11 @@ export class Context implements IContext {
387
388
  */
388
389
  renderer!: WebGLRenderer;
389
390
  /**
390
- * The effect composer can be used to render postprocessing effects. If assigned then it will automatically render the scene every frame.
391
+ * The effect composer used for rendering postprocessing effects.
392
+ * @deprecated Use `context.postprocessing.composer` instead.
391
393
  */
392
- composer: EffectComposer | ThreeEffectComposer | null = null;
394
+ get composer(): EffectComposer | ThreeEffectComposer | null { return this.postprocessing.composer; }
395
+ set composer(value: EffectComposer | ThreeEffectComposer | null) { this.postprocessing.composer = value; }
393
396
 
394
397
  // #region internal script lists
395
398
  /**
@@ -504,6 +507,8 @@ export class Context implements IContext {
504
507
  input: Input;
505
508
  /** access physics related methods (e.g. raycasting). To access the phyiscs engine use `context.physics.engine` */
506
509
  physics: Physics;
510
+ /** access postprocessing effects stack. Add/remove effects and configure adaptive performance settings */
511
+ postprocessing: PostProcessing;
507
512
  /** access networking methods (use it to send or listen to messages or join a networking backend) */
508
513
  connection: NetworkConnection;
509
514
  /** @deprecated AssetDatabase is deprecated */
@@ -564,6 +569,7 @@ export class Context implements IContext {
564
569
  this.time = new Time();
565
570
  this.input = new Input(this);
566
571
  this.physics = new Physics(this);
572
+ this.postprocessing = new PostProcessing(this);
567
573
  this.connection = new NetworkConnection(this);
568
574
  // eslint-disable-next-line @typescript-eslint/no-deprecated
569
575
  this.assets = new AssetDatabase();
@@ -1778,6 +1784,9 @@ export class Context implements IContext {
1778
1784
  if (this.renderer.toneMapping !== NoToneMapping)
1779
1785
  patchTonemapping(this);
1780
1786
 
1787
+ // Update postprocessing stack (applies dirty effects, adaptive multisampling/pixel ratio)
1788
+ this.postprocessing.update();
1789
+
1781
1790
  if (this.composer && !this.isInXR) {
1782
1791
  // if a camera is passed in we need to check if we need to update the composer's camera
1783
1792
  if (camera && "setMainCamera" in this.composer) {
@@ -8,6 +8,7 @@ import { activeInHierarchyFieldName } from "./engine_constants.js";
8
8
  import { editorGuidKeyName } from "./engine_constants.js";
9
9
  import { $isUsingInstancing, InstancingUtil } from "./engine_instancing.js";
10
10
  import { processNewScripts } from "./engine_mainloop_utils.js";
11
+ import type { syncInstantiate } from "./engine_networking_instantiate.js";
11
12
  import { InstantiateIdProvider } from "./engine_networking_instantiate.js";
12
13
  import { assign, ISerializable } from "./engine_serialization_core.js";
13
14
  import { Context, registerComponent } from "./engine_setup.js";
@@ -19,7 +20,11 @@ import { apply } from "./js-extensions/index.js";
19
20
  const debug = getParam("debuggetcomponent");
20
21
  const debugInstantiate = getParam("debuginstantiate");
21
22
 
23
+ /**
24
+ * Options for instantiating a GameObject, used in {@link instantiate} and {@link syncInstantiate}
25
+ */
22
26
  export type IInstantiateOptions = {
27
+ /** The ID provider for generating unique IDs / guids */
23
28
  idProvider?: UIDProvider;
24
29
  //** parent guid or object */
25
30
  parent?: string | Object3D;
@@ -12,6 +12,7 @@ import { patchLayers } from "./js-extensions/Layers.js";
12
12
  import { initObject3DExtensions } from "./js-extensions/Object3D.js";
13
13
  import { initVectorExtensions } from "./js-extensions/Vector.js";
14
14
  import { initWebComponents } from "./webcomponents/init.js";
15
+ import { initXR } from "./xr/init.js";
15
16
 
16
17
  let initialized = false;
17
18
 
@@ -42,4 +43,5 @@ export function initEngine() {
42
43
  initAnimationAutoplay();
43
44
  initSkyboxAttributes();
44
45
  initSceneSwitcherAttributes();
46
+ initXR();
45
47
  }
@@ -1088,7 +1088,8 @@ export class Input implements IInput {
1088
1088
  if (this.context.isInAR) return;
1089
1089
  if (this.canReceiveInput(evt) === false) return;
1090
1090
  if (evt.target instanceof HTMLElement) {
1091
- evt.target.setPointerCapture(evt.pointerId);
1091
+ try { evt.target.setPointerCapture(evt.pointerId); }
1092
+ catch { /* may fail during pointer lock */ }
1092
1093
  }
1093
1094
  const id = this.getPointerId(evt);
1094
1095
  if (debug) showBalloonMessage(`pointer down #${id}, identifier:${evt.pointerId}`);
@@ -560,25 +560,6 @@ const $savedTextureTransforms = Symbol("savedTextureTransforms");
560
560
  */
561
561
  type ObjectRenderCallback = (this: Object3D, renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, material: Material, group: Group) => void;
562
562
 
563
- /**
564
- * Collect all materials from an object and its children
565
- * @internal
566
- */
567
- function collectMaterials(object: Object3D, materials: Set<Material>): void {
568
- const obj = object as Object3D & { material?: Material | Material[] };
569
- if (obj.material) {
570
- if (Array.isArray(obj.material)) {
571
- obj.material.forEach(mat => materials.add(mat));
572
- } else {
573
- materials.add(obj.material);
574
- }
575
- }
576
-
577
- // For Groups, collect materials from children too
578
- if (object.type === "Group") {
579
- object.children.forEach(child => collectMaterials(child, materials));
580
- }
581
- }
582
563
 
583
564
  /**
584
565
  * Find property block by checking this object and parent (if parent is a Group).
@@ -626,7 +607,7 @@ const beforeRenderListTransparentChangedTransmission = new WeakMap<Object3D, num
626
607
  * @internal
627
608
  */
628
609
  const onBeforeRenderListPush = function (this: Object3D, _object: Object3D, _geometry: BufferGeometry, material: Material, _group: Group) {
629
- const block = registry.getBlock(_object);
610
+ const block = findPropertyBlockAndOwner(this)?.block;
630
611
  if (!block) {
631
612
  return;
632
613
  }
@@ -248,6 +248,7 @@ export class OwnershipModel {
248
248
  private _gainSubscription: (response: GainedOwnershipBroadcastResponse) => void;
249
249
  private _lostSubscription: (guid: string) => void;
250
250
  private _hasOwnerResponse: (response: OwnershipResponse) => void;
251
+ private _pendingOwnershipResolve: ((value: boolean) => void) | null = null;
251
252
 
252
253
  constructor(connection: NetworkConnection, guid: string) {
253
254
  this.connection = connection;
@@ -291,7 +292,6 @@ export class OwnershipModel {
291
292
  }
292
293
 
293
294
  private waitForHasOwnershipRequestResponse(res: OwnershipResponse) {
294
- // console.log(res);
295
295
  if (res.guid === this.guid) {
296
296
  if (this._isWaitingForOwnershipResponseCallback) {
297
297
  this.connection.stopListen(OwnershipEvent.ResponseHasOwner, this._isWaitingForOwnershipResponseCallback);
@@ -301,47 +301,58 @@ export class OwnershipModel {
301
301
  if (!res.value) {
302
302
  if (debugOwner)
303
303
  console.log("request ownership", this.guid)
304
- this.requestOwnership();
304
+ this.connection.send(OwnershipEvent.RequestOwnership, { guid: this.guid });
305
305
  }
306
306
  }
307
307
  }
308
308
 
309
309
 
310
+
310
311
  /**
311
312
  * Requests ownership and waits asynchronously until ownership is granted or timeout occurs.
312
313
  * @returns Promise that resolves with this OwnershipModel when ownership is gained
313
314
  * @throws Rejects with "Timeout" if ownership is not gained within ~1 second
314
315
  * @example
315
316
  * ```ts
316
- * try {
317
- * await ownership.requestOwnershipAsync();
317
+ * const owned = await ownership.requestOwnership();
318
+ * if (owned) {
318
319
  * // Ownership granted, safe to modify object
319
- * } catch(e) {
320
- * console.warn("Could not gain ownership:", e);
321
320
  * }
322
321
  * ```
323
322
  */
324
- public requestOwnershipAsync(): Promise<OwnershipModel> {
325
- return new Promise((resolve, reject) => {
326
- this.requestOwnership();
327
- let updates = 0;
328
- const waitForOwnership = () => {
329
- if (updates++ > 10) return reject("Timeout");
330
- setTimeout(() => {
331
- if (this.hasOwnership) resolve(this);
332
- else waitForOwnership();
333
- }, 100);
334
- };
335
- waitForOwnership();
323
+ /**
324
+ * Requests ownership of this object from the networking server.
325
+ * Returns a Promise that resolves with `true` when ownership is granted, or `false` on timeout/failure.
326
+ * If ownership is already held, resolves immediately with `true`.
327
+ * @returns Promise that resolves with `true` if ownership was gained, `false` otherwise
328
+ */
329
+ public requestOwnership(): Promise<boolean> {
330
+ if (this._hasOwnership) return Promise.resolve(true);
331
+
332
+ // Cancel any previous pending request
333
+ this._pendingOwnershipResolve?.(false);
334
+ this._pendingOwnershipResolve = null;
335
+
336
+ if (debugOwner) console.log("Request ownership", this.guid);
337
+ this.connection.send(OwnershipEvent.RequestOwnership, { guid: this.guid });
338
+
339
+ return new Promise((resolve) => {
340
+ this._pendingOwnershipResolve = resolve;
341
+
342
+ // Timeout after ~2 seconds
343
+ setTimeout(() => {
344
+ if (this._pendingOwnershipResolve === resolve) {
345
+ this._pendingOwnershipResolve = null;
346
+ resolve(false);
347
+ }
348
+ }, 2000);
336
349
  });
337
350
  }
338
351
 
339
352
  /**
340
- * Requests ownership of this object from the networking server.
341
- * Ownership may not be granted immediately - check `hasOwnership` property or use `requestOwnershipAsync()`.
342
- * @returns this OwnershipModel instance for method chaining
353
+ * @deprecated Use `requestOwnership()` instead for a more reliable way to gain ownership
343
354
  */
344
- public requestOwnership(): OwnershipModel {
355
+ public async requestOwnershipAsync(): Promise<OwnershipModel> {
345
356
  if (debugOwner) console.log("Request ownership", this.guid);
346
357
  this.connection.send(OwnershipEvent.RequestOwnership, { guid: this.guid });
347
358
  return this;
@@ -374,16 +385,23 @@ export class OwnershipModel {
374
385
  this.connection.stopListen(OwnershipEvent.ResponseHasOwner, this._isWaitingForOwnershipResponseCallback);
375
386
  this._isWaitingForOwnershipResponseCallback = null;
376
387
  }
388
+ // Clean up pending ownership request
389
+ this._pendingOwnershipResolve?.(false);
390
+ this._pendingOwnershipResolve = null;
377
391
  }
378
392
 
379
393
  private onGainedOwnership(res: GainedOwnershipBroadcastResponse) {
380
394
  if (res.guid === this.guid) {
381
395
  this._isOwned = true;
382
- // console.log(res.owner, connection.connectionId)
383
396
  if (this.connection.connectionId === res.owner) {
384
397
  if (debugOwner)
385
398
  console.log("GAINED OWNERSHIP", this.guid)
386
399
  this._hasOwnership = true;
400
+ // Resolve pending ownership request
401
+ if (this._pendingOwnershipResolve) {
402
+ this._pendingOwnershipResolve(true);
403
+ this._pendingOwnershipResolve = null;
404
+ }
387
405
  }
388
406
  else this._hasOwnership = false;
389
407
  }
@@ -394,6 +412,11 @@ export class OwnershipModel {
394
412
  console.log("LOST OWNERSHIP", this.guid)
395
413
  this._hasOwnership = false;
396
414
  this._isOwned = false;
415
+ // Resolve pending ownership request as failed
416
+ if (this._pendingOwnershipResolve) {
417
+ this._pendingOwnershipResolve(false);
418
+ this._pendingOwnershipResolve = null;
419
+ }
397
420
  }
398
421
  }
399
422
  }