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.

244 lines (243 loc) • 10.8 kB
import type { Collider, ColliderDesc, QueryFilterFlags, World } from '@dimforge/rapier3d-compat'; import { Mesh, Vector3, Vector4Like } from 'three'; import type { ICollider, IComponent, IContext, IPhysicsEngine, IRigidbody, Vec2, Vec3 } from './engine_types.js'; import { ShapeOverlapResult } from './engine_types.js'; /** * Rapier physics engine implementation for Needle Engine. * * Rapier is a fast, cross-platform physics engine that provides realistic physics simulation * for rigidbodies, colliders, joints, and collision detection. It runs entirely in WebAssembly * for high performance. * * **Features:** * - Rigidbody simulation (dynamic, kinematic, static) * - Multiple collider shapes (box, sphere, capsule, mesh, convex hull) * - Raycasting and shape overlap queries against physics colliders * - Collision and trigger events * - Joints (fixed, hinge, etc.) * - Continuous collision detection (CCD) * - Physics materials (friction, bounciness) * * **Access:** * The Rapier physics engine is accessible via `this.context.physics.engine` from any component. * Rapier is automatically initialized when physics components are used. * * **Using the Rapier Module Directly:** * Rapier is lazy-loaded for performance. You can access the raw Rapier module via {@link MODULES.RAPIER_PHYSICS}. * Use `MODULES.RAPIER_PHYSICS.load()` to load the module, or `MODULES.RAPIER_PHYSICS.ready()` to wait for it without triggering a load. * Once loaded, the module is available at `MODULES.RAPIER_PHYSICS.MODULE`. * * **Note:** * This is the low-level physics engine implementation. For general raycasting (against all scene objects), * use {@link Physics.raycast} instead. Use this class for physics-specific operations like applying forces, * raycasting against colliders only, or accessing the Rapier world directly. * * @example Applying forces to a rigidbody * ```ts * const rb = this.gameObject.getComponent(Rigidbody); * if (rb) { * this.context.physics.engine?.addForce(rb, { x: 0, y: 10, z: 0 }, true); * } * ``` * @example Raycasting against physics colliders only * ```ts * const origin = { x: 0, y: 5, z: 0 }; * const direction = { x: 0, y: -1, z: 0 }; * const hit = this.context.physics.engine?.raycast(origin, direction); * if (hit) { * console.log("Hit collider:", hit.collider.name); * } * ``` * @example Accessing Rapier world directly * ```ts * const rapierWorld = this.context.physics.engine?.world; * if (rapierWorld) { * // Direct access to Rapier API * console.log("Gravity:", rapierWorld.gravity); * } * ``` * @example Using the Rapier module directly * ```ts * import { MODULES } from "@needle-tools/engine"; * * // Load the Rapier module * const RAPIER = await MODULES.RAPIER_PHYSICS.load(); * * // Now you can use Rapier types and create custom physics objects * const rigidBodyDesc = RAPIER.RigidBodyDesc.dynamic() * .setTranslation(0, 10, 0); * * // Or access the already-loaded module * if (MODULES.RAPIER_PHYSICS.MODULE) { * const colliderDesc = MODULES.RAPIER_PHYSICS.MODULE.ColliderDesc.ball(1.0); * } * ``` * @see {@link Rigidbody} for physics simulation component * @see {@link Collider} for collision detection component * @see {@link Physics} for general raycasting and physics utilities * @see {@link MODULES.RAPIER_PHYSICS} for direct access to the Rapier module * @link https://rapier.rs/docs/ for Rapier documentation * @link https://engine.needle.tools/docs/reference/components.html#physics * @link https://engine.needle.tools/docs/how-to-guides/scripting/use-physics.html */ export declare class RapierPhysics implements IPhysicsEngine { debugRenderColliders: boolean; debugRenderRaycasts: boolean; removeBody(obj: IComponent): void; updateBody(comp: ICollider | IRigidbody, translation: boolean, rotation: boolean): void; updateProperties(obj: IRigidbody | ICollider): void; addForce(rigidbody: IRigidbody, force: Vec3, wakeup: boolean): void; addImpulse(rigidbody: IRigidbody, force: Vec3, wakeup: boolean): void; getLinearVelocity(comp: IRigidbody | ICollider): Vec3 | null; getAngularVelocity(rb: IRigidbody): Vec3 | null; resetForces(rb: IRigidbody, wakeup: boolean): void; resetTorques(rb: IRigidbody, wakeup: boolean): void; applyImpulse(rb: IRigidbody, vec: Vec3, wakeup: boolean): void; wakeup(rb: IRigidbody): void; isSleeping(rb: IRigidbody): boolean | undefined; setAngularVelocity(rb: IRigidbody, vec: Vec3, wakeup: boolean): void; setLinearVelocity(rb: IRigidbody, vec: Vec3, wakeup: boolean): void; private readonly context?; private _initializePromise?; private _isInitialized; constructor(ctx: IContext); get isInitialized(): boolean; initialize(): Promise<boolean>; private internalInitialization; /** Check is the physics engine has been initialized and the call can be made */ private validate; private rapierRay; private raycastVectorsBuffer; 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; filterGroups?: number; /** Return false to ignore this collider */ filterPredicate?: (c: ICollider) => boolean; /** When enabled the hit object's layer will be tested. If layer 2 is enabled the object will be ignored (Layer 2 == IgnoreRaycast) * If not set the raycast will ignore objects in the IgnoreRaycast layer (default: true) * @default undefined */ useIgnoreRaycastLayer?: boolean; }): null | { point: Vector3; collider: ICollider; }; 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; filterGroups?: number; /** Return false to ignore this collider */ filterPredicate?: (c: ICollider) => boolean; /** When enabled the hit object's layer will be tested. If layer 2 is enabled the object will be ignored (Layer 2 == IgnoreRaycast) * If not set the raycast will ignore objects in the IgnoreRaycast layer (default: true) * @default undefined */ useIgnoreRaycastLayer?: boolean; }): null | { point: Vector3; normal: Vector3; collider: ICollider; }; private getPhysicsRay; private rapierSphere; private rapierBox; private readonly rapierColliderArray; private readonly rapierIdentityRotation; private readonly rapierForwardVector; /** Precice sphere overlap detection using rapier against colliders * @param point center of the sphere in worldspace * @param radius radius of the sphere * @returns array of colliders that overlap with the sphere. Note: they currently only contain the collider and the gameobject */ sphereOverlap(point: Vector3, radius: number): Array<ShapeOverlapResult>; /** box overlap detection using rapier against colliders * @param point center of the box in worldspace * @param size size of the box * @param rotation quaternion representation of the rotation in world space * @returns array of colliders that overlap with the box. Note: they currently only contain the collider and the gameobject */ boxOverlap(point: Vector3, size: Vector3, rotation?: Vector4Like | null): Array<ShapeOverlapResult>; private shapeOverlap; enabled: boolean; /** Get access to the rapier world */ get world(): World | undefined; private _tempPosition; private _tempQuaternion; private _tempScale; private _tempMatrix; private static _didLoadPhysicsEngine; private _isUpdatingPhysicsWorld; get isUpdating(): boolean; private _world?; private _hasCreatedWorld; private eventQueue?; private collisionHandler?; private objects; private bodies; private _meshCache; private _gravity; get gravity(): Vec3; set gravity(value: Vec3); clearCaches(): void; addBoxCollider(collider: ICollider, size: Vector3): Promise<void>; addSphereCollider(collider: ICollider): Promise<void>; addCapsuleCollider(collider: ICollider, height: number, radius: number): Promise<void>; addMeshCollider(collider: ICollider, mesh: Mesh, convex: boolean, extraScale?: Vector3): Promise<void>; updatePhysicsMaterial(col: ICollider): void; /** 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; /** * Creates a collider in the physics world. * * @param collider - The collider component. * @param desc - The collider description. * @returns The created collider. * * @throws Will throw an error if the physics world is not initialized. Make sure to call `initialize()` before creating colliders. */ createCollider(collider: ICollider, desc: ColliderDesc): Collider | null; /** * Updates the collision groups of a collider. * * @param collider - The collider to update. */ private updateColliderCollisionGroups; private getRigidbody; private internal_getRigidbody; private internalUpdateColliderProperties; private internalUpdateRigidbodyProperties; private lines?; step(dt?: number): void; postStep(): void; private updateDebugRendering; /** sync rendered objects with physics world (except for colliders without rigidbody) */ private syncObjects; private syncPhysicsBody; private readonly _tempCenterPos; private readonly _tempCenterVec; private readonly _tempCenterQuaternion; private tryApplyCenter; private static _matricesBuffer; private getRigidbodyRelativeMatrix; private static centerConnectionPos; private static centerConnectionRot; addFixedJoint(body1: IRigidbody, body2: IRigidbody): void; /** The joint prevents any relative movement between two rigid-bodies, except for relative rotations along one axis. This is typically used to simulate wheels, fans, etc. They are characterized by one local anchor as well as one local axis on each rigid-body. */ addHingeJoint(body1: IRigidbody, body2: IRigidbody, anchor: { x: number; y: number; z: number; }, axis: { x: number; y: number; z: number; }): void; private calculateJointRelativeMatrices; }