UNPKG

@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
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 {};