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.

314 lines (313 loc) • 14 kB
import { Matrix4, Object3D, Quaternion, Ray, Vector3 } from "three"; import { Context } from "../engine_context.js"; import { IPointerHitEventReceiver } from "../engine_input.js"; import type { ButtonName, IGameObject, Vec3, XRControllerButtonName, XRGestureName } from "../engine_types.js"; import type { NeedleXRHitTestResult, NeedleXRSession } from "./NeedleXRSession.js"; /** true when selectstart was ever received. * On VisionOS 1.1 we always have select events (as per the spec), so this is always true */ declare type ControllerAxes = "xr-standard-thumbstick" | "xr-standard-touchpad"; declare type StickName = "xr-standard-thumbstick" | "xr-standard-touchpad"; declare type Mapping = "xr-standard"; declare type ComponentType = "button" | "thumbstick" | "squeeze" | "touchpad"; declare type GamepadKey = "button" | "xAxis" | "yAxis"; declare type NeedleXRControllerButtonName = ButtonName | "primary-button" | "primary"; declare type ComponentMap = { type: ComponentType; rootNodeName?: string; gamepadIndices?: { [key in GamepadKey]?: number; }; visualResponses?: { [key: string]: { states: Array<string>; }; }; }; declare type InputDeviceLayout = { selectComponentId: string; components: { [key: string]: ComponentMap; }; mapping: Mapping; gamepad: Array<XRControllerButtonName>; axes: Array<{ componentId: ControllerAxes; axis: "x-axis" | "y-axis"; }>; }; /** * A NeedleXRController wraps a connected XRInputDevice that is either a physical controller or a hand * You can access specific buttons using `getButton` and `getStick` * To get spatial data in rig space (position, rotation) use the `gripPosition`, `gripQuaternion`, `rayPosition` and `rayQuaternion` properties * To get spatial data in world space use the `gripWorldPosition`, `gripWorldQuaternion`, `rayWorldPosition` and `rayWorldQuaternion` properties * Inputs will also be emitted as pointer events on `this.context.input` - so you can receive controller inputs on objects using the appropriate input events on your components (e.g. `onPointerDown`, `onPointerUp` etc) - use the `pointerType` property to check if the event is from a controller or not * @link https://developer.mozilla.org/en-US/docs/Web/API/XRInputSource * @category XR */ export declare class NeedleXRController implements IPointerHitEventReceiver { /** the Needle XR Session */ readonly xr: NeedleXRSession; get context(): Context; /** * https://developer.mozilla.org/en-US/docs/Web/API/XRInputSource */ readonly inputSource: XRInputSource; /** the input source index */ readonly index: number; /** When enabled the controller will create input events in the Needle Engine input system (e.g. when a button is pressed or the controller is moved) * You can disable this if you don't want inputs to go through the input system but be aware that this will result in `onPointerDown` component callbacks to not be invoked anymore for this XRController */ emitEvents: boolean; /** Is the controller still connected? */ get connected(): boolean; private _connected; get isTracking(): boolean; private _isTracking; /** the input source gamepad giving raw access to the gamepad values * You should usually use the `getButton` and `getStick` methods instead to get access to named buttons and sticks */ get gamepad(): Gamepad | undefined; private __gamepad?; /** @returns true if this is a hand (otherwise this is a controller) */ get isHand(): boolean; /** * If this is a hand then this is the hand info (XRHand) * @link https://developer.mozilla.org/en-US/docs/Web/API/XRHand */ get hand(): XRHand | undefined; private __hand?; /** threejs XRHandSpace, shorthand for `context.renderer.xr.getHand(controllerIndex)` * @link https://threejs.org/docs/#api/en/renderers/webxr/WebXRManager.getHand */ get handObject(): import("three").XRHandSpace; /** The input source profiles */ get profiles(): string[]; /** The device input layout */ get layout(): InputDeviceLayout | undefined; /** shorthand for `inputSource.targetRayMode` */ get targetRayMode(): (XRTargetRayMode | "transient-pointer"); /** shorthand for `inputSource.targetRaySpace` */ get targetRaySpace(): XRSpace; /** shorthand for `inputSource.gripSpace` */ get gripSpace(): XRSpace | undefined; /** * If the controller if held in the left or right hand (or if it's a left or right hand) **/ get side(): XRHandedness; private __side; /** is right side. shorthand for `side === 'right'` */ get isRight(): boolean; /** is left side. shorthand for `side === 'left'` */ get isLeft(): boolean; /** is XR stylus, e.g. Logitech MX Ink */ get isStylus(): boolean; /** The XRTransientInputHitTestSource can be used to perform hit tests with the controller ray against the real world. * see https://developer.mozilla.org/en-US/docs/Web/API/XRSession/requestHitTestSourceForTransientInput for more information * Requires the hit-test feature to be enabled in the XRSession * * NOTE: The hit test source should be cancelled once it's not needed anymore. Call `cancelHitTestSource` to do this */ getHitTestSource(): XRTransientInputHitTestSource | undefined; get hasHitTestSource(): XRTransientInputHitTestSource | undefined; /** Make sure to cancel the hittest source once it's not needed anymore */ cancelHitTestSource(): void; private _hitTestSource; private _hasSelectEvent; get hasSelectEvent(): boolean; private _isMxInk; private _isMetaQuestTouchController; /** Perform a hit test against the XR planes or meshes. shorthand for `xr.getHitTest(controller)` * @returns the hit test result (with position and rotation in worldspace) or null if no hit was found */ getHitTest(): NeedleXRHitTestResult | null; /** This is cleared at the beginning of each frame */ private readonly _handJointPoses; /** Get the hand joint pose from the current XRFrame. Results are cached for a frame to avoid calling getJointPose multiple times */ getHandJointPose(joint: XRJointSpace, frame?: XRFrame): XRJointPose | null | undefined; /** Grip matrix in grip space */ private readonly _gripMatrix; /** Grip position in grip space */ private readonly _gripPosition; /** Grip rotation in grip space */ private readonly _gripQuaternion; private readonly _linearVelocity; private readonly _rayPositionRaw; private readonly _rayRotationRaw; /** ray matrix in grip space */ private readonly _rayMatrix; /** Ray position in rig space */ private readonly _rayPosition; /** Ray rotation in rig space */ private readonly _rayQuaternion; /** Grip position in rig space */ get gripPosition(): Vector3; /** Grip rotation in rig space */ get gripQuaternion(): Quaternion; get gripMatrix(): Matrix4; /** Grip linear velocity in rig space * @link https://developer.mozilla.org/en-US/docs/Web/API/XRPose/linearVelocity */ get gripLinearVelocity(): Vector3; /** Ray position in rig space */ get rayPosition(): Vector3; /** Ray rotation in rig space */ get rayQuaternion(): Quaternion; /** Controller grip position in worldspace */ get gripWorldPosition(): Vector3; private readonly _gripWorldPosition; /** Controller grip rotation in wordspace */ get gripWorldQuaternion(): Quaternion; private readonly _gripWorldQuaternion; /** Controller ray position in worldspace (this value is calculated once per frame by default - call `updateRayWorldPosition` to force an update) */ get rayWorldPosition(): Vector3; private readonly _rayWorldPosition; /** Recalculates the ray world position */ updateRayWorldPosition(): void; /** Controller ray rotation in wordspace (this value is calculated once per frame by default - call `updateRayWorldQuaternion` to force an update) */ get rayWorldQuaternion(): Quaternion; private readonly _rayWorldQuaternion; get pinchPosition(): Vector3; private readonly _pinchPosition; /** Recalculates the ray world quaternion */ updateRayWorldQuaternion(): void; /** The controller ray in worldspace */ get ray(): Ray; private readonly _ray; /** Recalculated once per update */ private _hand_wristDotUp; /** * The dot product of the hand palm with the up vector. * This is a number between -1 and 1, where 1 means the palm is directly up and -1 means the palm is directly down (upside down). * This value is undefined if there's no hand */ get handWristDotUp(): number | undefined; /** * @returns true if the hand is upside down */ get isHandUpsideDown(): boolean; /** * @returns true if the hand is upside down and we got a pinch down event this frame. */ get isTeleportGesture(): boolean | undefined; /** The controller object space. * You can use it to attach objects to the controller. * Children will be automatically detached and put into the scene when the controller disconnects */ get object(): IGameObject; private readonly _object; private readonly _gripSpaceObject?; private readonly _raySpaceObject?; /** Assigned the model that you use for rendering. This can be used as a hint for other components */ model: Object3D | null; private readonly _debugAxesHelper; private readonly _debugGripAxesHelper; private readonly _debugRayAxesHelper; /** returns the URL of the default controller model */ getModelUrl(): Promise<string | null>; constructor(session: NeedleXRSession, device: XRInputSource, index: number); private _hitTestSourcePromise; private _requestHitTestSource; onPointerHits: (_evt: any) => void; onUpdate(frame: XRFrame): void; onRenderDebug(): void; private onUpdateFrame; /** Called when the input source disconnects */ onDisconnected(): void; /** * Get a gamepad button * @link https://github.com/immersive-web/webxr-gamepads-module/blob/main/gamepads-module-explainer.md * @param key the controller button name e.g. x-button * @returns the gamepad button if it exists on the controller - otherwise undefined */ getButton(key: NeedleXRControllerButtonName): NeedleGamepadButton | undefined | null; /** Get a gesture state */ getGesture(key: XRGestureName): NeedleGamepadButton | null; /** * Get the pointer id for a specific button of this input device. * This is useful if you want to check if a button (e.g. trigger) is currently being in use which can be queried on the inputsystem. * @returns the pointer id for the button or undefined if the button is not supported * @example * ```ts * const pointerId = controller.getPointerId("primary"); * if (pointerId !== undefined) { * const isUsed = this.context.input.getPointerUsed(pointerId); * console.log(controller.side, "used?", isUsed); * } * ``` */ getPointerId(button: number): number; getPointerId(button: NeedleXRControllerButtonName | XRGestureName): number | undefined; private readonly _needleGamepadButtons; /** combine the InputState information + the GamepadButton information (since GamepadButtons can not be extended) */ private toNeedleGamepadButton; /** * Get the values of a controller joystick * @link https://github.com/immersive-web/webxr-gamepads-module/blob/main/gamepads-module-explainer.md * @returns the stick values where x is left/right, y is up/down and z is the button value */ getStick(key: StickName | "primary"): Vec3; private readonly _buttonMap; private _motioncontroller?; private _layout; private getMotionController; private initialize; /** * When enabled the controller will automatically emit pointer down events to the Needle Engine Input System. * @default true */ emitPointerDownEvent: boolean; /** * When enabled the controller will automatically emit pointer up events to the Needle Engine Input System. * @default true */ emitPointerUpEvent: boolean; /** * When enabled the controller will automatically emit pointer move events to the Needle Engine Input System. * @default true */ emitPointerMoveEvent: boolean; /** * The distance threshold for pointer move events. This value is in units in rig space * @default 0.03 */ pointerMoveDistanceThreshold: number; /** * The angle threshold for pointer move events. This value is in radians. * @default 0.05 */ pointerMoveAngleThreshold: number; private subscribeEvents; private unsubscribeEvents; private _selectButtonIndex; private _squeezeButtonIndex; private onSelectStart; private onSelectEnd; private onSequeezeStart; private onSequeezeEnd; /** Index = button index */ private readonly states; private updateInputEvents; private _didMoveLastFrame; private readonly _lastPointerMovePosition; private readonly _lastPointerMoveQuaternion; private onUpdateMove; /** cached spatial pointer init object. We re-use it to not have */ private readonly pointerInit; private emitPointerEvent; } /** Enhanced GamepadButton with `isDown` and `isUp` information */ declare class NeedleGamepadButton { /** The index of the button in the input gamepad */ readonly index: number | undefined; readonly name: string; touched: boolean; pressed: boolean; value: number; /** was the button just pressed down the last update */ isDown: boolean; /** was the button just released the last update */ isUp: boolean; constructor(index: number | undefined, name: string); } export {};