@needle-tools/engine
Version:
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.
446 lines (445 loc) • 19.9 kB
TypeScript
import { Intersection, Ray, Vector2 } from 'three';
import { Context } from './engine_setup.js';
import type { ButtonName, CursorTypeName, IGameObject, IInput, Vec2 } from './engine_types.js';
import { type EnumToPrimitiveUnion } from './engine_utils.js';
/**
* Types of pointer input devices supported by Needle Engine.
*/
export declare const enum PointerType {
/** Mouse or trackpad input */
Mouse = "mouse",
/** Touch screen input */
Touch = "touch",
/** XR controller input (e.g., VR controllers) */
Controller = "controller",
/** XR hand tracking input */
Hand = "hand"
}
export type PointerTypeNames = EnumToPrimitiveUnion<PointerType>;
declare const enum PointerEnumType {
PointerDown = "pointerdown",
PointerUp = "pointerup",
PointerMove = "pointermove"
}
declare const enum KeyboardEnumType {
KeyDown = "keydown",
KeyUp = "keyup",
KeyPressed = "keypress"
}
/**
* Event types that can be listened to via {@link Input.addEventListener}.
* @see {@link NEPointerEvent} for pointer event data
* @see {@link NEKeyboardEvent} for keyboard event data
*/
export declare const enum InputEvents {
/** Fired when a pointer button is pressed */
PointerDown = "pointerdown",
/** Fired when a pointer button is released */
PointerUp = "pointerup",
/** Fired when a pointer moves */
PointerMove = "pointermove",
/** Fired when a key is pressed down */
KeyDown = "keydown",
/** Fired when a key is released */
KeyUp = "keyup",
/** Fired when a key produces a character value */
KeyPressed = "keypress"
}
/** e.g. `pointerdown` */
type PointerEventNames = EnumToPrimitiveUnion<PointerEnumType>;
type KeyboardEventNames = EnumToPrimitiveUnion<KeyboardEnumType>;
export type InputEventNames = PointerEventNames | KeyboardEventNames;
declare type PointerEventListener = (evt: NEPointerEvent) => void;
declare type KeyboardEventListener = (evt: NEKeyboardEvent) => void;
export declare type NEPointerEventInit = PointerEventInit & {
clientZ?: number;
origin: object;
pointerId: number;
/** the index of the device */
deviceIndex: number;
pointerType: PointerTypeNames;
mode: XRTargetRayMode;
ray?: Ray;
/** The control object for this input. In the case of spatial devices the controller,
* otherwise a generated object in screen space. The object may not be in the scene. */
device: IGameObject;
buttonName: ButtonName | "none";
};
declare type OnPointerHitsEvent = (args: OnPointerHitEvent) => void;
declare type OnPointerHitEvent = {
/** The object that raised the event */
sender: object;
/** The pointer event that invoked the event */
event: NEPointerEvent;
/** The intersections that were generated from this event (or are associated with this event in any way) */
hits: Intersection[];
};
export interface IPointerHitEventReceiver {
onPointerHits: OnPointerHitsEvent;
}
/** An intersection that is potentially associated with a pointer event */
export declare type NEPointerEventIntersection = Intersection & {
event?: NEPointerEvent;
};
/**
* Extended PointerEvent with Needle Engine-specific data.
* Contains information about the input device, spatial data for XR, and world-space ray.
*
* @example Accessing event data in a component
* ```ts
* onPointerDown(args: PointerEventData) {
* const evt = args.event;
* console.log(`Pointer ${evt.pointerId} (${evt.pointerType})`);
* if (evt.isSpatial) {
* console.log("XR input, ray:", evt.ray);
* }
* }
* ```
*
* @see {@link Input} for the input management system
* @see {@link PointerType} for available pointer types
*/
export declare class NEPointerEvent extends PointerEvent {
/**
* Spatial input data
*/
clientZ?: number;
/** the device index: mouse and touch are always 0, otherwise e.g. index of the connected Gamepad or XRController */
readonly deviceIndex: number;
/** The origin of the event contains a reference to the creator of this event.
* This can be the Needle Engine input system or e.g. a XR controller.
* Implement `onPointerHits` to receive the intersections of this event.
*/
readonly origin: object & Partial<IPointerHitEventReceiver>;
/** the browser event that triggered this event (if any) */
readonly source: Event | null;
/** Is the pointer event created via a touch on screen or a spatial device like a XR controller or hand tracking? */
readonly mode: XRTargetRayMode | "transient-pointer";
/** Returns true if the input was emitted in 3D space (and not by e.g. clicking on a 2D screen). You can use {@link mode} if you need more information about the input source */
get isSpatial(): boolean;
/** A ray in worldspace for the event.
* If the ray is undefined you can also use `space.worldForward` and `space.worldPosition` */
get ray(): Ray;
private set ray(value);
/**@returns true if this event has a ray. If you access the ray property a ray will automatically created */
get hasRay(): boolean;
private _ray;
/** The device space (this object is not necessarily rendered in the scene but you can access or copy the matrix)
* E.g. you can access the input world space source position with `space.worldPosition` or world direction with `space.worldForward`
*/
readonly space: IGameObject;
/** true if this event is a click */
isClick: boolean;
/** true if this event is a double click */
isDoubleClick: boolean;
/** @returns `true` if the event is marked to be used (when `use()` has been called). Default: `false` */
get used(): boolean;
private _used;
/** Call to mark an event to be used */
use(): void;
/** Identifier for this pointer event.
* For mouse and touch this is always 0.
* For XR input: a combination of the deviceIndex + button to uniquely identify the exact input (e.g. LeftController:Button0 = 0, RightController:Button1 = 11)
*/
get pointerId(): number;
private readonly _pointerid;
/** What type of input created this event: touch, mouse, xr controller, xr hand tracking... */
get pointerType(): PointerTypeNames;
private readonly _pointerType;
/**
* The button name that raised this event (e.g. for mouse events "left", "right", "middle" or for XRTrigger "xr-standard-trigger" or "xr-standard-thumbstick")
* Use {@link button} to get the numeric button index (e.g. 0, 1, 2...) on the controller or mouse.
*/
readonly buttonName?: ButtonName | "none";
/** The input that raised this event like `pointerdown` */
get type(): InputEventNames;
private readonly _type;
/** metadata can be used to associate additional information with the event */
readonly metadata: {};
/** intersections that were generated from this event (or are associated with this event in any way) */
readonly intersections: NEPointerEventIntersection[];
constructor(type: InputEvents | InputEventNames, source: Event | null, init: NEPointerEventInit);
private _immediatePropagationStopped;
get immediatePropagationStopped(): boolean;
private _propagationStopped;
get propagationStopped(): boolean;
stopImmediatePropagation(): void;
stopPropagation(): void;
}
export declare class NEKeyboardEvent extends KeyboardEvent {
source?: Event;
constructor(type: InputEvents, source: Event, init: KeyboardEventInit);
stopImmediatePropagation(): void;
}
export declare class KeyEventArgs {
key: string;
keyType: string;
source?: Event;
constructor(evt: KeyboardEvent);
}
export declare enum InputEventQueue {
Early = -100,
Default = 0,
Late = 100
}
declare type EventListenerOptions = {
/** For addEventListener: The queue to add the listener to. Listeners in the same queue are called in the order they were added. Default is 0.
* For removeEventListener: The queue to remove the listener from. If no queue is specified the listener will be removed from all queues
*/
queue?: InputEventQueue | number;
/** If true, the listener will be removed after it is invoked once. */
once?: boolean;
/** The listener will be removed when the given AbortSignal object's `abort()` method is called. If not specified, no AbortSignal is associated with the listener. */
signal?: AbortSignal;
};
/**
* Handles all input events including mouse, touch, keyboard, and XR controllers.
* Access via `this.context.input` from any component.
*
* @example Checking mouse/pointer state
* ```ts
* update() {
* if (this.context.input.mouseDown) {
* console.log("Mouse button pressed");
* }
* if (this.context.input.mouseClick) {
* console.log("Click detected");
* }
* const pos = this.context.input.mousePosition;
* console.log(`Mouse at: ${pos.x}, ${pos.y}`);
* }
* ```
* @example Keyboard input
* ```ts
* update() {
* if (this.context.input.isKeyDown("Space")) {
* console.log("Space pressed this frame");
* }
* if (this.context.input.isKeyPressed("w")) {
* console.log("W key is held down");
* }
* }
* ```
* @example Event-based input
* ```ts
* onEnable() {
* this.context.input.addEventListener("pointerdown", this.onPointerDown);
* }
* onDisable() {
* this.context.input.removeEventListener("pointerdown", this.onPointerDown);
* }
* onPointerDown = (evt: NEPointerEvent) => {
* console.log("Pointer down:", evt.pointerId);
* }
* ```
*
* @see {@link NEPointerEvent} for pointer event data
* @see {@link InputEvents} for available event types
* @see {@link PointerType} for pointer device types
* @link https://engine.needle.tools/docs/scripting.html
*/
export declare class Input implements IInput {
/** This is a list of event listeners per event type (e.g. pointerdown, pointerup, keydown...). Each entry contains a priority and list of listeners.
* That way users can control if they want to receive events before or after other listeners (e.g subscribe to pointer events before the EventSystem receives them) - this allows certain listeners to be always invoked first (or last) and stop propagation
* Listeners per event are sorted
*/
private readonly _eventListeners;
/** Adds an event listener for the specified event type. The callback will be called when the event is triggered.
* @param type The event type to listen for
* @param callback The callback to call when the event is triggered
* @param options The options for adding the event listener.
* @example Basic usage
* ```ts
* input.addEventListener("pointerdown", (evt) => {
* console.log("Pointer down", evt.pointerId, evt.pointerType);
* });
* ```
* @example Adding a listener that is called after all other listeners
* By using a higher value for the queue the listener will be called after other listeners (default queue is 0).
* ```ts
* input.addEventListener("pointerdown", (evt) => {
* console.log("Pointer down", evt.pointerId, evt.pointerType);
* }, { queue: 10 });
* ```
* @example Adding a listener that is only called once
* ```ts
* input.addEventListener("pointerdown", (evt) => {
* console.log("Pointer down", evt.pointerId, evt.pointerType);
* }, { once: true });
* ```
*/
addEventListener(type: PointerEventNames, callback: PointerEventListener, options?: EventListenerOptions): any;
addEventListener(type: KeyboardEventNames, callback: KeyboardEventListener, options?: EventListenerOptions): any;
/** Removes the event listener from the specified event type. If no queue is specified the listener will be removed from all queues.
* @param type The event type to remove the listener from
* @param callback The callback to remove
* @param options The options for removing the event listener
*/
removeEventListener(type: PointerEventNames, callback: PointerEventListener, options?: EventListenerOptions): any;
removeEventListener(type: KeyboardEventNames, callback: KeyboardEventListener, options?: EventListenerOptions): any;
private dispatchEvent;
_doubleClickTimeThreshold: number;
_longPressTimeThreshold: number;
get mousePosition(): Vector2;
get mousePositionRC(): Vector2;
get mouseDown(): boolean;
get mouseUp(): boolean;
/** Is the primary pointer clicked (usually the left button). This is equivalent to `input.click` */
get mouseClick(): boolean;
/** Was a double click detected for the primary pointer? This is equivalent to `input.doubleClick` */
get mouseDoubleClick(): boolean;
get mousePressed(): boolean;
get mouseWheelChanged(): boolean;
/** Is the primary pointer double clicked (usually the left button). This is equivalent to `input.mouseDoubleClick` */
get click(): boolean;
/** Was a double click detected for the primary pointer? */
get doubleClick(): boolean;
/**
* Get a connected Gamepad
* Note: For a gamepad to be available to the browser it must have received input before while the page was focused.
* @link https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API/Using_the_Gamepad_API
* @returns The gamepad or null if no gamepad is connected
*/
getGamepad(index?: number): Gamepad | null;
private readonly _setCursorTypes;
/** @deprecated use setCursor("pointer") */
setCursorPointer(): void;
/** @deprecated use unsetCursor() */
setCursorNormal(): void;
/**
* Set a custom cursor. This will set the cursor type until unsetCursor is called
*/
setCursor(type: CursorTypeName): void;
/**
* Unset a custom cursor. This will set the cursor type to the previous type or default
*/
unsetCursor(type: CursorTypeName): void;
private updateCursor;
/**
* Check if a pointer id is currently used.
*/
getIsPointerIdInUse(pointerId: number): boolean;
/** how many pointers are currently pressed */
getPointerPressedCount(): number;
/**
* Gets the position of the given pointer index in pixel
* @param i The pointer index
* @returns The position of the pointer in pixel
*/
getPointerPosition(i: number): Vector2 | null;
getPointerPositionLastFrame(i: number): Vector2 | null;
getPointerPositionDelta(i: number): Vector2 | null;
/**
* The pointer position in screenspace coordinates (-1 to 1) where 0 is the center of the screen.
* This can be useful for e.g. raycasting (see https://threejs.org/docs/#api/en/core/Raycaster.setFromCamera)
*/
getPointerPositionRC(i: number): Vector2 | null;
getPointerDown(i: number): boolean;
getPointerUp(i: number): boolean;
getPointerPressed(i: number): boolean;
getPointerClicked(i: number): boolean;
getPointerDoubleClicked(i: number): boolean;
getPointerDownTime(i: number): number;
getPointerUpTime(i: number): number;
getPointerLongPress(i: number): boolean;
getIsMouse(i: number): boolean;
getIsTouch(i: number): boolean;
getTouchesPressedCount(): number;
getMouseWheelChanged(i?: number): boolean;
getMouseWheelDeltaY(i?: number): number;
getPointerEvent(i: number): Event | undefined;
foreachPointerId(pointerType?: string | PointerType | string[] | PointerType[]): Generator<number>;
foreachTouchId(): Generator<number>;
private _pointerIsActive;
private context;
private _pointerDown;
private _pointerUp;
private _pointerClick;
private _pointerDoubleClick;
private _pointerPressed;
private _pointerPositions;
private _pointerPositionsLastFrame;
private _pointerPositionsDelta;
private _pointerPositionsRC;
private _pointerPositionDown;
private _pointerDownTime;
private _pointerUpTime;
private _pointerUpTimestamp;
private _pointerIds;
private _pointerTypes;
private _mouseWheelChanged;
private _mouseWheelDeltaY;
private _pointerEvent;
/** current pressed pointer events. Used to check if any of those events was used */
private _pointerEventsPressed;
/** This is added/updated for pointers. screenspace pointers set this to the camera near plane */
private _pointerSpace;
private readonly _pressedStack;
private onDownButton;
private onReleaseButton;
/** the first button that was down and is currently pressed */
getFirstPressedButtonForPointer(pointerId: number): number | undefined;
/** the last (most recent) button that was down and is currently pressed */
getLatestPressedButtonForPointer(pointerId: number): number | undefined;
/** Get a key (if any) that was just pressed this frame (this is only true for the frame it was pressed down) */
getKeyDown(): string | null;
/** Get true or false if the given key was pressed this frame */
getKeyDown(key: KeyCode | ({} & string)): boolean;
/** Get a key (if any) that is currently being pressed (held down) */
getKeyPressed(): string | null;
/** Get true or false if the given key is pressed */
getKeyPressed(key: KeyCode | ({} & string)): boolean;
/** Get a key (if any) that was released in this frame */
getKeyUp(): string | null;
/** Get true or false if the given key was released this frame */
getKeyUp(key: KeyCode | ({} & string)): boolean;
isKeyDown(keyCode: KeyCode | ({} & string)): boolean;
isKeyUp(keyCode: KeyCode | ({} & string)): boolean;
isKeyPressed(keyCode: KeyCode | ({} & string)): boolean;
private getCodeForCommonKeyName;
createInputEvent(args: NEPointerEvent): void;
convertScreenspaceToRaycastSpace<T extends Vec2 | Vector2>(vec2: T): T;
/** @internal */
constructor(context: Context);
/** this is the html element we subscribed to for events */
private _htmlEventSource;
bindEvents(): void;
unbindEvents(): void;
dispose(): void;
private onLostFocus;
private readonly _receivedPointerMoveEventsThisFrame;
private onEndOfFrame;
private canReceiveInput;
private onContextMenu;
private keysPressed;
private onKeyDown;
private onKeyPressed;
private onKeyUp;
private onWheelWindow;
private onMouseWheel;
private onPointerDown;
private onPointerMove;
private onPointerCancel;
private onPointerUp;
private getPointerId;
private getButtonName;
private onTouchStart;
private onTouchMove;
private onTouchEnd;
private readonly tempNearPlaneVector;
private readonly tempFarPlaneVector;
private readonly tempLookMatrix;
private getAndUpdateSpatialObjectForScreenPosition;
private isInRect;
private onDown;
private onMove;
private onUp;
private updatePointerPosition;
/** get the next free id */
private getPointerIndex;
private setPointerState;
private setPointerStateT;
private onDispatchEvent;
}
export declare type KeyCode = "Tab" | "Enter" | "ShiftLeft" | "ShiftRight" | "ControlLeft" | "ControlRight" | "AltLeft" | "AltRight" | "Pause" | "CapsLock" | "Escape" | "Space" | "PageUp" | "PageDown" | "End" | "Home" | "ArrowLeft" | "ArrowUp" | "ArrowRight" | "ArrowDown" | "Insert" | "Delete" | "Digit0" | "Digit1" | "Digit2" | "Digit3" | "Digit4" | "Digit5" | "Digit6" | "Digit7" | "Digit8" | "Digit9" | "KeyA" | "KeyB" | "KeyC" | "KeyD" | "KeyE" | "KeyF" | "KeyG" | "KeyH" | "KeyI" | "KeyJ" | "KeyK" | "KeyL" | "KeyM" | "KeyN" | "KeyO" | "KeyP" | "KeyQ" | "KeyR" | "KeyS" | "KeyT" | "KeyU" | "KeyV" | "KeyW" | "KeyX" | "KeyY" | "KeyZ" | "Select" | "Numpad0" | "Numpad1" | "Numpad2" | "Numpad3" | "Numpad4" | "Numpad5" | "Numpad6" | "Numpad7" | "Numpad8" | "Numpad9" | "Multiply" | "Add" | "Subtract" | "Decimal" | "Divide" | "F1" | "F2" | "F3" | "F4" | "F5" | "F6" | "F7" | "F8" | "F9" | "F10" | "F11" | "F12";
export {};