@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
416 lines (415 loc) • 18.8 kB
TypeScript
/// <reference types="webxr" />
import type { QueryFilterFlags } 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[];
};
/** All possible model types that Needle Engine can load */
export type Model = (GLTF | FBX | OBJ);
/** 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 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 {
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 {
initialize(ctx: IContext): Promise<boolean>;
step(dt: number): void;
postStep(): any;
get isUpdating(): boolean;
/** clear all possibly cached data (e.g. mesh data when creating scaled mesh colliders) */
clearCaches(): any;
enabled: boolean;
get world(): any;
set gravity(vec3: Vec3);
get gravity(): Vec3;
/** Get the rapier body for a Needle component */
getBody(obj: ICollider | IRigidbody): null | any;
/** Get the Needle Engine component for a rapier object */
getComponent(rapierObject: object): IComponent | null;
/** Fast raycast against physics colliders
* @param origin ray origin in screen or worldspace
* @param direction ray direction in worldspace
* @param options additional options
*/
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). See https://rapier.rs/docs/user_guides/javascript/colliders#collision-groups-and-solver-groups
* For example membership 0x0001 and filter 0x0002 should be 0x00010002 */
filterGroups?: number;
/** Return false to ignore this collider */
filterPredicate?: (collider: ICollider) => boolean;
}): RaycastResult;
/** raycast that also gets the normal vector. If you don't need it use raycast() */
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). See https://rapier.rs/docs/user_guides/javascript/colliders#collision-groups-and-solver-groups
* For example membership 0x0001 and filter 0x0002 should be 0x00010002 */
filterGroups?: number;
/** Return false to ignore this collider */
filterPredicate?: (collider: ICollider) => boolean;
}): RaycastResult;
sphereOverlap(point: Vector3, radius: number): Array<SphereOverlapResult>;
addSphereCollider(collider: ICollider): any;
addBoxCollider(collider: ICollider, size: Vector3): any;
addCapsuleCollider(collider: ICollider, radius: number, height: number): any;
addMeshCollider(collider: ICollider, mesh: Mesh, convex: boolean, scale: Vector3): any;
updatePhysicsMaterial(collider: ICollider): any;
wakeup(rb: IRigidbody): any;
isSleeping(rb: IRigidbody): boolean | undefined;
updateProperties(rb: IRigidbody | ICollider): any;
resetForces(rb: IRigidbody, wakeup: boolean): any;
resetTorques(rb: IRigidbody, wakeup: boolean): any;
addForce(rb: IRigidbody, vec: Vec3, wakeup: boolean): any;
applyImpulse(rb: IRigidbody, vec: Vec3, wakeup: boolean): any;
/** Returns the linear velocity of a rigidbody or the rigidbody of a collider */
getLinearVelocity(rb: IRigidbody | ICollider): Vec3 | null;
getAngularVelocity(rb: IRigidbody): Vec3 | null;
setAngularVelocity(rb: IRigidbody, vec: Vec3, wakeup: boolean): any;
setLinearVelocity(rb: IRigidbody, vec: Vec3, wakeup: boolean): any;
updateBody(comp: ICollider | IRigidbody, translation: boolean, rotation: boolean): any;
removeBody(body: IComponent): any;
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;
}
/** 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 {};