Needle Engine

Changes between version 3.31.6 and 3.32.19-alpha
Files changed (236) hide show
  1. src/engine-schemes/vrUserStateBuffer.fbs +0 -0
  2. plugins/vite/config.js +1 -0
  3. plugins/vite/copyfiles.js +4 -1
  4. plugins/vite/defines.js +4 -1
  5. plugins/vite/dependency-watcher.js +23 -17
  6. plugins/vite/index.js +4 -0
  7. plugins/vite/meta.js +2 -0
  8. src/engine-components/export/usdz/extensions/behavior/Actions.ts +3 -2
  9. src/engine-components/AlignmentConstraint.ts +3 -2
  10. src/engine-components/Animation.ts +4 -3
  11. src/engine-components/export/usdz/extensions/Animation.ts +5 -4
  12. src/engine-components/AnimationCurve.ts +18 -2
  13. src/engine-components/AnimationUtils.ts +4 -3
  14. src/engine-components/export/usdz/utils/animationutils.ts +5 -4
  15. src/engine-components/Animator.ts +6 -5
  16. src/engine-components/AnimatorController.ts +21 -12
  17. src/engine-components/postprocessing/Effects/Antialiasing.ts +1 -0
  18. src/engine-components/api.ts +7 -9
  19. src/engine/api.ts +21 -22
  20. src/engine-components/export/usdz/extensions/behavior/AudioExtension.ts +3 -2
  21. src/engine-components/AudioListener.ts +2 -1
  22. src/engine-components/AudioSource.ts +103 -41
  23. src/engine-components/avatar/Avatar_Brain_LookAt.ts +5 -4
  24. src/engine-components/avatar/Avatar_MouthShapes.ts +4 -3
  25. src/engine-components/avatar/AvatarBlink_Simple.ts +3 -2
  26. src/engine-components/avatar/AvatarEyeLook_Rotation.ts +5 -4
  27. src/engine-components/AvatarLoader.ts +6 -5
  28. src/engine-components/AxesHelper.ts +3 -2
  29. src/engine-components/ui/BaseUIComponent.ts +27 -25
  30. src/engine-components/BasicIKConstraint.ts +3 -2
  31. src/engine-components/export/usdz/extensions/behavior/Behaviour.ts +1 -1
  32. src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts +12 -13
  33. src/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.ts +2 -2
  34. src/engine-components/postprocessing/Effects/Bloom.ts +1 -0
  35. src/engine-components/BoxHelperComponent.ts +4 -3
  36. src/engine-components/ui/Button.ts +17 -15
  37. src/engine-components/Camera.ts +12 -13
  38. src/engine-components/CameraUtils.ts +8 -7
  39. src/engine-components/ui/Canvas.ts +45 -20
  40. src/engine-components/ui/CanvasGroup.ts +3 -3
  41. src/engine-components/CharacterController.ts +5 -4
  42. src/engine-components/postprocessing/Effects/ChromaticAberration.ts +1 -0
  43. src/engine-components/Collider.ts +12 -5
  44. src/engine-components/postprocessing/Effects/ColorAdjustments.ts +2 -1
  45. src/engine-components/Component.ts +114 -21
  46. src/engine-components/codegen/components.ts +10 -15
  47. src/engine-components/ContactShadows.ts +3 -3
  48. src/engine/debug/debug_console.ts +8 -4
  49. src/engine/debug/debug_overlay.ts +8 -7
  50. src/engine/debug/debug.ts +6 -2
  51. src/engine-components/DeleteBox.ts +1 -0
  52. src/engine-components/postprocessing/Effects/DepthOfField.ts +2 -1
  53. src/engine-components/DeviceFlag.ts +1 -1
  54. src/engine-components/DragControls.ts +949 -182
  55. src/engine-components/DropListener.ts +5 -4
  56. src/engine-components/Duplicatable.ts +74 -91
  57. src/engine/engine_addressables.ts +7 -6
  58. src/engine/engine_assetdatabase.ts +2 -1
  59. src/engine/engine_camera.ts +2 -1
  60. src/engine/engine_components.ts +7 -6
  61. src/engine/engine_constants.ts +5 -2
  62. src/engine/engine_context.ts +112 -70
  63. src/engine/engine_create_objects.ts +13 -1
  64. src/engine/engine_element_loading.ts +41 -16
  65. src/engine/engine_element_overlay.ts +17 -0
  66. src/engine/engine_element.ts +66 -13
  67. src/engine/engine_gameobject.ts +56 -44
  68. src/engine/engine_gizmos.ts +68 -26
  69. src/engine/engine_gltf_builtin_components.ts +11 -9
  70. src/engine/engine_gltf.ts +4 -3
  71. src/engine/engine_hot_reload.ts +2 -2
  72. src/engine/engine_input.ts +480 -185
  73. src/engine/engine_license.ts +26 -12
  74. src/engine/engine_lifecycle_api.ts +28 -4
  75. src/engine/engine_lifecycle_functions_internal.ts +2 -2
  76. src/engine/engine_lightdata.ts +4 -3
  77. src/engine/engine_loaders.ts +3 -3
  78. src/engine/engine_mainloop_utils.ts +34 -7
  79. src/engine/engine_networking_auto.ts +1 -1
  80. src/engine/engine_networking_files_default_components.ts +2 -1
  81. src/engine/engine_networking_files.ts +8 -7
  82. src/engine/engine_networking_instantiate.ts +17 -12
  83. src/engine/engine_networking_peer.ts +2 -1
  84. src/engine/engine_networking_streams.ts +8 -7
  85. src/engine/engine_networking.ts +12 -8
  86. src/engine/engine_physics_rapier.ts +62 -47
  87. src/engine/engine_physics.ts +23 -18
  88. src/engine/engine_playerview.ts +2 -1
  89. src/engine/engine_scenelighting.ts +5 -4
  90. src/engine/engine_scenetools.ts +9 -8
  91. src/engine/engine_serialization_builtin_serializer.ts +18 -8
  92. src/engine/engine_serialization_core.ts +8 -8
  93. src/engine/engine_serialization.ts +4 -5
  94. src/engine/engine_shaders.ts +4 -3
  95. src/engine/engine_texture.ts +2 -1
  96. src/engine/engine_three_utils.ts +18 -4
  97. src/engine/engine_time.ts +4 -3
  98. src/engine/engine_types.ts +27 -4
  99. src/engine/engine_util_decorator.ts +3 -2
  100. src/engine/engine_utils_screenshot.ts +2 -1
  101. src/engine/engine_utils.ts +70 -6
  102. src/engine/engine.ts +3 -3
  103. src/engine-components/ui/EventSystem.ts +281 -185
  104. src/engine-components/EventTrigger.ts +2 -2
  105. src/engine/extensions/EXT_texture_exr.ts +3 -2
  106. src/engine/extensions/extension_utils.ts +2 -1
  107. src/engine-components/export/usdz/Extension.ts +2 -1
  108. src/engine/extensions/extensions.ts +12 -11
  109. src/engine-components/js-extensions/ExtensionUtils.ts +1 -0
  110. src/engine-components/FlyControls.ts +2 -1
  111. src/engine-components/Fog.ts +2 -1
  112. src/engine-components/Gizmos.ts +5 -4
  113. src/engine-components/export/gltf/GltfExport.ts +6 -6
  114. src/engine-components/ui/Graphic.ts +8 -7
  115. src/engine-components/GridHelper.ts +4 -3
  116. src/engine-components/GroundProjection.ts +11 -5
  117. src/engine-components/ui/Image.ts +2 -1
  118. src/engine-components/export/usdz/index.ts +3 -3
  119. src/engine-components/postprocessing/index.ts +2 -2
  120. src/engine-components/timeline/index.ts +2 -2
  121. src/engine-components/webxr/index.ts +2 -3
  122. src/engine/extensions/index.ts +2 -2
  123. src/engine-components/ui/InputField.ts +4 -4
  124. src/engine-components/Interactable.ts +6 -14
  125. src/engine-components/Joints.ts +1 -0
  126. src/engine-components/ui/Layout.ts +3 -3
  127. src/engine-components/Light.ts +10 -13
  128. src/engine-components/LODGroup.ts +5 -4
  129. src/engine-components/debug/LogStats.ts +1 -1
  130. src/engine-components/utils/LookAt.ts +4 -4
  131. src/engine-components/LookAtConstraint.ts +3 -2
  132. src/engine/extensions/NEEDLE_animator_controller_model.ts +3 -2
  133. src/engine/extensions/NEEDLE_components.ts +7 -6
  134. src/engine/extensions/NEEDLE_gameobject_data.ts +3 -3
  135. src/engine/extensions/NEEDLE_lighting_settings.ts +7 -6
  136. src/engine/extensions/NEEDLE_lightmaps.ts +8 -7
  137. src/engine/extensions/NEEDLE_persistent_assets.ts +3 -2
  138. src/engine/extensions/NEEDLE_progressive.ts +5 -3
  139. src/engine/extensions/NEEDLE_render_objects.ts +18 -18
  140. src/engine/extensions/NEEDLE_techniques_webgl.ts +8 -5
  141. src/needle-engine.ts +0 -3
  142. src/engine-components/NestedGltf.ts +4 -4
  143. src/engine-components/Networking.ts +1 -1
  144. src/engine-components/js-extensions/Object3D.ts +11 -12
  145. src/engine-components/OffsetConstraint.ts +4 -3
  146. src/engine-components/utils/OpenURL.ts +8 -40
  147. src/engine-components/OrbitControls.ts +15 -15
  148. src/engine-components/ui/Outline.ts +3 -2
  149. src/engine-components/ParticleSystem.ts +39 -34
  150. src/engine-components/ParticleSystemModules.ts +205 -24
  151. src/engine-components/ParticleSystemSubEmitter.ts +4 -3
  152. src/engine-components/postprocessing/Effects/Pixelation.ts +4 -3
  153. src/engine-components/timeline/PlayableDirector.ts +10 -9
  154. src/engine-components/PlayerColor.ts +19 -14
  155. src/engine-components-experimental/networking/PlayerSync.ts +119 -26
  156. src/engine-components/ui/PointerEvents.ts +118 -30
  157. src/engine-components/postprocessing/PostProcessingEffect.ts +5 -4
  158. src/engine-components/postprocessing/PostProcessingHandler.ts +5 -4
  159. src/engine-components-experimental/Presentation.ts +1 -1
  160. src/engine-components/ui/Raycaster.ts +27 -8
  161. src/engine-components/ui/RaycastUtils.ts +2 -1
  162. src/engine-components/ui/RectTransform.ts +6 -5
  163. src/engine-components/ReflectionProbe.ts +3 -2
  164. src/engine/codegen/register_types.ts +17 -28
  165. src/engine-components/Renderer.ts +60 -38
  166. src/engine-components/RendererLightmap.ts +3 -2
  167. src/engine-components/js-extensions/RGBAColor.ts +2 -1
  168. src/engine-components/RigidBody.ts +15 -6
  169. src/engine-components/SceneSwitcher.ts +13 -12
  170. src/engine-schemes/schemes.ts +2 -1
  171. src/engine-components/ScreenCapture.ts +9 -8
  172. src/engine-components/postprocessing/Effects/ScreenspaceAmbientOcclusion.ts +1 -0
  173. src/engine-components/postprocessing/Effects/ScreenspaceAmbientOcclusionN8.ts +3 -2
  174. src/engine-components/ShadowCatcher.ts +4 -3
  175. src/engine-components/timeline/SignalAsset.ts +2 -2
  176. src/engine-components/Skybox.ts +10 -9
  177. src/engine-components/SmoothFollow.ts +5 -4
  178. src/engine-components/ui/SpatialHtml.ts +1 -0
  179. src/engine-components/SpatialTrigger.ts +4 -3
  180. src/engine-components/SpectatorCamera.ts +23 -33
  181. src/engine-components/SpriteRenderer.ts +4 -3
  182. src/engine-components/SyncedCamera.ts +13 -13
  183. src/engine-components/SyncedRoom.ts +2 -2
  184. src/engine-components/SyncedTransform.ts +25 -7
  185. src/engine/tests/test_utils.ts +1 -1
  186. src/engine-components/TestRunner.ts +6 -5
  187. src/engine-components/ui/Text.ts +9 -8
  188. src/engine-components/export/usdz/ThreeUSDZExporter.ts +36 -33
  189. src/engine-components/postprocessing/Effects/TiltShiftEffect.ts +4 -3
  190. src/engine-components/timeline/TimelineTracks.ts +60 -22
  191. src/engine-components/postprocessing/Effects/Tonemapping.ts +1 -0
  192. src/engine-components/TransformGizmo.ts +6 -5
  193. src/engine/extensions/usage_tracker.ts +2 -1
  194. src/engine-components/export/usdz/USDZExporter.ts +41 -94
  195. src/engine-components/export/usdz/extensions/USDZText.ts +6 -5
  196. src/engine-components/export/usdz/extensions/USDZUI.ts +13 -9
  197. plugins/types/userconfig.d.ts +3 -0
  198. src/engine-components/ui/Utils.ts +4 -2
  199. src/engine-components/js-extensions/Vector.ts +2 -1
  200. src/engine-components/VideoPlayer.ts +12 -9
  201. src/engine-components/postprocessing/Effects/Vignette.ts +3 -2
  202. src/engine-components/Voip.ts +6 -5
  203. src/engine-components/postprocessing/Volume.ts +7 -6
  204. src/engine-schemes/vr-user-state-buffer.ts +37 -30
  205. src/engine-components/webxr/WebARCameraBackground.ts +46 -53
  206. src/engine-components/webxr/WebARSessionRoot.ts +390 -164
  207. src/engine-components/webxr/WebXR.ts +211 -672
  208. src/engine-components/webxr/WebXRAvatar.ts +10 -300
  209. src/engine-components/webxr/WebXRController.ts +0 -1168
  210. src/engine-components/webxr/WebXRGrabRendering.ts +0 -151
  211. src/engine-components/webxr/WebXRImageTracking.ts +70 -78
  212. src/engine-components/webxr/WebXRPlaneTracking.ts +56 -49
  213. src/engine-components/webxr/WebXRRig.ts +45 -8
  214. src/engine-components/webxr/WebXRSync.ts +0 -463
  215. src/engine-components/XRFlag.ts +0 -139
  216. plugins/common/buildinfo.js +56 -0
  217. plugins/vite/buildinfo.js +23 -0
  218. src/engine-schemes/README.md +2 -0
  219. src/engine-components/webxr/Avatar.ts +232 -0
  220. src/engine/engine_xr.ts +2 -0
  221. src/engine/xr/index.ts +5 -0
  222. src/engine/xr/internal.ts +35 -0
  223. src/engine/xr/NeedleXRController.ts +785 -0
  224. src/engine/xr/NeedleXRSession.ts +1290 -0
  225. src/engine/xr/NeedleXRSync.ts +221 -0
  226. src/engine/xr/SceneTransition.ts +79 -0
  227. src/engine-components/webxr/TeleportTarget.ts +9 -0
  228. src/engine/xr/TempXRContext.ts +183 -0
  229. src/engine-components/webxr/types.ts +4 -0
  230. src/engine/xr/utils.ts +40 -0
  231. src/engine-components/webxr/WebXRButtons.ts +348 -0
  232. src/engine-components/webxr/controllers/XRControllerFollow.ts +67 -0
  233. src/engine-components/webxr/controllers/XRControllerModel.ts +307 -0
  234. src/engine-components/webxr/controllers/XRControllerMovement.ts +340 -0
  235. src/engine-components/webxr/XRFlag.ts +143 -0
  236. src/engine/xr/XRRig.ts +9 -0
src/engine-schemes/vrUserStateBuffer.fbs CHANGED
File without changes
plugins/vite/config.js CHANGED
@@ -68,6 +68,7 @@
68
68
  return "assets";
69
69
  }
70
70
 
71
+ /** @returns the fullpath of the build */
71
72
  export function getOutputDirectory() {
72
73
  const projectConfig = tryLoadProjectConfig();
73
74
  return process.cwd() + "/" + (projectConfig?.buildDirectory || "dist");
plugins/vite/copyfiles.js CHANGED
@@ -36,7 +36,10 @@
36
36
  const needleConfig = tryLoadProjectConfig();
37
37
  if (needleConfig) {
38
38
  assetsDirName = needleConfig.assetsDirectory;
39
- while(assetsDirName.startsWith('/')) assetsDirName = assetsDirName.substring(1);
39
+ while (assetsDirName.startsWith('/')) assetsDirName = assetsDirName.substring(1);
40
+
41
+ if (needleConfig.buildDirectory)
42
+ outdirName = needleConfig.buildDirectory;
40
43
  }
41
44
 
42
45
  if (copyIncludesFromEngine !== false) {
plugins/vite/defines.js CHANGED
@@ -26,7 +26,7 @@
26
26
  // console.log("Update vite defines -------------------------------------------");
27
27
  if (!viteConfig.define) viteConfig.define = {};
28
28
  const version = tryGetNeedleEngineVersion();
29
- console.log("Needle Engine Version:", version, needleEngineConfig?.generator);
29
+ console.log("Needle Engine Version: " + version, needleEngineConfig?.generator ?? "(unknown generator)");
30
30
  if (version)
31
31
  viteConfig.define.NEEDLE_ENGINE_VERSION = "\"" + version + "\"";
32
32
  if (needleEngineConfig)
@@ -42,6 +42,9 @@
42
42
  if (viteConfig.define.NEEDLE_USE_RAPIER === undefined) {
43
43
  viteConfig.define.NEEDLE_USE_RAPIER = useRapier;
44
44
  }
45
+
46
+ // this gives a timestamp containing the timezone
47
+ viteConfig.define.NEEDLE_PROJECT_BUILD_TIME = "\"" + new Date().toString() + "\"";
45
48
  }
46
49
  }
47
50
  }
plugins/vite/dependency-watcher.js CHANGED
@@ -39,11 +39,14 @@
39
39
  });
40
40
  }
41
41
 
42
- function triggerReloadOnClients() {
43
- log("Triggering reload on clients (todo)", currentClients.size)
44
- // for (const client of currentClients) {
45
- // client.send(JSON.stringify({ type: "full-reload" }));
46
- // }
42
+ async function triggerReloadOnClients() {
43
+ log(`Triggering reload on ${currentClients.size} clients...`)
44
+ for (const client of currentClients) {
45
+ client.send(JSON.stringify({ type: "full-reload" }));
46
+ }
47
+ return new Promise((resolve) => {
48
+ setTimeout(resolve, 100);
49
+ });
47
50
  }
48
51
 
49
52
 
@@ -81,9 +84,6 @@
81
84
  modified = true;
82
85
  }
83
86
  if (modified || requireInstall) {
84
- if (modified) {
85
- log("package.json has changed. Require install?", requireInstall)
86
- }
87
87
 
88
88
  let requireReload = false;
89
89
  if (!requireInstall) {
@@ -95,7 +95,7 @@
95
95
  if (newPackageJson.dependencies) {
96
96
  for (const key in newPackageJson.dependencies) {
97
97
  if (packageJson.dependencies[key] !== newPackageJson.dependencies[key] && newPackageJson.dependencies[key] !== undefined) {
98
- log("Dependency added", key)
98
+ log("Detected new dependency: " + key)
99
99
  requireReload = true;
100
100
  requireInstall = true;
101
101
  }
@@ -104,13 +104,16 @@
104
104
  if (packageJson.devDependencies) {
105
105
  for (const key in packageJson.devDependencies) {
106
106
  if (packageJson.devDependencies[key] !== newPackageJson.devDependencies[key] && newPackageJson.devDependencies[key] !== undefined) {
107
- log("DevDependency added", key)
107
+ log("Detected new devDependency: " + key)
108
108
  requireReload = true;
109
109
  requireInstall = true;
110
110
  }
111
111
  }
112
112
  }
113
113
 
114
+ if (modified) {
115
+ log("package.json has changed. Require install: " + (requireInstall ? "yes" : "no"))
116
+ }
114
117
 
115
118
  packageJsonSize = packageJsonStat.size;
116
119
  lastEditTime = packageJsonStat.mtime;
@@ -119,7 +122,7 @@
119
122
  restart(server, projectDir, cachePath);
120
123
  }
121
124
  }
122
- }, 1000);
125
+ }, 2000);
123
126
  }
124
127
 
125
128
  function testIfInstallIsRequired(projectDir, packageJson) {
@@ -142,7 +145,7 @@
142
145
  }
143
146
  }
144
147
  }
145
- log("Dependency not installed", key)
148
+ log("Dependency not installed: " + key)
146
149
  return true;
147
150
  }
148
151
  else {
@@ -161,9 +164,11 @@
161
164
  const isRange = expectedVersion.includes("^") || expectedVersion.includes(">") || expectedVersion.includes("<");
162
165
  if (!isRange) {
163
166
  const packageJsonPath = path.join(depPath, "package.json");
167
+ /** @type {String} */
164
168
  const installedVersion = JSON.parse(readFileSync(packageJsonPath, "utf8")).version;
165
- if (expectedVersion !== installedVersion) {
166
- log(`Dependency ${key} is installed but version is not correct. Expected ${expectedVersion} but got ${installedVersion}`)
169
+ // fix check for cases where the version contains a alias e.g. npm:[email protected]
170
+ if (expectedVersion.trimEnd().endsWith(installedVersion.trim()) == false) {
171
+ log(`Dependency ${key} is installed but version is not the right one. Expected \"${expectedVersion}/" but got \"${installedVersion}\"`)
167
172
  return true;
168
173
  }
169
174
  }
@@ -194,13 +199,13 @@
194
199
  }
195
200
 
196
201
  if (id !== restartId) return;
197
- if (Date.now() - lastRestartTime < 1000) return;
202
+ if (Date.now() - lastRestartTime < 3000) return;
198
203
  log("Restarting server...")
199
204
  lastRestartTime = Date.now();
200
205
  requireInstall = false;
201
206
  if (existsSync(cachePath))
202
207
  rmSync(cachePath, { recursive: true, force: true });
203
- triggerReloadOnClients();
208
+ await triggerReloadOnClients();
204
209
 
205
210
  // touch vite config to trigger reload
206
211
  // const viteConfigPath = path.join(projectDir, "vite.config.js");
@@ -212,8 +217,9 @@
212
217
  // }
213
218
 
214
219
  // check if server is running
215
- if (server.httpServer.listening)
220
+ if (server.httpServer.listening) {
216
221
  server.restart();
222
+ }
217
223
  isRunningRestart = false;
218
224
  console.log("-----------------------------------------------")
219
225
  }
plugins/vite/index.js CHANGED
@@ -42,6 +42,7 @@
42
42
 
43
43
  import { vite_4_4_hack } from "./vite-4.4-hack.js";
44
44
  import { needleImportsLogger } from "./imports-logger.js";
45
+ import { needleBuildInfo } from "./buildinfo.js";
45
46
 
46
47
 
47
48
  export * from "./gzip.js";
@@ -57,6 +58,8 @@
57
58
  */
58
59
  export const needlePlugins = async (command, config, userSettings) => {
59
60
 
61
+ if(!config) config = {}
62
+
60
63
  // ensure we have user settings initialized with defaults
61
64
  userSettings = { ...defaultUserSettings, ...userSettings }
62
65
  const array = [
@@ -67,6 +70,7 @@
67
70
  needlePoster(command, config, userSettings),
68
71
  needleReload(command, config, userSettings),
69
72
  needleBuild(command, config, userSettings),
73
+ needleBuildInfo(command, config, userSettings),
70
74
  needleCopyFiles(command, config, userSettings),
71
75
  needleTransformCodegen(command, config, userSettings),
72
76
  needleDrop(command, config, userSettings),
plugins/vite/meta.js CHANGED
@@ -109,6 +109,8 @@
109
109
  }
110
110
  else console.log("WARN: could not find needle engine package.json")
111
111
 
112
+ tags.push({ tag: 'meta', attrs: { name: 'needle:buildtime', content: new Date().toISOString() } });
113
+
112
114
  return { html, tags }
113
115
  },
114
116
  }
src/engine-components/export/usdz/extensions/behavior/Actions.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { Object3D, Matrix4, Material, BufferGeometry } from "three";
1
+ import { BufferGeometry,Material, Matrix4, Object3D } from "three";
2
+
3
+ import { USDDocument,USDObject } from "../../ThreeUSDZExporter.js";
2
4
  import { ActionBuilder, ActionModel } from "./BehavioursBuilder.js";
3
- import { USDObject, USDDocument } from "../../ThreeUSDZExporter.js";
4
5
 
5
6
  export abstract class DocumentAction {
6
7
 
src/engine-components/AlignmentConstraint.ts CHANGED
@@ -1,7 +1,8 @@
1
- import { Behaviour, GameObject } from "./Component.js";
2
- import * as utils from "./../engine/engine_three_utils.js";
3
1
  import { Vector3 } from "three";
2
+
4
3
  import { serializable } from "../engine/engine_serialization_decorator.js";
4
+ import * as utils from "./../engine/engine_three_utils.js";
5
+ import { Behaviour, GameObject } from "./Component.js";
5
6
 
6
7
  export class AlignmentConstraint extends Behaviour {
7
8
 
src/engine-components/Animation.ts CHANGED
@@ -1,10 +1,11 @@
1
- import { Behaviour } from "./Component.js";
2
1
  import { AnimationAction, AnimationClip, AnimationMixer, LoopOnce, LoopRepeat } from "three";
3
- import { MixerEvent } from "./Animator.js";
2
+
3
+ import { Mathf } from "../engine/engine_math.js";
4
4
  import { serializable } from "../engine/engine_serialization_decorator.js";
5
- import { Mathf } from "../engine/engine_math.js";
6
5
  import type { Vec2 } from "../engine/engine_types.js";
7
6
  import { getParam } from "../engine/engine_utils.js";
7
+ import { MixerEvent } from "./Animator.js";
8
+ import { Behaviour } from "./Component.js";
8
9
 
9
10
  const debug = getParam("debuganimation");
10
11
 
src/engine-components/export/usdz/extensions/Animation.ts CHANGED
@@ -1,9 +1,10 @@
1
+ import { AnimationClip, Bone,Interpolant, KeyframeTrack, Matrix4, Object3D, PropertyBinding, Quaternion, Vector3 } from "three";
2
+
3
+ import { getParam } from "../../../../engine/engine_utils.js";
4
+ import { Animator } from "../../../Animator.js";
1
5
  import { GameObject } from "../../../Component.js";
2
- import { getParam } from "../../../../engine/engine_utils.js";
3
- import { USDObject, buildMatrix, findStructuralNodesInBoneHierarchy, usdNumberFormatting as fn, getPathToSkeleton } from "../ThreeUSDZExporter.js";
4
6
  import type { IUSDExporterExtension } from "../Extension.js";
5
- import { Object3D, Matrix4, Vector3, Quaternion, Interpolant, AnimationClip, KeyframeTrack, PropertyBinding, Bone } from "three";
6
- import { Animator } from "../../../Animator.js";
7
+ import { buildMatrix, findStructuralNodesInBoneHierarchy, getPathToSkeleton,usdNumberFormatting as fn, USDObject } from "../ThreeUSDZExporter.js";
7
8
 
8
9
  const debug = getParam("debugusdzanimation");
9
10
  const debugSerialization = getParam("debugusdzanimationserialization");
src/engine-components/AnimationCurve.ts CHANGED
@@ -23,6 +23,22 @@
23
23
  @serializable(Keyframe)
24
24
  keys!: Array<Keyframe>;
25
25
 
26
+ clone() {
27
+ const curve = new AnimationCurve();
28
+ curve.keys = this.keys?.map(k => {
29
+ const key = new Keyframe();
30
+ key.time = k.time;
31
+ key.value = k.value;
32
+ key.inTangent = k.inTangent;
33
+ key.inWeight = k.inWeight;
34
+ key.outTangent = k.outTangent;
35
+ key.outWeight = k.outWeight;
36
+ key.weightedMode = k.weightedMode;
37
+ return key;
38
+ }) || [];
39
+ return curve;
40
+ }
41
+
26
42
  get duration(): number {
27
43
  if (!this.keys || this.keys.length == 0) return 0;
28
44
  return this.keys[this.keys.length - 1].time;
@@ -38,9 +54,9 @@
38
54
  for (let i = 0; i < this.keys.length; i++) {
39
55
  const kf = this.keys[i];
40
56
  if (kf.time <= time) {
41
- const hasNextKeyframe = i+1 < this.keys.length;
57
+ const hasNextKeyframe = i + 1 < this.keys.length;
42
58
  if (hasNextKeyframe) {
43
- const nextKf = this.keys[i+1];
59
+ const nextKf = this.keys[i + 1];
44
60
  // if the next
45
61
  if (nextKf.time < time) continue;
46
62
  // tangents are set to Infinity if interpolation is set to constant - in that case we should always return the floored value
src/engine-components/AnimationUtils.ts CHANGED
@@ -1,11 +1,12 @@
1
+ import { Object3D } from "three";
1
2
  import type { GLTF } from "three/examples/jsm/loaders/GLTFLoader.js";
3
+
4
+ import { addNewComponent } from "../engine/engine_components.js";
2
5
  import { ContextEvent, ContextRegistry } from "../engine/engine_context_registry.js";
3
- import { addNewComponent } from "../engine/engine_components.js";
6
+ import { Animation } from "./Animation.js";
4
7
  import { Animator } from "./Animator.js";
5
- import { Animation } from "./Animation.js";
6
8
  import { GameObject } from "./Component.js";
7
9
  import { PlayableDirector } from "./timeline/PlayableDirector.js";
8
- import { Object3D } from "three";
9
10
 
10
11
 
11
12
  const $objectAnimationKey = Symbol("objectIsAnimatedData");
src/engine-components/export/usdz/utils/animationutils.ts CHANGED
@@ -1,9 +1,10 @@
1
+ import { AnimationClip,Object3D } from "three";
2
+
3
+ import { getParam } from "../../../../engine/engine_utils.js";
4
+ import { Animation } from "../../../Animation.js";
1
5
  import { Animator } from "../../../Animator.js";
2
- import { Animation } from "../../../Animation.js";
3
- import { Object3D, AnimationClip } from "three";
6
+ import { Behaviour, GameObject } from "../../../Component.js";
4
7
  import { AnimationExtension } from "../extensions/Animation.js";
5
- import { Behaviour, GameObject } from "../../../Component.js";
6
- import { getParam } from "../../../../engine/engine_utils.js";
7
8
  import { PlayAnimationOnClick } from "../extensions/behavior/BehaviourComponents.js";
8
9
 
9
10
  const debug = getParam("debugusdz");
src/engine-components/Animator.ts CHANGED
@@ -1,11 +1,12 @@
1
- import { Behaviour } from "./Component.js";
2
- import type { AnimationActionLoopStyles, AnimationAction, AnimationMixer } from "three";
1
+ import type { AnimationAction, AnimationActionLoopStyles, AnimationMixer } from "three";
2
+
3
+ import { Mathf } from "../engine/engine_math.js";
4
+ import { serializable } from "../engine/engine_serialization_decorator.js";
3
5
  import { getParam } from "../engine/engine_utils.js";
4
6
  import type { AnimatorControllerModel } from "../engine/extensions/NEEDLE_animator_controller_model.js";
7
+ import { getObjectAnimated } from "./AnimationUtils.js";
5
8
  import { AnimatorController } from "./AnimatorController.js";
6
- import { serializable } from "../engine/engine_serialization_decorator.js";
7
- import { Mathf } from "../engine/engine_math.js";
8
- import { getObjectAnimated } from "./AnimationUtils.js";
9
+ import { Behaviour } from "./Component.js";
9
10
 
10
11
  const debug = getParam("debuganimator");
11
12
 
src/engine-components/AnimatorController.ts CHANGED
@@ -1,15 +1,16 @@
1
- import { Animator } from "./Animator.js";
2
- import type { AnimatorControllerModel, Condition, State, Transition } from "../engine/extensions/NEEDLE_animator_controller_model.js";
3
- import { AnimatorConditionMode, AnimatorControllerParameterType, AnimatorStateInfo, createMotion, StateMachineBehaviour } from "../engine/extensions/NEEDLE_animator_controller_model.js";
4
1
  import { AnimationAction, AnimationClip, AnimationMixer, AxesHelper, Euler, KeyframeTrack, LoopOnce, Object3D, Quaternion, Vector3 } from "three";
5
- import { deepClone, getParam } from "../engine/engine_utils.js";
2
+
3
+ import { isDevEnvironment } from "../engine/debug/index.js";
4
+ import { Mathf } from "../engine/engine_math.js";
5
+ import { InstantiateIdProvider } from "../engine/engine_networking_instantiate.js";
6
+ import { assign,SerializationContext, TypeSerializer } from "../engine/engine_serialization_core.js";
6
7
  import { Context } from "../engine/engine_setup.js";
8
+ import { isAnimationAction } from "../engine/engine_three_utils.js";
7
9
  import { TypeStore } from "../engine/engine_typestore.js";
8
- import { SerializationContext, TypeSerializer, assign } from "../engine/engine_serialization_core.js";
9
- import { Mathf } from "../engine/engine_math.js";
10
- import { isAnimationAction } from "../engine/engine_three_utils.js";
11
- import { isDevEnvironment } from "../engine/debug/index.js";
12
- import { InstantiateIdProvider } from "../engine/engine_networking_instantiate.js";
10
+ import { deepClone, getParam } from "../engine/engine_utils.js";
11
+ import type { AnimatorControllerModel, Condition, State, Transition } from "../engine/extensions/NEEDLE_animator_controller_model.js";
12
+ import { AnimatorConditionMode, AnimatorControllerParameterType, AnimatorStateInfo, createMotion, StateMachineBehaviour } from "../engine/extensions/NEEDLE_animator_controller_model.js";
13
+ import { Animator } from "./Animator.js";
13
14
 
14
15
  const debug = getParam("debuganimatorcontroller");
15
16
  const debugRootMotion = getParam("debugrootmotion");
@@ -212,6 +213,7 @@
212
213
  console.warn("AnimatorController has not been resolved, can not create model from string", this.model);
213
214
  return null;
214
215
  }
216
+ if (debug) console.warn("AnimatorController clone()", this.model);
215
217
  // clone runtime controller but dont clone clip or action
216
218
  const clonedModel = deepClone(this.model, (_owner, _key, _value) => {
217
219
  if (_value === null || _value === undefined) return true;
@@ -224,6 +226,8 @@
224
226
  }
225
227
  // dont clone AnimationClip
226
228
  if (_value["tracks"] !== undefined) return false;
229
+ // when assigned __concreteInstance during serialization
230
+ if (_value instanceof AnimatorController) return false;
227
231
  return true;
228
232
  }) as AnimatorControllerModel;
229
233
  console.assert(clonedModel !== this.model);
@@ -581,7 +585,7 @@
581
585
  }
582
586
 
583
587
  private createActions(_animator: Animator) {
584
- // console.trace(this.model, _animator);
588
+ if (debug) console.log("AnimatorController createActions", this.model);
585
589
  for (const layer of this.model.layers) {
586
590
  const sm = layer.stateMachine;
587
591
  for (let index = 0; index < sm.states.length; index++) {
@@ -608,8 +612,13 @@
608
612
  if (this.animator && state.motion.clips) {
609
613
  // TODO: we have to compare by name because on instantiate we clone objects but not the node object
610
614
  const mapping = state.motion.clips?.find(e => e.node.name === this.animator?.gameObject?.name);
611
- // console.log(state.name, mapping?.clip);
612
- state.motion.clip = mapping?.clip;
615
+ if (!mapping) {
616
+ if (debug || isDevEnvironment()) {
617
+ console.warn("Could not find clip for animator \"" + this.animator?.gameObject?.name + "\"", state.motion.clips.map(c => c.node.name));
618
+ }
619
+ }
620
+ else
621
+ state.motion.clip = mapping.clip;
613
622
  }
614
623
 
615
624
  // ensure we have a clip to blend to
src/engine-components/postprocessing/Effects/Antialiasing.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { EdgeDetectionMode, SMAAEffect, SMAAPreset } from "postprocessing";
2
+
2
3
  import { serializable } from "../../../engine/engine_serialization.js";
3
4
  import { type EffectProviderResult, PostProcessingEffect } from "../PostProcessingEffect.js";
4
5
  import { VolumeParameter } from "../VolumeParameter.js";
src/engine-components/api.ts CHANGED
@@ -1,22 +1,20 @@
1
+ export * from "./codegen/components.js";
1
2
  export { Behaviour, Component, GameObject } from "./Component.js"
2
- export * from "./codegen/components.js";
3
3
 
4
4
  // We dont want to export everything in the extensions
5
+ export { ClearFlags } from "./Camera.js"
6
+ export * from "./export/index.js"
7
+ export * from "./js-extensions/Object3D.js";
5
8
  export * from "./js-extensions/RGBAColor.js";
6
- export * from "./js-extensions/Object3D.js";
7
- export * from "./XRFlag.js"
8
-
9
- export * from "./export/index.js"
10
9
  export * from "./postprocessing/index.js"
10
+ export { type ISceneEventListener } from "./SceneSwitcher.js";
11
11
  export * from "./timeline/index.js"
12
12
  export * from "./ui/index.js"
13
13
  export * from "./webxr/index.js"
14
+ export * from "./webxr/XRFlag.js"
14
15
 
15
- export { ClearFlags } from "./Camera.js"
16
- export { type ISceneEventListener } from "./SceneSwitcher.js";
17
-
18
16
  import "./CameraUtils.js"
19
17
  import "./AnimationUtils.js"
20
18
 
21
19
  export { ParticleSystemBaseBehaviour, type QParticle, type QParticleBehaviour } from "./ParticleSystem.js"
22
-
20
+ export { ParticleSystemShapeType } from "./ParticleSystemModules.js"
src/engine/api.ts CHANGED
@@ -1,42 +1,42 @@
1
1
 
2
- export * from "./extensions/index.js";
2
+ export * from "./debug/index.js";
3
3
  export * from "./engine_addressables.js";
4
4
  export * from "./engine_application.js";
5
5
  export * from "./engine_assetdatabase.js";
6
- export * from "./engine_create_objects.js";
7
- export * from "./engine_components_internal.js";
8
6
  export * from "./engine_components.js";
9
7
  export * from "./engine_components_internal.js";
8
+ export * from "./engine_components_internal.js";
9
+ export * from "./engine_constants.js";
10
+ export * from "./engine_context.js";
10
11
  export * from "./engine_context_registry.js";
11
- export * from "./engine_context.js";
12
12
  export * from "./engine_coroutine.js"
13
- export * from "./engine_constants.js";
14
- export * from "./debug/index.js";
13
+ export * from "./engine_create_objects.js";
15
14
  export * from "./engine_element.js";
15
+ export * from "./engine_element_attributes.js";
16
16
  export * from "./engine_element_loading.js";
17
- export * from "./engine_element_attributes.js";
17
+ export * from "./engine_gameobject.js";
18
18
  export { Gizmos } from "./engine_gizmos.js"
19
19
  export * from "./engine_gltf.js";
20
20
  export * from "./engine_hot_reload.js";
21
- export * from "./engine_gameobject.js";
21
+ export * from "./engine_input.js";
22
+ export { InstancingUtil } from "./engine_instancing.js";
23
+ export { hasIndieLicense,hasProLicense } from "./engine_license.js";
24
+ export * from "./engine_lifecycle_api.js";
25
+ export * from "./engine_math.js";
22
26
  export * from "./engine_networking.js";
23
- export * from "./engine_networking_types.js";
24
27
  export { syncField } from "./engine_networking_auto.js";
25
28
  export * from "./engine_networking_files.js";
26
29
  export * from "./engine_networking_instantiate.js";
30
+ export * from "./engine_networking_peer.js";
27
31
  export * from "./engine_networking_streams.js";
32
+ export * from "./engine_networking_types.js";
28
33
  export * from "./engine_networking_utils.js";
29
- export * from "./engine_networking_peer.js";
30
34
  export * from "./engine_patcher.js";
31
- export * from "./engine_playerview.js";
32
35
  export * from "./engine_physics.js";
33
36
  export * from "./engine_physics.types.js";
34
37
  export * from "./engine_physics_rapier.js";
38
+ export * from "./engine_playerview.js";
35
39
  export * from "./engine_scenelighting.js";
36
- export * from "./engine_input.js";
37
- export * from "./engine_lifecycle_api.js";
38
- export * from "./engine_math.js";
39
- export * from "./js-extensions/index.js";
40
40
  export * from "./engine_scenetools.js";
41
41
  export * from "./engine_serialization.js";
42
42
  export { type ISerializable } from "./engine_serialization_core.js";
@@ -44,12 +44,11 @@
44
44
  export * from "./engine_three_utils.js";
45
45
  export * from "./engine_time.js";
46
46
  export * from "./engine_types.js";
47
+ export { registerType,TypeStore } from "./engine_typestore.js";
48
+ export { prefix,validate } from "./engine_util_decorator.js";
49
+ export * from "./engine_utils.js";
47
50
  export * from "./engine_utils_screenshot.js";
48
51
  export * from "./engine_web_api.js";
49
- export * from "./engine_utils.js";
50
-
51
- export { TypeStore, registerType } from "./engine_typestore.js";
52
-
53
- export { InstancingUtil } from "./engine_instancing.js";
54
- export { validate, prefix } from "./engine_util_decorator.js";
55
- export { hasProLicense, hasIndieLicense } from "./engine_license.js";
52
+ export * from "./engine_xr.js";
53
+ export * from "./extensions/index.js";
54
+ export * from "./js-extensions/index.js";
src/engine-components/export/usdz/extensions/behavior/AudioExtension.ts CHANGED
@@ -1,8 +1,9 @@
1
+ import { Object3D } from "three";
2
+
3
+ import { AudioSource } from "../../../../AudioSource.js";
1
4
  import { GameObject } from "../../../../Component.js";
2
5
  import type { IUSDExporterExtension } from "../../Extension.js";
3
6
  import { USDObject, USDWriter, USDZExporterContext } from "../../ThreeUSDZExporter.js";
4
- import { Object3D } from "three";
5
- import { AudioSource } from "../../../../AudioSource.js";
6
7
 
7
8
  export class AudioExtension implements IUSDExporterExtension {
8
9
 
src/engine-components/AudioListener.ts CHANGED
@@ -1,7 +1,8 @@
1
- import { Behaviour, GameObject } from "./Component.js";
2
1
  import { AudioListener as ThreeAudioListener } from "three";
2
+
3
3
  import { AudioSource } from "./AudioSource.js";
4
4
  import { Camera } from "./Camera.js";
5
+ import { Behaviour, GameObject } from "./Component.js";
5
6
 
6
7
 
7
8
  export class AudioListener extends Behaviour {
src/engine-components/AudioSource.ts CHANGED
@@ -1,11 +1,14 @@
1
- import { Behaviour, GameObject } from "./Component.js";
1
+ import { Audio, AudioContext, AudioLoader, PositionalAudio, Vector3 } from "three";
2
2
  import { PositionalAudioHelper } from 'three/examples/jsm/helpers/PositionalAudioHelper.js';
3
+
4
+ import { isDevEnvironment } from "../engine/debug/index.js";
5
+ import { ApplicationEvents } from "../engine/engine_application.js";
6
+ import { Mathf } from "../engine/engine_math.js";
7
+ import { serializable } from "../engine/engine_serialization_decorator.js";
8
+ import { getTempVector } from "../engine/engine_three_utils.js";
9
+ import * as utils from "../engine/engine_utils.js";
3
10
  import { AudioListener } from "./AudioListener.js";
4
- import * as utils from "../engine/engine_utils.js";
5
- import { serializable } from "../engine/engine_serialization_decorator.js";
6
- import { ApplicationEvents } from "../engine/engine_application.js";
7
- import { Audio, AudioContext, AudioLoader, PositionalAudio } from "three";
8
- import { isDevEnvironment } from "../engine/debug/index.js";
11
+ import { Behaviour, GameObject } from "./Component.js";
9
12
 
10
13
 
11
14
  const debug = utils.getParam("debugaudio");
@@ -65,6 +68,9 @@
65
68
  playOnAwake: boolean = false;
66
69
 
67
70
  @serializable()
71
+ preload: boolean = false;
72
+
73
+ @serializable()
68
74
  get loop(): boolean {
69
75
  if (this.sound) this._loop = this.sound.getLoop();
70
76
  return this._loop;
@@ -140,23 +146,69 @@
140
146
  if (!listener && this.context.mainCamera) listener = GameObject.addNewComponent(this.context.mainCamera, AudioListener);
141
147
  if (listener?.listener) {
142
148
  this.sound = new PositionalAudio(listener.listener);
143
- this.gameObject.add(this.sound);
149
+ this.gameObject?.add(this.sound);
150
+
151
+ // this._listener = listener;
152
+ // this._originalSoundMatrixWorldFunction = this.sound.updateMatrixWorld;
153
+ // this.sound.updateMatrixWorld = this._onSoundMatrixWorld;
144
154
  }
145
155
  else if (debug) console.warn("No audio listener found in scene - can not play audio");
146
156
  }
147
157
  return this.sound;
148
158
  }
149
159
 
160
+ // This is a hacky workaround to get the PositionalAudio behave like a 2D audio source
161
+ // private _listener: AudioListener | null = null;
162
+ // private _originalSoundMatrixWorldFunction: Function | null = null;
163
+ // private _onSoundMatrixWorld = (force: boolean) => {
164
+ // if (this._spatialBlend > .05) {
165
+ // if (this._originalSoundMatrixWorldFunction) {
166
+ // this._originalSoundMatrixWorldFunction.call(this.sound, force);
167
+ // }
168
+ // }
169
+ // else {
170
+ // // we use another object's matrix world function (but bound to the positional audio)
171
+ // // this is just a little trick to prevent calling the PositionalAudio's updateMatrixWorld function
172
+ // this.gameObject.updateMatrixWorld?.call(this.sound, force);
173
+ // if (this.sound && this._listener) {
174
+ // this.sound.gain.connect(this._listener.listener.getInput());
175
+ // // const pos = getTempVector().setFromMatrixPosition(this._listener.gameObject.matrixWorld);
176
+ // // const ctx = this.sound.context;
177
+ // // const delay = this._listener.listener.timeDelta;
178
+ // // const time = ctx.currentTime ;
179
+ // // this.sound.panner.positionX.setValueAtTime(pos.x, time);
180
+ // // this.sound.panner.positionY.setValueAtTime(pos.y, time);
181
+ // // this.sound.panner.positionZ.setValueAtTime(pos.z, time);
182
+ // // this.sound.panner.orientationX.setValueAtTime(0, time);
183
+ // // this.sound.panner.orientationY.setValueAtTime(0, time);
184
+ // // this.sound.panner.orientationZ.setValueAtTime(-1, time);
185
+ // }
186
+ // }
187
+ // }
188
+
150
189
  public get ShouldPlay(): boolean { return this.shouldPlay; }
151
190
 
191
+ /** Get the audio context from the Sound */
192
+ public get audioContext() {
193
+ return this.sound?.context;
194
+ }
152
195
 
153
196
  awake() {
154
- if(debug) console.log(this);
197
+ if (debug) console.log(this);
155
198
  this.audioLoader = new AudioLoader();
156
199
  if (this.playOnAwake) this.shouldPlay = true;
200
+
201
+ if (this.preload) {
202
+ if (typeof this.clip === "string") {
203
+ this.audioLoader.load(this.clip, this.createAudio, () => { }, console.error);
204
+ }
205
+ }
157
206
  }
158
207
 
159
208
  onEnable(): void {
209
+ if (this.sound)
210
+ this.gameObject.add(this.sound);
211
+
160
212
  if (!AudioSource.userInteractionRegistered) {
161
213
  AudioSource.registerWaitForAllowAudio(() => {
162
214
  if (this.enabled && !this.destroyed && this.shouldPlay)
@@ -202,50 +254,56 @@
202
254
  this.sound?.setVolume(this.volume);
203
255
  }
204
256
 
205
- private lerp = (x, y, a) => x * (1 - a) + y * a;
206
-
207
257
  private createAudio = (buffer?: AudioBuffer) => {
208
- if (debug) console.log("audio buffer loaded");
209
- AudioSource.registerWaitForAllowAudio(() => {
210
- if (debug)
211
- console.log("finished loading", buffer);
258
+ if (debug) console.log("AudioBuffer finished loading", buffer);
212
259
 
213
- const sound = this.Sound;
214
- if (!sound) {
215
- console.warn("Failed getting sound", this.name);
216
- return;
217
- }
218
- if (sound.isPlaying)
219
- sound.stop();
260
+ const sound = this.Sound;
261
+ if (!sound) {
262
+ if (debug) console.warn("Failed getting sound?", this.name);
263
+ return;
264
+ }
220
265
 
221
- if (buffer)
222
- sound.setBuffer(buffer);
223
- sound.loop = this._loop;
224
- if (this.context.application.muted) sound.setVolume(0);
225
- else sound.setVolume(this.volume);
226
- sound.autoplay = this.shouldPlay;
227
- // sound.setDistanceModel('linear');
228
- // sound.setRolloffFactor(1);
229
- this.applySpatialDistanceSettings();
230
- // sound.setDirectionalCone(180, 360, 0.1);
231
- if (sound.isPlaying)
232
- sound.stop();
266
+ if (sound.isPlaying)
267
+ sound.stop();
233
268
 
234
- if (debug) console.log(this.name, this.shouldPlay, AudioSource.userInteractionRegistered, this);
269
+ if (buffer) sound.setBuffer(buffer);
270
+ sound.loop = this._loop;
271
+ if (this.context.application.muted) sound.setVolume(0);
272
+ else sound.setVolume(this.volume);
273
+ sound.autoplay = this.shouldPlay && AudioSource.userInteractionRegistered;
235
274
 
236
- if (this.shouldPlay && AudioSource.userInteractionRegistered)
237
- this.play();
238
- });
275
+ this.applySpatialDistanceSettings();
276
+
277
+ if (sound.isPlaying)
278
+ sound.stop();
279
+
280
+ // const src = sound.context.createBufferSource();
281
+ // src.buffer = sound.buffer;
282
+ // src.connect(sound.panner);
283
+ // src.start(this.audioContext?.currentTime);
284
+ // const gain = sound.context.createGain();
285
+ // gain.gain.value = 1 - this.spatialBlend;
286
+ // src.connect(gain);
287
+
288
+ // make sure we only play the sound if the user has interacted with the page
289
+ AudioSource.registerWaitForAllowAudio(this.__onAllowAudioCallback);
239
290
  }
291
+ private __onAllowAudioCallback = () => {
292
+ if (this.shouldPlay)
293
+ this.play();
294
+ }
240
295
 
241
296
  private applySpatialDistanceSettings() {
242
297
  const sound = this.sound;
243
298
  if (!sound) return;
244
299
  this._needUpdateSpatialDistanceSettings = false;
245
- const dist = this.lerp(10 * this._maxDistance / Math.max(0.0001, this.spatialBlend), this._minDistance, this.spatialBlend);
300
+ const dist = Mathf.lerp(10 * this._maxDistance / Math.max(0.0001, this.spatialBlend), this._minDistance, this.spatialBlend);
246
301
  if (debug) console.log(this.name, this._minDistance, this._maxDistance, this.spatialBlend, "Ref distance=" + dist);
247
302
  sound.setRefDistance(dist);
248
303
  sound.setMaxDistance(Math.max(0.01, this._maxDistance));
304
+ // sound.setRolloffFactor(this.spatialBlend);
305
+ // sound.panner.positionZ.automationRate
306
+
249
307
  // https://developer.mozilla.org/en-US/docs/Web/API/PannerNode/distanceModel
250
308
  switch (this.rollOffMode) {
251
309
  case AudioRolloffMode.Logarithmic:
@@ -269,7 +327,7 @@
269
327
  }
270
328
  }
271
329
 
272
- private onNewClip(clip?: string | MediaStream) {
330
+ private async onNewClip(clip?: string | MediaStream) {
273
331
  if (clip) this.clip = clip;
274
332
  if (typeof clip === "string") {
275
333
  if (debug)
@@ -285,7 +343,10 @@
285
343
  this._lastClipStartedLoading = clip;
286
344
  if (debug)
287
345
  console.log("load audio", clip);
288
- this.audioLoader.load(clip, this.createAudio, () => { }, console.error);
346
+ const buffer = await this.audioLoader.loadAsync(clip).catch(console.error);
347
+ this._lastClipStartedLoading = null;
348
+ if (buffer)
349
+ this.createAudio(buffer);
289
350
  }
290
351
  else console.warn("Unsupported audio clip type", clip)
291
352
  }
@@ -328,6 +389,7 @@
328
389
  if (this.sound && !this.sound.isPlaying) {
329
390
  const muted = this.context.application.muted;
330
391
  if (muted) this.sound.setVolume(0);
392
+ this.gameObject?.add(this.sound);
331
393
 
332
394
  if (this.clip instanceof MediaStream) {
333
395
 
@@ -411,7 +473,7 @@
411
473
  this._hasEnded = true;
412
474
  if (debug)
413
475
  console.log("Audio clip ended", this.clip);
414
- this.sound.dispatchEvent({ type: 'ended', target: this });
476
+ this.dispatchEvent(new CustomEvent("ended", { detail: this }));
415
477
  }
416
478
 
417
479
  // this.gameObject.position.x = Math.sin(time.time) * 2;
src/engine-components/avatar/Avatar_Brain_LookAt.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import * as THREE from "three";
2
+
3
+ import { OwnershipModel } from "../../engine/engine_networking.js";
4
+ import type { IModel } from "../../engine/engine_networking_types.js";
5
+ import { Context } from "../../engine/engine_setup.js";
6
+ import * as utils from "../../engine/engine_three_utils.js";
2
7
  import { TypeStore } from "../../engine/engine_typestore.js";
3
8
  import { Behaviour, GameObject } from "../Component.js";
4
9
  import { AvatarMarker } from "../webxr/WebXRAvatar.js";
5
- import * as utils from "../../engine/engine_three_utils.js";
6
- import { OwnershipModel } from "../../engine/engine_networking.js";
7
- import { Context } from "../../engine/engine_setup.js";
8
- import type { IModel } from "../../engine/engine_networking_types.js";
9
10
 
10
11
  export class Avatar_POI {
11
12
 
src/engine-components/avatar/Avatar_MouthShapes.ts CHANGED
@@ -1,9 +1,10 @@
1
+ import { Object3D } from "three";
2
+
3
+ import { serializable } from "../../engine/engine_serialization_decorator.js";
4
+ import * as utils from "../../engine/engine_utils.js";
1
5
  import { Behaviour, GameObject } from "../Component.js";
2
6
  import { Voip } from "../Voip.js";
3
7
  import { AvatarMarker } from "../webxr/WebXRAvatar.js";
4
- import * as utils from "../../engine/engine_utils.js";
5
- import { Object3D } from "three";
6
- import { serializable } from "../../engine/engine_serialization_decorator.js";
7
8
 
8
9
  const debug = utils.getParam("debugmouth");
9
10
 
src/engine-components/avatar/AvatarBlink_Simple.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { Object3D } from "three";
2
+
3
+ import { serializable } from "../../engine/engine_serialization_decorator.js";
2
4
  import { Behaviour, GameObject } from "../Component.js";
3
- import { XRFlag, XRState } from "../XRFlag.js";
4
- import { serializable } from "../../engine/engine_serialization_decorator.js";
5
+ import { XRFlag, XRState } from "../webxr/XRFlag.js";
5
6
 
6
7
 
7
8
  export class AvatarBlink_Simple extends Behaviour {
src/engine-components/avatar/AvatarEyeLook_Rotation.ts CHANGED
@@ -1,10 +1,11 @@
1
- import { Behaviour, GameObject } from "../Component.js";
2
- import * as utils from "../../engine/engine_three_utils.js"
3
1
  import * as THREE from "three";
4
- import { Avatar_Brain_LookAt } from "./Avatar_Brain_LookAt.js";
5
- import { serializable } from "../../engine/engine_serialization_decorator.js";
6
2
  import { Object3D } from "three";
7
3
 
4
+ import { serializable } from "../../engine/engine_serialization_decorator.js";
5
+ import * as utils from "../../engine/engine_three_utils.js"
6
+ import { Behaviour, GameObject } from "../Component.js";
7
+ import { Avatar_Brain_LookAt } from "./Avatar_Brain_LookAt.js";
8
+
8
9
  export class AvatarEyeLook_Rotation extends Behaviour {
9
10
 
10
11
  @serializable(Object3D)
src/engine-components/AvatarLoader.ts CHANGED
@@ -1,12 +1,13 @@
1
+ import { Box3, Object3D, Vector3 } from "three";
1
2
  import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
2
- import * as utils from "../engine/engine_utils.js"
3
+
4
+ import { InstantiateOptions } from "../engine/engine_gameobject.js";
5
+ import { getLoader } from "../engine/engine_gltf.js";
3
6
  import * as loaders from "../engine/engine_loaders.js"
4
7
  import { Context } from "../engine/engine_setup.js";
8
+ import * as utils from "../engine/engine_utils.js"
9
+ import { download_file } from "../engine/engine_web_api.js";
5
10
  import { GameObject } from "./Component.js";
6
- import { download_file } from "../engine/engine_web_api.js";
7
- import { getLoader } from "../engine/engine_gltf.js";
8
- import { InstantiateOptions } from "../engine/engine_gameobject.js";
9
- import { Box3, Object3D, Vector3 } from "three";
10
11
 
11
12
  const debug = utils.getParam("debugavatar");
12
13
 
src/engine-components/AxesHelper.ts CHANGED
@@ -1,7 +1,8 @@
1
- import { Behaviour } from "./Component.js";
1
+ import { AxesHelper as _AxesHelper } from "three";
2
+
2
3
  import * as params from "../engine/engine_default_parameters.js";
3
4
  import { serializable } from "../engine/engine_serialization_decorator.js";
4
- import { AxesHelper as _AxesHelper } from "three";
5
+ import { Behaviour } from "./Component.js";
5
6
 
6
7
  export class AxesHelper extends Behaviour {
7
8
  @serializable()
src/engine-components/ui/BaseUIComponent.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  // import { Canvas } from './Canvas.js';
2
+ import { AxesHelper, Object3D } from 'three';
2
3
  import * as ThreeMeshUI from 'three-mesh-ui';
4
+
5
+ import { showGizmos } from '../../engine/engine_default_parameters.js';
6
+ import { getParam } from '../../engine/engine_utils.js';
3
7
  import { Behaviour, GameObject } from "../Component.js";
4
8
  import { EventSystem } from "./EventSystem.js";
5
- import { showGizmos } from '../../engine/engine_default_parameters.js';
6
- import { AxesHelper, Object3D } from 'three';
7
9
  import type { ICanvas } from './Interfaces.js';
8
- import { getParam } from '../../engine/engine_utils.js';
9
10
  export const includesDir = "./include";
10
11
 
11
12
  const debug = getParam("debugshadowcomponents");
@@ -24,22 +25,38 @@
24
25
 
25
26
  export const $shadowDomOwner = Symbol("shadowDomOwner");
26
27
 
28
+ /** Derive from this class if you want to implement your own UI components
29
+ * It provides utility methods and simplifies managing the underlying three-mesh-ui hierarchy
30
+ */
27
31
  export class BaseUIComponent extends Behaviour {
28
32
 
33
+ /** Is this object on the root of the UI hierarchy ? */
29
34
  isRoot() { return this.Root?.gameObject === this.gameObject; }
30
35
 
36
+ /** Access the parent canvas component */
31
37
  get canvas() {
32
38
  const cv = this.Root as any as ICanvas;
33
39
  if (cv?.isCanvas) return cv;
34
40
  return null;
35
41
  }
42
+ /** @deprecated use `canvas` */
43
+ protected get Canvas() {
44
+ return this.canvas;
45
+ }
36
46
 
47
+ /** Mark the UI dirty which will trigger an THREE-Mesh-UI update */
37
48
  markDirty() {
38
49
  EventSystem.markUIDirty(this.context);
39
50
  }
40
51
 
41
- shadowComponent: ThreeMeshUI.Block | null = null;
52
+ /** the underlying three-mesh-ui */
53
+ get shadowComponent() { return this._shadowComponent }
54
+ private set shadowComponent(val: Object3D | null) {
55
+ this._shadowComponent = val;
56
+ }
42
57
 
58
+ private _shadowComponent: Object3D | null = null;
59
+
43
60
  private _controlsChildLayout = true;
44
61
  get controlsChildLayout(): boolean { return this._controlsChildLayout; }
45
62
  set controlsChildLayout(val: boolean) {
@@ -58,11 +75,6 @@
58
75
  return this._root;
59
76
  }
60
77
 
61
- // TODO: rename to canvas
62
- protected get Canvas() {
63
- return this.canvas;
64
- }
65
-
66
78
  // private _intermediate?: Object3D;
67
79
  protected _parentComponent?: BaseUIComponent | null = undefined;
68
80
 
@@ -77,7 +89,10 @@
77
89
  super.onEnable();
78
90
  }
79
91
 
80
- //@ts-ignore
92
+ /** Add a three-mesh-ui object to the UI hierarchy
93
+ * @param container the three-mesh-ui object to add
94
+ * @param parent the parent component to add the object to
95
+ */
81
96
  protected addShadowComponent(container: any, parent?: BaseUIComponent) {
82
97
 
83
98
  this.removeShadowComponent();
@@ -134,21 +149,7 @@
134
149
  if(debug) console.log(this.shadowComponent)
135
150
  }
136
151
 
137
-
138
- set(_state: object) {
139
- // if (!this.shadowComponent) return;
140
- // this.traverseOwnedShadowComponents(this.shadowComponent, this, o => {
141
- // for (const ch of o.children) {
142
- // console.log(this, ch);
143
- // if (ch.isUI && typeof ch.set === "function") {
144
- // // ch.set(state);
145
- // // ch.update(true, true, true);
146
- // }
147
- // }
148
- // })
149
- }
150
-
151
- protected setShadowComponentOwner(current: Object3D | null | undefined) {
152
+ protected setShadowComponentOwner(current: ThreeMeshUI.MeshUIBaseElement | Object3D | null | undefined) {
152
153
  if (!current) return;
153
154
  // TODO: only traverse our own hierarchy, we can stop if we find another owner
154
155
  if (current[$shadowDomOwner] === undefined || current[$shadowDomOwner] === this) {
@@ -171,6 +172,7 @@
171
172
  }
172
173
  }
173
174
 
175
+ /** Remove the underlying UI object from the hierarchy */
174
176
  protected removeShadowComponent() {
175
177
  if (this.shadowComponent) {
176
178
  this.shadowComponent.removeFromParent();
src/engine-components/BasicIKConstraint.ts CHANGED
@@ -1,7 +1,8 @@
1
- import { Behaviour, GameObject } from "./Component.js";
2
- import * as utils from "./../engine/engine_three_utils.js";
3
1
  import { Vector3 } from "three";
4
2
 
3
+ import * as utils from "./../engine/engine_three_utils.js";
4
+ import { Behaviour, GameObject } from "./Component.js";
5
+
5
6
  export class BasicIKConstraint extends Behaviour {
6
7
 
7
8
  private from!: GameObject;
src/engine-components/export/usdz/extensions/behavior/Behaviour.ts CHANGED
@@ -1,8 +1,8 @@
1
+ import { getParam } from "../../../../../engine/engine_utils.js";
1
2
  import { GameObject } from "../../../../Component.js";
2
3
  import type { IUSDExporterExtension } from "../../Extension.js";
3
4
  import { USDObject, USDWriter, USDZExporterContext } from "../../ThreeUSDZExporter.js";
4
5
  import { BehaviorModel } from "./BehavioursBuilder.js";
5
- import { getParam } from "../../../../../engine/engine_utils.js";
6
6
 
7
7
  const debug = getParam("debugusdz");
8
8
 
src/engine-components/export/usdz/extensions/behavior/BehaviourComponents.ts CHANGED
@@ -1,21 +1,20 @@
1
+ import { Group,Material, Mesh, Object3D, Quaternion, Vector3 } from "three";
2
+
3
+ import { isDevEnvironment, showBalloonWarning } from "../../../../../engine/debug/index.js";
4
+ import { serializable } from "../../../../../engine/engine_serialization_decorator.js";
5
+ import { getWorldPosition, getWorldQuaternion, getWorldScale, setWorldPosition, setWorldQuaternion, setWorldScale } from "../../../../../engine/engine_three_utils.js";
6
+ import type { State } from "../../../../../engine/extensions/NEEDLE_animator_controller_model.js";
7
+ import { NEEDLE_progressive } from "../../../../../engine/extensions/NEEDLE_progressive.js";
8
+ import { Animator } from "../../../../Animator.js";
9
+ import { AudioSource } from "../../../../AudioSource.js";
1
10
  import { Behaviour, GameObject } from "../../../../Component.js";
2
- import { Animator } from "../../../../Animator.js";
3
11
  import { Renderer } from "../../../../Renderer.js";
4
- import { serializable } from "../../../../../engine/engine_serialization_decorator.js";
5
12
  import type { IPointerClickHandler, PointerEventData } from "../../../../ui/PointerEvents.js";
13
+ import { ObjectRaycaster,Raycaster } from "../../../../ui/Raycaster.js";
14
+ import { USDDocument, USDObject, USDZExporterContext } from "../../ThreeUSDZExporter.js";
6
15
  import { AnimationExtension, RegisteredAnimationInfo, type UsdzAnimation } from "../Animation.js";
7
- import { getWorldPosition, getWorldQuaternion, getWorldScale, setWorldPosition, setWorldQuaternion, setWorldScale } from "../../../../../engine/engine_three_utils.js";
8
-
9
- import { Object3D, Material, Vector3, Quaternion, Mesh, Group } from "three";
10
- import { USDDocument, USDObject, USDZExporterContext } from "../../ThreeUSDZExporter.js";
11
-
12
16
  import type { BehaviorExtension, UsdzBehaviour } from "./Behaviour.js";
13
- import { ActionBuilder, ActionModel, AuralMode, BehaviorModel, type IBehaviorElement, MotionType, PlayAction, Space, TriggerBuilder, GroupActionModel, MultiplePerformOperation } from "./BehavioursBuilder.js";
14
- import { AudioSource } from "../../../../AudioSource.js";
15
- import { NEEDLE_progressive } from "../../../../../engine/extensions/NEEDLE_progressive.js";
16
- import { isDevEnvironment, showBalloonWarning } from "../../../../../engine/debug/index.js";
17
- import { Raycaster, ObjectRaycaster } from "../../../../ui/Raycaster.js";
18
- import type { State } from "../../../../../engine/extensions/NEEDLE_animator_controller_model.js";
17
+ import { ActionBuilder, ActionModel, AuralMode, BehaviorModel, GroupActionModel, type IBehaviorElement, MotionType, MultiplePerformOperation,PlayAction, Space, TriggerBuilder } from "./BehavioursBuilder.js";
19
18
 
20
19
  function ensureRaycaster(obj: GameObject) {
21
20
  if (!obj) return;
src/engine-components/export/usdz/extensions/behavior/BehavioursBuilder.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { Object3D } from "three";
2
- import { USDDocument, USDObject, USDWriter, makeNameSafeForUSD } from "../../ThreeUSDZExporter.js";
3
2
 
3
+ import { getParam } from "../../../../../engine/engine_utils.js";
4
+ import { makeNameSafeForUSD,USDDocument, USDObject, USDWriter } from "../../ThreeUSDZExporter.js";
4
5
  import { BehaviorExtension } from "./Behaviour.js";
5
- import { getParam } from "../../../../../engine/engine_utils.js";
6
6
 
7
7
  const debug = getParam("debugusdz");
8
8
 
src/engine-components/postprocessing/Effects/Bloom.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { BlendFunction, BloomEffect, SelectiveBloomEffect } from "postprocessing";
2
+
2
3
  import { serializable } from "../../../engine/engine_serialization.js";
3
4
  import { PostProcessingEffect } from "../PostProcessingEffect.js";
4
5
  import { VolumeParameter } from "../VolumeParameter.js";
src/engine-components/BoxHelperComponent.ts CHANGED
@@ -1,8 +1,9 @@
1
- import { Behaviour } from "./Component.js";
2
- import { getParam } from "../engine/engine_utils.js";
1
+ import { Box3, Color, type ColorRepresentation, LineSegments, Object3D, Vector3 } from "three";
2
+
3
3
  import { CreateWireCube, Gizmos } from "../engine/engine_gizmos.js";
4
4
  import { getWorldPosition, getWorldScale } from "../engine/engine_three_utils.js";
5
- import { Box3, Color, type ColorRepresentation, LineSegments, Object3D, Vector3 } from "three";
5
+ import { getParam } from "../engine/engine_utils.js";
6
+ import { Behaviour } from "./Component.js";
6
7
 
7
8
  const gizmos = getParam("gizmos");
8
9
  const debug = getParam("debugboxhelper");
src/engine-components/ui/Button.ts CHANGED
@@ -1,15 +1,15 @@
1
+ import { showBalloonMessage } from "../../engine/debug/index.js";
2
+ import { Gizmos } from "../../engine/engine_gizmos.js";
3
+ import { PointerType } from "../../engine/engine_input.js";
4
+ import { serializable } from "../../engine/engine_serialization_decorator.js";
5
+ import { getParam } from "../../engine/engine_utils.js";
6
+ import { Animator } from "../Animator.js";
1
7
  import { Behaviour, GameObject } from "../Component.js";
2
8
  import { EventList } from "../EventList.js";
9
+ import { RGBAColor } from "../js-extensions/RGBAColor.js";
10
+ import { Image } from "./Image.js";
3
11
  import type { IPointerClickHandler, IPointerEnterHandler, IPointerEventHandler, IPointerExitHandler, PointerEventData } from "./PointerEvents.js";
4
- import { Image } from "./Image.js";
5
- import { RGBAColor } from "../js-extensions/RGBAColor.js";
6
- import { serializable } from "../../engine/engine_serialization_decorator.js";
7
- import { Animator } from "../Animator.js";
8
- import { getParam } from "../../engine/engine_utils.js";
9
- import { showBalloonMessage } from "../../engine/debug/index.js";
10
12
  import { GraphicRaycaster, ObjectRaycaster, Raycaster } from "./Raycaster.js";
11
- import { PointerType } from "../../engine/engine_input.js";
12
- import { Gizmos } from "../../engine/engine_gizmos.js";
13
13
 
14
14
  const debug = getParam("debugbutton");
15
15
 
@@ -65,12 +65,12 @@
65
65
  @serializable(EventList)
66
66
  onClick?: EventList;
67
67
 
68
- private _isHovered: boolean = false;
68
+ private _isHovered: number = 0;
69
69
 
70
70
  onPointerEnter(_) {
71
+ this._isHovered += 1;
71
72
  if (debug)
72
- console.log("Button Enter", this.animationTriggers?.highlightedTrigger, this.animator);
73
- this._isHovered = true;
73
+ console.warn("Button Enter", this._isHovered, this.animationTriggers?.highlightedTrigger, this.animator);
74
74
  if (!this.interactable) return;
75
75
  if (this.transition == Transition.Animation && this.animationTriggers && this.animator) {
76
76
  this.animator.setTrigger(this.animationTriggers.highlightedTrigger);
@@ -82,10 +82,12 @@
82
82
  }
83
83
 
84
84
  onPointerExit() {
85
+ this._isHovered -= 1;
85
86
  if (debug)
86
- console.log("Button Exit", this.animationTriggers?.highlightedTrigger, this.animator);
87
- this._isHovered = false;
87
+ console.log("Button Exit", this._isHovered, this.animationTriggers?.highlightedTrigger, this.animator);
88
88
  if (!this.interactable) return;
89
+ if (this._isHovered > 0) return;
90
+ this._isHovered = 0;
89
91
  if (this.transition == Transition.Animation && this.animationTriggers && this.animator) {
90
92
  this.animator.setTrigger(this.animationTriggers.normalTrigger);
91
93
  }
@@ -120,10 +122,10 @@
120
122
  }
121
123
 
122
124
  onPointerClick(args: PointerEventData) {
123
- if (!this.interactable || args.pointerId !== 0) return;
125
+ if (!this.interactable) return;
124
126
 
127
+ if (args.button !== 0 && args.event.pointerType === PointerType.Mouse) return;
125
128
  // Button clicks should only run with left mouse button while using mouse
126
- if(args.pointerId !== 0 && this.context.input.getIsMouse(args.pointerId)) return;
127
129
  if (debug) {
128
130
  console.warn("Button Click", this.onClick);
129
131
  showBalloonMessage("CLICKED button " + this.name + " at " + this.context.time.frameCount);
src/engine-components/Camera.ts CHANGED
@@ -1,17 +1,17 @@
1
+ import { EquirectangularReflectionMapping, OrthographicCamera, PerspectiveCamera, Ray, SRGBColorSpace, Vector3 } from "three";
2
+ import { Texture } from "three";
3
+
4
+ import { isDevEnvironment, showBalloonMessage, showBalloonWarning } from "../engine/debug/index.js";
5
+ import { Gizmos } from "../engine/engine_gizmos.js";
6
+ import { serializable } from "../engine/engine_serialization_decorator.js";
7
+ import { Context } from "../engine/engine_setup.js";
8
+ import { RenderTexture } from "../engine/engine_texture.js";
9
+ import { getWorldPosition } from "../engine/engine_three_utils.js";
10
+ import type { ICamera } from "../engine/engine_types.js"
11
+ import { getParam } from "../engine/engine_utils.js";
1
12
  import { Behaviour, GameObject } from "./Component.js";
2
- import { getParam } from "../engine/engine_utils.js";
3
- import { serializable } from "../engine/engine_serialization_decorator.js";
4
13
  import { RGBAColor } from "./js-extensions/RGBAColor.js";
5
- import { Context, XRSessionMode } from "../engine/engine_setup.js";
6
- import type { ICamera } from "../engine/engine_types.js"
7
- import { isDevEnvironment, showBalloonMessage, showBalloonWarning } from "../engine/debug/index.js";
8
- import { getWorldPosition } from "../engine/engine_three_utils.js";
9
- import { Gizmos } from "../engine/engine_gizmos.js";
10
-
11
- import { EquirectangularReflectionMapping, OrthographicCamera, PerspectiveCamera, Ray, SRGBColorSpace, Vector3 } from "three";
12
14
  import { OrbitControls } from "./OrbitControls.js";
13
- import { RenderTexture } from "../engine/engine_texture.js";
14
- import { Texture } from "three";
15
15
 
16
16
  export enum ClearFlags {
17
17
  Skybox = 1,
@@ -350,7 +350,6 @@
350
350
  if (this._backgroundBlurriness !== undefined)
351
351
  this.context.scene.backgroundBlurriness = this._backgroundBlurriness;
352
352
  if (this._backgroundIntensity !== undefined)
353
- //@ts-ignore
354
353
  this.context.scene.backgroundIntensity = this._backgroundIntensity;
355
354
 
356
355
  break;
@@ -392,7 +391,7 @@
392
391
  if (debug)
393
392
  showBalloonMessage("Environment blend mode: " + environmentBlendMode + " on " + navigator.userAgent);
394
393
  let transparent = environmentBlendMode === 'additive' || environmentBlendMode === 'alpha-blend';
395
- if (context.xrSessionMode === XRSessionMode.ImmersiveAR) {
394
+ if (context.isInAR) {
396
395
  if (environmentBlendMode === "opaque") {
397
396
  // workaround for Quest 2 returning opaque when it should be alpha-blend
398
397
  // check user agent if this is the Quest browser and return true if so
src/engine-components/CameraUtils.ts CHANGED
@@ -1,14 +1,15 @@
1
- import { OrbitControls } from "./OrbitControls.js";
1
+ import { Object3D } from "three";
2
+
3
+ import { getCameraController } from "../engine/engine_camera.js";
2
4
  import { addNewComponent, getOrAddComponent } from "../engine/engine_components.js";
3
- import { Object3D } from "three";
4
- import type { ICamera, IContext } from "../engine/engine_types.js";
5
- import { RGBAColor } from "./js-extensions/RGBAColor.js";
5
+ import { Context } from "../engine/engine_context.js";
6
6
  import { ContextEvent, ContextRegistry } from "../engine/engine_context_registry.js";
7
- import { getCameraController } from "../engine/engine_camera.js";
8
- import { Camera, ClearFlags } from "./Camera.js";
9
7
  import { NeedleEngineHTMLElement } from "../engine/engine_element.js";
8
+ import type { ICamera, IContext } from "../engine/engine_types.js";
10
9
  import { getParam } from "../engine/engine_utils.js";
11
- import { Context } from "../engine/engine_context.js";
10
+ import { Camera, ClearFlags } from "./Camera.js";
11
+ import { RGBAColor } from "./js-extensions/RGBAColor.js";
12
+ import { OrbitControls } from "./OrbitControls.js";
12
13
 
13
14
  const debug = getParam("debugmissingcamera");
14
15
 
src/engine-components/ui/Canvas.ts CHANGED
@@ -1,17 +1,19 @@
1
- import { updateRenderSettings as updateRenderSettingsRecursive } from "./Utils.js";
1
+ import { Matrix4, Object3D } from "three";
2
+ import * as ThreeMeshUI from 'three-mesh-ui'
3
+
4
+ import { Mathf } from "../../engine/engine_math.js";
2
5
  import { serializable } from "../../engine/engine_serialization_decorator.js";
3
6
  import { FrameEvent } from "../../engine/engine_setup.js";
7
+ import { delayForFrames, getParam } from "../../engine/engine_utils.js";
8
+ import { NeedleXREventArgs } from "../../engine/xr/index.js";
9
+ import { Camera } from "../Camera.js";
10
+ import { GameObject } from "../Component.js";
4
11
  import { BaseUIComponent, UIRootComponent } from "./BaseUIComponent.js";
5
- import { GameObject } from "../Component.js";
6
- import { Matrix4, Object3D } from "three";
7
- import { RectTransform } from "./RectTransform.js";
12
+ import { EventSystem } from "./EventSystem.js";
8
13
  import type { ICanvas, ICanvasEventReceiver, ILayoutGroup, IRectTransform } from "./Interfaces.js";
9
- import { Camera } from "../Camera.js";
10
- import { EventSystem } from "./EventSystem.js";
11
- import * as ThreeMeshUI from 'three-mesh-ui'
12
- import { getParam } from "../../engine/engine_utils.js";
13
14
  import { LayoutGroup } from "./Layout.js";
14
- import { Mathf } from "../../engine/engine_math.js";
15
+ import { RectTransform } from "./RectTransform.js";
16
+ import { updateRenderSettings as updateRenderSettingsRecursive } from "./Utils.js";
15
17
 
16
18
  export enum RenderMode {
17
19
  ScreenSpaceOverlay = 0,
@@ -200,19 +202,37 @@
200
202
  }
201
203
  }
202
204
 
205
+ async onEnterXR(args: NeedleXREventArgs) {
206
+ // workaround for https://linear.app/needle/issue/NE-4114
207
+ if (this.screenspace) {
208
+ if (args.xr.isVR || args.xr.isPassThrough) {
209
+ this.gameObject.visible = false;
210
+ }
211
+ }
212
+ else {
213
+ this.gameObject.visible = false;
214
+ await delayForFrames(1).then(()=>{
215
+ this.gameObject.visible = true;
216
+ });
217
+ }
218
+ }
219
+ onLeaveXR(args: NeedleXREventArgs): void {
220
+ if (this.screenspace) {
221
+ if (args.xr.isVR || args.xr.isPassThrough) {
222
+ this.gameObject.visible = true;
223
+ }
224
+ }
225
+ }
226
+
203
227
  onBeforeRenderRoutine = () => {
204
- if (this.context.isInVR) {
205
- this.onUpdateRenderMode();
206
- this.handleLayoutUpdates();
207
- // TODO TMUI @swingingtom - For VR this is so we don't have text clipping
208
- this.shadowComponent?.updateMatrixWorld(true);
209
- this.shadowComponent?.updateWorldMatrix(true, true);
210
- this.invokeBeforeRenderEvents();
211
- EventSystem.ensureUpdateMeshUI(ThreeMeshUI, this.context, true);
228
+ this.previousParent = this.gameObject.parent;
229
+ if ((this.context.xr?.isVR || this.context.xr?.isPassThrough) && this.screenspace) {
230
+ // see https://linear.app/needle/issue/NE-4114
231
+ this.gameObject.visible = false;
232
+ this.gameObject.removeFromParent();
212
233
  return;
213
234
  }
214
235
 
215
- this.previousParent = this.gameObject.parent;
216
236
  // console.log(this.previousParent?.name + "/" + this.gameObject.name);
217
237
 
218
238
  if (this.renderOnTop || this.screenspace) {
@@ -231,7 +251,12 @@
231
251
  }
232
252
 
233
253
  onAfterRenderRoutine = () => {
234
- if(this.context.isInVR) return;
254
+ if ((this.context.xr?.isVR || this.context.xr?.isPassThrough) && this.screenspace) {
255
+ this.previousParent?.add(this.gameObject);
256
+ // this is currently causing an error during XR (https://linear.app/needle/issue/NE-4114)
257
+ // this.gameObject.visible = true;
258
+ return;
259
+ }
235
260
  if ((this.screenspace || this.renderOnTop) && this.previousParent && this.context.mainCamera) {
236
261
  if (this.screenspace) {
237
262
  const camObj = this.context.mainCamera;
@@ -276,7 +301,7 @@
276
301
  for (const ch of this._rectTransforms) {
277
302
  if (matrixWorldChanged) ch.markDirty();
278
303
  let layout = this._layoutGroups.get(ch.gameObject);
279
- if(ch.isDirty && !layout){
304
+ if (ch.isDirty && !layout) {
280
305
  layout = ch.gameObject.getComponentInParent(LayoutGroup) as LayoutGroup;
281
306
  }
282
307
  if (ch.isDirty || layout?.isDirty) {
src/engine-components/ui/CanvasGroup.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { Graphic } from "./Graphic.js";
1
+ import { serializable } from "../../engine/engine_serialization_decorator.js";
2
2
  import { FrameEvent } from "../../engine/engine_setup.js";
3
3
  import { Behaviour, GameObject } from "../Component.js";
4
+ import { BaseUIComponent } from "./BaseUIComponent.js";
5
+ import { Graphic } from "./Graphic.js";
4
6
  import { type ICanvasGroup, type IHasAlphaFactor } from "./Interfaces.js";
5
- import { serializable } from "../../engine/engine_serialization_decorator.js";
6
- import { BaseUIComponent } from "./BaseUIComponent.js";
7
7
 
8
8
 
9
9
  export class CanvasGroup extends Behaviour implements ICanvasGroup {
src/engine-components/CharacterController.ts CHANGED
@@ -1,14 +1,15 @@
1
1
  import { Quaternion, Ray, Vector2, Vector3 } from "three";
2
+
2
3
  import { Mathf } from "../engine/engine_math.js";
4
+ import { RaycastOptions } from "../engine/engine_physics.js";
3
5
  import { serializable } from "../engine/engine_serialization.js";
6
+ import { getWorldPosition } from "../engine/engine_three_utils.js";
4
7
  import { Collision } from "../engine/engine_types.js";
8
+ import { getParam } from "../engine/engine_utils.js";
9
+ import { Animator } from "./Animator.js"
5
10
  import { CapsuleCollider } from "./Collider.js";
6
11
  import { Behaviour, GameObject } from "./Component.js";
7
12
  import { Rigidbody } from "./RigidBody.js";
8
- import { Animator } from "./Animator.js"
9
- import { RaycastOptions } from "../engine/engine_physics.js";
10
- import { getWorldPosition } from "../engine/engine_three_utils.js";
11
- import { getParam } from "../engine/engine_utils.js";
12
13
 
13
14
  const debug = getParam("debugcharactercontroller");
14
15
 
src/engine-components/postprocessing/Effects/ChromaticAberration.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { ChromaticAberrationEffect } from "postprocessing";
2
2
  import { Vector2 } from "three";
3
+
3
4
  import { serializable } from "../../../engine/engine_serialization.js";
4
5
  import { type EffectProviderResult, PostProcessingEffect } from "../PostProcessingEffect.js";
5
6
  import { VolumeParameter } from "../VolumeParameter.js";
src/engine-components/Collider.ts CHANGED
@@ -1,13 +1,14 @@
1
- import { Behaviour } from "./Component.js";
2
- import { Rigidbody } from "./RigidBody.js";
1
+ import { Group, Mesh, Vector3 } from "three"
2
+
3
+ import type { PhysicsMaterial } from "../engine/engine_physics.types.js";
3
4
  import { serializable } from "../engine/engine_serialization_decorator.js";
4
- import { Group, Mesh, Vector3 } from "three"
5
+ import { getWorldScale } from "../engine/engine_three_utils.js";
5
6
  // import { IColliderProvider, registerColliderProvider } from "../engine/engine_physics.js";
6
7
  import type { IBoxCollider, ICollider, ISphereCollider } from "../engine/engine_types.js";
7
- import { getWorldScale } from "../engine/engine_three_utils.js";
8
- import type { PhysicsMaterial } from "../engine/engine_physics.types.js";
9
8
  import { validate } from "../engine/engine_util_decorator.js";
10
9
  import { unwatchWrite, watchWrite } from "../engine/engine_utils.js";
10
+ import { Behaviour } from "./Component.js";
11
+ import { Rigidbody } from "./RigidBody.js";
11
12
 
12
13
 
13
14
  export class Collider extends Behaviour implements ICollider {
@@ -105,8 +106,14 @@
105
106
  onEnable() {
106
107
  super.onEnable();
107
108
  this.context.physics.engine?.addBoxCollider(this, this.size);
109
+ watchWrite(this.gameObject.scale, this.updateProperties);
108
110
  }
109
111
 
112
+ onDisable(): void {
113
+ super.onDisable();
114
+ unwatchWrite(this.gameObject.scale, this.updateProperties);
115
+ }
116
+
110
117
  onValidate(): void {
111
118
  this.updateProperties();
112
119
  }
src/engine-components/postprocessing/Effects/ColorAdjustments.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import { BrightnessContrastEffect, HueSaturationEffect } from "postprocessing";
2
+ import { LinearToneMapping, NoToneMapping } from "three";
3
+
2
4
  import { serializable } from "../../../engine/engine_serialization.js";
3
5
  import { type EffectProviderResult, PostProcessingEffect } from "../PostProcessingEffect.js";
4
6
  import { VolumeParameter } from "../VolumeParameter.js";
5
7
  import { registerCustomEffectType } from "../VolumeProfile.js";
6
- import { LinearToneMapping, NoToneMapping } from "three";
7
8
 
8
9
 
9
10
  export class ColorAdjustments extends PostProcessingEffect {
src/engine-components/Component.ts CHANGED
@@ -1,15 +1,17 @@
1
- import { Mathf } from "../engine/engine_math.js";
2
- import * as threeutils from "../engine/engine_three_utils.js";
1
+ import { Euler, Object3D, Quaternion, Scene, Vector3 } from "three";
2
+
3
+ import { isDevEnvironment } from "../engine/debug/index.js";
4
+ import { addNewComponent, destroyComponentInstance, findObjectOfType, findObjectsOfType, getComponent, getComponentInChildren, getComponentInParent, getComponents, getComponentsInChildren, getComponentsInParent, getOrAddComponent, moveComponentInstance, removeComponent } from "../engine/engine_components.js";
3
5
  import { activeInHierarchyFieldName } from "../engine/engine_constants.js";
4
- import { Context, FrameEvent } from "../engine/engine_setup.js";
6
+ import { destroy, findByGuid, foreachComponent, HideFlags, IInstantiateOptions, instantiate, isActiveInHierarchy, isActiveSelf, isDestroyed, isUsingInstancing, markAsInstancedRendered, setActive } from "../engine/engine_gameobject.js";
5
7
  import * as main from "../engine/engine_mainloop_utils.js";
6
8
  import { syncDestroy, syncInstantiate } from "../engine/engine_networking_instantiate.js";
7
- import type { ConstructorConcrete, SourceIdentifier, IComponent, IGameObject, Constructor, GuidsMap, Collision, ICollider } from "../engine/engine_types.js";
8
- import { addNewComponent, destroyComponentInstance, findObjectOfType, findObjectsOfType, getComponent, getComponentInChildren, getComponentInParent, getComponents, getComponentsInChildren, getComponentsInParent, getOrAddComponent, moveComponentInstance, removeComponent } from "../engine/engine_components.js";
9
- import { findByGuid, destroy, InstantiateOptions, instantiate, HideFlags, foreachComponent, markAsInstancedRendered, isActiveInHierarchy, isActiveSelf, isUsingInstancing, setActive, isDestroyed, IInstantiateOptions } from "../engine/engine_gameobject.js";
9
+ import { Context, FrameEvent } from "../engine/engine_setup.js";
10
+ import * as threeutils from "../engine/engine_three_utils.js";
11
+ import type { Collision, Constructor, ConstructorConcrete, GuidsMap, ICollider, IComponent, IGameObject, SourceIdentifier } from "../engine/engine_types.js";
12
+ import { INeedleXRSessionEventReceiver, NeedleXRControllerEventArgs, NeedleXREventArgs } from "../engine/engine_xr.js";
13
+ import { IPointerEventHandler, PointerEventData } from "./ui/PointerEvents.js";
10
14
 
11
- import { Euler, Object3D, Quaternion, Scene, Vector3 } from "three";
12
- import { showBalloonWarning, isDevEnvironment } from "../engine/debug/index.js";
13
15
 
14
16
  // export interface ISerializationCallbackReceiver {
15
17
  // onBeforeSerialize?(): object | void;
@@ -81,8 +83,8 @@
81
83
  * @param instance object to instantiate
82
84
  * @param opts options for the instantiation (e.g. with what parent, position, etc.)
83
85
  */
84
- public static instantiate(instance: GameObject | Object3D | null, opts: IInstantiateOptions | null = null): GameObject | null {
85
- return instantiate(instance, opts) as GameObject | null;
86
+ public static instantiate(instance: GameObject | Object3D, opts: IInstantiateOptions | null = null): GameObject {
87
+ return instantiate(instance, opts) as GameObject;
86
88
  }
87
89
 
88
90
  /** Destroys a object on all connected clients (if you are in a networked session)
@@ -123,7 +125,7 @@
123
125
  main.addScriptToArrays(comp, context!);
124
126
  if (comp.__internalDidAwakeAndStart) return;
125
127
  if (context!.new_script_start.includes(comp) === false) {
126
- context!.new_script_start.push(comp as Behaviour);
128
+ context!.new_script_start.push(comp as Component);
127
129
  }
128
130
  }, true);
129
131
  }
@@ -254,7 +256,7 @@
254
256
  return getComponentsInParent(go, typeName, arr);
255
257
  }
256
258
 
257
- public static getAllComponents(go: IGameObject | Object3D): Behaviour[] {
259
+ public static getAllComponents(go: IGameObject | Object3D): Component[] {
258
260
  const componentsList = go.userData?.components;
259
261
  const newList = [...componentsList];
260
262
  return newList;
@@ -294,7 +296,7 @@
294
296
  abstract set worldQuaternion(val: Quaternion);
295
297
  abstract get worldQuaternion(): Quaternion;
296
298
  abstract set worldRotation(val: Vector3);
297
- abstract get worldRotation(): Vector3;
299
+ abstract get worldRotation(): Vector3;
298
300
  abstract set worldScale(val: Vector3);
299
301
  abstract get worldScale(): Vector3;
300
302
 
@@ -305,17 +307,28 @@
305
307
 
306
308
 
307
309
 
308
- export class Component implements IComponent, EventTarget {
310
+ /** Needle Engine component base class. Derive from this component to implement your own using the provided lifecycle methods. Components can be added to threejs objects using `GameObject.addComponent`.
311
+ *
312
+ * The most common lifecycle methods are `awake`, `start`, `onEanble`, `onDisable` `update` and `onDestroy`.
313
+ * XR specific callbacks include `onEnterXR`, `onLeaveXR`, `onUpdateXR`, `onControllerAdded` and `onControllerRemoved`.
314
+ * To receive pointer events implement `onPointerDown`, `onPointerUp`, `onPointerEnter`, `onPointerExit` and `onPointerMove`.
315
+ */
316
+ export abstract class Component implements IComponent, EventTarget,
317
+ Partial<INeedleXRSessionEventReceiver>,
318
+ Partial<IPointerEventHandler>
319
+ {
309
320
 
310
321
  get isComponent(): boolean { return true; }
311
322
 
312
323
  private __context: Context | undefined;
324
+ /** Use the context to get access to many Needle Engine features and use physics, timing, access the camera or scene */
313
325
  get context(): Context {
314
326
  return this.__context ?? Context.Current;
315
327
  }
316
328
  set context(context: Context) {
317
329
  this.__context = context;
318
330
  }
331
+ /** shorthand for `this.context.scene` */
319
332
  get scene(): Scene { return this.context.scene; }
320
333
 
321
334
  get layer(): number {
@@ -355,7 +368,7 @@
355
368
  return this.gameObject?.userData.hideFlags;
356
369
  }
357
370
 
358
-
371
+ /** @returns true if the object is enabled and active in the hierarchy */
359
372
  get activeAndEnabled(): boolean {
360
373
  if (this.destroyed) return false;
361
374
  if (this.__isEnabled === false) return false;
@@ -385,19 +398,27 @@
385
398
  this.gameObject[activeInHierarchyFieldName] = val;
386
399
  }
387
400
 
401
+ /** the object this component is attached to. Note that this is a threejs Object3D with some additional features */
388
402
  gameObject!: GameObject;
403
+ /** the unique identifier for this component */
389
404
  guid: string = "invalid";
405
+ /** holds the source identifier this object was created with/from (e.g. if it was part of a glTF file the sourceId holds the url to the glTF) */
390
406
  sourceId?: SourceIdentifier;
391
407
  // transform: Object3D = nullObject;
392
408
 
393
409
  /** called on a component with a map of old to new guids (e.g. when instantiate generated new guids and e.g. timeline track bindings needs to remape them) */
394
410
  resolveGuids?(guidsMap: GuidsMap): void;
395
411
 
396
- /** called once when the component becomes active for the first time */
412
+ /** called once when the component becomes active for the first time (once per component)
413
+ * This is the first callback to be called */
397
414
  awake() { }
398
- /** called every time when the component gets enabled (this is invoked after awake and before start) */
415
+ /** called every time when the component gets enabled (this is invoked after awake and before start)
416
+ * or when it becomes active in the hierarchy (e.g. if a parent object or this.gameObject gets set to visible)
417
+ */
399
418
  onEnable() { }
419
+ /** called every time the component gets disabled or if a parent object (or this.gameObject) gets set to invisible */
400
420
  onDisable() { }
421
+ /** Called when the component gets destroyed */
401
422
  onDestroy() {
402
423
  this.__destroyed = true;
403
424
  }
@@ -409,11 +430,17 @@
409
430
  /** Called for all scripts when the context gets paused or unpaused */
410
431
  onPausedChanged?(isPaused: boolean, wasPaused: boolean): void;
411
432
 
433
+ /** called at the beginning of a frame (once per component) */
412
434
  start?(): void;
435
+ /** first callback in a frame (called every frame when implemented) */
413
436
  earlyUpdate?(): void;
437
+ /** regular callback in a frame (called every frame when implemented) */
414
438
  update?(): void;
439
+ /** late callback in a frame (called every frame when implemented) */
415
440
  lateUpdate?(): void;
441
+ /** called before the scene gets rendered in the main update loop */
416
442
  onBeforeRender?(frame: XRFrame | null): void;
443
+ /** called after the scene was rendered */
417
444
  onAfterRender?(): void;
418
445
 
419
446
  onCollisionEnter?(col: Collision);
@@ -424,18 +451,79 @@
424
451
  onTriggerStay?(col: ICollider);
425
452
  onTriggerExit?(col: ICollider);
426
453
 
454
+
455
+ /** Optional callback, you can implement this to only get callbacks for VR or AR sessions if necessary.
456
+ * @returns true if the mode is supported (if false the mode is not supported by this component and it will not receive XR callbacks for this mode)
457
+ */
458
+ supportsXR?(mode: XRSessionMode): boolean;
459
+ /** Called before the XR session is requested. Use this callback if you want to modify the session init features */
460
+ onBeforeXR?(mode: XRSessionMode, args: XRSessionInit): void;
461
+ /** Callback when this component joins a xr session (or becomes active in a running XR session) */
462
+ onEnterXR?(args: NeedleXREventArgs): void;
463
+ /** Callback when a xr session updates (while it is still active in XR session) */
464
+ onUpdateXR?(args: NeedleXREventArgs): void;
465
+ /** Callback when this component exists a xr session (or when it becomes inactive in a running XR session) */
466
+ onLeaveXR?(args: NeedleXREventArgs): void;
467
+ /** Callback when a controller is connected/added while in a XR session
468
+ * OR when the component joins a running XR session that has already connected controllers
469
+ * OR when the component becomes active during a running XR session that has already connected controllers */
470
+ onXRControllerAdded?(args: NeedleXRControllerEventArgs): void;
471
+ /** callback when a controller is removed while in a XR session
472
+ * OR when the component becomes inactive during a running XR session
473
+ */
474
+ onXRControllerRemoved?(args: NeedleXRControllerEventArgs): void;
475
+
476
+
477
+ /* IPointerEventReceiver */
478
+ /* @inheritdoc */
479
+ onPointerEnter?(args: PointerEventData);
480
+ onPointerMove?(args: PointerEventData);
481
+ onPointerExit?(args: PointerEventData);
482
+ onPointerDown?(args: PointerEventData);
483
+ onPointerUp?(args: PointerEventData);
484
+ onPointerClick?(args: PointerEventData);
485
+
486
+
487
+ /** starts a coroutine (javascript generator function)
488
+ * `yield` will wait for the next frame:
489
+ * - Use `yield WaitForSeconds(1)` to wait for 1 second.
490
+ * - Use `yield WaitForFrames(10)` to wait for 10 frames.
491
+ * - Use `yield new Promise(...)` to wait for a promise to resolve.
492
+ * @param routine generator function to start
493
+ * @param evt event to register the coroutine for (default: FrameEvent.Update). Note that all coroutine FrameEvent callbacks are invoked after the matching regular component callbacks. For example `FrameEvent.Update` will be called after regular component `update()` methods)
494
+ * @returns the generator function (use it to stop the coroutine with `stopCoroutine`)
495
+ * @example
496
+ * ```ts
497
+ * onEnable() { this.startCoroutine(this.myCoroutine()); }
498
+ * private *myCoroutine() {
499
+ * while(this.activeAndEnabled) {
500
+ * console.log("Hello World", this.context.time.frame);
501
+ * // wait for 5 frames
502
+ * for(let i = 0; i < 5; i++) yield;
503
+ * }
504
+ * }
505
+ * ```
506
+ */
427
507
  startCoroutine(routine: Generator, evt: FrameEvent = FrameEvent.Update): Generator {
428
508
  return this.context.registerCoroutineUpdate(this, routine, evt);
429
509
  }
430
-
510
+ /**
511
+ * Stop a coroutine that was previously started with `startCoroutine`
512
+ * @param routine the routine to be stopped
513
+ * @param evt the frame event to unregister the routine from (default: FrameEvent.Update)
514
+ */
431
515
  stopCoroutine(routine: Generator, evt: FrameEvent = FrameEvent.Update): void {
432
516
  this.context.unregisterCoroutineUpdate(routine, evt);
433
517
  }
434
518
 
519
+ /** @returns true if this component was destroyed (`this.destroy()`) or the whole object this component was part of */
435
520
  public get destroyed(): boolean {
436
521
  return this.__destroyed;
437
522
  }
438
523
 
524
+ /**
525
+ * Destroys this component (and removes it from the object)
526
+ */
439
527
  public destroy() {
440
528
  if (this.__destroyed) return;
441
529
  this.__internalDestroy();
@@ -464,7 +552,11 @@
464
552
 
465
553
  /** @internal */
466
554
  constructor() {
467
- this.__internalNewInstanceCreated();
555
+ this.__didAwake = false;
556
+ this.__didStart = false;
557
+ this.__didEnable = false;
558
+ this.__isEnabled = undefined;
559
+ this.__destroyed = false;
468
560
  }
469
561
 
470
562
 
@@ -666,5 +758,6 @@
666
758
  }
667
759
  }
668
760
 
669
- export class Behaviour extends Component {
670
- }
761
+ // For legacy reasons we need to export this as well
762
+ // (and we don't use extend to inherit the component docs)
763
+ export { Component as Behaviour };
src/engine-components/codegen/components.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /* eslint-disable */
1
2
  // Export types
2
3
  export class __Ignore {}
3
4
  export { ActionBuilder } from "../export/usdz/extensions/behavior/BehavioursBuilder.js";
@@ -11,11 +12,11 @@
11
12
  export { Animator } from "../Animator.js";
12
13
  export { AnimatorController } from "../AnimatorController.js";
13
14
  export { Antialiasing } from "../postprocessing/Effects/Antialiasing.js";
14
- export { AttachedObject } from "../webxr/WebXRController.js";
15
15
  export { AudioExtension } from "../export/usdz/extensions/behavior/AudioExtension.js";
16
16
  export { AudioListener } from "../AudioListener.js";
17
17
  export { AudioSource } from "../AudioSource.js";
18
18
  export { AudioTrackHandler } from "../timeline/TimelineTracks.js";
19
+ export { Avatar } from "../webxr/Avatar.js";
19
20
  export { Avatar_Brain_LookAt } from "../avatar/Avatar_Brain_LookAt.js";
20
21
  export { Avatar_MouthShapes } from "../avatar/Avatar_MouthShapes.js";
21
22
  export { Avatar_MustacheShake } from "../avatar/Avatar_MustacheShake.js";
@@ -30,7 +31,6 @@
30
31
  export { BasicIKConstraint } from "../BasicIKConstraint.js";
31
32
  export { BehaviorExtension } from "../export/usdz/extensions/behavior/Behaviour.js";
32
33
  export { BehaviorModel } from "../export/usdz/extensions/behavior/BehavioursBuilder.js";
33
- export { Behaviour } from "../Component.js";
34
34
  export { Bloom } from "../postprocessing/Effects/Bloom.js";
35
35
  export { BoxCollider } from "../Collider.js";
36
36
  export { BoxGizmo } from "../Gizmos.js";
@@ -51,7 +51,6 @@
51
51
  export { ColorAdjustments } from "../postprocessing/Effects/ColorAdjustments.js";
52
52
  export { ColorBySpeedModule } from "../ParticleSystemModules.js";
53
53
  export { ColorOverLifetimeModule } from "../ParticleSystemModules.js";
54
- export { Component } from "../Component.js";
55
54
  export { ContactShadows } from "../ContactShadows.js";
56
55
  export { ControlTrackHandler } from "../timeline/TimelineTracks.js";
57
56
  export { CustomBranding } from "../export/usdz/USDZExporter.js";
@@ -88,7 +87,6 @@
88
87
  export { Image } from "../ui/Image.js";
89
88
  export { InheritVelocityModule } from "../ParticleSystemModules.js";
90
89
  export { InputField } from "../ui/InputField.js";
91
- export { Interactable } from "../Interactable.js";
92
90
  export { Light } from "../Light.js";
93
91
  export { LimitVelocityOverLifetimeModule } from "../ParticleSystemModules.js";
94
92
  export { LODGroup } from "../LODGroup.js";
@@ -102,6 +100,7 @@
102
100
  export { MeshRenderer } from "../Renderer.js";
103
101
  export { MinMaxCurve } from "../ParticleSystemModules.js";
104
102
  export { MinMaxGradient } from "../ParticleSystemModules.js";
103
+ export { NeedleWebXRHtmlElement } from "../webxr/WebXRButtons.js";
105
104
  export { NestedGltf } from "../NestedGltf.js";
106
105
  export { Networking } from "../Networking.js";
107
106
  export { NoiseModule } from "../ParticleSystemModules.js";
@@ -125,7 +124,6 @@
125
124
  export { PreliminaryAction } from "../export/usdz/extensions/behavior/BehaviourComponents.js";
126
125
  export { PreliminaryTrigger } from "../export/usdz/extensions/behavior/BehaviourComponents.js";
127
126
  export { RawImage } from "../ui/Image.js";
128
- export { Raycaster } from "../ui/Raycaster.js";
129
127
  export { Rect } from "../ui/RectTransform.js";
130
128
  export { RectTransform } from "../ui/RectTransform.js";
131
129
  export { ReflectionProbe } from "../ReflectionProbe.js";
@@ -153,6 +151,7 @@
153
151
  export { SizeOverLifetimeModule } from "../ParticleSystemModules.js";
154
152
  export { SkinnedMeshRenderer } from "../Renderer.js";
155
153
  export { SmoothFollow } from "../SmoothFollow.js";
154
+ export { SpatialGrabRaycaster } from "../ui/Raycaster.js";
156
155
  export { SpatialHtml } from "../ui/SpatialHtml.js";
157
156
  export { SpatialTrigger } from "../SpatialTrigger.js";
158
157
  export { SpatialTriggerReceiver } from "../SpatialTrigger.js";
@@ -167,7 +166,7 @@
167
166
  export { SyncedRoom } from "../SyncedRoom.js";
168
167
  export { SyncedTransform } from "../SyncedTransform.js";
169
168
  export { TapGestureTrigger } from "../export/usdz/extensions/behavior/BehaviourComponents.js";
170
- export { TeleportTarget } from "../webxr/WebXRController.js";
169
+ export { TeleportTarget } from "../webxr/TeleportTarget.js";
171
170
  export { TestRunner } from "../TestRunner.js";
172
171
  export { TestSimulateUserData } from "../TestRunner.js";
173
172
  export { Text } from "../ui/Text.js";
@@ -197,20 +196,16 @@
197
196
  export { Volume } from "../postprocessing/Volume.js";
198
197
  export { VolumeParameter } from "../postprocessing/VolumeParameter.js";
199
198
  export { VolumeProfile } from "../postprocessing/VolumeProfile.js";
200
- export { VRUserState } from "../webxr/WebXRSync.js";
201
- export { WebAR } from "../webxr/WebXR.js";
202
199
  export { WebARCameraBackground } from "../webxr/WebARCameraBackground.js";
203
200
  export { WebARSessionRoot } from "../webxr/WebARSessionRoot.js";
204
201
  export { WebXR } from "../webxr/WebXR.js";
205
- export { WebXRAvatar } from "../webxr/WebXRAvatar.js";
206
- export { WebXRController } from "../webxr/WebXRController.js";
207
202
  export { WebXRImageTracking } from "../webxr/WebXRImageTracking.js";
208
203
  export { WebXRImageTrackingModel } from "../webxr/WebXRImageTracking.js";
209
204
  export { WebXRPlaneTracking } from "../webxr/WebXRPlaneTracking.js";
210
- export { WebXRSync } from "../webxr/WebXRSync.js";
211
205
  export { WebXRTrackedImage } from "../webxr/WebXRImageTracking.js";
212
- export { XRFlag } from "../XRFlag.js";
213
- export { XRGrabModel } from "../webxr/WebXRGrabRendering.js";
214
- export { XRGrabRendering } from "../webxr/WebXRGrabRendering.js";
206
+ export { XRControllerFollow } from "../webxr/controllers/XRControllerFollow.js";
207
+ export { XRControllerModel } from "../webxr/controllers/XRControllerModel.js";
208
+ export { XRControllerMovement } from "../webxr/controllers/XRControllerMovement.js";
209
+ export { XRFlag } from "../webxr/XRFlag.js";
215
210
  export { XRRig } from "../webxr/WebXRRig.js";
216
- export { XRState } from "../XRFlag.js";
211
+ export { XRState } from "../webxr/XRFlag.js";
src/engine-components/ContactShadows.ts CHANGED
@@ -1,11 +1,11 @@
1
- import { Behaviour } from "./Component.js";
2
- import { serializable } from "../engine/engine_serialization_decorator.js";
3
-
4
1
  import { CustomBlending, DoubleSide, Group, Matrix4, MaxEquation, Mesh, MeshBasicMaterial, MeshDepthMaterial, MinEquation, OrthographicCamera, PlaneGeometry, ShaderMaterial, WebGLRenderTarget } from "three";
5
2
  import { HorizontalBlurShader } from 'three/examples/jsm/shaders/HorizontalBlurShader.js';
6
3
  import { VerticalBlurShader } from 'three/examples/jsm/shaders/VerticalBlurShader.js';
4
+
5
+ import { serializable } from "../engine/engine_serialization_decorator.js";
7
6
  import { getParam } from "../engine/engine_utils.js"
8
7
  import { setCustomVisibility } from "../engine/js-extensions/Layers.js";
8
+ import { Behaviour } from "./Component.js";
9
9
 
10
10
  const debug = getParam("debugcontactshadows");
11
11
 
src/engine/debug/debug_console.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { getErrorCount } from "./debug_overlay.js";
2
- import { getParam, isMobileDevice } from "../engine_utils.js";
3
1
  import { isLocalNetwork } from "../engine_networking_utils.js";
2
+ import { getParam, isMobileDevice, isQuest } from "../engine_utils.js";
3
+ import { isDevEnvironment } from "./debug.js";
4
+ import { getErrorCount, makeErrorsVisibleForDevelopment } from "./debug_overlay.js";
4
5
 
5
6
  let consoleInstance: any = null;
6
7
  let consoleHtmlElement: HTMLElement | null = null;
@@ -22,8 +23,11 @@
22
23
  currentUrl.searchParams.set("console", "1");
23
24
  console.log("🌵 Tip: You can add the \"?console\" query parameter to the url to show the debug console (on mobile it will automatically open in the bottom right corner when your get errors during development)", "\nOpen this page console: " + currentUrl.toString());
24
25
  }
25
- const isMobile = isMobileDevice();
26
+ const isMobile = isMobileDevice() || (isQuest() && isDevEnvironment());
26
27
  if (isMobile) {
28
+ // we need to invoke this here - otherwise we will miss errors that happen after the console is loaded
29
+ // and calling the method from the root needle-engine.ts import is evaluated later (if we import the method from the toplevel file and then invoke it)
30
+ makeErrorsVisibleForDevelopment();
27
31
  beginWatchingLogs();
28
32
  createConsole(true);
29
33
  if (isMobile) {
@@ -191,7 +195,7 @@
191
195
  }
192
196
  `;
193
197
  consoleHtmlElement?.prepend(styles);
194
- if (startHidden === true)
198
+ if (startHidden === true && getErrorCount() <= 0)
195
199
  hideDebugConsole();
196
200
  console.log("🌵 Debug console has loaded");
197
201
  }
src/engine/debug/debug_overlay.ts CHANGED
@@ -1,6 +1,6 @@
1
+ import { ContextRegistry } from "../engine_context_registry.js";
2
+ import { isLocalNetwork } from "../engine_networking_utils.js";
1
3
  import { getParam } from "../engine_utils.js";
2
- import { isLocalNetwork } from "../engine_networking_utils.js";
3
- import { ContextRegistry } from "../engine_context_registry.js";
4
4
 
5
5
  const debug = getParam("debugdebug");
6
6
  let hide = false;
@@ -15,7 +15,7 @@
15
15
  }
16
16
 
17
17
  export function getErrorCount() {
18
- return errorCount;
18
+ return _errorCount;
19
19
  }
20
20
 
21
21
  const originalConsoleError = console.error;
@@ -37,9 +37,10 @@
37
37
  if (hide) return;
38
38
  const isLocal = isLocalNetwork();
39
39
  if (debug) console.log("Is this a local network?", isLocal);
40
- if (isLocal) {
40
+ if (isLocal)
41
+ {
41
42
  if (debug)
42
- console.log(window.location.hostname);
43
+ console.warn("Patch console", window.location.hostname);
43
44
  console.error = patchedConsoleError;
44
45
  window.addEventListener("error", (event) => {
45
46
  if (hide) return;
@@ -66,10 +67,10 @@
66
67
  }
67
68
 
68
69
 
69
- let errorCount = 0;
70
+ let _errorCount = 0;
70
71
 
71
72
  function onReceivedError() {
72
- errorCount += 1;
73
+ _errorCount += 1;
73
74
  }
74
75
 
75
76
  function onParseError(args: Array<any>) {
src/engine/debug/debug.ts CHANGED
@@ -1,10 +1,13 @@
1
+ import { isLocalNetwork } from "../engine_networking_utils.js";
2
+ import { getParam } from "../engine_utils.js";
3
+ import { showDebugConsole } from "./debug_console.js";
1
4
  import { addLog, LogType, setAllowOverlayMessages } from "./debug_overlay.js";
2
- import { showDebugConsole } from "./debug_console.js";
3
- import { isLocalNetwork } from "../engine_networking_utils.js";
4
5
 
5
6
  export { showDebugConsole }
6
7
  export { LogType, setAllowOverlayMessages };
7
8
 
9
+ const noDevLogs = getParam("nodevlogs");
10
+
8
11
  /** Displays a debug message on screen for a certain amount of time */
9
12
  export function showBalloonMessage(text: string, logType: LogType = LogType.Log): void {
10
13
  addLog(logType, text);
@@ -22,6 +25,7 @@
22
25
 
23
26
  /** True when the application runs on a local url */
24
27
  export function isDevEnvironment(): boolean {
28
+ if (noDevLogs) return false;
25
29
  if (_manuallySetDevEnvironment !== undefined) return _manuallySetDevEnvironment;
26
30
  return isLocalNetwork();
27
31
  }
src/engine-components/DeleteBox.ts CHANGED
@@ -1,5 +1,6 @@
1
1
 
2
2
  import * as THREE from "three";
3
+
3
4
  import { syncDestroy } from "../engine/engine_networking_instantiate.js";
4
5
  import { getParam } from "../engine/engine_utils.js";
5
6
  import { BoxHelperComponent } from "./BoxHelperComponent.js";
src/engine-components/postprocessing/Effects/DepthOfField.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { DepthOfFieldEffect } from "postprocessing";
2
+
3
+ import { Mathf } from "../../../engine/engine_math.js";
2
4
  import { serializable } from "../../../engine/engine_serialization.js";
3
- import { Mathf } from "../../../engine/engine_math.js";
4
5
  import { getParam, isMobileDevice } from "../../../engine/engine_utils.js";
5
6
  import { PostProcessingEffect } from "../PostProcessingEffect.js";
6
7
  import { VolumeParameter } from "../VolumeParameter.js";
src/engine-components/DeviceFlag.ts CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
+ import { serializable } from "../engine/engine_serialization_decorator.js";
2
3
  import { isMobileDevice } from "../engine/engine_utils.js";
3
- import { serializable } from "../engine/engine_serialization_decorator.js";
4
4
  import { Behaviour, GameObject } from "./Component.js";
5
5
 
6
6
 
src/engine-components/DragControls.ts CHANGED
@@ -1,104 +1,126 @@
1
- import { GameObject } from "./Component.js";
2
- import { SyncedTransform } from "./SyncedTransform.js";
3
- import type { IPointerDownHandler, IPointerEnterHandler, IPointerEventHandler, IPointerExitHandler, IPointerUpHandler, PointerEventData } from "./ui/PointerEvents.js";
1
+ import { AxesHelper, Box3, BufferGeometry, Camera, Color, Event, Line, LineBasicMaterial, Matrix3, Matrix4, Mesh, MeshBasicMaterial, Object3D, Plane, PlaneHelper, Quaternion, Ray, Raycaster, SphereGeometry, Vector3 } from "three";
2
+
3
+ import { Gizmos } from "../engine/engine_gizmos.js";
4
+ import { InstancingUtil } from "../engine/engine_instancing.js";
5
+ import { Mathf } from "../engine/engine_math.js";
6
+ import { RaycastOptions } from "../engine/engine_physics.js";
7
+ import { serializable } from "../engine/engine_serialization_decorator.js";
4
8
  import { Context } from "../engine/engine_setup.js";
5
- import { Interactable, UsageMarker } from "./Interactable.js";
6
- import { Rigidbody } from "./RigidBody.js";
7
- import { WebXR } from "./webxr/WebXR.js";
9
+ import { getWorldPosition, setWorldPosition } from "../engine/engine_three_utils.js";
10
+ import { IGameObject } from "../engine/engine_types.js";
11
+ import { getParam } from "../engine/engine_utils.js";
12
+ import { NeedleXRSession } from "../engine/engine_xr.js";
8
13
  import { Avatar_POI } from "./avatar/Avatar_Brain_LookAt.js";
9
- import { RaycastOptions } from "../engine/engine_physics.js";
10
- import { getWorldPosition, setWorldPosition } from "../engine/engine_three_utils.js";
11
- import type { KeyCode } from "../engine/engine_input.js";
12
- import { nameofFactory } from "../engine/engine_utils.js";
13
- import { InstancingUtil } from "../engine/engine_instancing.js";
14
+ import { Behaviour, GameObject } from "./Component.js";
15
+ import { UsageMarker } from "./Interactable.js";
14
16
  import { OrbitControls } from "./OrbitControls.js";
15
- import { BufferGeometry, Camera, Color, Line, LineBasicMaterial, Matrix4, Mesh, MeshBasicMaterial, Object3D, Plane, Ray, Raycaster, SphereGeometry, Vector2, Vector3 } from "three";
17
+ import { Rigidbody } from "./RigidBody.js";
18
+ import { SyncedTransform } from "./SyncedTransform.js";
19
+ import type { IPointerEventHandler, PointerEventData } from "./ui/PointerEvents.js";
16
20
  import { ObjectRaycaster } from "./ui/Raycaster.js";
17
- import { serializable } from "../engine/engine_serialization_decorator.js";
18
21
 
19
- const debug = false;
22
+ const debug = getParam("debugdrag");
20
23
 
21
- export enum DragEvents {
22
- SelectStart = "selectstart",
23
- SelectEnd = "selectend",
24
+ export enum DragMode {
25
+ /** Object stays at the same horizontal plane as it started. Commonly used for objects on the floor */
26
+ XZPlane = 0,
27
+ /** Object is dragged as if it was attached to the pointer. In 2D, that means it's dragged along the camera screen plane. In XR, it's dragged by the controller/hand. */
28
+ Attached = 1,
29
+ /** Object is dragged along the initial raycast hit normal. */
30
+ HitNormal = 2,
31
+ /** Combination of XZ and Screen based on the viewing angle. Low angles result in Screen dragging and higher angles in XZ dragging. */
32
+ DynamicViewAngle = 3,
33
+ /** The drag plane is adjusted dynamically while dragging. */
34
+ SnapToSurfaces = 4,
24
35
  }
25
36
 
26
- interface SelectArgs {
27
- selected: Object3D;
28
- attached: Object3D | GameObject | null;
29
- }
37
+ export class DragControls extends Behaviour implements IPointerEventHandler {
30
38
 
39
+ // dragPlane (floor, object, view)
40
+ // snap to surface (snap orientation?)
41
+ // two-handed drag (scale, rotate, move)
42
+ // keep upright (no tilt)
31
43
 
32
- export interface IDragEventListener {
33
- onDragStart?();
34
- onDragEnd?();
35
- }
44
+ /** How and where the object is dragged along. */
45
+ @serializable()
46
+ public dragMode: DragMode = DragMode.DynamicViewAngle;
36
47
 
37
- export class DragControls extends Interactable implements IPointerEventHandler {
48
+ /** Snap dragged objects to a XYZ grid – 0 means: no snapping. */
49
+ @serializable()
50
+ public snapGridResolution: number = 0.0;
51
+
52
+ /** Keep the original rotation of the dragged object. */
53
+ @serializable()
54
+ public keepRotation: boolean = true;
55
+
56
+ /** How and where the object is dragged along while dragging in XR. */
57
+ @serializable()
58
+ public xrDragMode: DragMode = DragMode.Attached;
38
59
 
39
- private static _active: number = 0;
40
- public static get HasAnySelected(): boolean { return this._active > 0; }
60
+ /** Keep the original rotation of the dragged object while dragging in XR. */
61
+ @serializable()
62
+ public xrKeepRotation: boolean = false;
41
63
 
42
- /** Show's drag gizmos when enabled */
64
+ /** Accelerate dragging objects closer / further away when in XR */
43
65
  @serializable()
44
- public showGizmo: boolean = true;
66
+ public xrDistanceDragFactor: number = 1;
45
67
 
46
- /** When enabled DragControls will drag vertically when the object is viewed from a low angle */
68
+ /** When enabled, draws a line from the dragged object downwards to the next raycast hit. */
47
69
  @serializable()
48
- public useViewAngle: boolean = true;
70
+ public showGizmo: boolean = false;
49
71
 
50
- public transformSelf: boolean = true;
51
- // public transformGroup: boolean = true;
52
- // public targets: Object3D[] | null = null;
72
+ // future:
73
+ // constraints?
53
74
 
54
- // private controls: Control | null = null;
75
+ public static get HasAnySelected(): boolean { return this._active > 0; }
76
+ private static _active: number = 0;
77
+
78
+ /** The object to be dragged – we pass this to handlers when they are created */
79
+ private targetObject: GameObject | null = null;
55
80
  private orbit: OrbitControls | null = null;
81
+ private _dragHelper: LegacyDragVisualsHelper | null = null;
82
+ private static lastHovered: Object3D;
83
+ private _draggingRigidbodies: Rigidbody[] = [];
84
+ private _potentialDragStartEvt: PointerEventData | null = null;
85
+ private _dragHandlers: Map<Object3D, IDragHandler> = new Map();
86
+ private _totalMovement: Vector3 = new Vector3();
87
+ /** A marker is attached to components that are currently interacted with, to e.g. prevent them from being deleted. */
88
+ private _marker: UsageMarker | null = null;
89
+ private _isDragging: boolean = false;
90
+ private _didDrag: boolean = false;
56
91
 
57
- private selectStartEventListener: ((controls: DragControls, args: SelectArgs) => void)[] = [];
58
- private selectEndEventListener: Array<Function> = [];
59
- private _dragHelper: DragHelper | null = null;
60
-
61
- constructor() {
62
- super();
63
- this.selectStartEventListener = [];
64
- this.selectEndEventListener = [];
65
- this._dragDelta = new Vector2();
92
+ setTargetObject(obj: Object3D | null) {
93
+ this.targetObject = obj as GameObject;
94
+ for (const handler of this._dragHandlers.values()) {
95
+ handler.setTargetObject(obj);
96
+ }
66
97
  }
67
98
 
68
-
69
- // TODO: Update DragEventListener code
70
- addDragEventListener(type: DragEvents, cb: (ctrls: DragControls, args: SelectArgs) => void | Function) {
71
- switch (type) {
72
- case DragEvents.SelectStart:
73
- this.selectStartEventListener.push(cb);
74
- break;
75
- case DragEvents.SelectEnd:
76
- this.selectEndEventListener.push(cb);
77
- break;
78
- }
99
+ awake() {
100
+ // initialize all data that may be cloned incorrectly otherwise
101
+ this._potentialDragStartEvt = null;
102
+ this._dragHandlers = new Map();
103
+ this._totalMovement = new Vector3();
104
+ this._marker = null;
105
+ this._isDragging = false;
106
+ this._didDrag = false;
107
+ this._dragHelper = null;
108
+ this._draggingRigidbodies = [];
79
109
  }
80
110
 
81
-
82
-
83
111
  start() {
84
112
  this.orbit = GameObject.findObjectOfType(OrbitControls, this.context);
85
- if (!this.gameObject.getComponentInParent(ObjectRaycaster)) {
113
+ if (!this.gameObject.getComponentInParent(ObjectRaycaster))
86
114
  this.gameObject.addNewComponent(ObjectRaycaster);
87
- }
88
115
  }
89
116
 
90
- private static lastHovered: Object3D;
91
- private _draggingRigidbodies: Rigidbody[] = [];
92
-
93
117
  private allowEdit(_obj: Object3D | null = null) {
94
118
  return this.context.connection.allowEditing;
95
119
  }
96
120
 
97
121
  onPointerEnter(evt: PointerEventData) {
98
122
  if (!this.allowEdit(this.gameObject)) return;
99
- if (WebXR.IsInWebXR) return;
100
- // const interactable = GameObject.getComponentInParent(evt.object, Interactable);
101
- // if (!interactable) return;
123
+ if (evt.mode !== "screen") return;
102
124
  const dc = GameObject.getComponentInParent(evt.object, DragControls);
103
125
  if (!dc || dc !== this) return;
104
126
  DragControls.lastHovered = evt.object;
@@ -107,83 +129,121 @@
107
129
 
108
130
  onPointerExit(evt: PointerEventData) {
109
131
  if (!this.allowEdit(this.gameObject)) return;
110
- if (WebXR.IsInWebXR) return;
132
+ if (evt.mode !== "screen") return;
111
133
  if (DragControls.lastHovered !== evt.object) return;
112
- // const interactable = GameObject.getComponentInParent(evt.object, Interactable);
113
- // if (!interactable) return;
114
134
  this.context.domElement.style.cursor = 'auto';
115
135
  }
116
136
 
117
- private _waitingForDragStart: PointerEventData | null = null;
118
-
119
137
  onPointerDown(args: PointerEventData) {
120
138
  if (!this.allowEdit(this.gameObject)) return;
121
- if (WebXR.IsInWebXR) return;
122
- DragControls._active += 1;
123
- this._dragDelta.set(0, 0);
124
- this._didDrag = false;
125
- // Clone to not modify the original event (and this event is used in the actual onDragStart method)
126
- this._waitingForDragStart = args.clone();
127
- args.stopPropagation();
128
- // disabling pointer controls here already, otherwise we get a few frames of movement event in orbit controls and this will rotate the camera sligthly AFTER drag controls dragging ends.
129
- if (this.orbit) this.orbit.enabled = false;
139
+ if (args.used) return;
140
+ DragControls.lastHovered = args.object;
141
+
142
+ if (args.button === 0) {
143
+ if (this._dragHandlers.size === 0) {
144
+ this._didDrag = false;
145
+ this._totalMovement.set(0, 0, 0);
146
+ this._potentialDragStartEvt = args;
147
+ }
148
+
149
+ DragControls._active += 1;
150
+
151
+ const newDragHandler = new DragPointerHandler(this, this.targetObject || this.gameObject);
152
+ this._dragHandlers.set(args.event.space, newDragHandler);
153
+
154
+ // We need to turn off OrbitControls immediately, otherwise they still get data for a short moment
155
+ // and they don't properly handle being disabled while already processing data (smoothing happens when enabling again)
156
+ if (this.orbit) this.orbit.enabled = false;
157
+
158
+ newDragHandler.onDragStart(args);
159
+
160
+ if (this._dragHandlers.size === 2) {
161
+ const iterator = this._dragHandlers.values();
162
+ const a = iterator.next().value;
163
+ const b = iterator.next().value;
164
+ const mtHandler = new MultiTouchDragHandler(this, this.targetObject || this.gameObject, a, b);
165
+ this._dragHandlers.set(this.gameObject, mtHandler);
166
+
167
+ mtHandler.onDragStart(args);
168
+ }
169
+
170
+ args.use();
171
+ }
130
172
  }
131
173
 
132
174
  onPointerMove(args: PointerEventData) {
133
- if(this._isDragging || this._waitingForDragStart !== null) args.use();
175
+ if (this._isDragging || this._potentialDragStartEvt !== null) args.use();
134
176
  }
135
177
 
136
178
  onPointerUp(args: PointerEventData) {
137
- this._waitingForDragStart = null;
179
+
180
+ if(debug) Gizmos.DrawLabel(args.point ?? this.gameObject.worldPosition, "POINTERUP:" + args.pointerId + ", " + args.button, .03, 3);
181
+
138
182
  if (!this.allowEdit(this.gameObject)) return;
139
- if (DragControls._active > 0)
140
- DragControls._active -= 1;
141
- if (WebXR.IsInWebXR) return;
142
- this.onDragEnd(args);
143
- args.stopPropagation();
144
- if (this.orbit) this.orbit.enabled = true;
183
+ if (args.button !== 0) return;
184
+ this._potentialDragStartEvt = null;
185
+
186
+ const handler = this._dragHandlers.get(args.event.space);
187
+ const mtHandler = this._dragHandlers.get(this.gameObject) as MultiTouchDragHandler;
188
+ if (mtHandler && (mtHandler.handlerA === handler || mtHandler.handlerB === handler)) {
189
+ // any of the two handlers has been released, so we can remove the multi-touch handler
190
+ this._dragHandlers.delete(this.gameObject);
191
+ mtHandler.onDragEnd(args);
192
+ }
193
+
194
+ if (handler) {
195
+ if (DragControls._active > 0)
196
+ DragControls._active -= 1;
197
+
198
+ if (handler.onDragEnd) handler.onDragEnd(args);
199
+ this._dragHandlers.delete(args.event.space);
200
+
201
+ if (this._dragHandlers.size === 0) {
202
+ this.onLastDragEnd(args);
203
+ }
204
+ args.use();
205
+ }
206
+
207
+ if (DragControls._active === 0) {
208
+ if (this.orbit) this.orbit.enabled = true;
209
+ }
145
210
  }
146
211
 
147
-
148
212
  update(): void {
149
- if (WebXR.IsInWebXR) return;
150
213
 
214
+ for (const handler of this._dragHandlers.values()) {
215
+ if (handler.collectMovementInfo) handler.collectMovementInfo();
216
+ // TODO this doesn't make sense, we should instead just use the max here
217
+ // or even better, each handler can decide on their own how to handle this
218
+ if (handler.getTotalMovement) this._totalMovement.add(handler.getTotalMovement());
219
+ }
220
+
151
221
  // drag start only after having dragged for some pixels
152
- if (this._waitingForDragStart) {
222
+ if (this._potentialDragStartEvt) {
153
223
  if (!this._didDrag) {
154
- // this is so we can e.g. process clicks without having a drag change the position
155
- // e.g. a click to rotate the object
156
- const delta = this.context.input.getPointerPositionDelta(0);
157
- if (delta)
158
- this._dragDelta.add(delta);
159
- if (this._dragDelta.length() > 2)
224
+ // this is so we can e.g. process clicks without having a drag change the position, e.g. a click to call a method.
225
+ // TODO probably needs to be treated differently for spatial (3D motion) and screen (2D pixel motion) drags
226
+ if (this._totalMovement.length() > 0.0003)
160
227</