@needle-tools/engine 4.11.0 → 4.11.1-next.0bef517

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 (33) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +3 -1
  3. package/dist/{needle-engine.bundle-IPerDSpg.min.js → needle-engine.bundle-CETlDRXF.min.js} +100 -100
  4. package/dist/{needle-engine.bundle-DhRclTK5.umd.cjs → needle-engine.bundle-DgECSnSa.umd.cjs} +97 -97
  5. package/dist/{needle-engine.bundle-Dh4X0Qy5.js → needle-engine.bundle-GfmZcJ_E.js} +2646 -2575
  6. package/dist/needle-engine.js +2 -2
  7. package/dist/needle-engine.min.js +1 -1
  8. package/dist/needle-engine.umd.cjs +1 -1
  9. package/lib/engine/engine_physics.js +2 -1
  10. package/lib/engine/engine_physics.js.map +1 -1
  11. package/lib/engine-components/OrbitControls.js +2 -0
  12. package/lib/engine-components/OrbitControls.js.map +1 -1
  13. package/lib/engine-components/splines/SplineWalker.d.ts +43 -4
  14. package/lib/engine-components/splines/SplineWalker.js +88 -12
  15. package/lib/engine-components/splines/SplineWalker.js.map +1 -1
  16. package/lib/engine-components/web/Clickthrough.d.ts +2 -0
  17. package/lib/engine-components/web/Clickthrough.js +23 -1
  18. package/lib/engine-components/web/Clickthrough.js.map +1 -1
  19. package/lib/engine-components/web/ScrollFollow.d.ts +4 -9
  20. package/lib/engine-components/web/ScrollFollow.js +26 -30
  21. package/lib/engine-components/web/ScrollFollow.js.map +1 -1
  22. package/lib/engine-components/web/ViewBox.d.ts +16 -0
  23. package/lib/engine-components/web/ViewBox.js +35 -3
  24. package/lib/engine-components/web/ViewBox.js.map +1 -1
  25. package/package.json +2 -2
  26. package/plugins/types/userconfig.d.ts +7 -0
  27. package/plugins/vite/build-pipeline.js +14 -6
  28. package/src/engine/engine_physics.ts +3 -3
  29. package/src/engine-components/OrbitControls.ts +5 -2
  30. package/src/engine-components/splines/SplineWalker.ts +99 -14
  31. package/src/engine-components/web/Clickthrough.ts +29 -1
  32. package/src/engine-components/web/ScrollFollow.ts +31 -33
  33. package/src/engine-components/web/ViewBox.ts +35 -5
@@ -96,6 +96,7 @@ export class ScrollFollow extends Behaviour {
96
96
  @serializable()
97
97
  invert: boolean = false;
98
98
 
99
+
99
100
  /**
100
101
  * **Experimental - might change in future updates**
101
102
  * If set, the scroll position will be read from the specified element instead of the window.
@@ -106,7 +107,7 @@ export class ScrollFollow extends Behaviour {
106
107
  htmlSelector: string | null = null;
107
108
 
108
109
  @serializable()
109
- private mode: "window" = "window";
110
+ mode: "window" = "window";
110
111
 
111
112
  /**
112
113
  * Event fired when the scroll position changes
@@ -124,17 +125,19 @@ export class ScrollFollow extends Behaviour {
124
125
  private _current_value: number = 0;
125
126
  private _target_value: number = 0;
126
127
  private _appliedValue: number = -1;
128
+ private _needsUpdate = false;
129
+ private _firstUpdate = false;
127
130
 
131
+ awake() {
132
+ this._firstUpdate = true;
133
+ }
128
134
 
129
- private _scrollStart: number = 0;
130
- private _scrollEnd: number = 0;
131
- private _scrollValue: number = 0;
132
- private _scrollContainerHeight: number = 0;
133
135
 
134
136
  /** @internal */
135
137
  onEnable() {
136
138
  window.addEventListener("wheel", this.updateCurrentScrollValue, { passive: true });
137
139
  this._appliedValue = -1;
140
+ this._needsUpdate = true;
138
141
  }
139
142
 
140
143
  /** @internal */
@@ -148,7 +151,7 @@ export class ScrollFollow extends Behaviour {
148
151
  this.updateCurrentScrollValue();
149
152
 
150
153
  if (this._target_value >= 0) {
151
- if (this.damping > 0) { // apply damping
154
+ if (this.damping > 0 && !this._firstUpdate) { // apply damping
152
155
  this._current_value = Mathf.lerp(this._current_value, this._target_value, this.context.time.deltaTime / this.damping);
153
156
  if (Math.abs(this._current_value - this._target_value) < 0.001) {
154
157
  this._current_value = this._target_value;
@@ -159,9 +162,10 @@ export class ScrollFollow extends Behaviour {
159
162
  }
160
163
  }
161
164
 
162
- // if (this._current_value !== this._appliedValue)
165
+ if (this._needsUpdate || this._current_value !== this._appliedValue)
163
166
  {
164
167
  this._appliedValue = this._current_value;
168
+ this._needsUpdate = false;
165
169
 
166
170
  let defaultPrevented = false;
167
171
  if (this.changed.listenerCount > 0) {
@@ -182,9 +186,6 @@ export class ScrollFollow extends Behaviour {
182
186
 
183
187
  const value = this.invert ? 1 - this._current_value : this._current_value;
184
188
 
185
- // const height = this._rangeEndValue - this._rangeStartValue;
186
- // const pixelValue = this._rangeStartValue + value * height;
187
-
188
189
  // apply scroll to target(s)
189
190
  if (Array.isArray(this.target)) {
190
191
  this.target.forEach(t => t && this.applyScroll(t, value));
@@ -197,21 +198,18 @@ export class ScrollFollow extends Behaviour {
197
198
  console.debug(`[ScrollFollow] ${this._current_value.toFixed(5)} — ${(this._target_value * 100).toFixed(0)}%, targets [${Array.isArray(this.target) ? this.target.length : 1}]`);
198
199
  }
199
200
  }
201
+
202
+ this._firstUpdate = false;
200
203
  }
201
204
  }
202
205
 
203
206
  private _lastSelectorValue: string | null = null;
204
207
  private _lastSelectorElement: Element | null = null;
205
- /** Top y */
206
- private _rangeStartValue: number = 0;
207
- /** Bottom y */
208
- private _rangeEndValue: number = 0;
209
208
 
210
209
  private updateCurrentScrollValue = () => {
211
210
 
212
211
  switch (this.mode) {
213
212
  case "window":
214
-
215
213
  if (this.htmlSelector?.length) {
216
214
  if (this.htmlSelector !== this._lastSelectorValue) {
217
215
  this._lastSelectorElement = document.querySelector(this.htmlSelector);
@@ -219,26 +217,20 @@ export class ScrollFollow extends Behaviour {
219
217
  }
220
218
  if (this._lastSelectorElement) {
221
219
  const rect = this._lastSelectorElement.getBoundingClientRect();
222
-
223
- this._scrollStart = rect.top + window.scrollY;
224
- this._scrollEnd = rect.height - window.innerHeight;
225
- this._scrollValue = -rect.top;
226
220
  this._target_value = -rect.top / (rect.height - window.innerHeight);
227
- this._rangeStartValue = rect.top + window.scrollY;
228
- this._rangeEndValue = this._rangeStartValue + rect.height - window.innerHeight;
229
- this._scrollContainerHeight = rect.height;
230
221
  break;
231
222
  }
232
223
  }
233
224
  else {
234
- this._scrollStart = 0;
235
- this._scrollEnd = window.document.body.scrollHeight - window.innerHeight;
236
- this._scrollValue = window.scrollY;
237
- this._target_value = this._scrollValue / (this._scrollEnd || 1);
238
- this._rangeStartValue = 0;
239
- this._rangeEndValue = document.body.scrollHeight;
240
- this._scrollContainerHeight = window.innerHeight;
225
+ if (window.document.body.scrollHeight <= window.innerHeight) {
226
+ // If the page is not scrollable we can still increment the scroll value to allow triggering timelines etc.
227
+ }
228
+ else {
229
+ const diff = window.document.body.scrollHeight - window.innerHeight;
230
+ this._target_value = window.scrollY / (diff || 1);
231
+ }
241
232
  }
233
+
242
234
  break;
243
235
  }
244
236
 
@@ -272,11 +264,12 @@ export class ScrollFollow extends Behaviour {
272
264
  target.intensity = value;
273
265
  }
274
266
  else if (target instanceof Object3D) {
267
+ const t = target as any;
275
268
  // When objects are assigned they're expected to move vertically based on scroll
276
- if (target["needle:scrollbounds"] === undefined) {
277
- target["needle:scrollbounds"] = getBoundingBox(target) || null;
269
+ if (t["needle:scrollbounds"] === undefined) {
270
+ t["needle:scrollbounds"] = getBoundingBox(target) || null;
278
271
  }
279
- const bounds = target["needle:scrollbounds"] as Box3;
272
+ const bounds = t["needle:scrollbounds"] as Box3;
280
273
  if (bounds) {
281
274
  // TODO: remap position to use upper screen edge and lower edge instead of center
282
275
  target.position.y = -bounds.min.y - value * (bounds.max.y - bounds.min.y);
@@ -422,13 +415,18 @@ export class ScrollFollow extends Behaviour {
422
415
  time += diff * weight;
423
416
  }
424
417
  }
425
- if (this.damping <= 0) {
418
+ if (this.damping <= 0 || this._firstUpdate) {
426
419
  director.time = time;
427
420
  }
428
421
  else {
429
422
  director.time = Mathf.lerp(director.time, time, this.context.time.deltaTime / this.damping);
430
423
  }
431
424
 
425
+ const delta = Math.abs(director.time - time);
426
+ if (delta > .001) { // if the time is > 1/100th of a second off we need another update
427
+ this._needsUpdate = true;
428
+ }
429
+
432
430
  if (debug && this.context.time.frame % 30 === 0) {
433
431
  console.log(`[ScrollFollow ] Timeline ${director.name}: ${time.toFixed(3)}`, weightsArray.map(w => `[${w.name} ${(w.weight * 100).toFixed(0)}%]`).join(", "));
434
432
  }
@@ -1,4 +1,4 @@
1
- import { Camera, Matrix4, PerspectiveCamera, Quaternion, Scene, Vector2, Vector3 } from "three";
1
+ import { Camera, Matrix4, PerspectiveCamera, Vector3 } from "three";
2
2
 
3
3
  import { isDevEnvironment } from "../../engine/debug/debug.js";
4
4
  import { Gizmos } from "../../engine/engine_gizmos.js";
@@ -13,6 +13,8 @@ import { Behaviour } from "../Component.js";
13
13
  const debugParam = getParam("debugviewbox");
14
14
  const disabledGizmoColor = new RGBAColor(.5, .5, .5, .5);
15
15
 
16
+ export type ViewBoxMode = "continuous" | "once";
17
+
16
18
  /**
17
19
  * This component can be used to automatically fit a certain box area into the camera view - no matter your screen size or aspect ratio.
18
20
  * This is useful for example to frame a character or object in the center of the screen and ensure it is always fully visible. You can also animate or scale the viewbox to create zoom or framing effects.
@@ -36,6 +38,9 @@ const disabledGizmoColor = new RGBAColor(.5, .5, .5, .5);
36
38
  @registerType
37
39
  export class ViewBox extends Behaviour {
38
40
 
41
+ /**
42
+ * All active ViewBox instances. The last one in the array is the currently active one.
43
+ */
39
44
  static readonly instances: ViewBox[] = [];
40
45
 
41
46
  /**
@@ -45,21 +50,39 @@ export class ViewBox extends Behaviour {
45
50
  @serializable()
46
51
  referenceFieldOfView: number = -1;
47
52
 
53
+ /**
54
+ * The mode determines if the viewbox should be applied once or continuously while it is the active viewbox.
55
+ * Options:
56
+ * - "once": The viewbox will be applied once when it becomes the active viewbox. This is useful if you want to fit the view once and then allow the user to zoom or pan freely.
57
+ * - "continuous": The viewbox will be applied continuously while it is the active viewbox. This is useful if you animate or scale the viewbox over time.
58
+ */
59
+ @serializable()
60
+ get mode() { return this._mode; }
61
+ set mode(v: ViewBoxMode) {
62
+ if (v === this._mode) return;
63
+ this._mode = v;
64
+ if (v === "once") this._applyCount = 0;
65
+ if (debugParam || this.debug) console.debug("[ViewBox] Set mode:", v);
66
+ }
67
+ private _mode: ViewBoxMode = "continuous";
68
+
48
69
  /**
49
70
  * Enable debug logs and rendering for this component instance
50
71
  */
51
72
  @serializable()
52
73
  debug: boolean = false;
53
74
 
75
+ /** @internal */
54
76
  onEnable(): void {
55
77
  if (debugParam || this.debug || isDevEnvironment()) console.debug("[ViewBox] Using camera fov:", this.referenceFieldOfView);
56
78
  // register instance
57
79
  ViewBox.instances.push(this);
58
-
80
+ this._applyCount = 0;
59
81
  this.removeUpdateCallback();
60
82
  this.context.pre_render_callbacks.push(this.internalUpdate);
61
83
  }
62
84
 
85
+ /** @internal */
63
86
  onDisable(): void {
64
87
  if (debugParam || this.debug) console.debug("[ViewBox] Disabled");
65
88
  // unregister instance
@@ -77,6 +100,7 @@ export class ViewBox extends Behaviour {
77
100
 
78
101
  private static readonly _tempProjectionMatrix: Matrix4 = new Matrix4();
79
102
  private static readonly _tempProjectionMatrixInverse: Matrix4 = new Matrix4();
103
+ private _applyCount = 0;
80
104
 
81
105
  private internalUpdate = () => {
82
106
  if (this.context.isInXR) return;
@@ -108,6 +132,11 @@ export class ViewBox extends Behaviour {
108
132
  return;
109
133
  }
110
134
 
135
+ if (this._applyCount >= 1 && this.mode === "once") {
136
+ return;
137
+ }
138
+ this._applyCount++;
139
+
111
140
  const domWidth = this.context.domWidth;
112
141
  const domHeight = this.context.domHeight;
113
142
 
@@ -129,7 +158,7 @@ export class ViewBox extends Behaviour {
129
158
  ViewBox._tempProjectionMatrix.copy(camera.projectionMatrix);
130
159
  ViewBox._tempProjectionMatrixInverse.copy(camera.projectionMatrixInverse);
131
160
  const view = camera.view;
132
- const zoom = camera.zoom;
161
+ const cameraZoom = camera.zoom;
133
162
  const aspect = camera.aspect;
134
163
  const fov = camera.fov;
135
164
  // Set values to default so we can calculate the box size correctly
@@ -194,6 +223,7 @@ export class ViewBox extends Behaviour {
194
223
  width / diffWidth,
195
224
  height / diffHeight
196
225
  );
226
+ const zoom = scale / (height * .5);
197
227
  // console.log({ scale, width, height, boxWidth: boxWidth * camera.aspect, boxHeight, diffWidth, diffHeight, aspect: camera.aspect, distance })
198
228
  // this.context.focusRectSettings.zoom = 1.39;
199
229
  // if (!this.context.focusRect) this.context.setCameraFocusRect(this.context.domElement);
@@ -202,13 +232,13 @@ export class ViewBox extends Behaviour {
202
232
  vec.project(camera);
203
233
  this.context.focusRectSettings.offsetX = vec.x;
204
234
  this.context.focusRectSettings.offsetY = vec.y;
205
- this.context.focusRectSettings.zoom = scale / (height * .5);
235
+ this.context.focusRectSettings.zoom = zoom;
206
236
  // if we don't have a focus rect yet, set it to the dom element
207
237
  if (!this.context.focusRect) this.context.setCameraFocusRect(this.context.domElement);
208
238
 
209
239
  // Reset values
210
240
  camera.view = view;
211
- camera.zoom = zoom;
241
+ camera.zoom = cameraZoom;
212
242
  camera.aspect = aspect;
213
243
  camera.fov = fov;
214
244
  camera.projectionMatrix.copy(ViewBox._tempProjectionMatrix);