@needle-tools/engine 4.4.0-ci.1 → 4.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/dist/assets/generateMeshBVH.worker-b7788939.js +25 -0
  3. package/dist/needle-engine.bundle.js +7043 -6766
  4. package/dist/needle-engine.bundle.light.js +7205 -6928
  5. package/dist/needle-engine.bundle.light.min.js +169 -139
  6. package/dist/needle-engine.bundle.light.umd.cjs +173 -143
  7. package/dist/needle-engine.bundle.min.js +169 -139
  8. package/dist/needle-engine.bundle.umd.cjs +172 -142
  9. package/dist/needle-engine.js +468 -467
  10. package/dist/needle-engine.light.js +468 -467
  11. package/dist/needle-engine.light.min.js +1 -1
  12. package/dist/needle-engine.light.umd.cjs +1 -1
  13. package/dist/needle-engine.min.js +1 -1
  14. package/dist/needle-engine.umd.cjs +1 -1
  15. package/dist/vendor.js +5108 -5122
  16. package/dist/vendor.light.js +5108 -5122
  17. package/dist/vendor.light.min.js +84 -84
  18. package/dist/vendor.light.umd.cjs +78 -78
  19. package/dist/vendor.min.js +84 -84
  20. package/dist/vendor.umd.cjs +78 -78
  21. package/lib/engine/codegen/register_types.js +2 -0
  22. package/lib/engine/codegen/register_types.js.map +1 -1
  23. package/lib/engine/engine_addressables.js.map +1 -1
  24. package/lib/engine/engine_components.js +3 -1
  25. package/lib/engine/engine_components.js.map +1 -1
  26. package/lib/engine/engine_context.d.ts +141 -14
  27. package/lib/engine/engine_context.js +164 -26
  28. package/lib/engine/engine_context.js.map +1 -1
  29. package/lib/engine/engine_element.js +12 -10
  30. package/lib/engine/engine_element.js.map +1 -1
  31. package/lib/engine/engine_gameobject.js +5 -0
  32. package/lib/engine/engine_gameobject.js.map +1 -1
  33. package/lib/engine/engine_license.d.ts +2 -0
  34. package/lib/engine/engine_license.js +103 -62
  35. package/lib/engine/engine_license.js.map +1 -1
  36. package/lib/engine/engine_networking_blob.js +40 -24
  37. package/lib/engine/engine_networking_blob.js.map +1 -1
  38. package/lib/engine/engine_physics_rapier.js +10 -9
  39. package/lib/engine/engine_physics_rapier.js.map +1 -1
  40. package/lib/engine/engine_serialization_core.js +6 -2
  41. package/lib/engine/engine_serialization_core.js.map +1 -1
  42. package/lib/engine/engine_utils_screenshot.js +1 -1
  43. package/lib/engine/engine_utils_screenshot.js.map +1 -1
  44. package/lib/engine/js-extensions/RGBAColor.d.ts +1 -0
  45. package/lib/engine/js-extensions/RGBAColor.js +56 -7
  46. package/lib/engine/js-extensions/RGBAColor.js.map +1 -1
  47. package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +4 -3
  48. package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js.map +1 -1
  49. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +6 -6
  50. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js.map +1 -1
  51. package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +4 -0
  52. package/lib/engine/webcomponents/needle menu/needle-menu.js +13 -2
  53. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  54. package/lib/engine/xr/NeedleXRController.js +2 -3
  55. package/lib/engine/xr/NeedleXRController.js.map +1 -1
  56. package/lib/engine/xr/NeedleXRSession.js +12 -12
  57. package/lib/engine/xr/NeedleXRSession.js.map +1 -1
  58. package/lib/engine-components/AudioSource.js +7 -0
  59. package/lib/engine-components/AudioSource.js.map +1 -1
  60. package/lib/engine-components/Camera.js +18 -12
  61. package/lib/engine-components/Camera.js.map +1 -1
  62. package/lib/engine-components/CameraUtils.js +8 -14
  63. package/lib/engine-components/CameraUtils.js.map +1 -1
  64. package/lib/engine-components/GroundProjection.js +1 -1
  65. package/lib/engine-components/GroundProjection.js.map +1 -1
  66. package/lib/engine-components/NeedleMenu.js +1 -1
  67. package/lib/engine-components/NeedleMenu.js.map +1 -1
  68. package/lib/engine-components/OrbitControls.js +3 -1
  69. package/lib/engine-components/OrbitControls.js.map +1 -1
  70. package/lib/engine-components/ReflectionProbe.d.ts +1 -0
  71. package/lib/engine-components/ReflectionProbe.js +16 -9
  72. package/lib/engine-components/ReflectionProbe.js.map +1 -1
  73. package/lib/engine-components/SceneSwitcher.js +28 -7
  74. package/lib/engine-components/SceneSwitcher.js.map +1 -1
  75. package/lib/engine-components/SyncedTransform.d.ts +6 -0
  76. package/lib/engine-components/SyncedTransform.js +10 -5
  77. package/lib/engine-components/SyncedTransform.js.map +1 -1
  78. package/lib/engine-components/codegen/components.d.ts +1 -0
  79. package/lib/engine-components/codegen/components.js +1 -0
  80. package/lib/engine-components/codegen/components.js.map +1 -1
  81. package/lib/engine-components/export/usdz/extensions/USDZUI.js +2 -1
  82. package/lib/engine-components/export/usdz/extensions/USDZUI.js.map +1 -1
  83. package/lib/engine-components/ui/BaseUIComponent.d.ts +0 -1
  84. package/lib/engine-components/ui/BaseUIComponent.js +1 -1
  85. package/lib/engine-components/ui/BaseUIComponent.js.map +1 -1
  86. package/lib/engine-components/ui/EventSystem.js +1 -1
  87. package/lib/engine-components/ui/EventSystem.js.map +1 -1
  88. package/lib/engine-components/ui/Graphic.js +1 -0
  89. package/lib/engine-components/ui/Graphic.js.map +1 -1
  90. package/lib/engine-components/ui/RaycastUtils.js +1 -1
  91. package/lib/engine-components/ui/RaycastUtils.js.map +1 -1
  92. package/lib/engine-components/ui/RectTransform.d.ts +2 -2
  93. package/lib/engine-components/ui/RectTransform.js +9 -6
  94. package/lib/engine-components/ui/RectTransform.js.map +1 -1
  95. package/lib/engine-components/ui/Symbols.d.ts +1 -0
  96. package/lib/engine-components/ui/Symbols.js +2 -0
  97. package/lib/engine-components/ui/Symbols.js.map +1 -0
  98. package/lib/engine-components/ui/Utils.js +1 -1
  99. package/lib/engine-components/ui/Utils.js.map +1 -1
  100. package/lib/engine-components/utils/EnvironmentScene.d.ts +1 -1
  101. package/lib/engine-components/utils/EnvironmentScene.js +1 -1
  102. package/lib/engine-components/utils/EnvironmentScene.js.map +1 -1
  103. package/package.json +3 -3
  104. package/plugins/common/license.js +115 -27
  105. package/plugins/types/userconfig.d.ts +8 -0
  106. package/plugins/vite/build-pipeline.js +1 -1
  107. package/plugins/vite/defines.js +3 -1
  108. package/plugins/vite/dependencies.js +23 -4
  109. package/plugins/vite/facebook-instant-games.js +7 -4
  110. package/plugins/vite/index.js +1 -1
  111. package/plugins/vite/license.js +2 -1
  112. package/src/engine/codegen/register_types.ts +2 -0
  113. package/src/engine/engine_addressables.ts +3 -2
  114. package/src/engine/engine_components.ts +2 -1
  115. package/src/engine/engine_context.ts +169 -33
  116. package/src/engine/engine_element.ts +10 -9
  117. package/src/engine/engine_gameobject.ts +7 -0
  118. package/src/engine/engine_license.ts +116 -67
  119. package/src/engine/engine_networking_blob.ts +47 -26
  120. package/src/engine/engine_physics_rapier.ts +10 -12
  121. package/src/engine/engine_serialization_core.ts +6 -1
  122. package/src/engine/engine_utils_screenshot.ts +1 -1
  123. package/src/engine/js-extensions/RGBAColor.ts +59 -8
  124. package/src/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +6 -6
  125. package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +6 -6
  126. package/src/engine/webcomponents/needle menu/needle-menu.ts +13 -2
  127. package/src/engine/xr/NeedleXRController.ts +2 -3
  128. package/src/engine/xr/NeedleXRSession.ts +12 -12
  129. package/src/engine-components/AudioSource.ts +8 -3
  130. package/src/engine-components/Camera.ts +34 -22
  131. package/src/engine-components/CameraUtils.ts +8 -16
  132. package/src/engine-components/GroundProjection.ts +1 -1
  133. package/src/engine-components/NeedleMenu.ts +1 -1
  134. package/src/engine-components/OrbitControls.ts +2 -1
  135. package/src/engine-components/ReflectionProbe.ts +15 -8
  136. package/src/engine-components/SceneSwitcher.ts +28 -11
  137. package/src/engine-components/SyncedTransform.ts +11 -6
  138. package/src/engine-components/codegen/components.ts +1 -0
  139. package/src/engine-components/export/usdz/extensions/USDZUI.ts +2 -2
  140. package/src/engine-components/ui/BaseUIComponent.ts +1 -1
  141. package/src/engine-components/ui/EventSystem.ts +1 -1
  142. package/src/engine-components/ui/Graphic.ts +1 -1
  143. package/src/engine-components/ui/RaycastUtils.ts +1 -1
  144. package/src/engine-components/ui/RectTransform.ts +10 -7
  145. package/src/engine-components/ui/Symbols.ts +2 -0
  146. package/src/engine-components/ui/Utils.ts +2 -1
  147. package/src/engine-components/utils/EnvironmentScene.ts +1 -1
@@ -11,7 +11,7 @@ const _licenseCheckResultChangedCallbacks: ((result: boolean) => void)[] = [];
11
11
  // This is modified by a bundler (e.g. vite)
12
12
  // Do not edit manually
13
13
  let NEEDLE_ENGINE_LICENSE_TYPE: string = "basic";
14
- if (debug)
14
+ if (debug)
15
15
  console.log("License Type: " + NEEDLE_ENGINE_LICENSE_TYPE)
16
16
 
17
17
  /** @internal */
@@ -33,15 +33,24 @@ export function hasIndieLicense() {
33
33
  return false;
34
34
  }
35
35
 
36
+ /** @internal */
37
+ export function hasEduLicense() {
38
+ switch (NEEDLE_ENGINE_LICENSE_TYPE) {
39
+ case "edu":
40
+ return true;
41
+ }
42
+ return false;
43
+ }
44
+
36
45
  /** @internal */
37
46
  export function hasCommercialLicense() {
38
- return hasProLicense() || hasIndieLicense();
47
+ return hasProLicense() || hasIndieLicense() || hasEduLicense();
39
48
  }
40
49
 
41
50
 
42
51
  /** @internal */
43
52
  export function onLicenseCheckResultChanged(cb: (result: boolean) => void) {
44
- if (hasProLicense() || hasIndieLicense())
53
+ if (hasProLicense() || hasIndieLicense() || hasEduLicense())
45
54
  return cb(true);
46
55
  _licenseCheckResultChangedCallbacks.push(cb);
47
56
  }
@@ -85,14 +94,17 @@ async function checkLicense() {
85
94
  invokeLicenseCheckResultChanged(true);
86
95
  }
87
96
  else if (res?.status === 403) {
97
+ invokeLicenseCheckResultChanged(false);
88
98
  applicationIsForbidden = true;
89
99
  applicationForbiddenText = await res.text();
90
100
  }
91
101
  else {
102
+ invokeLicenseCheckResultChanged(false);
92
103
  if (debug) console.log("License check failed with status " + res?.status);
93
104
  }
94
105
  }
95
106
  catch (err) {
107
+ invokeLicenseCheckResultChanged(false);
96
108
  if (debug) console.error("License check failed", err);
97
109
  }
98
110
  }
@@ -167,7 +179,9 @@ async function handleForbidden(ctx: IContext) {
167
179
 
168
180
  async function showLicenseInfo(ctx: IContext) {
169
181
  try {
170
- if (hasCommercialLicense() !== true) return onNonCommercialVersionDetected(ctx);
182
+ if (!hasProLicense() && !hasIndieLicense()) {
183
+ return onNonCommercialVersionDetected(ctx);
184
+ }
171
185
  }
172
186
  catch (err) {
173
187
  if (debug) console.log("License check failed", err)
@@ -179,85 +193,120 @@ async function showLicenseInfo(ctx: IContext) {
179
193
 
180
194
 
181
195
  async function onNonCommercialVersionDetected(ctx: IContext) {
196
+
182
197
  // if the engine loads faster than the license check, we need to capture the ready event here
183
198
  let isReady = false;
184
199
  ctx.domElement.addEventListener("ready", () => isReady = true);
185
200
 
186
201
  await runtimeLicenseCheckPromise?.catch(() => { });
187
- if (hasCommercialLicense()) return;
188
- logNonCommercialUse();
202
+
203
+
204
+ if (hasProLicense() || hasIndieLicense()) return;
205
+ if (hasCommercialLicense() === false) logNonCommercialUse();
189
206
 
190
207
  // check if the engine is already ready (meaning has finished loading)
191
- // if (isReady) {
192
- // insertNonCommercialUseHint(ctx);
193
- // }
194
- // else {
195
- // ctx.domElement.addEventListener("ready", () => {
196
- // insertNonCommercialUseHint(ctx);
197
- // });
198
- // }
208
+ if (isReady) {
209
+ insertNonCommercialUseHint(ctx);
210
+ }
211
+ else {
212
+ ctx.domElement.addEventListener("ready", () => {
213
+ insertNonCommercialUseHint(ctx);
214
+ });
215
+ }
199
216
  }
200
217
 
201
218
  // const licenseElementIdentifier = "needle-license-element";
202
219
  // const licenseDuration = 10000;
203
220
  // const licenseDelay = 1200;
204
- // function insertNonCommercialUseHint(ctx: IContext) {
205
-
206
- // return;
207
- // const banner = LicenseBanner.create();
208
- // ctx.domElement.shadowRoot?.appendChild(banner);
209
- // let bannerStyle = `
210
- // position: absolute;
211
- // bottom: 20px;
212
- // right: 20px;
213
- // opacity: 0;
214
- // transform: translateY(10px);
215
- // transition: all .5s ease-in-out 1s;
216
- // pointer: cursor;
217
- // `;
218
- // banner.style.cssText = bannerStyle;
219
- // let expectedBannerStyle = banner.style.cssText;
220
- // setTimeout(() => {
221
- // bannerStyle = bannerStyle.replace("opacity: 0", "opacity: 1");
222
- // bannerStyle = bannerStyle.replace("transform: translateY(10px)", "transform: translateY(0)");
223
- // banner.style.cssText = bannerStyle;
224
- // expectedBannerStyle = banner.style.cssText;
225
- // }, 100);
226
-
227
- // // ensure the banner is always visible
228
- // const interval = setInterval(() => {
229
- // const parent = ctx.domElement.shadowRoot || ctx.domElement;
230
- // if (banner.parentNode !== parent) {
231
- // parent.appendChild(banner);
232
- // }
233
- // if (expectedBannerStyle != banner.style.cssText) {
234
- // banner.style.cssText = bannerStyle;
235
- // expectedBannerStyle = banner.style.cssText;
236
- // showBalloonError("This website violates the Needle Engine License! Please contact hi@needle.tools");
237
- // }
238
- // }, 1000);
239
-
240
- // if (hasIndieLicense()) {
241
- // const removeDelay = licenseDuration + licenseDelay;
242
- // setTimeout(() => {
243
- // clearInterval(interval);
244
- // banner?.remove();
245
- // // show the logo every x minutes
246
- // const intervalInMinutes = 5;
247
- // setTimeout(() => {
248
- // if (ctx.domElement.parentNode)
249
- // insertNonCommercialUseHint(ctx);
250
- // }, 1000 * 60 * intervalInMinutes)
251
- // }, removeDelay);
252
- // }
253
-
254
- // }
221
+ function insertNonCommercialUseHint(ctx: IContext) {
222
+
223
+ const style = `
224
+ position: relative;
225
+ display: block;
226
+ background-size: 20px;
227
+ background-position: 10px 5px;
228
+ background-repeat:no-repeat;
229
+ background-image:url('${base64Logo}');
230
+ background-max-size: 40px;
231
+ padding: 10px;
232
+ padding-left: 30px;
233
+ `;
234
+ if (NEEDLE_ENGINE_LICENSE_TYPE === "edu") {
235
+ console.log("%c " + "Supported by Needle for Education – https://needle.tools", style);
236
+ }
237
+ else {
238
+ // if the user has a basic license we already show the logo in the menu and log a license message
239
+ return;
240
+ }
241
+
242
+ const banner = document.createElement("div");
243
+ banner.className = "needle-non-commercial-use";
244
+ banner.innerHTML = "Made with Needle for Education";
245
+ ctx.domElement.shadowRoot?.appendChild(banner);
246
+ let bannerStyle = `
247
+ position: absolute;
248
+ font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
249
+ font-size: 12px;
250
+ color: rgb(100, 100, 100);
251
+ /*mix-blend-mode: difference;*/
252
+ background-color: transparent;
253
+ z-index: 10000;
254
+
255
+ cursor: pointer;
256
+ user-select: none;
257
+ opacity: 0;
258
+
259
+ bottom: 6px;
260
+ right: 12px;
261
+ transform: translateY(0px);
262
+ transition: all .5s ease-in-out 1s;
263
+ `;
264
+ banner.style.cssText = bannerStyle;
265
+ banner.addEventListener("click", () => { window.open("https://needle.tools", "_blank") });
266
+ let expectedBannerStyle = banner.style.cssText;
267
+ setTimeout(() => {
268
+ bannerStyle = bannerStyle.replace("opacity: 0", "opacity: 1");
269
+ bannerStyle = bannerStyle.replace("transform: translateY(10px)", "transform: translateY(0)");
270
+ banner.style.cssText = bannerStyle;
271
+ expectedBannerStyle = banner.style.cssText;
272
+ }, 100);
273
+
274
+ // ensure the banner is always visible
275
+ const interval = setInterval(() => {
276
+ const parent = ctx.domElement.shadowRoot || ctx.domElement;
277
+ if (banner.parentNode !== parent) {
278
+ parent.appendChild(banner);
279
+ }
280
+ if (expectedBannerStyle != banner.style.cssText) {
281
+ banner.style.cssText = bannerStyle;
282
+ expectedBannerStyle = banner.style.cssText;
283
+ }
284
+ }, 1000);
285
+
286
+ if (hasEduLicense()) {
287
+ const removeDelay = 20_000;
288
+ setTimeout(() => {
289
+ clearInterval(interval);
290
+ banner?.remove();
291
+ // show the logo every x minutes
292
+ const intervalInMinutes = 5;
293
+ setTimeout(() => {
294
+ if (ctx.domElement.parentNode)
295
+ insertNonCommercialUseHint(ctx);
296
+ }, 1000 * 60 * intervalInMinutes)
297
+ }, removeDelay);
298
+ }
299
+
300
+ }
301
+
302
+
303
+ const base64Logo = "data:image/webp;base64,UklGRrABAABXRUJQVlA4WAoAAAAQAAAAHwAAHwAAQUxQSKEAAAARN6CmbSM4WR7vdARON11EBDq3fLiNbVtVzpMCPlKAEzsx0Y/x+Ovuv4dn0EFE/ydAvz6YggXzgh5sVgXM/zOC/4sii7qgGvB5N7hmuQYwkvazWAu1JPW41FXSHq6pnaQWvqYH18Fc0j1hO/BFTtIeSBlJi5w6qIIO7IOrwhFsB2Yxukif0FTRLpXswHR8MxbslKe9VZsn/Ub5C7YFOpqSTABWUDgg6AAAAFAGAJ0BKiAAIAA+7VyoTqmkpCI3+qgBMB2JbACdMt69DwMIQBLhkTO6XwY00UEDK6cNIDnuNibPf0EgAP7Y1myuiQHLDsF/0h5unrGh6WAbv7aegg2ZMd3uRKfT/3SJztcaujYfTvMXspfCTmYcoO6a+vhC3ss4M8uM58t4siiu59I4aOl59e9Sr6xoxYlHf2v+NnBNpJYeJf8jABQAId/PXuBkLEFkiCucgSGEcfhvajql/j3reCGl0M5/9gQWy7ayNPs+wlvIxFnNfSlfuND4CZOCyxOHhRqOmHN4ULHo3tCSrUNvgAA=";
304
+
255
305
  let lastLogTime = 0;
256
306
  async function logNonCommercialUse(_logo?: string) {
257
307
  const now = Date.now();
258
308
  if (now - lastLogTime < 2000) return;
259
309
  lastLogTime = now;
260
- const logo = "data:image/webp;base64,UklGRrABAABXRUJQVlA4WAoAAAAQAAAAHwAAHwAAQUxQSKEAAAARN6CmbSM4WR7vdARON11EBDq3fLiNbVtVzpMCPlKAEzsx0Y/x+Ovuv4dn0EFE/ydAvz6YggXzgh5sVgXM/zOC/4sii7qgGvB5N7hmuQYwkvazWAu1JPW41FXSHq6pnaQWvqYH18Fc0j1hO/BFTtIeSBlJi5w6qIIO7IOrwhFsB2Yxukif0FTRLpXswHR8MxbslKe9VZsn/Ub5C7YFOpqSTABWUDgg6AAAAFAGAJ0BKiAAIAA+7VyoTqmkpCI3+qgBMB2JbACdMt69DwMIQBLhkTO6XwY00UEDK6cNIDnuNibPf0EgAP7Y1myuiQHLDsF/0h5unrGh6WAbv7aegg2ZMd3uRKfT/3SJztcaujYfTvMXspfCTmYcoO6a+vhC3ss4M8uM58t4siiu59I4aOl59e9Sr6xoxYlHf2v+NnBNpJYeJf8jABQAId/PXuBkLEFkiCucgSGEcfhvajql/j3reCGl0M5/9gQWy7ayNPs+wlvIxFnNfSlfuND4CZOCyxOHhRqOmHN4ULHo3tCSrUNvgAA=";
261
310
  const style = `
262
311
  position: relative;
263
312
  display: block;
@@ -265,7 +314,7 @@ async function logNonCommercialUse(_logo?: string) {
265
314
  background-size: 20px;
266
315
  background-position: 10px 5px;
267
316
  background-repeat:no-repeat;
268
- background-image:url('${logo}');
317
+ background-image:url('${base64Logo}');
269
318
  background-max-size: 40px;
270
319
  margin-bottom: 5px;
271
320
  margin-top: .3em;
@@ -1,6 +1,7 @@
1
1
  // workaround for this is not a function when deployed
2
2
  // see https://github.com/pvorb/node-md5/issues/52
3
3
  import md5 from "md5";
4
+ import { FileLoader } from "three";
4
5
 
5
6
  import { showBalloonWarning } from "./debug/index.js";
6
7
  import { hasCommercialLicense } from "./engine_license.js";
@@ -219,35 +220,55 @@ export namespace BlobStorage {
219
220
  }
220
221
 
221
222
  export async function download(url: string, progressCallback?: (prog: ProgressEvent) => void): Promise<Uint8Array | null> {
222
- const response = await fetch(url);
223
-
224
- const reader = response.body?.getReader();
225
- const contentLength = response.headers.get('Content-Length');
226
- const total = contentLength ? parseInt(contentLength) : 0;
227
-
228
- if (!reader) return null;
229
-
230
- let received: number = 0;
231
- const chunks: Uint8Array[] = [];
232
- while (true) {
233
- const { done, value } = await reader.read();
234
- if (value) {
235
- chunks.push(value);
236
- received += value.length;
237
- progressCallback?.call(null, new ProgressEvent('progress', { loaded: received, total: total }));
238
- }
239
223
 
240
- if (done) {
241
- break;
224
+ // Using a FileLoader here instead of manually fetching so we're able to use the three.js cache system
225
+ const loader = new FileLoader();
226
+ loader.setResponseType('arraybuffer');
227
+ // loader.setRequestHeader( this.requestHeader );
228
+ // loader.setWithCredentials( this.withCredentials );
229
+ const res = await loader.loadAsync(url, prog => {
230
+ if (progressCallback) {
231
+ progressCallback.call(null, prog);
242
232
  }
233
+ });
234
+ if (!(res instanceof ArrayBuffer)) {
235
+ console.error("Download failed, no arraybuffer returned");
236
+ return null;
243
237
  }
244
- const final = new Uint8Array(received);
245
- let position = 0;
246
- for (const chunk of chunks) {
247
- final.set(chunk, position);
248
- position += chunk.length;
249
- }
250
- return final;
238
+ return new Uint8Array(res);
239
+
240
+
241
+ // Old solution: this didn't re-use the three.js loading cache. Using a FileLoader the GLTFLoader under the hood is smart enough to NOT start a new download request
242
+
243
+ // const response = await fetch(url);
244
+
245
+ // const reader = response.body?.getReader();
246
+ // const contentLength = response.headers.get('Content-Length');
247
+ // const total = contentLength ? parseInt(contentLength) : 0;
248
+
249
+ // if (!reader) return null;
250
+
251
+ // let received: number = 0;
252
+ // const chunks: Uint8Array[] = [];
253
+ // while (true) {
254
+ // const { done, value } = await reader.read();
255
+ // if (value) {
256
+ // chunks.push(value);
257
+ // received += value.length;
258
+ // progressCallback?.call(null, new ProgressEvent('progress', { loaded: received, total: total }));
259
+ // }
260
+
261
+ // if (done) {
262
+ // break;
263
+ // }
264
+ // }
265
+ // const final = new Uint8Array(received);
266
+ // let position = 0;
267
+ // for (const chunk of chunks) {
268
+ // final.set(chunk, position);
269
+ // position += chunk.length;
270
+ // }
271
+ // return final;
251
272
  }
252
273
  }
253
274
 
@@ -598,17 +598,15 @@ export class RapierPhysics implements IPhysicsEngine {
598
598
  scale.multiplyScalar(0.5);
599
599
 
600
600
  // prevent negative scale
601
- if (scale.x < 0)
602
- scale.x = Math.abs(scale.x);
603
- if (scale.y < 0)
604
- scale.y = Math.abs(scale.y);
605
- if (scale.z < 0)
606
- scale.z = Math.abs(scale.z);
601
+ if (scale.x < 0) scale.x = Math.abs(scale.x);
602
+ if (scale.y < 0) scale.y = Math.abs(scale.y);
603
+ if (scale.z < 0) scale.z = Math.abs(scale.z);
607
604
 
608
605
  // prevent zero scale - seems normals are flipped otherwise
609
- if (scale.x == 0) scale.x = 0.0000001;
610
- if (scale.y == 0) scale.y = 0.0000001;
611
- if (scale.z == 0) scale.z = 0.0000001;
606
+ const minSize = 0.0000001;
607
+ if (scale.x < minSize) scale.x = minSize;
608
+ if (scale.y < minSize) scale.y = minSize;
609
+ if (scale.z < minSize) scale.z = minSize;
612
610
 
613
611
  const desc = MODULES.RAPIER_PHYSICS.MODULE.ColliderDesc.cuboid(scale.x, scale.y, scale.z);
614
612
  // const objectLayerMask = collider.gameObject.layers.mask;
@@ -994,9 +992,9 @@ export class RapierPhysics implements IPhysicsEngine {
994
992
  const sc = col as IBoxCollider;
995
993
  const obj = col.gameObject;
996
994
  const scale = getWorldScale(obj, this._tempPosition);
997
- const newX = sc.size.x * 0.5 * scale.x;
998
- const newY = sc.size.y * 0.5 * scale.y;
999
- const newZ = sc.size.z * 0.5 * scale.z;
995
+ const newX = Math.abs(sc.size.x * 0.5 * scale.x);
996
+ const newY = Math.abs(sc.size.y * 0.5 * scale.y);
997
+ const newZ = Math.abs(sc.size.z * 0.5 * scale.z);
1000
998
  sizeHasChanged = cuboid.halfExtents.x !== newX || cuboid.halfExtents.y !== newY || cuboid.halfExtents.z !== newZ;
1001
999
  cuboid.halfExtents.x = newX;
1002
1000
  cuboid.halfExtents.y = newY;
@@ -517,11 +517,14 @@ function deserializeObjectWithType(data: any, typeOrConstructor: ConstructorConc
517
517
  // then we dont need to do anything else
518
518
  if (!typeIsFunction && currentValue) {
519
519
  if (currentValue instanceof Material) return currentValue;
520
- if (currentValue instanceof Texture) return currentValue;
521
520
  if (currentValue instanceof Mesh) return currentValue;
522
521
  if (currentValue instanceof BufferGeometry) return currentValue;
523
522
  if (currentValue instanceof AnimationClip) return currentValue;
523
+ // We need to call the RenderTexture deserializer so we can not just return here:
524
+ // if (currentValue instanceof Texture) return currentValue;
525
+ // So this has now been moved down below the deserializer code
524
526
  }
527
+
525
528
  // Removed this line because it prevents assigning serialized values to existing instances for e.g. EventList
526
529
  // https://linear.app/needle/issue/NE-5350
527
530
  // if (!typeIsFunction && currentValue instanceof type) return currentValue;
@@ -594,6 +597,8 @@ function deserializeObjectWithType(data: any, typeOrConstructor: ConstructorConc
594
597
 
595
598
  // console.log(type.prototype.get("$serializedTypes"));
596
599
 
600
+ // We check textures AFTER running deserializers because some textures might need to be converted to a RenderTexture
601
+ if (currentValue instanceof Texture) return currentValue;
597
602
 
598
603
  let instance: any = undefined;
599
604
  if (data && (data.isMaterial || data.isTexture || data.isObject3D || data instanceof AnimationClip)) {
@@ -175,7 +175,7 @@ export function screenshot2(opts: ScreenshotOptionsDataUrl | ScreenshotOptionsTe
175
175
  }
176
176
 
177
177
  const renderer = context.renderer;
178
- const isXRScreenshot = renderer.xr.enabled;
178
+ const isXRScreenshot = renderer.xr.enabled && renderer.xr.isPresenting;
179
179
 
180
180
 
181
181
  // Perform XR screenshot in onBeforeRender (after the screenshot we want to render the original camera view)
@@ -20,14 +20,15 @@ export class RGBAColor extends Color {
20
20
  */
21
21
  constructor(r: number, g: number, b: number, a: number);
22
22
  constructor(r: number | ColorRepresentation, g?: number, b?: number, a?: number) {
23
- // if the user passed in the r, g, b, a components
24
- if (g != undefined && b != undefined) {
25
- super(r as number, g, b);
26
- this.alpha = a as number;
27
- }
28
- // if the user passed in a color representation
29
- else {
30
- super(r);
23
+ // AMD compilation creates recursive super calls with local function wrappers
24
+ // Call super() first with the minimum arguments needed
25
+ super();
26
+
27
+ if (typeof r === "number" && typeof g === "number" && typeof b === "number") {
28
+ this.set(r, g, b);
29
+ this.alpha = typeof a === "number" ? a : 1;
30
+ } else if (r !== undefined) {
31
+ this.set(r as ColorRepresentation);
31
32
  this.alpha = 1;
32
33
  }
33
34
  }
@@ -73,4 +74,54 @@ export class RGBAColor extends Color {
73
74
  this.alpha = array[offset + 3];
74
75
  return super.fromArray(array, offset);
75
76
  }
77
+
78
+ static fromColorRepresentation(col: ColorRepresentation) {
79
+
80
+ if (typeof col === "string") {
81
+ if (col.trim() === "transparent") {
82
+ return new RGBAColor(0, 0, 0, 0);
83
+ }
84
+ // handle hex colors with alpha
85
+ if (col.startsWith("#") && col.length === 9) {
86
+ const hex = parseInt(col.slice(1, 9), 16);
87
+ const r = (hex >> 24) & 0xff;
88
+ const g = (hex >> 16) & 0xff;
89
+ const b = (hex >> 8) & 0xff;
90
+ const a = (hex >> 0) & 0xff;
91
+ return new RGBAColor(r / 255, g / 255, b / 255, a / 255);
92
+ }
93
+ // handle hex colors
94
+ else if (col.startsWith("#")) {
95
+ const hex = parseInt(col.slice(1), 16);
96
+ const r = (hex >> 16) & 0xff;
97
+ const g = (hex >> 8) & 0xff;
98
+ const b = (hex >> 0) & 0xff;
99
+ return new RGBAColor(r / 255, g / 255, b / 255, 1);
100
+ }
101
+ // handle rgba string
102
+ else if (col.startsWith("rgba")) {
103
+ const rgba = col.slice(5, -1).split(",").map(Number);
104
+ return new RGBAColor(rgba[0] / 255, rgba[1] / 255, rgba[2] / 255, rgba[3]);
105
+ }
106
+ // handle rgb string
107
+ else if (col.startsWith("rgb")) {
108
+ const rgb = col.slice(4, -1).split(",").map(Number);
109
+ return new RGBAColor(rgb[0] / 255, rgb[1] / 255, rgb[2] / 255, 1);
110
+ }
111
+ }
112
+ else if (Array.isArray(col)) {
113
+ // handle rgba colors
114
+ if (col.length === 4) {
115
+ return new RGBAColor(col[0], col[1], col[2], col[3]);
116
+ }
117
+ // handle rgb colors
118
+ else if (col.length === 3) {
119
+ return new RGBAColor(col[0], col[1], col[2], 1);
120
+ }
121
+ else {
122
+ console.error("Invalid color array length. Expected 3 or 4, got " + col.length);
123
+ }
124
+ }
125
+ return new RGBAColor(col);
126
+ }
76
127
  }
@@ -3,13 +3,13 @@ import { MeshBVH } from 'three-mesh-bvh';
3
3
 
4
4
  // Modified according to https://github.com/gkjohnson/three-mesh-bvh/issues/636#issuecomment-2209571751
5
5
  import { WorkerBase } from "three-mesh-bvh/src/workers/utils/WorkerBase.js";
6
- import generateMeshBVHWorker from "three-mesh-bvh/src/workers/generateMeshBVH.worker.js?worker&inline";
7
6
 
8
7
  export class GenerateMeshBVHWorker extends WorkerBase {
9
8
 
10
9
  constructor() {
11
-
12
- super(new generateMeshBVHWorker());
10
+ super(new Worker(new URL("three-mesh-bvh/src/workers/generateMeshBVH.worker.js", import.meta.url), {
11
+ type: 'module'
12
+ }));
13
13
  this.name = 'GenerateMeshBVHWorker';
14
14
 
15
15
  }
@@ -27,10 +27,10 @@ export class GenerateMeshBVHWorker extends WorkerBase {
27
27
 
28
28
  }
29
29
 
30
- worker.onerror = e => {
31
-
32
- reject( new Error( `GenerateMeshBVHWorker: ${ e.message }` ) );
30
+ worker.onerror = e => {
33
31
 
32
+ reject(new Error(`[GenerateMeshBVHWorker] ${e.message || "Unknown error. Please check the server console. If you're using vite try adding 'three-mesh-bvh' to 'optimizeDeps.exclude' in your vite.config.js"}`));
33
+
34
34
  };
35
35
 
36
36
  worker.onmessage = e => {
@@ -155,7 +155,7 @@ export class NeedleSpatialMenu {
155
155
  private readonly positionFilter = new OneEuroFilterXYZ(90, .5);
156
156
 
157
157
  private updateMenu() {
158
- performance.mark('NeedleSpatialMenu updateMenu start');
158
+ //performance.mark('NeedleSpatialMenu updateMenu start');
159
159
  const menu = this.getMenu();
160
160
  this.handleNeedleWatermark();
161
161
  this._context.scene.add(menu as any);
@@ -200,14 +200,14 @@ export class NeedleSpatialMenu {
200
200
  }
201
201
 
202
202
  if (this.uiisDirty) {
203
- performance.mark('SpatialMenu.update.uiisDirty.start');
203
+ //performance.mark('SpatialMenu.update.uiisDirty.start');
204
204
  this.uiisDirty = false;
205
205
  ThreeMeshUI.update();
206
- performance.mark('SpatialMenu.update.uiisDirty.end');
207
- performance.measure('SpatialMenu.update.uiisDirty', 'SpatialMenu.update.uiisDirty.start', 'SpatialMenu.update.uiisDirty.end');
206
+ //performance.mark('SpatialMenu.update.uiisDirty.end');
207
+ //performance.measure('SpatialMenu.update.uiisDirty', 'SpatialMenu.update.uiisDirty.start', 'SpatialMenu.update.uiisDirty.end');
208
208
  }
209
- performance.mark('NeedleSpatialMenu updateMenu end');
210
- performance.measure('SpatialMenu.update', 'NeedleSpatialMenu updateMenu start', 'NeedleSpatialMenu updateMenu end');
209
+ //performance.mark('NeedleSpatialMenu updateMenu end');
210
+ //performance.measure('SpatialMenu.update', 'NeedleSpatialMenu updateMenu start', 'NeedleSpatialMenu updateMenu end');
211
211
  }
212
212
 
213
213
  private ensureRenderOnTop(obj: Object3D, level: number = 0) {
@@ -174,6 +174,10 @@ export class NeedleMenu {
174
174
  this._spatialMenu?.showNeedleLogo(visible);
175
175
  // setTimeout(()=>this.showNeedleLogo(!visible), 1000);
176
176
  }
177
+ /** @returns true if the logo is visible */
178
+ get logoIsVisible() {
179
+ return this._menu.logoIsVisible;
180
+ }
177
181
  /** When enabled=true the menu will be visible in VR/AR sessions */
178
182
  showSpatialMenu(enabled: boolean) {
179
183
  this._spatialMenu.setEnabled(enabled);
@@ -640,7 +644,7 @@ export class NeedleMenuElement extends HTMLElement {
640
644
 
641
645
  </style>
642
646
 
643
- <div id="root" class="logo-visible floating-panel-style bottom">
647
+ <div id="root" class="logo-hidden floating-panel-style bottom">
644
648
  <div class="wrapper">
645
649
  <div class="foldout">
646
650
  <div class="options" part="options">
@@ -699,6 +703,9 @@ export class NeedleMenuElement extends HTMLElement {
699
703
  if (visible === undefined) visible = false;
700
704
  this.___onSetLogoVisible(visible);
701
705
  }
706
+ else {
707
+ this.___onSetLogoVisible(true);
708
+ }
702
709
  }));
703
710
  } catch (e) {
704
711
  console.error("[Needle Menu] License check failed.", e);
@@ -822,13 +829,17 @@ export class NeedleMenuElement extends HTMLElement {
822
829
  this._userRequestedLogoVisible = visible;
823
830
  if (!visible) {
824
831
  if (!hasCommercialLicense() || debugNonCommercial) {
825
- console.warn("Needle Menu: You need a PRO license to hide the Needle Engine logo.");
832
+ console.warn("[Needle Engine] You need a PRO license to hide the Needle Engine logo in production.");
826
833
  const localNetwork = isLocalNetwork()
827
834
  if (!localNetwork) return;
828
835
  }
829
836
  }
830
837
  this.___onSetLogoVisible(visible);
831
838
  }
839
+ /** @returns true if the logo is visible */
840
+ get logoIsVisible() {
841
+ return !this.root.classList.contains("logo-hidden");
842
+ }
832
843
 
833
844
  private ___onSetLogoVisible(visible: boolean) {
834
845
  this.logoContainer.style.display = "";
@@ -381,12 +381,11 @@ export class NeedleXRController implements IPointerHitEventReceiver {
381
381
  }
382
382
 
383
383
  onUpdate(frame: XRFrame) {
384
- performance.mark('NeedleXRController onUpdate start');
385
384
  this.onUpdateFrame(frame);
386
385
  this.updateInputEvents();
387
386
  this.onUpdateMove();
388
- performance.mark('NeedleXRController onUpdate end');
389
- performance.measure('NeedleXRController onUpdate', 'NeedleXRController onUpdate start', 'NeedleXRController onUpdate end');
387
+ //performance.mark('NeedleXRController onUpdate end');
388
+ //performance.measure('NeedleXRController onUpdate', 'NeedleXRController onUpdate start', 'NeedleXRController onUpdate end');
390
389
  }
391
390
 
392
391
  onRenderDebug() {