threefiz
Version:
Implementation of a physics engine based on the [Three.js](https://github.com/mrdoob/three.js) library.
198 lines (172 loc) • 6.14 kB
text/typescript
import { Box3, Matrix3, Mesh, Quaternion, Vector3 } from "three";
import Debug from "./Debug";
import * as collisionInfo from "./Collision";
const v = new Vector3();
const q = new Quaternion();
const angularAxis = new Vector3();
type RigidBodyProps = {
mesh: Mesh;
position: Vector3;
rotation: Quaternion;
velocity: Vector3;
angularVelocity: Vector3;
invertedInertia: Matrix3;
density: number;
mass: number;
restitution: number;
isStatic: boolean;
debug: Debug;
friction: number;
aabb: Box3;
};
abstract class RigidBody {
mesh: Mesh;
position: Vector3;
protected velocity: Vector3;
protected rotation: Quaternion;
protected prevPosition: Vector3;
protected prevRotation: Quaternion;
protected angularVelocity: Vector3;
protected invertedInertia: Matrix3;
density: number;
mass: number;
restitution: number;
isStatic: boolean;
debug: Debug;
friction: number;
aabb: Box3;
needsUpdate: boolean;
private aabbOrigin: Box3;
constructor({
mesh = new Mesh(),
position = new Vector3(),
rotation = new Quaternion(),
velocity = new Vector3(),
angularVelocity = new Vector3(),
invertedInertia = new Matrix3(),
density = 1,
mass = 1,
restitution = 0.5,
isStatic = false,
friction = 0.5,
}: Partial<RigidBodyProps> = {}) {
this.mesh = mesh;
this.mesh.matrixAutoUpdate = false;
this.position = position;
this.rotation = rotation;
this.prevPosition = this.position.clone();
this.prevRotation = this.rotation.clone();
this.velocity = velocity;
this.angularVelocity = angularVelocity;
this.invertedInertia = invertedInertia;
this.mass = mass;
this.isStatic = isStatic;
if (density < 0) {
console.warn("Density cannot be less than 0");
}
this.density = Math.max(0, density);
if (restitution > 1 || restitution < 0) {
console.warn("Restitution cannot be greater than 1 or less than 0");
}
this.restitution = Math.min(Math.max(restitution, 0), 1);
if (friction > 1 || friction < 0) {
console.warn("Friction cannot be greater than 1 or less than 0");
}
this.friction = Math.min(Math.max(friction, 0), 1);
this.mesh.position.copy(this.position);
this.mesh.quaternion.copy(this.rotation);
this.debug = new Debug(this);
this.aabb = new Box3();
this.aabb.setFromObject(this.mesh, true);
this.aabbOrigin = this.aabb.clone();
this.needsUpdate = true;
}
abstract intersects(object: RigidBody): boolean;
abstract updateCollider(): void;
abstract getCollision(object: RigidBody): {
point: Vector3;
normal: Vector3;
depth: number;
};
get isMoved() {
return (
!this.prevPosition.equals(this.position) ||
!this.prevRotation.equals(this.rotation)
);
}
updatePosition(dT: number) {
this.prevPosition.copy(this.position);
this.position.add(v.copy(this.velocity).multiplyScalar(dT));
this.mesh.position.copy(this.position);
}
updateRotation(dT: number) {
this.prevRotation.copy(this.rotation);
let angle = this.angularVelocity.length() * dT;
angularAxis.copy(this.angularVelocity).normalize();
q.setFromAxisAngle(angularAxis, angle);
this.rotation.multiplyQuaternions(q, this.rotation);
this.mesh.quaternion.copy(this.rotation);
}
updateAABB() {
this.aabb.copy(this.aabbOrigin);
this.aabb.applyMatrix4(this.mesh.matrixWorld);
}
getVelocity = (target: Vector3) => target.copy(this.velocity);
setVelocity(fn: (vector: Vector3) => Vector3) {
this.velocity.copy(fn(this.getVelocity(v)));
}
getAngularVelocity = (target: Vector3) => target.copy(this.angularVelocity);
setAngularVelocity(fn: (vector: Vector3) => Vector3) {
this.angularVelocity.copy(fn(this.getAngularVelocity(v)));
}
getPosition = (target: Vector3) => target.copy(this.position);
setPosition(fn: (p: Vector3) => Vector3) {
this.position.copy(fn(this.getPosition(v)));
}
getRotation = (target: Quaternion) => target.copy(this.rotation);
setRotation(fn: (r: Quaternion) => Quaternion) {
this.rotation.copy(fn(this.getRotation(q)));
}
getInvertedInertia = (target: Matrix3) => target.copy(this.invertedInertia);
resolveIntersection(object: RigidBody, normal: Vector3, depth: number) {
const thisState = this.isStatic ? 0 : 1;
const objectState = object.isStatic ? 0 : 1;
const thisOffset = (thisState / (thisState + objectState)) * depth;
const objectOffset = (objectState / (thisState + objectState)) * depth;
this.position.addScaledVector(normal, thisOffset);
object.position.addScaledVector(normal, -objectOffset);
}
resolveCollision(object: RigidBody) {
const data = this.getCollision(object);
const { point, normal, depth } = data;
// for some reason the collision data is not detected correctly sometimes
if (
Object.values(point).some((v: number) => isNaN(v)) ||
Object.values(normal).some((v: number) => isNaN(v)) ||
depth === Infinity ||
isNaN(depth)
)
return;
if (data.depth > 1e-10) {
collisionInfo.update(this, object, point, normal);
this.resolveIntersection(object, normal, depth);
const j = collisionInfo.getImpulse();
if (!this.isStatic) {
collisionInfo.applyLinearVelocity(this, j);
collisionInfo.applyAngularVelocity(this, j);
}
if (!object.isStatic) {
collisionInfo.applyLinearVelocity(object, -j);
collisionInfo.applyAngularVelocity(object, -j);
}
const jT = collisionInfo.getFrictionImpulse();
const relativeFraction = (this.friction + object.friction) * 0.5;
if (!this.isStatic)
collisionInfo.applyFriction(this, relativeFraction, jT, j);
if (!object.isStatic)
collisionInfo.applyFriction(object, -relativeFraction, -jT, -j);
}
}
}
export default RigidBody;
export type { RigidBodyProps };