UNPKG

@rpgjs/physic

Version:

A deterministic 2D top-down physics library for RPG, sandbox and MMO games

185 lines (184 loc) 4.63 kB
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