@rpgjs/physic
Version:
A deterministic 2D top-down physics library for RPG, sandbox and MMO games
185 lines (184 loc) • 4.63 kB
JavaScript
import { Vector2 } from "./index2.js";
import { AABB } from "./index4.js";
import { AABBCollider } from "./index12.js";
class CircleCollider {
/**
* Creates a new circle collider
*
* @param entity - Entity this collider belongs to
*/
constructor(entity) {
this.entity = entity;
}
/**
* Gets the radius of the circle
*
* @returns Radius
*/
getRadius() {
return this.entity.radius;
}
/**
* Gets the center position of the circle
*
* @returns Center position
*/
getCenter() {
return this.entity.position;
}
/**
* @inheritdoc
*/
getBounds() {
const radius = this.entity.radius;
const center = this.entity.position;
return new AABB(
center.x - radius,
center.y - radius,
center.x + radius,
center.y + radius
);
}
/**
* @inheritdoc
*/
testCollision(other) {
if (other instanceof CircleCollider) {
return this.testCircleCircle(other);
} else if (other instanceof AABBCollider) {
return this.testCircleAABB(other);
}
return null;
}
/**
* Tests collision with another circle
*
* @param other - Other circle collider
* @returns Collision info or null
*/
testCircleCircle(other) {
const centerA = this.getCenter();
const centerB = other.getCenter();
const radiusA = this.getRadius();
const radiusB = other.getRadius();
const distance = centerA.distanceTo(centerB);
const minDistance = radiusA + radiusB;
if (distance >= minDistance) {
return null;
}
const depth = minDistance - distance;
let normal;
if (distance < 1e-5) {
normal = new Vector2(1, 0);
} else {
normal = centerB.sub(centerA).normalize();
}
const contactPoint = centerA.add(normal.mul(radiusA));
return {
entityA: this.entity,
entityB: other.entity,
contacts: [
{
point: contactPoint,
normal,
depth
}
],
normal,
depth
};
}
/**
* Tests collision with an AABB
*
* @param other - AABB collider
* @returns Collision info or null
*/
testCircleAABB(other) {
const circleCenter = this.getCenter();
const circleRadius = this.getRadius();
const aabb = other.getBounds();
const closestX = Math.max(aabb.minX, Math.min(circleCenter.x, aabb.maxX));
const closestY = Math.max(aabb.minY, Math.min(circleCenter.y, aabb.maxY));
const distanceSq = (circleCenter.x - closestX) ** 2 + (circleCenter.y - closestY) ** 2;
if (distanceSq > circleRadius * circleRadius) {
return null;
}
const distance = Math.sqrt(distanceSq);
const depth = circleRadius - distance;
let normal;
if (distance < 1e-5) {
const distToLeft = circleCenter.x - aabb.minX;
const distToRight = aabb.maxX - circleCenter.x;
const distToBottom = circleCenter.y - aabb.minY;
const distToTop = aabb.maxY - circleCenter.y;
const minDist = Math.min(distToLeft, distToRight, distToBottom, distToTop);
if (minDist === distToLeft) {
normal = new Vector2(-1, 0);
} else if (minDist === distToRight) {
normal = new Vector2(1, 0);
} else if (minDist === distToBottom) {
normal = new Vector2(0, -1);
} else {
normal = new Vector2(0, 1);
}
} else {
normal = new Vector2(closestX - circleCenter.x, closestY - circleCenter.y).normalize();
}
const contactPoint = new Vector2(closestX, closestY);
return {
entityA: this.entity,
entityB: other.getEntity(),
contacts: [
{
point: contactPoint,
normal,
depth
}
],
normal,
depth
};
}
/**
* @inheritdoc
*/
getContactPoints(other) {
const collision = this.testCollision(other);
return collision?.contacts ?? [];
}
/**
* @inheritdoc
*/
getEntity() {
return this.entity;
}
/**
* @inheritdoc
*/
raycast(ray) {
const center = this.getCenter();
const radius = this.getRadius();
const m = ray.origin.sub(center);
const b = m.dot(ray.direction);
const c = m.dot(m) - radius * radius;
if (c > 0 && b > 0) return null;
const discr = b * b - c;
if (discr < 0) return null;
let t = -b - Math.sqrt(discr);
if (t < 0) t = 0;
if (t > ray.length) return null;
const point = ray.getPoint(t);
const normal = point.sub(center).normalize();
return {
entity: this.entity,
point,
normal,
distance: t
};
}
}
export {
CircleCollider
};
//# sourceMappingURL=index11.js.map