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