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.

579 lines (578 loc) • 25.2 kB
/// <reference types="webxr" /> import type { QueryFilterFlags, World } from "@dimforge/rapier3d-compat"; import { AnimationClip, Color, Material, Mesh, Object3D, Quaternion } from "three"; import { Vector3 } from "three"; import { type GLTF as THREE_GLTF } from "three/examples/jsm/loaders/GLTFLoader.js"; import type { Camera as CameraComponent } from "../engine-components/api.js"; import type { Context } from "./engine_context.js"; import { InstantiateContext } from "./engine_gameobject.js"; import { CollisionDetectionMode, type PhysicsMaterial, RigidbodyConstraints } from "./engine_physics.types.js"; import type { NeedleXRSession } from "./engine_xr.js"; /** Removes all properties that match TypesToFilter */ type FilterTypes<T, TypesToFilter> = { [P in keyof T as T[P] extends TypesToFilter ? never : P]: T[P]; }; /** Removes all undefined functions */ type NoUndefinedNoFunctions<T> = FilterTypes<T, Function | undefined | null>; type FilterStartingWith<T, Prefix extends string> = { [K in keyof T as K extends string ? (K extends `${Prefix}${infer _}` ? never : K) : never]: T[K]; }; /** Removes all properties that start with an underscore */ type NoInternals<T> = FilterStartingWith<T, "_">; type NoInternalNeedleEngineState<T> = Omit<T, "destroyed" | "gameObject" | "activeAndEnabled" | "context" | "isComponent" | "scene" | "up" | "forward" | "right" | "worldRotation" | "worldEuler" | "worldPosition" | "worldQuaternion">; export type ComponentInit<T> = Partial<NoInternalNeedleEngineState<NoInternals<NoUndefinedNoFunctions<T>>>>; /** GLTF, GLB or VRM */ export type GLTF = THREE_GLTF; /** FBX type */ export type FBX = { animations: AnimationClip[]; scene: Object3D; scenes: Object3D[]; }; /** OBJ type */ export type OBJ = { animations: AnimationClip[]; scene: Object3D; scenes: Object3D[]; }; export type CustomModel = { animations: AnimationClip[]; scene: Object3D; scenes: Object3D[]; }; /** All possible model types that Needle Engine can load */ export type Model = (GLTF | FBX | OBJ | CustomModel); export declare function isGLTFModel(model: Model): model is GLTF; /** A loaded model */ export type LoadedModel = { src: string; file: Model; }; /** used to find data registered via gltf files e.g. find lightmaps for a Renderer component that were shipped inside a gltf */ export declare type SourceIdentifier = string; export type Constructor<T> = abstract new (...args: any[]) => T; export type ConstructorConcrete<T> = new (...args: any[]) => T; export type GuidsMap = { [key: string]: string; }; export interface UIDProvider { seed: number; generateUUID(): string; } export declare type CoroutineData = { comp: IComponent; main: Generator; chained?: Array<Generator>; }; export interface ITime { get time(): number; get deltaTime(): number; } export interface IInput { convertScreenspaceToRaycastSpace(vec: Vec2): void; getPointerPosition(i: number): Vec2 | null; } export interface IPhysics { engine?: IPhysicsEngine; } export type IContext = Context; export type INeedleXRSession = NeedleXRSession; export declare interface INeedleEngineComponent extends HTMLElement { getAROverlayContainer(): HTMLElement; onEnterAR(session: XRSession, overlayContainer: HTMLElement): any; onExitAR(session: XRSession): any; } export declare enum HideFlags { None = 0, /** When enabled the glTF exporter will omit this object and all children from being exported */ DontExport = 1 } export declare interface IGameObject extends Object3D { /** the object's unique identifier */ guid: string | undefined; /** if the object is enabled in the hierarchy (usually equivalent to `visible`) */ activeSelf: boolean; /** call to destroy this object including all components that are attached to it. Will destroy all children recursively */ destroy(): void; /** Add a new component to this object. Expects a component type (e.g. `addNewComponent(Animator)`) */ addNewComponent<T extends IComponent>(type: Constructor<T>, init?: ComponentInit<T>): T; /** Remove a component from this object. Expected a component instance * @returns the removed component (equal to the passed in component) */ addComponent<T extends IComponent>(comp: T | ConstructorConcrete<T>, init?: ComponentInit<T>): T; removeComponent<T extends IComponent>(comp: T): T; /** Searches for a component type on this object. If no component of the searched type exists yet a new instance will be created and returned */ getOrAddComponent<T>(typeName: Constructor<T> | null): T; /** Tries to find a component of a type on this object. * @returns the first instance of a component on this object that matches the passed in type or null if no component of this type (or a subtype) exists */ getComponent<T>(type: Constructor<T>): T | null; /** @returns all components of a certain type on this object */ getComponents<T>(type: Constructor<T>, arr?: T[]): Array<T>; /** Finds a component of a certain type on this object OR a child object if any exists */ getComponentInChildren<T>(type: Constructor<T>): T | null; /** Finds all components of a certain type on this object AND all children (recursively) */ getComponentsInChildren<T>(type: Constructor<T>, arr?: T[]): Array<T>; /** Finds a component of a certain type on this object OR a parent object if any exists */ getComponentInParent<T>(type: Constructor<T>): T | null; /** Finds all components of a certain type on this object AND all parents (recursively) */ getComponentsInParent<T>(type: Constructor<T>, arr?: T[]): Array<T>; get worldPosition(): Vector3; set worldPosition(val: Vector3); get worldQuaternion(): Quaternion; set worldQuaternion(val: Quaternion); get worldRotation(): Vector3; set worldRotation(val: Vector3); get worldScale(): Vector3; set worldScale(val: Vector3); get worldForward(): Vector3; get worldRight(): Vector3; get worldUp(): Vector3; } export interface IHasGuid { guid: string; } export interface IComponent extends IHasGuid { get isComponent(): boolean; /** the object this component is attached to */ gameObject: IGameObject; enabled: boolean; sourceId?: SourceIdentifier; get name(): string; get layer(): number; get destroyed(): boolean; get tag(): string; context: any; get activeAndEnabled(): boolean; /** @internal */ __internalNewInstanceCreated(init?: ComponentInit<any>): any; /** @internal */ _internalInit(init?: ComponentInit<any>): any; /** @internal */ __internalAwake(): any; /** @internal */ __internalStart(): any; /** @internal */ __internalEnable(isAddingOrRemovingFromScene?: boolean): any; /** @internal */ __internalDisable(isAddingOrRemovingFromScene?: boolean): any; /** @internal */ __internalDestroy(): any; /** @internal */ resolveGuids?(guidsMap: GuidsMap): void; /** experimental, called when the script is registered for the first time, this is called even if the component is not enabled. */ registering?(): any; awake(): any; onEnable(): any; onDisable(): any; onDestroy(): any; destroy(): any; /** called for properties decorated with the @validate decorator */ onValidate?(property?: string): any; /** called when this.context.isPaused changes or when rendering loop changes due to changing DOM element visibility * e.g. when the DOM element becomes hidden or out ot view */ onPausedChanged?(isPaused: boolean, wasPaused: boolean): any; start?(): void; earlyUpdate?(): void; update?(): void; lateUpdate?(): void; onBeforeRender?(frame: XRFrame | null): void; onAfterRender?(): void; onCollisionEnter?(col: Collision): any; onCollisionExit?(col: Collision): any; onCollisionStay?(col: Collision): any; onTriggerEnter?(col: ICollider): any; onTriggerStay?(col: ICollider): any; onTriggerExit?(col: ICollider): any; get forward(): Vector3; get worldPosition(): Vector3; get worldQuaternion(): Quaternion; } export declare function isComponent(obj: any): obj is IComponent; export type ICamera = CameraComponent; export type IAnimationComponent = Pick<IComponent, "gameObject"> & { isAnimationComponent: boolean; addClip?(clip: AnimationClip): any; }; /** Interface for a camera controller component that can be attached to a camera to control it */ export declare interface ICameraController { get isCameraController(): boolean; } export declare interface ILight extends IComponent { intensity: number; color: Color; } export declare interface ISharedMaterials { [num: number]: Material; get length(): number; } export declare interface IRenderer extends IComponent { sharedMaterial: Material; get sharedMaterials(): ISharedMaterials; } export declare interface IEventList { readonly isEventList: true; __internalOnInstantiate(map: InstantiateContext): IEventList; } export declare interface ICollider extends IComponent { get isCollider(): any; attachedRigidbody: IRigidbody | null; /** * Note: Make sure to call updatePhysicsMaterial after having changed this property */ isTrigger: boolean; /** * The physics material determines how the collider interacts with other colliders (e.g. bouncyness) * Note: Make sure to call updatePhysicsMaterial after having changed this property */ sharedMaterial?: PhysicsMaterial; center?: Vec3 & { multiply(vec: Vec3): any; }; updateProperties(): void; updatePhysicsMaterial(): void; /** The collider membership indicates what groups the collider is part of (e.g. group 2 and 3) * An `undefined` array indicates that the collider is part of all groups * Note: Make sure to call updateProperties after having changed this property * Default: [0] */ membership?: number[]; /** The collider filter indicates what groups the collider can interact with (e.g. group 3 and 4) * An `undefined` array indicates that the collider can interact with all groups * Note: Make sure to call updateProperties after having changed this property * Default: undefined */ filter?: number[]; } export declare interface ISphereCollider extends ICollider { radius: number; } export declare interface IBoxCollider extends ICollider { size: Vec3; } export declare interface IRigidbody extends IComponent { get isRigidbody(): boolean; constraints: RigidbodyConstraints; isKinematic: boolean; /** When true the mass will automatically calculated by attached colliders */ autoMass: boolean; mass: number; drag: number; angularDrag: number; useGravity: boolean; centerOfMass: Vec3; gravityScale: number; dominanceGroup: number; collisionDetectionMode: CollisionDetectionMode; lockPositionX: boolean; lockPositionY: boolean; lockPositionZ: boolean; lockRotationX: boolean; lockRotationY: boolean; lockRotationZ: boolean; } export declare const $physicsKey: unique symbol; export declare type ICollisionContext = { getCollider(obj: Object3D): ICollider; }; export type Vec2 = { x: number; y: number; }; export type Vec3 = { x: number; y: number; z: number; }; export type Vec4 = { x: number; y: number; z: number; w: number; }; /** * Holds information about physics contacts */ export declare class ContactPoint { private readonly _point; private readonly _normal; private readonly _tangentVelocity; /** the distance of the collision point */ readonly distance: number; /** the impulse velocity */ readonly impulse: number; readonly friction: number; /** worldspace point */ get point(): Vector3; /** worldspace normal */ get normal(): Vector3; /** worldspace tangent */ get tangentVelocity(): Vector3; /** @internal */ constructor(point: Vec3, dist: number, normal: Vec3, impulse: number, friction: number, tangentVelocity: Vec3); } /** * Holds information about a collision event. Includes a list of contact points and the colliders involved */ export declare class Collision { /** The contact points of this collision. Contains information about positions, normals, distance, friction, impulse... */ readonly contacts: ContactPoint[]; /** @internal */ constructor(obj: IGameObject, otherCollider: ICollider, contacts: ContactPoint[]); /** the gameobject this collision event belongs to (e.g. if onCollisionEnter is called then `me` is the same as `this.gameObject`) */ readonly me: IGameObject; private _collider; /** the other collider the collision happened with */ get collider(): ICollider; private _gameObject; /** the other object the collision happened with */ get gameObject(): IGameObject; /** the other rigidbody we hit, null if none attached */ get rigidBody(): IRigidbody | null; } export type RaycastResult = null | { point: Vector3; collider: ICollider; normal?: Vector3; }; export declare class SphereOverlapResult { object: Object3D; collider: ICollider; constructor(object: Object3D, collider: ICollider); } export interface IPhysicsEngine { /** Initializes the physics engine */ initialize(): Promise<boolean>; /** Indicates whether the physics engine has been initialized */ get isInitialized(): boolean; /** Advances physics simulation by the given time step */ step(dt: number): void; postStep(): any; /** Indicates whether the physics engine is currently updating */ get isUpdating(): boolean; /** Clears all cached data (e.g., mesh data when creating scaled mesh colliders) */ clearCaches(): any; /** Enables or disables the physics engine */ enabled: boolean; /** Returns the underlying physics world object */ get world(): World | undefined; /** Sets the gravity vector for the physics simulation */ set gravity(vec3: Vec3); /** Gets the current gravity vector */ get gravity(): Vec3; /** * Gets the rapier physics body for a Needle component * @param obj The collider or rigidbody component * @returns The underlying physics body or null if not found */ getBody(obj: ICollider | IRigidbody): null | any; /** * Gets the Needle Engine component for a rapier physics object * @param rapierObject The rapier physics object * @returns The associated component or null if not found */ getComponent(rapierObject: object): IComponent | null; /** * Performs a fast raycast against physics colliders * @param origin Ray origin in screen or worldspace * @param direction Ray direction in worldspace * @param options Additional raycast configuration options * @returns Raycast result containing hit point and collider, or null if no hit */ raycast(origin?: Vec2 | Vec3, direction?: Vec3, options?: { maxDistance?: number; /** True if you want to also hit objects when the raycast starts from inside a collider */ solid?: boolean; queryFilterFlags?: QueryFilterFlags; /** * Raycast filter groups. Groups are used to apply the collision group rules for the scene query. * The scene query will only consider hits with colliders with collision groups compatible with * this collision group (using the bitwise test described in the collision groups section). * For example membership 0x0001 and filter 0x0002 should be 0x00010002 * @see https://rapier.rs/docs/user_guides/javascript/colliders#collision-groups-and-solver-groups */ filterGroups?: number; /** * Predicate to filter colliders in raycast results * @param collider The collider being tested * @returns False to ignore this collider, true to include it */ filterPredicate?: (collider: ICollider) => boolean; }): RaycastResult; /** * Performs a raycast that also returns the normal vector at the hit point * @param origin Ray origin in screen or worldspace * @param direction Ray direction in worldspace * @param options Additional raycast configuration options * @returns Raycast result containing hit point, normal, and collider, or null if no hit */ raycastAndGetNormal(origin?: Vec2 | Vec3, direction?: Vec3, options?: { maxDistance?: number; /** True if you want to also hit objects when the raycast starts from inside a collider */ solid?: boolean; queryFilterFlags?: QueryFilterFlags; /** * Raycast filter groups. Groups are used to apply the collision group rules for the scene query. * The scene query will only consider hits with colliders with collision groups compatible with * this collision group (using the bitwise test described in the collision groups section). * For example membership 0x0001 and filter 0x0002 should be 0x00010002 * @see https://rapier.rs/docs/user_guides/javascript/colliders#collision-groups-and-solver-groups */ filterGroups?: number; /** * Predicate to filter colliders in raycast results * @param collider The collider being tested * @returns False to ignore this collider, true to include it */ filterPredicate?: (collider: ICollider) => boolean; }): RaycastResult; /** * Finds all colliders within a sphere * @param point The center point of the sphere * @param radius The radius of the sphere * @returns Array of objects that overlap with the sphere */ sphereOverlap(point: Vector3, radius: number): Array<SphereOverlapResult>; /** * Adds a sphere collider to the physics world * @param collider The collider component to add */ addSphereCollider(collider: ICollider): any; /** * Adds a box collider to the physics world * @param collider The collider component to add * @param size The size of the box */ addBoxCollider(collider: ICollider, size: Vector3): any; /** * Adds a capsule collider to the physics world * @param collider The collider component to add * @param radius The radius of the capsule * @param height The height of the capsule */ addCapsuleCollider(collider: ICollider, radius: number, height: number): any; /** * Adds a mesh collider to the physics world * @param collider The collider component to add * @param mesh The mesh to use for collision * @param convex Whether the collision mesh should be treated as convex * @param scale Optional scale to apply to the mesh */ addMeshCollider(collider: ICollider, mesh: Mesh, convex: boolean, scale?: Vector3 | undefined): any; /** * Updates the physics material properties of a collider * @param collider The collider to update */ updatePhysicsMaterial(collider: ICollider): any; /** * Wakes up a sleeping rigidbody * @param rb The rigidbody to wake up */ wakeup(rb: IRigidbody): any; /** * Checks if a rigidbody is currently sleeping * @param rb The rigidbody to check * @returns Whether the rigidbody is sleeping or undefined if cannot be determined */ isSleeping(rb: IRigidbody): boolean | undefined; /** * Updates the physical properties of a rigidbody or collider * @param rb The rigidbody or collider to update */ updateProperties(rb: IRigidbody | ICollider): any; /** * Resets all forces acting on a rigidbody * @param rb The rigidbody to reset forces on * @param wakeup Whether to wake up the rigidbody */ resetForces(rb: IRigidbody, wakeup: boolean): any; /** * Resets all torques acting on a rigidbody * @param rb The rigidbody to reset torques on * @param wakeup Whether to wake up the rigidbody */ resetTorques(rb: IRigidbody, wakeup: boolean): any; /** * Adds a continuous force to a rigidbody * @param rb The rigidbody to add force to * @param vec The force vector to add * @param wakeup Whether to wake up the rigidbody */ addForce(rb: IRigidbody, vec: Vec3, wakeup: boolean): any; /** * Applies an instantaneous impulse to a rigidbody * @param rb The rigidbody to apply impulse to * @param vec The impulse vector to apply * @param wakeup Whether to wake up the rigidbody */ applyImpulse(rb: IRigidbody, vec: Vec3, wakeup: boolean): any; /** * Gets the linear velocity of a rigidbody or the rigidbody attached to a collider * @param rb The rigidbody or collider to get velocity from * @returns The linear velocity vector or null if not available */ getLinearVelocity(rb: IRigidbody | ICollider): Vec3 | null; /** * Gets the angular velocity of a rigidbody * @param rb The rigidbody to get angular velocity from * @returns The angular velocity vector or null if not available */ getAngularVelocity(rb: IRigidbody): Vec3 | null; /** * Sets the angular velocity of a rigidbody * @param rb The rigidbody to set angular velocity for * @param vec The angular velocity vector to set * @param wakeup Whether to wake up the rigidbody */ setAngularVelocity(rb: IRigidbody, vec: Vec3, wakeup: boolean): any; /** * Sets the linear velocity of a rigidbody * @param rb The rigidbody to set linear velocity for * @param vec The linear velocity vector to set * @param wakeup Whether to wake up the rigidbody */ setLinearVelocity(rb: IRigidbody, vec: Vec3, wakeup: boolean): any; /** * Updates the position and/or rotation of a physics body * @param comp The collider or rigidbody component to update * @param translation Whether to update the position * @param rotation Whether to update the rotation */ updateBody(comp: ICollider | IRigidbody, translation: boolean, rotation: boolean): any; /** * Removes a physics body from the simulation * @param body The component whose physics body should be removed */ removeBody(body: IComponent): any; /** * Gets the physics body for a component * @param obj The collider or rigidbody component * @returns The underlying physics body or null if not found */ getBody(obj: ICollider | IRigidbody): null | any; addFixedJoint(body1: IRigidbody, body2: IRigidbody): any; addHingeJoint(body1: IRigidbody, body2: IRigidbody, anchor: Vec3, axis: Vec3): any; /** Enable to render collider shapes */ debugRenderColliders: boolean; /** Enable to visualize raycasts in the scene with gizmos */ debugRenderRaycasts: boolean; } /** * Available cursor types * @link https://developer.mozilla.org/en-US/docs/Web/CSS/cursor */ export type CursorTypeName = "auto" | "default" | "none" | "context-menu" | "help" | "pointer" | "progress" | "wait" | "cell" | "crosshair" | "text" | "vertical-text" | "alias" | "copy" | "move" | "no-drop" | "not-allowed" | "grab" | "grabbing" | "all-scroll" | "col-resize" | "row-resize" | "n-resize" | "e-resize" | "s-resize" | "w-resize" | "nw-resize" | "se-resize" | "sw-resize" | "ew-resize" | "ns-resize" | "nesw-resize" | "nwse-resize" | "zoom-in" | "zoom-out"; /** Typical mouse button names for most devices */ export type MouseButtonName = "left" | "right" | "middle"; /** Button names on typical controllers (since there seems to be no agreed naming) * https://w3c.github.io/gamepad/#remapping */ export type GamepadButtonName = "a-button" | "b-button" | "x-button" | "y-button"; /** Needle-defined names for stylus (MX Ink) */ export type StylusButtonName = "stylus-touch" | "stylus-tip"; /** Button names as used in the xr profile */ export type XRControllerButtonName = "thumbrest" | "xr-standard-trigger" | "xr-standard-squeeze" | "xr-standard-thumbstick" | "xr-standard-touchpad" | "menu" | GamepadButtonName | StylusButtonName; export type XRGestureName = "pinch"; /** All known (types) button names for various devices and cases combined. You should use the device specific names if you e.g. know you only deal with a mouse use MouseButtonName */ export type ButtonName = "unknown" | MouseButtonName | GamepadButtonName | XRControllerButtonName | XRGestureName; export {};