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

265 lines (226 loc) • 9.13 kB
import { BufferGeometry, Group, Mesh, Object3D, Vector3 } from "three" import { addComponent } from "../engine/engine_components.js"; import type { PhysicsMaterial } from "../engine/engine_physics.types.js"; import { serializable } from "../engine/engine_serialization_decorator.js"; import { getWorldScale } from "../engine/engine_three_utils.js"; // import { IColliderProvider, registerColliderProvider } from "../engine/engine_physics.js"; import type { IBoxCollider, ICollider, ISphereCollider } from "../engine/engine_types.js"; import { validate } from "../engine/engine_util_decorator.js"; import { unwatchWrite, watchWrite } from "../engine/engine_utils.js"; import { NEEDLE_progressive } from "../engine/extensions/NEEDLE_progressive.js"; import { Behaviour } from "./Component.js"; import { Rigidbody } from "./RigidBody.js"; /** * Collider is the base class for all colliders. A collider is a physical shape that is used to detect collisions with other objects in the scene. * Colliders are used in combination with a Rigidbody to create physical interactions between objects. * Colliders are registered with the physics engine when they are enabled and removed when they are disabled. * @category Physics * @group Components */ export class Collider extends Behaviour implements ICollider { /** @internal */ get isCollider(): any { return true; } /** * The Rigidbody that this collider is attached to. */ @serializable(Rigidbody) attachedRigidbody: Rigidbody | null = null; /** * When `true` the collider will not be used for collision detection but will still trigger events. */ @serializable() isTrigger: boolean = false; /** * The physics material that is used for the collider. This material defines physical properties of the collider such as friction and bounciness. */ @serializable() sharedMaterial?: PhysicsMaterial; /** * The layers that the collider is assigned to. */ @serializable() membership: number[] = [0]; /** * The layers that the collider will interact with. * @inheritdoc */ @serializable() filter?: number[]; /** @internal */ awake() { super.awake(); if (!this.attachedRigidbody) this.attachedRigidbody = this.gameObject.getComponentInParent(Rigidbody); } /** @internal */ start() { if (!this.attachedRigidbody) this.attachedRigidbody = this.gameObject.getComponentInParent(Rigidbody); } /** @internal */ onEnable() { // a rigidbody is not assigned if we export an asset if (!this.attachedRigidbody) this.attachedRigidbody = this.gameObject.getComponentInParent(Rigidbody); } /** @internal */ onDisable() { this.context.physics.engine?.removeBody(this); } /** Returns the underlying physics body from the physics engine (if any) - the component must be enabled and active in the scene */ get body() { return this.context.physics.engine?.getBody(this); } /** * Apply the collider properties to the physics engine. */ updateProperties = () => { this.context.physics.engine?.updateProperties(this); } /** Requests an update of the physics material in the physics engine */ updatePhysicsMaterial(){ this.context.physics.engine?.updatePhysicsMaterial(this); } } /** * SphereCollider is a collider that represents a sphere shape. * @category Physics * @group Components */ export class SphereCollider extends Collider implements ISphereCollider { @validate() @serializable() radius: number = .5; @serializable(Vector3) center: Vector3 = new Vector3(0, 0, 0); onEnable() { super.onEnable(); this.context.physics.engine?.addSphereCollider(this); watchWrite(this.gameObject.scale, this.updateProperties); } onDisable(): void { super.onDisable(); unwatchWrite(this.gameObject.scale, this.updateProperties); } onValidate(): void { this.updateProperties(); } } /** * BoxCollider is a collider that represents a box shape. * @category Physics * @group Components */ export class BoxCollider extends Collider implements IBoxCollider { static add(obj: Mesh, opts?: { rigidbody: boolean }) { const collider = new BoxCollider(); if (!obj.geometry.boundingBox) obj.geometry.computeBoundingBox(); const bb = obj.geometry.boundingBox!; collider.size = bb!.getSize(new Vector3()) || new Vector3(1, 1, 1); collider.center = bb!.getCenter(new Vector3()) || new Vector3(0, 0, 0); addComponent(obj, collider); if (opts?.rigidbody === true) { addComponent(obj, Rigidbody, { isKinematic: false }); } return collider; } @validate() @serializable(Vector3) size: Vector3 = new Vector3(1, 1, 1); @serializable(Vector3) center: Vector3 = new Vector3(0, 0, 0); onEnable() { super.onEnable(); this.context.physics.engine?.addBoxCollider(this, this.size); watchWrite(this.gameObject.scale, this.updateProperties); } onDisable(): void { super.onDisable(); unwatchWrite(this.gameObject.scale, this.updateProperties); } onValidate(): void { this.updateProperties(); } } /** * MeshCollider is a collider that represents a mesh shape. * The mesh collider can be used to create a collider from a mesh. * @category Physics * @group Components */ export class MeshCollider extends Collider { /** * The mesh that is used for the collider. */ @serializable(Mesh) sharedMesh?: Mesh; /** When `true` the collider won't have holes or entrances. * If you wan't this mesh collider to be able to *contain* other objects this should be set to `false` */ @serializable() convex: boolean = false; onEnable() { super.onEnable(); if (!this.context.physics.engine) return; if (!this.sharedMesh?.isMesh) { // HACK using the renderer mesh if (this.gameObject instanceof Mesh) { this.sharedMesh = this.gameObject; } } const LOD = 0; if (this.sharedMesh?.isMesh) { this.context.physics.engine.addMeshCollider(this, this.sharedMesh, this.convex, getWorldScale(this.gameObject)); NEEDLE_progressive.assignMeshLOD(this.sharedMesh, LOD).then(res => { if (res && this.activeAndEnabled && this.context.physics.engine && this.sharedMesh) { this.context.physics.engine.removeBody(this); this.sharedMesh.geometry = res; this.context.physics.engine.addMeshCollider(this, this.sharedMesh, this.convex, getWorldScale(this.gameObject)); } }) } else { const group = this.sharedMesh as any as Group; if (group?.isGroup) { console.warn(`MeshCollider mesh is a group \"${this.sharedMesh?.name || this.gameObject.name}\", adding all children as colliders. This is currently not fully supported (colliders can not be removed from world again)`, this); const promises = new Array<Promise<BufferGeometry | null>>(); for (const ch in group.children) { const child = group.children[ch] as Mesh; if (child.isMesh) { this.context.physics.engine.addMeshCollider(this, child, this.convex, getWorldScale(this.gameObject)); promises.push(NEEDLE_progressive.assignMeshLOD(child, LOD)); } } Promise.all(promises).then(res => { if (res.some(r => r) == false) return; this.context.physics.engine?.removeBody(this); const mesh = new Mesh(); for (const r of res) { if (r && this.activeAndEnabled) { mesh.geometry = r; this.context.physics.engine?.addMeshCollider(this, mesh, this.convex, getWorldScale(this.gameObject)); } } }); } } } } /** * CapsuleCollider is a collider that represents a capsule shape. * @category Physics * @group Components */ export class CapsuleCollider extends Collider { @serializable(Vector3) center: Vector3 = new Vector3(0, 0, 0); @serializable() radius: number = .5; @serializable() height: number = 2; onEnable() { super.onEnable(); this.context.physics.engine?.addCapsuleCollider(this, this.height, this.radius); } }