UNPKG

@rpgjs/physic

Version:

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

124 lines (123 loc) 4.72 kB
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