@needle-tools/engine 4.4.0-beta → 4.4.0-beta.10

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 (155) hide show
  1. package/CHANGELOG.md +65 -2
  2. package/dist/assets/generateMeshBVH.worker-b7788939.js +25 -0
  3. package/dist/needle-engine.bundle.js +6808 -6645
  4. package/dist/needle-engine.bundle.light.js +7071 -6908
  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.d.ts +0 -9
  10. package/dist/needle-engine.js +468 -467
  11. package/dist/needle-engine.light.d.ts +0 -9
  12. package/dist/needle-engine.light.js +468 -467
  13. package/dist/needle-engine.light.min.js +1 -1
  14. package/dist/needle-engine.light.umd.cjs +1 -1
  15. package/dist/needle-engine.min.js +1 -1
  16. package/dist/needle-engine.umd.cjs +1 -1
  17. package/dist/postprocessing.js +1673 -1527
  18. package/dist/postprocessing.light.js +1673 -1527
  19. package/dist/postprocessing.light.min.js +80 -66
  20. package/dist/postprocessing.light.umd.cjs +83 -69
  21. package/dist/postprocessing.min.js +80 -66
  22. package/dist/postprocessing.umd.cjs +83 -69
  23. package/dist/vendor.js +5109 -5123
  24. package/dist/vendor.light.js +5109 -5123
  25. package/dist/vendor.light.min.js +84 -84
  26. package/dist/vendor.light.umd.cjs +78 -78
  27. package/dist/vendor.min.js +84 -84
  28. package/dist/vendor.umd.cjs +78 -78
  29. package/lib/engine/codegen/register_types.js +2 -0
  30. package/lib/engine/codegen/register_types.js.map +1 -1
  31. package/lib/engine/engine_addressables.js.map +1 -1
  32. package/lib/engine/engine_components.js +3 -1
  33. package/lib/engine/engine_components.js.map +1 -1
  34. package/lib/engine/engine_context.d.ts +10 -1
  35. package/lib/engine/engine_context.js +33 -12
  36. package/lib/engine/engine_context.js.map +1 -1
  37. package/lib/engine/engine_element.js +12 -10
  38. package/lib/engine/engine_element.js.map +1 -1
  39. package/lib/engine/engine_gameobject.js +5 -0
  40. package/lib/engine/engine_gameobject.js.map +1 -1
  41. package/lib/engine/engine_license.d.ts +2 -0
  42. package/lib/engine/engine_license.js +103 -62
  43. package/lib/engine/engine_license.js.map +1 -1
  44. package/lib/engine/engine_networking_blob.js +40 -24
  45. package/lib/engine/engine_networking_blob.js.map +1 -1
  46. package/lib/engine/engine_physics_rapier.js +10 -9
  47. package/lib/engine/engine_physics_rapier.js.map +1 -1
  48. package/lib/engine/engine_serialization_core.js +6 -2
  49. package/lib/engine/engine_serialization_core.js.map +1 -1
  50. package/lib/engine/engine_utils_screenshot.js +1 -1
  51. package/lib/engine/engine_utils_screenshot.js.map +1 -1
  52. package/lib/engine/js-extensions/RGBAColor.d.ts +1 -0
  53. package/lib/engine/js-extensions/RGBAColor.js +48 -0
  54. package/lib/engine/js-extensions/RGBAColor.js.map +1 -1
  55. package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +4 -3
  56. package/lib/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js.map +1 -1
  57. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +6 -6
  58. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js.map +1 -1
  59. package/lib/engine/webcomponents/needle menu/needle-menu.d.ts +4 -0
  60. package/lib/engine/webcomponents/needle menu/needle-menu.js +13 -2
  61. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  62. package/lib/engine/xr/NeedleXRController.js +2 -3
  63. package/lib/engine/xr/NeedleXRController.js.map +1 -1
  64. package/lib/engine/xr/NeedleXRSession.js +12 -12
  65. package/lib/engine/xr/NeedleXRSession.js.map +1 -1
  66. package/lib/engine-components/AudioSource.js +7 -0
  67. package/lib/engine-components/AudioSource.js.map +1 -1
  68. package/lib/engine-components/Camera.js +18 -12
  69. package/lib/engine-components/Camera.js.map +1 -1
  70. package/lib/engine-components/CameraUtils.js +8 -14
  71. package/lib/engine-components/CameraUtils.js.map +1 -1
  72. package/lib/engine-components/GroundProjection.js +1 -1
  73. package/lib/engine-components/GroundProjection.js.map +1 -1
  74. package/lib/engine-components/NeedleMenu.js +1 -1
  75. package/lib/engine-components/NeedleMenu.js.map +1 -1
  76. package/lib/engine-components/OrbitControls.js +3 -1
  77. package/lib/engine-components/OrbitControls.js.map +1 -1
  78. package/lib/engine-components/ReflectionProbe.d.ts +1 -0
  79. package/lib/engine-components/ReflectionProbe.js +16 -9
  80. package/lib/engine-components/ReflectionProbe.js.map +1 -1
  81. package/lib/engine-components/SceneSwitcher.js +28 -7
  82. package/lib/engine-components/SceneSwitcher.js.map +1 -1
  83. package/lib/engine-components/SyncedTransform.d.ts +6 -0
  84. package/lib/engine-components/SyncedTransform.js +10 -5
  85. package/lib/engine-components/SyncedTransform.js.map +1 -1
  86. package/lib/engine-components/codegen/components.d.ts +1 -0
  87. package/lib/engine-components/codegen/components.js +1 -0
  88. package/lib/engine-components/codegen/components.js.map +1 -1
  89. package/lib/engine-components/export/usdz/extensions/USDZUI.js +2 -1
  90. package/lib/engine-components/export/usdz/extensions/USDZUI.js.map +1 -1
  91. package/lib/engine-components/ui/BaseUIComponent.d.ts +0 -1
  92. package/lib/engine-components/ui/BaseUIComponent.js +1 -1
  93. package/lib/engine-components/ui/BaseUIComponent.js.map +1 -1
  94. package/lib/engine-components/ui/EventSystem.js +1 -1
  95. package/lib/engine-components/ui/EventSystem.js.map +1 -1
  96. package/lib/engine-components/ui/Graphic.js +1 -0
  97. package/lib/engine-components/ui/Graphic.js.map +1 -1
  98. package/lib/engine-components/ui/RaycastUtils.js +1 -1
  99. package/lib/engine-components/ui/RaycastUtils.js.map +1 -1
  100. package/lib/engine-components/ui/RectTransform.d.ts +2 -2
  101. package/lib/engine-components/ui/RectTransform.js +9 -6
  102. package/lib/engine-components/ui/RectTransform.js.map +1 -1
  103. package/lib/engine-components/ui/Symbols.d.ts +1 -0
  104. package/lib/engine-components/ui/Symbols.js +2 -0
  105. package/lib/engine-components/ui/Symbols.js.map +1 -0
  106. package/lib/engine-components/ui/Utils.js +1 -1
  107. package/lib/engine-components/ui/Utils.js.map +1 -1
  108. package/lib/engine-components/utils/EnvironmentScene.d.ts +1 -1
  109. package/lib/engine-components/utils/EnvironmentScene.js +1 -1
  110. package/lib/engine-components/utils/EnvironmentScene.js.map +1 -1
  111. package/package.json +3 -3
  112. package/plugins/common/buildinfo.js +1 -0
  113. package/plugins/common/cloud.js +2 -0
  114. package/plugins/common/license.js +174 -33
  115. package/plugins/types/userconfig.d.ts +5 -0
  116. package/plugins/vite/build-pipeline.js +4 -3
  117. package/plugins/vite/dependencies.js +17 -7
  118. package/plugins/vite/facebook-instant-games.js +7 -4
  119. package/plugins/vite/index.js +1 -1
  120. package/src/engine/codegen/register_types.ts +2 -0
  121. package/src/engine/engine_addressables.ts +3 -2
  122. package/src/engine/engine_components.ts +2 -1
  123. package/src/engine/engine_context.ts +36 -12
  124. package/src/engine/engine_element.ts +10 -9
  125. package/src/engine/engine_gameobject.ts +7 -0
  126. package/src/engine/engine_license.ts +116 -67
  127. package/src/engine/engine_networking_blob.ts +47 -26
  128. package/src/engine/engine_physics_rapier.ts +10 -12
  129. package/src/engine/engine_serialization_core.ts +6 -1
  130. package/src/engine/engine_utils_screenshot.ts +1 -1
  131. package/src/engine/js-extensions/RGBAColor.ts +50 -0
  132. package/src/engine/physics/workers/mesh-bvh/GenerateMeshBVHWorker.js +6 -6
  133. package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +6 -6
  134. package/src/engine/webcomponents/needle menu/needle-menu.ts +13 -2
  135. package/src/engine/xr/NeedleXRController.ts +2 -3
  136. package/src/engine/xr/NeedleXRSession.ts +12 -12
  137. package/src/engine-components/AudioSource.ts +8 -3
  138. package/src/engine-components/Camera.ts +34 -22
  139. package/src/engine-components/CameraUtils.ts +8 -16
  140. package/src/engine-components/GroundProjection.ts +1 -1
  141. package/src/engine-components/NeedleMenu.ts +1 -1
  142. package/src/engine-components/OrbitControls.ts +2 -1
  143. package/src/engine-components/ReflectionProbe.ts +15 -8
  144. package/src/engine-components/SceneSwitcher.ts +28 -11
  145. package/src/engine-components/SyncedTransform.ts +11 -6
  146. package/src/engine-components/codegen/components.ts +1 -0
  147. package/src/engine-components/export/usdz/extensions/USDZUI.ts +2 -2
  148. package/src/engine-components/ui/BaseUIComponent.ts +1 -1
  149. package/src/engine-components/ui/EventSystem.ts +1 -1
  150. package/src/engine-components/ui/Graphic.ts +1 -1
  151. package/src/engine-components/ui/RaycastUtils.ts +1 -1
  152. package/src/engine-components/ui/RectTransform.ts +10 -7
  153. package/src/engine-components/ui/Symbols.ts +2 -0
  154. package/src/engine-components/ui/Utils.ts +2 -1
  155. package/src/engine-components/utils/EnvironmentScene.ts +1 -1
@@ -408,7 +408,7 @@ export class Context implements IContext {
408
408
 
409
409
  get isCreated() { return this._isCreated; }
410
410
 
411
- private _sizeChanged: boolean = false;
411
+ private _needsUpdateSize: boolean = false;
412
412
  private _isCreated: boolean = false;
413
413
  private _isCreating: boolean = false;
414
414
  private _isVisible: boolean = false;
@@ -446,11 +446,11 @@ export class Context implements IContext {
446
446
  this.animations = new AnimationsRegistry(this);
447
447
 
448
448
 
449
- const resizeCallback = () => this._sizeChanged = true;
449
+ const resizeCallback = () => this._needsUpdateSize = true;
450
450
  window.addEventListener('resize', resizeCallback);
451
451
  this._disposeCallbacks.push(() => window.removeEventListener('resize', resizeCallback));
452
452
 
453
- const resizeObserver = new ResizeObserver(_ => this._sizeChanged = true);
453
+ const resizeObserver = new ResizeObserver(_ => this._needsUpdateSize = true);
454
454
  resizeObserver.observe(this.domElement);
455
455
  this._disposeCallbacks.push(() => resizeObserver.disconnect());
456
456
 
@@ -519,15 +519,30 @@ export class Context implements IContext {
519
519
 
520
520
 
521
521
  /** will request a renderer size update the next render call (will call updateSize the next update) */
522
- requestSizeUpdate() { this._sizeChanged = true; }
522
+ requestSizeUpdate() { this._needsUpdateSize = true; }
523
523
 
524
524
  /** Clamps the renderer max resolution. If undefined the max resolution is not clamped. Default is undefined */
525
525
  maxRenderResolution?: Vec2;
526
526
 
527
+ /** Control the renderer devicePixelRatio.
528
+ * **Options**
529
+ * - `auto` - Needle Engine automatically sets the pixel ratio to the current window.devicePixelRatio.
530
+ * - `manual` - Needle Engine will not change the renderer pixel ratio. You can set it manually.
531
+ * - `number` - Needle Engine will set the pixel ratio to the given number. The change will be applied to the renderer and the composer (if used) at the end of the current frame.
532
+ */
533
+ get devicePixelRatio() { return this._devicePixelRatio; }
534
+ set devicePixelRatio(val: "auto" | "manual" | number) {
535
+ if (val !== this._devicePixelRatio) {
536
+ this._devicePixelRatio = val;
537
+ this._needsUpdateSize = true;
538
+ }
539
+ }
540
+ private _devicePixelRatio: "auto" | "manual" | number = "auto";
541
+
527
542
  /** update the renderer and canvas size */
528
543
  updateSize(force: boolean = false) {
529
544
  if (force || (!this.isManagedExternally && this.renderer.xr?.isPresenting === false)) {
530
- this._sizeChanged = false;
545
+ this._needsUpdateSize = false;
531
546
  const scaleFactor = this.resolutionScaleFactor;
532
547
  let width = this.domWidth * scaleFactor;
533
548
  let height = this.domHeight * scaleFactor;
@@ -540,15 +555,24 @@ export class Context implements IContext {
540
555
  const camera = this.mainCamera as PerspectiveCamera;
541
556
  this.updateAspect(camera);
542
557
  this.renderer.setSize(width, height, true);
543
- this.renderer.setPixelRatio(window.devicePixelRatio);
544
558
  // avoid setting pixel values here since this can cause pingpong updates
545
559
  // e.g. when system scale is set to 125%
546
560
  // https://github.com/needle-tools/needle-engine-support/issues/69
547
561
  this.renderer.domElement.style.width = "100%";
548
562
  this.renderer.domElement.style.height = "100%";
563
+
564
+ const devicePixelRatio = typeof this.devicePixelRatio === "number"
565
+ ? this.devicePixelRatio
566
+ : this.devicePixelRatio === "auto"
567
+ ? window.devicePixelRatio
568
+ : undefined;
569
+ if (devicePixelRatio !== undefined) {
570
+ this.renderer.setPixelRatio(devicePixelRatio);
571
+ }
572
+
549
573
  if (this.composer) {
550
574
  this.composer.setSize?.call(this.composer, width, height);
551
- if ("setPixelRatio" in this.composer && typeof this.composer.setPixelRatio === "function")
575
+ if (devicePixelRatio !== undefined && "setPixelRatio" in this.composer && typeof this.composer.setPixelRatio === "function")
552
576
  this.composer.setPixelRatio?.call(this.composer, window.devicePixelRatio);
553
577
  }
554
578
  }
@@ -1016,7 +1040,7 @@ export class Context implements IContext {
1016
1040
  // this.composer.setSize(this.domWidth, this.domHeight);
1017
1041
  }
1018
1042
 
1019
- this._sizeChanged = true;
1043
+ this._needsUpdateSize = true;
1020
1044
 
1021
1045
  if (this._stats) {
1022
1046
  this._stats.showPanel(0);
@@ -1186,11 +1210,11 @@ export class Context implements IContext {
1186
1210
  if (frame === undefined) frame = null;
1187
1211
  if (isDevEnvironment() || debug || looputils.hasNewScripts()) {
1188
1212
  try {
1189
- performance.mark('update.start');
1213
+ //performance.mark('update.start');
1190
1214
  this.internalStep(timestamp, frame);
1191
1215
  this._renderlooperrors = 0;
1192
- performance.mark('update.end');
1193
- performance.measure('NE Frame', 'update.start', 'update.end');
1216
+ //performance.mark('update.end');
1217
+ //performance.measure('NE Frame', 'update.start', 'update.end');
1194
1218
  }
1195
1219
  catch (err) {
1196
1220
  this._renderlooperrors += 1;
@@ -1361,7 +1385,7 @@ export class Context implements IContext {
1361
1385
  this.executeCoroutines(FrameEvent.OnBeforeRender);
1362
1386
  invokeLifecycleFunctions(this, FrameEvent.OnBeforeRender);
1363
1387
 
1364
- if (this._sizeChanged)
1388
+ if (this._needsUpdateSize)
1365
1389
  this.updateSize();
1366
1390
 
1367
1391
  if (this.pre_render_callbacks) {
@@ -12,6 +12,7 @@ import { NeedleLoader } from "./engine_scenetools.js";
12
12
  import { Context, ContextCreateArgs } from "./engine_setup.js";
13
13
  import { type INeedleEngineComponent, type LoadedModel } from "./engine_types.js";
14
14
  import { getParam } from "./engine_utils.js";
15
+ import { RGBAColor } from "./js-extensions/RGBAColor.js";
15
16
  import { ensureFonts } from "./webcomponents/fonts.js";
16
17
 
17
18
  //
@@ -177,6 +178,9 @@ export class NeedleEngineHTMLElement extends HTMLElement implements INeedleEngin
177
178
  -webkit-touch-callout: none;
178
179
  -webkit-user-drag: none;
179
180
  -webkit-user-modify: none;
181
+
182
+ left: 0;
183
+ top: 0;
180
184
  }
181
185
  :host .content {
182
186
  position: absolute;
@@ -517,7 +521,7 @@ export class NeedleEngineHTMLElement extends HTMLElement implements INeedleEngin
517
521
 
518
522
  private applyAttributes() {
519
523
  // set tonemapping if configured
520
- if (this._context.renderer) {
524
+ if (this._context?.renderer) {
521
525
  const attribute = this.getAttribute("tonemapping") || this.getAttribute("tone-mapping") as TonemappingAttributeOptions | null | undefined;
522
526
  switch (attribute?.toLowerCase()) {
523
527
  case "none":
@@ -555,15 +559,12 @@ export class NeedleEngineHTMLElement extends HTMLElement implements INeedleEngin
555
559
  }
556
560
 
557
561
  const backgroundColor = this.getAttribute("background-color");
558
- if (this._context) {
562
+ if (this._context?.renderer) {
559
563
  if (typeof backgroundColor === "string" && backgroundColor.length > 0) {
560
- const currentBackground = this._context.scene.background;
561
- if (currentBackground instanceof Color) {
562
- currentBackground.set(backgroundColor);
563
- }
564
- else {
565
- this._context.scene.background = new Color(backgroundColor);
566
- }
564
+ const rgbaColor = RGBAColor.fromColorRepresentation(backgroundColor);
565
+ if(debug) console.debug("<needle-engine> background-color changed, str:", backgroundColor, "→", rgbaColor)
566
+ this._context.renderer.setClearColor(rgbaColor, rgbaColor.alpha);
567
+ this.context.scene.background = null;
567
568
  }
568
569
  }
569
570
  }
@@ -1,5 +1,6 @@
1
1
  import { Bone, Object3D, Quaternion, SkinnedMesh, Vector3 } from "three";
2
2
 
3
+ import { $shadowDomOwner } from "../engine-components/ui/Symbols.js";
3
4
  import { type AssetReference } from "./engine_addressables.js";
4
5
  import { __internalNotifyObjectDestroyed as __internalRemoveReferences, disposeObjectResources } from "./engine_assetdatabase.js";
5
6
  import { ComponentLifecycleEvents } from "./engine_components_internal.js";
@@ -380,6 +381,12 @@ function internalInstantiate(
380
381
  )
381
382
  : GameObject | Object3D | null {
382
383
  if (!instance) return null;
384
+
385
+ // Don't clone UI shadow objects
386
+ if (instance[$shadowDomOwner]) {
387
+ return null;
388
+ }
389
+
383
390
  // prepare, remove things that dont work out of the box
384
391
  // e.g. user data we want to manually clone
385
392
  // also children throw errors (e.g. recursive toJson with nested meshes)
@@ -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)
@@ -73,4 +73,54 @@ export class RGBAColor extends Color {
73
73
  this.alpha = array[offset + 3];
74
74
  return super.fromArray(array, offset);
75
75
  }
76
+
77
+ static fromColorRepresentation(col: ColorRepresentation) {
78
+
79
+ if (typeof col === "string") {
80
+ if (col.trim() === "transparent") {
81
+ return new RGBAColor(0, 0, 0, 0);
82
+ }
83
+ // handle hex colors with alpha
84
+ if (col.startsWith("#") && col.length === 9) {
85
+ const hex = parseInt(col.slice(1, 9), 16);
86
+ const r = (hex >> 24) & 0xff;
87
+ const g = (hex >> 16) & 0xff;
88
+ const b = (hex >> 8) & 0xff;
89
+ const a = (hex >> 0) & 0xff;
90
+ return new RGBAColor(r / 255, g / 255, b / 255, a / 255);
91
+ }
92
+ // handle hex colors
93
+ else if (col.startsWith("#")) {
94
+ const hex = parseInt(col.slice(1), 16);
95
+ const r = (hex >> 16) & 0xff;
96
+ const g = (hex >> 8) & 0xff;
97
+ const b = (hex >> 0) & 0xff;
98
+ return new RGBAColor(r / 255, g / 255, b / 255, 1);
99
+ }
100
+ // handle rgba string
101
+ else if (col.startsWith("rgba")) {
102
+ const rgba = col.slice(5, -1).split(",").map(Number);
103
+ return new RGBAColor(rgba[0] / 255, rgba[1] / 255, rgba[2] / 255, rgba[3]);
104
+ }
105
+ // handle rgb string
106
+ else if (col.startsWith("rgb")) {
107
+ const rgb = col.slice(4, -1).split(",").map(Number);
108
+ return new RGBAColor(rgb[0] / 255, rgb[1] / 255, rgb[2] / 255, 1);
109
+ }
110
+ }
111
+ else if (Array.isArray(col)) {
112
+ // handle rgba colors
113
+ if (col.length === 4) {
114
+ return new RGBAColor(col[0], col[1], col[2], col[3]);
115
+ }
116
+ // handle rgb colors
117
+ else if (col.length === 3) {
118
+ return new RGBAColor(col[0], col[1], col[2], 1);
119
+ }
120
+ else {
121
+ console.error("Invalid color array length. Expected 3 or 4, got " + col.length);
122
+ }
123
+ }
124
+ return new RGBAColor(col);
125
+ }
76
126
  }