@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
text/typescript
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.
*/
(Rigidbody)
attachedRigidbody: Rigidbody | null = null;
/**
* When `true` the collider will not be used for collision detection but will still trigger events.
*/
()
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.
*/
()
sharedMaterial?: PhysicsMaterial;
/**
* The layers that the collider is assigned to.
*/
()
membership: number[] = [0];
/**
* The layers that the collider will interact with.
* @inheritdoc
*/
()
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 {
()
()
radius: number = .5;
(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;
}
()
(Vector3)
size: Vector3 = new Vector3(1, 1, 1);
(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.
*/
(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` */
()
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 {
(Vector3)
center: Vector3 = new Vector3(0, 0, 0);
()
radius: number = .5;
()
height: number = 2;
onEnable() {
super.onEnable();
this.context.physics.engine?.addCapsuleCollider(this, this.height, this.radius);
}
}