@needle-tools/engine 5.1.0-alpha.4 → 5.1.0-alpha.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 (96) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/components.needle.json +1 -1
  3. package/dist/{needle-engine.bundle-B7cqsI4c.js → needle-engine.bundle-C-LG00ZZ.js} +6570 -6271
  4. package/dist/{needle-engine.bundle-AjVIot3d.min.js → needle-engine.bundle-D7tzaiYE.min.js} +157 -157
  5. package/dist/{needle-engine.bundle-DQCuBTVp.umd.cjs → needle-engine.bundle-OPkPmdUM.umd.cjs} +140 -140
  6. package/dist/needle-engine.d.ts +668 -191
  7. package/dist/needle-engine.js +597 -595
  8. package/dist/needle-engine.min.js +1 -1
  9. package/dist/needle-engine.umd.cjs +1 -1
  10. package/dist/three.js +1 -0
  11. package/dist/three.min.js +21 -21
  12. package/dist/three.umd.cjs +16 -16
  13. package/lib/engine/api.d.ts +2 -0
  14. package/lib/engine/api.js +2 -0
  15. package/lib/engine/api.js.map +1 -1
  16. package/lib/engine/codegen/register_types.js +10 -10
  17. package/lib/engine/codegen/register_types.js.map +1 -1
  18. package/lib/engine/engine_camera.fit.js +16 -4
  19. package/lib/engine/engine_camera.fit.js.map +1 -1
  20. package/lib/engine/engine_context.d.ts +20 -7
  21. package/lib/engine/engine_context.js +29 -14
  22. package/lib/engine/engine_context.js.map +1 -1
  23. package/lib/engine/engine_context_eventbus.d.ts +47 -0
  24. package/lib/engine/engine_context_eventbus.js +47 -0
  25. package/lib/engine/engine_context_eventbus.js.map +1 -0
  26. package/lib/engine/engine_input.d.ts +23 -4
  27. package/lib/engine/engine_input.js +2 -1
  28. package/lib/engine/engine_input.js.map +1 -1
  29. package/lib/engine/engine_physics_rapier.d.ts +10 -0
  30. package/lib/engine/engine_physics_rapier.js +6 -0
  31. package/lib/engine/engine_physics_rapier.js.map +1 -1
  32. package/lib/engine/engine_types.d.ts +10 -0
  33. package/lib/engine-components/AnimationBuilder.d.ts +158 -0
  34. package/lib/engine-components/AnimationBuilder.js +305 -0
  35. package/lib/engine-components/AnimationBuilder.js.map +1 -0
  36. package/lib/engine-components/Animator.js +6 -1
  37. package/lib/engine-components/Animator.js.map +1 -1
  38. package/lib/engine-components/AnimatorController.builder.d.ts +101 -23
  39. package/lib/engine-components/AnimatorController.builder.js +88 -20
  40. package/lib/engine-components/AnimatorController.builder.js.map +1 -1
  41. package/lib/engine-components/AnimatorController.js +2 -0
  42. package/lib/engine-components/AnimatorController.js.map +1 -1
  43. package/lib/engine-components/ContactShadows.d.ts +1 -0
  44. package/lib/engine-components/ContactShadows.js +14 -1
  45. package/lib/engine-components/ContactShadows.js.map +1 -1
  46. package/lib/engine-components/DropListener.js +3 -0
  47. package/lib/engine-components/DropListener.js.map +1 -1
  48. package/lib/engine-components/OrbitControls.d.ts +0 -2
  49. package/lib/engine-components/OrbitControls.js +14 -1
  50. package/lib/engine-components/OrbitControls.js.map +1 -1
  51. package/lib/engine-components/SceneSwitcher.js +3 -0
  52. package/lib/engine-components/SceneSwitcher.js.map +1 -1
  53. package/lib/engine-components/api.d.ts +1 -0
  54. package/lib/engine-components/api.js +1 -0
  55. package/lib/engine-components/api.js.map +1 -1
  56. package/lib/engine-components/codegen/components.d.ts +6 -6
  57. package/lib/engine-components/codegen/components.js +6 -6
  58. package/lib/engine-components/codegen/components.js.map +1 -1
  59. package/lib/engine-components/postprocessing/Effects/Tonemapping.utils.d.ts +1 -1
  60. package/lib/engine-components/timeline/PlayableDirector.d.ts +7 -7
  61. package/lib/engine-components/timeline/PlayableDirector.js +6 -6
  62. package/lib/engine-components/timeline/PlayableDirector.js.map +1 -1
  63. package/lib/engine-components/timeline/TimelineBuilder.d.ts +175 -9
  64. package/lib/engine-components/timeline/TimelineBuilder.js +108 -2
  65. package/lib/engine-components/timeline/TimelineBuilder.js.map +1 -1
  66. package/lib/engine-components/timeline/TimelineTracks.d.ts +15 -7
  67. package/lib/engine-components/timeline/TimelineTracks.js +22 -14
  68. package/lib/engine-components/timeline/TimelineTracks.js.map +1 -1
  69. package/lib/engine-components/web/CursorFollow.d.ts +0 -1
  70. package/lib/engine-components/web/CursorFollow.js +0 -1
  71. package/lib/engine-components/web/CursorFollow.js.map +1 -1
  72. package/package.json +1 -1
  73. package/plugins/common/cloud.js +6 -1
  74. package/plugins/vite/license.js +19 -1
  75. package/src/engine/api.ts +3 -0
  76. package/src/engine/codegen/register_types.ts +10 -10
  77. package/src/engine/engine_camera.fit.ts +15 -4
  78. package/src/engine/engine_context.ts +30 -15
  79. package/src/engine/engine_context_eventbus.ts +73 -0
  80. package/src/engine/engine_input.ts +27 -6
  81. package/src/engine/engine_physics_rapier.ts +20 -6
  82. package/src/engine/engine_types.ts +22 -12
  83. package/src/engine-components/AnimationBuilder.ts +472 -0
  84. package/src/engine-components/Animator.ts +6 -1
  85. package/src/engine-components/AnimatorController.builder.ts +163 -37
  86. package/src/engine-components/AnimatorController.ts +1 -0
  87. package/src/engine-components/ContactShadows.ts +15 -1
  88. package/src/engine-components/DropListener.ts +3 -0
  89. package/src/engine-components/OrbitControls.ts +16 -5
  90. package/src/engine-components/SceneSwitcher.ts +3 -0
  91. package/src/engine-components/api.ts +1 -0
  92. package/src/engine-components/codegen/components.ts +6 -6
  93. package/src/engine-components/timeline/PlayableDirector.ts +20 -20
  94. package/src/engine-components/timeline/TimelineBuilder.ts +277 -17
  95. package/src/engine-components/timeline/TimelineTracks.ts +24 -16
  96. package/src/engine-components/web/CursorFollow.ts +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/engine",
3
- "version": "5.1.0-alpha.4",
3
+ "version": "5.1.0-alpha.5",
4
4
  "description": "Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.",
5
5
  "main": "dist/needle-engine.min.js",
6
6
  "exports": {
@@ -1,2 +1,7 @@
1
1
 
2
- export const NEEDLE_CLOUD_CLI_NAME = "needle-cloud@version-1";
2
+ const defaultVersion = "version-1";
3
+ const version = process.env.NEEDLE_CLOUD_CLI_VERSION_OVERRIDE || defaultVersion;
4
+ if (process.env.NEEDLE_CLOUD_CLI_VERSION_OVERRIDE) {
5
+ console.warn(`\n⚠️ WARNING: NEEDLE_CLOUD_CLI_VERSION_OVERRIDE is set to "${process.env.NEEDLE_CLOUD_CLI_VERSION_OVERRIDE}" — using needle-cloud@${version} instead of the default needle-cloud@${defaultVersion}.\n This should only be used for hotfixes!\n`);
6
+ }
7
+ export const NEEDLE_CLOUD_CLI_NAME = `needle-cloud@${version}`;
@@ -9,6 +9,7 @@ import { loadConfig } from './config.js';
9
9
  */
10
10
  export function needleLicense(command, config, userSettings) {
11
11
  let license = undefined;
12
+ let appliedLicense = false;
12
13
 
13
14
  return {
14
15
  name: "needle:license",
@@ -31,7 +32,15 @@ export function needleLicense(command, config, userSettings) {
31
32
 
32
33
  },
33
34
  async transform(src, id) {
34
- const isNeedleEngineFile = id.includes("engine/engine_license") || id.includes("needle-tools_engine");
35
+ if (appliedLicense === true) {
36
+ return;
37
+ }
38
+
39
+ // Vite 4 and 8 handling:
40
+ const isNeedleEngineFile = id.includes("engine/engine_license")
41
+ || id.includes("needle-tools_engine")
42
+ || id.includes("@needle-tools")
43
+ || id.includes("needle-engine");
35
44
  // sometimes the actual license parameter is in a unnamed chunk file
36
45
  const isViteChunkFile = id.includes("chunk") && id.includes(".vite");
37
46
  if (isNeedleEngineFile || isViteChunkFile) {
@@ -44,12 +53,21 @@ export function needleLicense(command, config, userSettings) {
44
53
  if (index >= 0) {
45
54
  const end = src.indexOf(";", index);
46
55
  if (end >= 0) {
56
+ appliedLicense = true;
47
57
  const line = src.substring(index, end);
48
58
  const replaced = "NEEDLE_ENGINE_LICENSE_TYPE = \"" + license + "\"";
49
59
  src = src.replace(line, replaced);
50
60
  return { code: src, map: null }
51
61
  }
52
62
  }
63
+ // @TODO: detect local needle engine dev setup and log error if not found
64
+ }
65
+ },
66
+ buildEnd() {
67
+ if (!appliedLicense) {
68
+ if (process.env.NEEDLE_TEST_ENV) {
69
+ console.error("ERR: License was not applied!");
70
+ }
53
71
  }
54
72
  }
55
73
  }
package/src/engine/api.ts CHANGED
@@ -167,6 +167,9 @@ export * from "./engine_constants.js";
167
167
  */
168
168
  export * from "./engine_context.js";
169
169
 
170
+ /** Typed event bus for decoupled component communication via {@link Context.events} */
171
+ export * from "./engine_context_eventbus.js";
172
+
170
173
  /** Registry for managing multiple engine contexts */
171
174
  export * from "./engine_context_registry.js";
172
175
 
@@ -101,12 +101,12 @@ import { TestRunner } from "../../engine-components/TestRunner.js";
101
101
  import { TestSimulateUserData } from "../../engine-components/TestRunner.js";
102
102
  import { PlayableDirector } from "../../engine-components/timeline/PlayableDirector.js";
103
103
  import { SignalReceiver } from "../../engine-components/timeline/SignalAsset.js";
104
- import { AnimationTrackHandler } from "../../engine-components/timeline/TimelineTracks.js";
105
- import { AudioTrackHandler } from "../../engine-components/timeline/TimelineTracks.js";
106
- import { MarkerTrackHandler } from "../../engine-components/timeline/TimelineTracks.js";
104
+ import { TimelineAnimationTrack } from "../../engine-components/timeline/TimelineTracks.js";
105
+ import { TimelineAudioTrack } from "../../engine-components/timeline/TimelineTracks.js";
106
+ import { TimelineMarkerTrack } from "../../engine-components/timeline/TimelineTracks.js";
107
107
  import { SignalTrackHandler } from "../../engine-components/timeline/TimelineTracks.js";
108
- import { ActivationTrackHandler } from "../../engine-components/timeline/TimelineTracks.js";
109
- import { ControlTrackHandler } from "../../engine-components/timeline/TimelineTracks.js";
108
+ import { TimelineActivationTrack } from "../../engine-components/timeline/TimelineTracks.js";
109
+ import { TimelineControlTrack } from "../../engine-components/timeline/TimelineTracks.js";
110
110
  import { TransformGizmo } from "../../engine-components/TransformGizmo.js";
111
111
  import { BaseUIComponent } from "../../engine-components/ui/BaseUIComponent.js";
112
112
  import { UIRootComponent } from "../../engine-components/ui/BaseUIComponent.js";
@@ -257,12 +257,12 @@ export function initBuiltinTypes() {
257
257
  TypeStore.add("TestSimulateUserData", TestSimulateUserData);
258
258
  TypeStore.add("PlayableDirector", PlayableDirector);
259
259
  TypeStore.add("SignalReceiver", SignalReceiver);
260
- TypeStore.add("AnimationTrackHandler", AnimationTrackHandler);
261
- TypeStore.add("AudioTrackHandler", AudioTrackHandler);
262
- TypeStore.add("MarkerTrackHandler", MarkerTrackHandler);
260
+ TypeStore.add("TimelineAnimationTrack", TimelineAnimationTrack);
261
+ TypeStore.add("TimelineAudioTrack", TimelineAudioTrack);
262
+ TypeStore.add("TimelineMarkerTrack", TimelineMarkerTrack);
263
263
  TypeStore.add("SignalTrackHandler", SignalTrackHandler);
264
- TypeStore.add("ActivationTrackHandler", ActivationTrackHandler);
265
- TypeStore.add("ControlTrackHandler", ControlTrackHandler);
264
+ TypeStore.add("TimelineActivationTrack", TimelineActivationTrack);
265
+ TypeStore.add("TimelineControlTrack", TimelineControlTrack);
266
266
  TypeStore.add("TransformGizmo", TransformGizmo);
267
267
  TypeStore.add("BaseUIComponent", BaseUIComponent);
268
268
  TypeStore.add("UIRootComponent", UIRootComponent);
@@ -257,12 +257,23 @@ export function fitCamera(options?: FitCameraOptions): null | FitCameraReturnTyp
257
257
  else {
258
258
  direction.sub(camera.worldPosition);
259
259
  }
260
- if (centerCamera === "y")
261
- direction.y = 0;
260
+ if (centerCamera === "y") {
261
+ // Preserve the camera's current elevation angle when it's already above the center,
262
+ // but clamp to a minimum elevation to prevent the camera from ending up at or below
263
+ // the scene center (which causes a "looking up from below" effect).
264
+ // direction points FROM camera TO center, so negative Y = camera is above center.
265
+ const horizontalLen = Math.sqrt(direction.x * direction.x + direction.z * direction.z);
266
+ if (horizontalLen > 0.0001) {
267
+ const minY = -horizontalLen * verticalOffset * 4;
268
+ if (direction.y > minY) direction.y = minY;
269
+ }
270
+ else {
271
+ // Camera is directly above/below center — pick a default slight angle from +Z
272
+ direction.set(0, -verticalOffset * 4, 1);
273
+ }
274
+ }
262
275
  direction.normalize();
263
276
  direction.multiplyScalar(distance);
264
- if (centerCamera === "y")
265
- direction.y += -verticalOffset * 4 * distance;
266
277
 
267
278
  let cameraLocalPosition = center.clone().sub(direction);
268
279
  if (options.cameraOffset) {
@@ -23,6 +23,7 @@ import { Application } from './engine_application.js';
23
23
  import { AssetDatabase } from './engine_assetdatabase.js';
24
24
  import { FocusRect, FocusRectSettings, updateCameraFocusRect } from './engine_camera.js';
25
25
  import { VERSION } from './engine_constants.js';
26
+ import { EventBus } from './engine_context_eventbus.js';
26
27
  import { ContextEvent, ContextRegistry } from './engine_context_registry.js';
27
28
  import { WaitForPromise } from './engine_coroutine.js';
28
29
  import { ObjectUtils } from "./engine_create_objects.js";
@@ -151,7 +152,7 @@ export function registerComponent(script: IComponent, context?: Context) {
151
152
  }
152
153
 
153
154
  /**
154
- * The Needle Engine context is the main access point that holds all the data and state of a Needle Engine application.
155
+ * The Needle Engine context is the main access point that holds all the data and state of a Needle Engine application.
155
156
  * It can be used to access the {@link Context.scene}, {@link Context.renderer}, {@link Context.mainCamera}, {@link Context.input}, {@link Context.physics}, {@link Context.time}, {@link Context.connection} (networking), and more.
156
157
  *
157
158
  * The context is automatically created when using the `<needle-engine>` web component.
@@ -522,19 +523,31 @@ export class Context implements IContext {
522
523
  private _fallbackCamera: PerspectiveCamera | null = null;
523
524
 
524
525
  /** access application state (e.g. if all audio should be muted) */
525
- application: Application;
526
+ get application(): Application { return this._application; }
527
+ private _application!: Application;
526
528
  /** access animation mixer used by components in the scene */
527
- animations: AnimationsRegistry;
529
+ get animations(): AnimationsRegistry { return this._animations; }
530
+ private _animations!: AnimationsRegistry;
528
531
  /** access timings (current frame number, deltaTime, timeScale, ...) */
529
- time: Time;
532
+ get time(): Time { return this._time; }
533
+ private _time!: Time;
530
534
  /** access input data (e.g. click or touch events) */
531
- input: Input;
535
+ get input(): Input { return this._input; }
536
+ private _input!: Input;
532
537
  /** access physics related methods (e.g. raycasting). To access the phyiscs engine use `context.physics.engine` */
533
- physics: Physics;
538
+ get physics(): Physics { return this._physics; }
539
+ private _physics!: Physics;
534
540
  /** access postprocessing effects stack. Add/remove effects and configure adaptive performance settings */
535
- postprocessing: PostProcessing;
541
+ get postprocessing(): PostProcessing { return this._postprocessing; }
542
+ private _postprocessing!: PostProcessing;
536
543
  /** access networking methods (use it to send or listen to messages or join a networking backend) */
537
- connection: NetworkConnection;
544
+ get connection(): NetworkConnection { return this._connection; }
545
+ private _connection!: NetworkConnection;
546
+ /** context-level event bus for decoupled component communication
547
+ * @see {@link ContextEventMap} for known event types
548
+ */
549
+ get events(): EventBus { return this._events; }
550
+ private _events = new EventBus();
538
551
  /** @deprecated AssetDatabase is deprecated */
539
552
  assets: AssetDatabase;
540
553
 
@@ -606,12 +619,12 @@ export class Context implements IContext {
606
619
  else this.scene = new Scene();
607
620
  if (args?.camera) this._mainCamera = args.camera;
608
621
 
609
- this.application = new Application(this);
610
- this.time = new Time();
611
- this.input = new Input(this);
612
- this.physics = new Physics(this);
613
- this.postprocessing = new PostProcessing(this);
614
- this.connection = new NetworkConnection(this);
622
+ this._application = new Application(this);
623
+ this._time = new Time();
624
+ this._input = new Input(this);
625
+ this._physics = new Physics(this);
626
+ this._postprocessing = new PostProcessing(this);
627
+ this._connection = new NetworkConnection(this);
615
628
  // eslint-disable-next-line @typescript-eslint/no-deprecated
616
629
  this.assets = new AssetDatabase();
617
630
  this.sceneLighting = new SceneLighting(this);
@@ -620,7 +633,7 @@ export class Context implements IContext {
620
633
  this.players = new PlayerViewManager(this);
621
634
  this.menu = new NeedleMenu(this);
622
635
  this.lodsManager = new LODsManager(this);
623
- this.animations = new AnimationsRegistry(this);
636
+ this._animations = new AnimationsRegistry(this);
624
637
  this.accessibility = new AccessibilityManager(this);
625
638
 
626
639
 
@@ -869,6 +882,8 @@ export class Context implements IContext {
869
882
 
870
883
  this._onBeforeRenderListeners.clear();
871
884
  this._onAfterRenderListeners.clear();
885
+ this._events.clear();
886
+ this._events = new EventBus();
872
887
 
873
888
  this.lights.length = 0;
874
889
 
@@ -0,0 +1,73 @@
1
+ import type { Object3D } from "three";
2
+
3
+ import type { IComponent } from "./engine_types.js";
4
+
5
+ /** Typed event map for {@link Context.events}.
6
+ * Known events get full autocomplete; custom events can be typed at the call site via generic parameter.
7
+ */
8
+ export interface ContextEventMap {
9
+ "scene-content-changed": {
10
+ /** The component that triggered the change (e.g. SceneSwitcher, DropListener) */
11
+ readonly source: IComponent;
12
+ /** The root object that was added/loaded */
13
+ readonly object: Object3D;
14
+ };
15
+ }
16
+
17
+ /** Options for {@link EventBus.on}. */
18
+ export interface EventBusListenerOptions {
19
+ /** If true the listener is automatically removed after the first invocation. */
20
+ once?: boolean;
21
+ }
22
+
23
+ /** Typed event bus. Known {@link ContextEventMap} events get full autocomplete.
24
+ * Custom events can be typed at the call site via generic parameter.
25
+ * @example Known events
26
+ * ```ts
27
+ * context.events.on("scene-content-changed", e => e.object);
28
+ * ```
29
+ * @example Custom events — type at call site
30
+ * ```ts
31
+ * context.events.emit<{ pts: number }>("scored", { pts: 10 });
32
+ * context.events.on<{ pts: number }>("scored", e => e.pts);
33
+ * ```
34
+ * @example Once
35
+ * ```ts
36
+ * context.events.on("scene-content-changed", e => { ... }, { once: true });
37
+ * ```
38
+ */
39
+ export class EventBus {
40
+ private _listeners = new Map<string, Function[]>();
41
+
42
+ /** Emit a known {@link ContextEventMap} event */
43
+ emit<K extends keyof ContextEventMap & string>(type: K, detail?: ContextEventMap[K]): void;
44
+ /** Emit a custom event with user-provided type */
45
+ emit<T>(type: string, detail?: T): void;
46
+ emit(type: string, detail?: unknown): void {
47
+ const arr = this._listeners.get(type);
48
+ if (arr) for (const cb of [...arr]) cb(detail);
49
+ }
50
+
51
+ /** Subscribe to a known {@link ContextEventMap} event. Returns an unsubscribe function. */
52
+ on<K extends keyof ContextEventMap & string>(type: K, callback: (args: ContextEventMap[K]) => void, options?: EventBusListenerOptions): () => void;
53
+ /** Subscribe to a custom event with user-provided type. Returns an unsubscribe function. */
54
+ on<T>(type: string, callback: (args: T) => void, options?: EventBusListenerOptions): () => void;
55
+ on(type: string, callback: Function, options?: EventBusListenerOptions): () => void {
56
+ let arr = this._listeners.get(type);
57
+ if (!arr) { arr = []; this._listeners.set(type, arr); }
58
+ const unsub = () => {
59
+ const i = arr.indexOf(wrapped);
60
+ if (i >= 0) arr.splice(i, 1);
61
+ };
62
+ const wrapped = options?.once
63
+ ? (...args: unknown[]) => { unsub(); callback(...args); }
64
+ : callback;
65
+ arr.push(wrapped);
66
+ return unsub;
67
+ }
68
+
69
+ /** Remove all listeners. Called when the context is cleared or destroyed. */
70
+ clear(): void {
71
+ this._listeners.clear();
72
+ }
73
+ }
@@ -343,14 +343,33 @@ export class Input implements IInput {
343
343
  private readonly _eventListeners: Record<string, RegisteredEventListenerValue> = {};
344
344
 
345
345
  /** Adds an event listener for the specified event type. The callback will be called when the event is triggered.
346
+ *
347
+ * Returns an unsubscribe function — call it to remove the listener.
348
+ * Pass it to {@link Behaviour.autoCleanup} for automatic lifecycle management.
349
+ *
346
350
  * @param type The event type to listen for
347
351
  * @param callback The callback to call when the event is triggered
348
352
  * @param options The options for adding the event listener.
349
- * @example Basic usage
353
+ * @returns A function that removes the event listener when called.
354
+ *
355
+ * @example With autoCleanup (recommended)
350
356
  * ```ts
351
- * input.addEventListener("pointerdown", (evt) => {
357
+ * export class MyComponent extends Behaviour {
358
+ * onEnable() {
359
+ * this.autoCleanup(this.context.input.addEventListener("pointerdown", (evt) => {
360
+ * console.log("Pointer down", evt.pointerId, evt.pointerType);
361
+ * }));
362
+ * }
363
+ * // Listener is automatically removed on disable — no manual cleanup needed!
364
+ * }
365
+ * ```
366
+ * @example Manual unsubscribe
367
+ * ```ts
368
+ * const off = input.addEventListener("pointerdown", (evt) => {
352
369
  * console.log("Pointer down", evt.pointerId, evt.pointerType);
353
370
  * });
371
+ * // later
372
+ * off();
354
373
  * ```
355
374
  * @example Adding a listener that is called after all other listeners
356
375
  * By using a higher value for the queue the listener will be called after other listeners (default queue is 0).
@@ -366,14 +385,14 @@ export class Input implements IInput {
366
385
  * }, { once: true });
367
386
  * ```
368
387
  */
369
- addEventListener(type: PointerEventNames, callback: PointerEventListener, options?: EventListenerOptions);
370
- addEventListener(type: KeyboardEventNames, callback: KeyboardEventListener, options?: EventListenerOptions);
371
- addEventListener(type: InputEvents | InputEventNames, callback: InputEventListener, options?: EventListenerOptions): void {
388
+ addEventListener(type: PointerEventNames, callback: PointerEventListener, options?: EventListenerOptions): () => void;
389
+ addEventListener(type: KeyboardEventNames, callback: KeyboardEventListener, options?: EventListenerOptions): () => void;
390
+ addEventListener(type: InputEvents | InputEventNames, callback: InputEventListener, options?: EventListenerOptions): () => void {
372
391
  if (!this._eventListeners[type]) this._eventListeners[type] = [];
373
392
 
374
393
  if (!callback || typeof callback !== "function") {
375
394
  console.error("Invalid call to addEventListener: callback is required and must be a function!");
376
- return;
395
+ return () => {};
377
396
  }
378
397
 
379
398
  if (!options) options = {};
@@ -392,6 +411,8 @@ export class Input implements IInput {
392
411
  } else {
393
412
  queueListeners.listeners.push({ callback, options });
394
413
  }
414
+
415
+ return () => this.removeEventListener(type as any, callback as any, options);
395
416
  }
396
417
  /** Removes the event listener from the specified event type. If no queue is specified the listener will be removed from all queues.
397
418
  * @param type The event type to remove the listener from
@@ -402,11 +402,16 @@ export class RapierPhysics implements IPhysicsEngine {
402
402
  filterGroups?: number,
403
403
  /** Return false to ignore this collider */
404
404
  filterPredicate?: (c: ICollider) => boolean,
405
- /** When enabled the hit object's layer will be tested. If layer 2 is enabled the object will be ignored (Layer 2 == IgnoreRaycast)
405
+ /** When enabled the hit object's layer will be tested. If layer 2 is enabled the object will be ignored (Layer 2 == IgnoreRaycast)
406
406
  * If not set the raycast will ignore objects in the IgnoreRaycast layer (default: true)
407
- * @default undefined
407
+ * @default undefined
408
408
  */
409
- useIgnoreRaycastLayer?: boolean
409
+ useIgnoreRaycastLayer?: boolean,
410
+ /** When true, trigger/sensor colliders will be included in the raycast results.
411
+ * By default trigger colliders are skipped.
412
+ * @default false
413
+ */
414
+ includeTriggers?: boolean,
410
415
  })
411
416
  : null | { point: Vector3, collider: ICollider } {
412
417
 
@@ -428,6 +433,8 @@ export class RapierPhysics implements IPhysicsEngine {
428
433
 
429
434
  const hit = this.world?.castRay(ray, maxDistance, solid, options?.queryFilterFlags, options?.filterGroups, undefined, undefined, (c) => {
430
435
  const component = c[$componentKey];
436
+ // Skip trigger/sensor colliders unless explicitly included
437
+ if (options?.includeTriggers !== true && component?.isTrigger) return false;
431
438
  if (options?.filterPredicate) return options.filterPredicate(component);
432
439
  if (options?.useIgnoreRaycastLayer !== false) {
433
440
  // ignore objects in the IgnoreRaycast=2 layer
@@ -453,11 +460,16 @@ export class RapierPhysics implements IPhysicsEngine {
453
460
  filterGroups?: number,
454
461
  /** Return false to ignore this collider */
455
462
  filterPredicate?: (c: ICollider) => boolean,
456
- /** When enabled the hit object's layer will be tested. If layer 2 is enabled the object will be ignored (Layer 2 == IgnoreRaycast)
463
+ /** When enabled the hit object's layer will be tested. If layer 2 is enabled the object will be ignored (Layer 2 == IgnoreRaycast)
457
464
  * If not set the raycast will ignore objects in the IgnoreRaycast layer (default: true)
458
- * @default undefined
465
+ * @default undefined
459
466
  */
460
- useIgnoreRaycastLayer?: boolean
467
+ useIgnoreRaycastLayer?: boolean,
468
+ /** When true, trigger/sensor colliders will be included in the raycast results.
469
+ * By default trigger colliders are skipped.
470
+ * @default false
471
+ */
472
+ includeTriggers?: boolean,
461
473
  })
462
474
  : null | { point: Vector3, normal: Vector3, collider: ICollider } {
463
475
 
@@ -478,6 +490,8 @@ export class RapierPhysics implements IPhysicsEngine {
478
490
 
479
491
  const hit = this.world?.castRayAndGetNormal(ray, maxDistance, solid, options?.queryFilterFlags, options?.filterGroups, undefined, undefined, (c) => {
480
492
  const component = c[$componentKey];
493
+ // Skip trigger/sensor colliders unless explicitly included
494
+ if (options?.includeTriggers !== true && component?.isTrigger) return false;
481
495
  if (options?.filterPredicate) return options.filterPredicate(component);
482
496
  if (options?.useIgnoreRaycastLayer !== false) {
483
497
  // ignore objects in the IgnoreRaycast=2 layer
@@ -521,20 +521,25 @@ export interface IPhysicsEngine {
521
521
  /** True if you want to also hit objects when the raycast starts from inside a collider */
522
522
  solid?: boolean,
523
523
  queryFilterFlags?: QueryFilterFlags,
524
- /**
525
- * Raycast filter groups. Groups are used to apply the collision group rules for the scene query.
526
- * The scene query will only consider hits with colliders with collision groups compatible with
524
+ /**
525
+ * Raycast filter groups. Groups are used to apply the collision group rules for the scene query.
526
+ * The scene query will only consider hits with colliders with collision groups compatible with
527
527
  * this collision group (using the bitwise test described in the collision groups section).
528
- * For example membership 0x0001 and filter 0x0002 should be 0x00010002
528
+ * For example membership 0x0001 and filter 0x0002 should be 0x00010002
529
529
  * @see https://rapier.rs/docs/user_guides/javascript/colliders#collision-groups-and-solver-groups
530
530
  */
531
531
  filterGroups?: number,
532
- /**
532
+ /**
533
533
  * Predicate to filter colliders in raycast results
534
534
  * @param collider The collider being tested
535
535
  * @returns False to ignore this collider, true to include it
536
536
  */
537
- filterPredicate?: (collider: ICollider) => boolean
537
+ filterPredicate?: (collider: ICollider) => boolean,
538
+ /** When true, trigger/sensor colliders will be included in the raycast results.
539
+ * By default trigger colliders are skipped.
540
+ * @default false
541
+ */
542
+ includeTriggers?: boolean,
538
543
  }): RaycastResult;
539
544
 
540
545
  /**
@@ -549,20 +554,25 @@ export interface IPhysicsEngine {
549
554
  /** True if you want to also hit objects when the raycast starts from inside a collider */
550
555
  solid?: boolean,
551
556
  queryFilterFlags?: QueryFilterFlags,
552
- /**
553
- * Raycast filter groups. Groups are used to apply the collision group rules for the scene query.
554
- * The scene query will only consider hits with colliders with collision groups compatible with
557
+ /**
558
+ * Raycast filter groups. Groups are used to apply the collision group rules for the scene query.
559
+ * The scene query will only consider hits with colliders with collision groups compatible with
555
560
  * this collision group (using the bitwise test described in the collision groups section).
556
- * For example membership 0x0001 and filter 0x0002 should be 0x00010002
561
+ * For example membership 0x0001 and filter 0x0002 should be 0x00010002
557
562
  * @see https://rapier.rs/docs/user_guides/javascript/colliders#collision-groups-and-solver-groups
558
563
  */
559
564
  filterGroups?: number,
560
- /**
565
+ /**
561
566
  * Predicate to filter colliders in raycast results
562
567
  * @param collider The collider being tested
563
568
  * @returns False to ignore this collider, true to include it
564
569
  */
565
- filterPredicate?: (collider: ICollider) => boolean
570
+ filterPredicate?: (collider: ICollider) => boolean,
571
+ /** When true, trigger/sensor colliders will be included in the raycast results.
572
+ * By default trigger colliders are skipped.
573
+ * @default false
574
+ */
575
+ includeTriggers?: boolean,
566
576
  }): RaycastResult;
567
577
 
568
578
  /**