139 lines
3.8 KiB
TypeScript
139 lines
3.8 KiB
TypeScript
import { Polygon } from "../geometry/polygon.ts";
|
|
import { SplineSegment } from "../geometry/spline.ts";
|
|
import { Vector } from "../geometry/vector.ts";
|
|
import { axisAlignedBoundingBox } from "./aa.ts";
|
|
import { CircleLike } from "./circular.ts";
|
|
|
|
export function satCollisionSpline(p: Polygon, spline: SplineSegment): boolean {
|
|
const numSegments = 100; // You can adjust the number of segments based on your needs
|
|
|
|
for (let i = 0; i < numSegments; i++) {
|
|
const t1 = i / numSegments;
|
|
const t2 = (i + 1) / numSegments;
|
|
|
|
const segmentStart = spline.getPointAtT(t1);
|
|
const segmentEnd = spline.getPointAtT(t2);
|
|
|
|
if (segmentIntersectsPolygon(p, segmentStart, segmentEnd)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
export function satCollisionPolygon(poly: Polygon, poly2: Polygon): boolean {
|
|
for (const edge of poly.getEdges()) {
|
|
const axis = edge.copy().normal().normalize();
|
|
const proj1 = projectPolygonOntoAxis(poly, axis);
|
|
const proj2 = projectPolygonOntoAxis(poly2, axis);
|
|
|
|
if (!overlap(proj1, proj2)) return false;
|
|
}
|
|
for (const edge of poly2.getEdges()) {
|
|
const axis = edge.copy().normal().normalize();
|
|
const proj1 = projectPolygonOntoAxis(poly, axis);
|
|
const proj2 = projectPolygonOntoAxis(poly2, axis);
|
|
|
|
if (!overlap(proj1, proj2)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
export function satCollisionCircle(p: Polygon, circle: CircleLike): boolean {
|
|
for (const edge of p.getEdges()) {
|
|
const axis = edge.copy().normal().normalize();
|
|
const proj1 = projectPolygonOntoAxis(p, axis);
|
|
const proj2 = projectCircleOntoAxis(circle, axis);
|
|
|
|
if (!overlap(proj1, proj2)) return false;
|
|
}
|
|
const center = new Vector(circle.center);
|
|
const nearest = p.getNearestPoint(center);
|
|
const axis = nearest.copy().sub(center).normalize();
|
|
const proj1 = projectPolygonOntoAxis(p, axis);
|
|
const proj2 = projectCircleOntoAxis(circle, axis);
|
|
|
|
if (!overlap(proj1, proj2)) return false;
|
|
return true;
|
|
}
|
|
export function satCollisionAABBCircle(
|
|
aabb: axisAlignedBoundingBox,
|
|
circle: CircleLike,
|
|
): boolean {
|
|
const p = new Polygon([
|
|
{ x: aabb.x, y: aabb.y },
|
|
{ x: aabb.x + aabb.w, y: aabb.y },
|
|
{ x: aabb.x + aabb.w, y: aabb.y + aabb.h },
|
|
{ x: aabb.x, y: aabb.y + aabb.h },
|
|
]);
|
|
return satCollisionCircle(p, circle);
|
|
}
|
|
|
|
function segmentIntersectsPolygon(
|
|
p: Polygon,
|
|
start: Vector,
|
|
end: Vector,
|
|
): boolean {
|
|
const edges = p.getEdges();
|
|
|
|
for (const edge of edges) {
|
|
// const axis = new Vector(-edge.y, edge.x).normalize();
|
|
const axis = edge.copy().normal().normalize();
|
|
|
|
const proj1 = projectPolygonOntoAxis(p, axis);
|
|
const proj2 = projectSegmentOntoAxis(start, end, axis);
|
|
|
|
if (!overlap(proj1, proj2)) {
|
|
return false; // No overlap, no intersection
|
|
}
|
|
}
|
|
|
|
return true; // Overlapping on all axes, intersection detected
|
|
}
|
|
|
|
function projectPolygonOntoAxis(
|
|
p: Polygon,
|
|
axis: Vector,
|
|
): { min: number; max: number } {
|
|
let min = Infinity;
|
|
let max = -Infinity;
|
|
|
|
for (const point of p.points) {
|
|
const dotProduct = point.copy().add(p.center).dot(axis);
|
|
min = Math.min(min, dotProduct);
|
|
max = Math.max(max, dotProduct);
|
|
}
|
|
|
|
return { min, max };
|
|
}
|
|
|
|
function projectSegmentOntoAxis(
|
|
start: Vector,
|
|
end: Vector,
|
|
axis: Vector,
|
|
): { min: number; max: number } {
|
|
const dotProductStart = start.dot(axis);
|
|
const dotProductEnd = end.dot(axis);
|
|
return {
|
|
min: Math.min(dotProductStart, dotProductEnd),
|
|
max: Math.max(dotProductStart, dotProductEnd),
|
|
};
|
|
}
|
|
|
|
function projectCircleOntoAxis(
|
|
c: CircleLike,
|
|
axis: Vector,
|
|
): { min: number; max: number } {
|
|
const dot = new Vector(c.center).dot(axis);
|
|
const min = dot - c.radius;
|
|
const max = dot + c.radius;
|
|
return { min, max };
|
|
}
|
|
|
|
function overlap(
|
|
proj1: { min: number; max: number },
|
|
proj2: { min: number; max: number },
|
|
): boolean {
|
|
return proj1.min <= proj2.max && proj1.max >= proj2.min;
|
|
}
|