@needle-tools/engine 5.1.4 → 5.1.5

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 (72) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/{needle-engine.bundle-CEba8eTv.js → needle-engine.bundle-CxsZfV6C.js} +5245 -5166
  3. package/dist/{needle-engine.bundle-BvxvjlSN.min.js → needle-engine.bundle-DUb8msPR.min.js} +140 -140
  4. package/dist/{needle-engine.bundle-GUaSFX8u.umd.cjs → needle-engine.bundle-D_h1sDs-.umd.cjs} +137 -137
  5. package/dist/needle-engine.d.ts +39 -30
  6. package/dist/needle-engine.js +242 -242
  7. package/dist/needle-engine.min.js +1 -1
  8. package/dist/needle-engine.umd.cjs +1 -1
  9. package/lib/engine/api.d.ts +1 -1
  10. package/lib/engine/api.js +1 -1
  11. package/lib/engine/api.js.map +1 -1
  12. package/lib/engine/debug/debug_console.js +39 -17
  13. package/lib/engine/debug/debug_console.js.map +1 -1
  14. package/lib/engine/debug/debug_spatial_console.js +14 -0
  15. package/lib/engine/debug/debug_spatial_console.js.map +1 -1
  16. package/lib/engine/engine_init.js +2 -2
  17. package/lib/engine/engine_init.js.map +1 -1
  18. package/lib/engine/engine_license.d.ts +7 -7
  19. package/lib/engine/engine_license.js +71 -71
  20. package/lib/engine/engine_license.js.map +1 -1
  21. package/lib/engine/engine_networking_blob.js +3 -3
  22. package/lib/engine/engine_utils_qrcode.js +2 -2
  23. package/lib/engine/engine_utils_screenshot.js +1 -0
  24. package/lib/engine/engine_utils_screenshot.js.map +1 -1
  25. package/lib/engine/engine_utils_screenshot.xr.js +1 -1
  26. package/lib/engine/engine_utils_screenshot.xr.js.map +1 -1
  27. package/lib/engine/webcomponents/needle menu/needle-menu-spatial.js +2 -2
  28. package/lib/engine/webcomponents/needle menu/needle-menu.js +18 -5
  29. package/lib/engine/webcomponents/needle menu/needle-menu.js.map +1 -1
  30. package/lib/engine/webcomponents/needle-engine.ar-overlay.js +33 -7
  31. package/lib/engine/webcomponents/needle-engine.ar-overlay.js.map +1 -1
  32. package/lib/engine/webcomponents/needle-engine.js +2 -2
  33. package/lib/engine/webcomponents/needle-engine.loading.js +2 -2
  34. package/lib/engine/webcomponents/needle-engine.loading.js.map +1 -1
  35. package/lib/engine/xr/NeedleXRSession.js +10 -4
  36. package/lib/engine/xr/NeedleXRSession.js.map +1 -1
  37. package/lib/engine/xr/NeedleXRSync.js +47 -47
  38. package/lib/engine/xr/NeedleXRSync.js.map +1 -1
  39. package/lib/engine/xr/TempXRContext.js +2 -2
  40. package/lib/engine-components/DragControls.d.ts +9 -0
  41. package/lib/engine-components/DragControls.js +174 -73
  42. package/lib/engine-components/DragControls.js.map +1 -1
  43. package/lib/engine-components/DragControlsConstraints.d.ts +11 -2
  44. package/lib/engine-components/DragControlsConstraints.js +18 -11
  45. package/lib/engine-components/DragControlsConstraints.js.map +1 -1
  46. package/lib/engine-components/OrbitControls.js +32 -6
  47. package/lib/engine-components/OrbitControls.js.map +1 -1
  48. package/lib/engine-components/export/usdz/USDZExporter.js +4 -4
  49. package/package.json +1 -1
  50. package/plugins/common/license.js +4 -4
  51. package/plugins/vite/license.js +4 -4
  52. package/src/engine/api.ts +1 -1
  53. package/src/engine/debug/debug_console.ts +33 -17
  54. package/src/engine/debug/debug_spatial_console.ts +15 -0
  55. package/src/engine/engine_init.ts +2 -2
  56. package/src/engine/engine_license.ts +68 -68
  57. package/src/engine/engine_networking_blob.ts +3 -3
  58. package/src/engine/engine_utils_qrcode.ts +2 -2
  59. package/src/engine/engine_utils_screenshot.ts +1 -0
  60. package/src/engine/engine_utils_screenshot.xr.ts +1 -1
  61. package/src/engine/webcomponents/needle menu/needle-menu-spatial.ts +2 -2
  62. package/src/engine/webcomponents/needle menu/needle-menu.ts +19 -5
  63. package/src/engine/webcomponents/needle-engine.ar-overlay.ts +33 -5
  64. package/src/engine/webcomponents/needle-engine.loading.ts +6 -6
  65. package/src/engine/webcomponents/needle-engine.ts +2 -2
  66. package/src/engine/xr/NeedleXRSession.ts +10 -3
  67. package/src/engine/xr/NeedleXRSync.ts +47 -50
  68. package/src/engine/xr/TempXRContext.ts +2 -2
  69. package/src/engine-components/DragControls.ts +189 -80
  70. package/src/engine-components/DragControlsConstraints.ts +20 -12
  71. package/src/engine-components/OrbitControls.ts +31 -4
  72. package/src/engine-components/export/usdz/USDZExporter.ts +4 -4
@@ -398,8 +398,11 @@ export class PlaneHeightLockConstraint implements IDragConstraint {
398
398
 
399
399
  /**
400
400
  * Used by {@link SnapToSurfacesDragStrategy} for two-pointer (multi-touch) drags.
401
- * Casts a downward ray each frame from the follow object's world position and adjusts
402
- * it so the dragged object's bounding-box contact face rests on the detected surface.
401
+ * Each frame `MultiTouchDragHandler` aims the snap ray from the pointer origin(s) through the
402
+ * current two-finger grab midpoint (see {@link setRay}); apply() casts that ray into the scene
403
+ * and places the follow object so its bounding-box contact face rests on the hit surface with
404
+ * the object origin at the hit point — the same "snap to what the pointer points at" semantics
405
+ * as the single-pointer surface raycast.
403
406
  *
404
407
  * Only injected into the constraint pipeline for multi-touch; the single-pointer path
405
408
  * continues to use {@link SnapToSurfacesDragStrategy.update} (pointer ray + drag plane
@@ -409,6 +412,10 @@ export class SnapToSurfaceConstraint implements IDragConstraint {
409
412
  private _gameObject: Object3D | null = null;
410
413
  private _boundsAtScaleOne: Box3 | null = null;
411
414
 
415
+ /** Snap ray for the current frame. Valid only after {@link setRay}; apply() is a no-op until then. */
416
+ private readonly _ray = new Ray();
417
+ private _hasRay = false;
418
+
412
419
  // Per-frame scratch vectors — avoids per-frame allocations.
413
420
  private readonly _t1 = new Vector3();
414
421
  private readonly _t2 = new Vector3();
@@ -420,19 +427,23 @@ export class SnapToSurfaceConstraint implements IDragConstraint {
420
427
  init(ctx: IDragConstraintContext): void {
421
428
  this._gameObject = ctx.gameObject;
422
429
  this._boundsAtScaleOne = ctx.boundsAtScaleOne;
430
+ this._hasRay = false;
431
+ }
432
+
433
+ /** Sets the world-space snap ray for this frame. Called by `MultiTouchDragHandler` before the
434
+ * constraint pipeline runs. `direction` must be normalized. */
435
+ setRay(origin: Vector3, direction: Vector3): void {
436
+ this._ray.origin.copy(origin);
437
+ this._ray.direction.copy(direction);
438
+ this._hasRay = true;
423
439
  }
424
440
 
425
441
  apply(followObject: GameObject): void {
426
- if (!this._gameObject || !this._boundsAtScaleOne) return;
442
+ if (!this._gameObject || !this._boundsAtScaleOne || !this._hasRay) return;
427
443
 
428
- // Cast a downward ray from 0.5 units above the follow object's current world position.
429
- // The small upward offset avoids self-intersection when the object sits on a surface.
430
- // World-Y down is used; it finds the nearest surface below the follow object.
431
444
  const currentWP = followObject.worldPosition;
432
- this._t1.set(currentWP.x, currentWP.y + 0.5, currentWP.z);
433
- const ray = new Ray(this._t1, _snapToSurfaceDown);
434
445
 
435
- const hits = this._context.physics.raycastFromRay(ray, {
446
+ const hits = this._context.physics.raycastFromRay(this._ray, {
436
447
  testObject: (o: Object3D) => o !== (followObject as unknown as Object3D) && o !== this._gameObject
437
448
  });
438
449
 
@@ -469,9 +480,6 @@ export class SnapToSurfaceConstraint implements IDragConstraint {
469
480
  }
470
481
  }
471
482
 
472
- /** Shared downward direction vector for {@link SnapToSurfaceConstraint}. */
473
- const _snapToSurfaceDown = new Vector3(0, -1, 0);
474
-
475
483
 
476
484
  // #region Utilities
477
485
  /**
@@ -778,12 +778,39 @@ export class OrbitControls extends Behaviour implements ICameraController {
778
778
  this._lookTargetEndPosition.clamp(min, max);
779
779
  }
780
780
  else {
781
- // Interactive use (pan/orbit): smoothly push the target back into bounds
781
+ // Interactive use (pan/orbit/zoom): smoothly push the target back into
782
+ // bounds as a RIGID shift — camera and target move by the same delta, so
783
+ // the camera-target offset is preserved (distance and viewing angles are
784
+ // untouched; the whole view slides back).
785
+ //
786
+ // Preserving the offset is load-bearing, not cosmetic: our three fork keeps
787
+ // a zoom-to-cursor gesture armed across frames until an update() settles
788
+ // with zero movement (required for damped zoom to converge). While armed,
789
+ // every update() re-derives `target` from the camera and turns any offset
790
+ // change into a dolly along the frozen cursor ray. Clamping only the target
791
+ // therefore changed the offset each frame, which re-derived the target
792
+ // outside the bounds again, which clamped again — a positive feedback loop
793
+ // that flung the camera away exponentially (observed 18m → 10^20m within
794
+ // ~70 frames). With a rigid shift the re-derived target reproduces the
795
+ // clamped one exactly, the correction decays geometrically and the gesture
796
+ // settles normally.
782
797
  const targetVector = this._controls.target;
783
- const newTarget = getTempVector(targetVector).clamp(min, max);
798
+ const delta = getTempVector(targetVector).clamp(min, max).sub(targetVector);
784
799
  const duration = .1;
785
- if (duration <= 0) targetVector.copy(newTarget);
786
- else targetVector.lerp(newTarget, Math.min(1, this.context.time.deltaTime / duration));
800
+ if (duration > 0) delta.multiplyScalar(Math.min(1, this.context.time.deltaTime / duration));
801
+ targetVector.add(delta);
802
+ // Apply the IDENTICAL delta to the camera position, in the controls'
803
+ // own space: OrbitControls derives its state from
804
+ // `object.position - target` directly (parent-naive — three's own
805
+ // contract says the camera must not sit under a transformed parent,
806
+ // and all of the controls' math treats position and target as the
807
+ // same space). Converting the delta through the camera's parent
808
+ // scales it whenever an ancestor is scaled, so camera and target
809
+ // would move by different amounts — that changes the derived offset
810
+ // every frame and re-creates the feedback loop (verified: a 0.456×
811
+ // scaled ancestor turned the clamp into a ×2.19 camera shift and the
812
+ // runaway persisted).
813
+ this._cameraObject?.position.add(delta);
787
814
  }
788
815
 
789
816
  if (debug) {
@@ -3,7 +3,7 @@ import { Euler, Material, Matrix4, Mesh, Object3D, Quaternion, Vector3 } from "t
3
3
 
4
4
  import { isDevEnvironment, showBalloonMessage, showBalloonWarning } from "../../../engine/debug/index.js";
5
5
  import { findObjectOfType } from "../../../engine/engine_components.js";
6
- import { KLaEb } from "../../../engine/engine_license.js";
6
+ import { otIKt } from "../../../engine/engine_license.js";
7
7
  import { serializable } from "../../../engine/engine_serialization.js";
8
8
  import { getFormattedDate, Progress } from "../../../engine/engine_time_utils.js";
9
9
  import { DeviceUtilities, getParam } from "../../../engine/engine_utils.js";
@@ -277,7 +277,7 @@ export class USDZExporter extends Behaviour {
277
277
  let name = this.exportFileName ?? this.objectToExport?.name ?? this.name;
278
278
  name += "-" + getFormattedDate(); // seems iOS caches the file in some cases, this ensures we always have a fresh file
279
279
 
280
- if (!KLaEb()) {
280
+ if (!otIKt()) {
281
281
  if (name !== "") name += "-";
282
282
  name += "MadeWithNeedle";
283
283
  }
@@ -682,7 +682,7 @@ export class USDZExporter extends Behaviour {
682
682
  if (debug)
683
683
  showBalloonMessage("Quicklook url: " + callToActionURL);
684
684
  if (callToActionURL) {
685
- if (!KLaEb()) {
685
+ if (!otIKt()) {
686
686
  console.warn("Quicklook closed: custom redirects require a Needle Engine Pro license: https://needle.tools/pricing", callToActionURL)
687
687
  }
688
688
  else {
@@ -697,7 +697,7 @@ export class USDZExporter extends Behaviour {
697
697
  private buildQuicklookOverlay(): CustomBranding {
698
698
  const obj: CustomBranding = {};
699
699
  if (this.customBranding) Object.assign(obj, this.customBranding);
700
- if (!KLaEb()) {
700
+ if (!otIKt()) {
701
701
  console.log("Custom Quicklook banner text requires pro license: https://needle.tools/pricing");
702
702
  obj.callToAction = "Close";
703
703
  obj.checkoutTitle = "🌵 Made with Needle";