@rpgjs/physic
Version:
A deterministic 2D top-down physics library for RPG, sandbox and MMO games
124 lines (123 loc) • 4.72 kB
JavaScript
import { Vector2 } from "./index2.js";
import { AABB } from "./index4.js";
import { Ray } from "./index21.js";
import { AABBCollider } from "./index12.js";
import { CircleCollider } from "./index11.js";
import { PolygonCollider } from "./index19.js";
import { createCollider } from "./index18.js";
function raycast(partition, origin, direction, maxDistance, mask, filter) {
const dir = direction.length() > 0 ? direction.normalize() : new Vector2(1, 0);
const end = origin.add(dir.mul(maxDistance));
const candidates = partition.raycast(new Ray(origin, dir, maxDistance), mask, filter);
if (candidates) return candidates;
const bounds = new AABB(
Math.min(origin.x, end.x),
Math.min(origin.y, end.y),
Math.max(origin.x, end.x),
Math.max(origin.y, end.y)
);
const entities = partition.queryAABB(bounds);
let best = null;
for (const e of entities) {
if (mask !== void 0 && (e.collisionCategory & mask) === 0) continue;
if (filter && !filter(e)) continue;
const collider = createCollider(e);
if (!collider) continue;
const hit = raycastCollider(collider, origin, dir, maxDistance);
if (!hit) continue;
if (!best || hit.distance < best.distance) best = hit;
}
return best;
}
function raycastCollider(collider, origin, dir, maxDistance) {
if (collider instanceof CircleCollider) return raycastCircle(collider, origin, dir, maxDistance);
if (collider instanceof AABBCollider) return raycastAABB(collider, origin, dir, maxDistance);
if (collider instanceof PolygonCollider) return raycastPolygon(collider, origin, dir, maxDistance);
return null;
}
function raycastCircle(circle, origin, dir, maxDistance) {
const c = circle.getCenter();
const r = circle.getRadius();
const m = origin.sub(c);
const b = m.dot(dir);
const cval = m.dot(m) - r * r;
if (cval > 0 && b > 0) return null;
const discr = b * b - cval;
if (discr < 0) return null;
const t = -b - Math.sqrt(discr);
if (t < 0) return null;
if (t > maxDistance) return null;
const point = origin.add(dir.mul(t));
const normal = point.sub(c).normalize();
return { entity: circle.getEntity(), point, normal, distance: t };
}
function raycastAABB(box, origin, dir, maxDistance) {
const b = box.getBounds();
let tmin = 0;
let tmax = maxDistance;
const invDx = 1 / (dir.x === 0 ? 1e-9 : dir.x);
const invDy = 1 / (dir.y === 0 ? 1e-9 : dir.y);
let tx1 = (b.minX - origin.x) * invDx;
let tx2 = (b.maxX - origin.x) * invDx;
let ty1 = (b.minY - origin.y) * invDy;
let ty2 = (b.maxY - origin.y) * invDy;
const tminX = Math.min(tx1, tx2);
const tmaxX = Math.max(tx1, tx2);
const tminY = Math.min(ty1, ty2);
const tmaxY = Math.max(ty1, ty2);
tmin = Math.max(tmin, Math.max(tminX, tminY));
tmax = Math.min(tmax, Math.min(tmaxX, tmaxY));
if (tmax < tmin || tmin < 0 || tmin > maxDistance) return null;
const point = origin.add(dir.mul(tmin));
let normal;
if (tmin === tminX) normal = new Vector2(dir.x > 0 ? -1 : 1, 0);
else normal = new Vector2(0, dir.y > 0 ? -1 : 1);
return { entity: box.getEntity(), point, normal, distance: tmin };
}
function raycastPolygon(poly, origin, dir, maxDistance) {
const end = origin.add(dir.mul(maxDistance));
let bestT = Number.POSITIVE_INFINITY;
let bestPoint = null;
let bestNormal = null;
const any = poly;
const parts = any["getWorldParts"] ? any["getWorldParts"]() : [];
for (const part of parts) {
for (let i = 0; i < part.length; i++) {
const a = part[i];
const b = part[(i + 1) % part.length];
if (!a || !b) continue;
const hit = segmentRayIntersection(a, b, origin, end);
if (!hit) continue;
const t = hit.distance;
if (t >= 0 && t <= maxDistance && t < bestT) {
bestT = t;
bestPoint = hit.point;
const edge = b.sub(a);
const n = new Vector2(-edge.y, edge.x).normalize();
bestNormal = n;
}
}
}
if (!bestPoint || !bestNormal || bestT === Number.POSITIVE_INFINITY) return null;
return { entity: poly.getEntity(), point: bestPoint, normal: bestNormal, distance: bestT };
}
function segmentRayIntersection(a, b, r0, r1) {
const v1 = r0.sub(a);
const v2 = b.sub(a);
const v3 = new Vector2(-(r1.y - r0.y), r1.x - r0.x);
const denom = v2.dot(v3);
if (Math.abs(denom) < 1e-9) return null;
const t1 = v2.cross(v1) / denom;
const t2 = v1.dot(v3) / denom;
if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) {
const hitPoint = new Vector2(r0.x + (r1.x - r0.x) * t1, r0.y + (r1.y - r0.y) * t1);
const dist = hitPoint.sub(r0).length();
return { point: hitPoint, distance: dist };
}
return null;
}
export {
raycast,
raycastCollider
};
//# sourceMappingURL=index20.js.map