refactors SAT to be less wet, adds spline and spline collision using SAT

This commit is contained in:
2023-11-03 22:28:44 -06:00
parent 9d8a0fc7d2
commit c6c4b46312
7 changed files with 712 additions and 211 deletions

View File

@@ -48,7 +48,15 @@ export class Polygon {
};
}
get aaHitbox(): axisAlignedBoundingBox {
_aabb?: axisAlignedBoundingBox;
get AABB(): axisAlignedBoundingBox {
if (!this._aabb) {
this._aabb = this.recalculateAABB();
}
return this._aabb;
}
recalculateAABB(): axisAlignedBoundingBox {
let smallestX, biggestX, smallestY, biggestY;
smallestX =
smallestY =
@@ -92,4 +100,24 @@ export class Polygon {
poly.center = poly.calcCenter();
return poly;
}
getEdges(): Vector[] {
const edges: Vector[] = [];
for (let i = 0; i < this.points.length; i++) {
const nextIndex = (i + 1) % this.points.length;
const edge = this.points[nextIndex].copy().add(this.center).sub(
this.points[i].copy().add(this.center),
);
edges.push(edge);
}
return edges;
}
getNearestPoint(p: Vector) {
let nearest = this.points[0];
for (const point of this.points) {
if (p.dist(point) < p.dist(nearest)) nearest = point;
}
return nearest;
}
}

246
geometry/spline.ts Normal file
View File

@@ -0,0 +1,246 @@
import { axisAlignedBoundingBox } from "../collision/aa.ts";
import { Point, Vector } from "./vector.ts";
export class SplineSegment {
points: [Vector, Vector, Vector, Vector];
length: number;
constructor(points: [Vector, Vector, Vector, Vector]) {
this.points = points;
this.length = this.calculateApproxLength(100);
}
draw(color?: string) {
const [a, b, c, d] = this.points;
doodler.drawBezier(a, b, c, d, {
strokeColor: color || "#ffffff50",
});
}
getPointAtT(t: number) {
const [a, b, c, d] = this.points;
const res = a.copy();
res.add(Vector.add(a.copy().mult(-3), b.copy().mult(3)).mult(t));
res.add(
Vector.add(
Vector.add(a.copy().mult(3), b.copy().mult(-6)),
c.copy().mult(3),
).mult(Math.pow(t, 2)),
);
res.add(
Vector.add(
Vector.add(a.copy().mult(-1), b.copy().mult(3)),
Vector.add(c.copy().mult(-3), d.copy()),
).mult(Math.pow(t, 3)),
);
return res;
}
getClosestPoint(v: Vector): [Vector, number, number] {
const samples = 25;
const resolution = 1 / samples;
let closest = this.points[0];
let closestDistance = this.points[0].dist(v);
let closestT = 0;
for (let i = 0; i < samples; i++) {
const point = this.getPointAtT(i * resolution);
const distance = v.dist(point);
if (distance < closestDistance) {
closest = point;
closestDistance = distance;
closestT = i * resolution;
}
}
return [closest, closestDistance, closestT];
}
getPointsWithinRadius(v: Vector, r: number) {
const points: [number, SplineSegment][] = [];
const samples = 25;
const resolution = 1 / samples;
for (let i = 0; i < samples + 1; i++) {
const point = this.getPointAtT(i * resolution);
const distance = v.dist(point);
if (distance < r) {
points.push([i * resolution, this]);
}
}
return points;
}
tangent(t: number) {
// dP(t) / dt = -3(1-t)^2 * P0 + 3(1-t)^2 * P1 - 6t(1-t) * P1 - 3t^2 * P2 + 6t(1-t) * P2 + 3t^2 * P3
const [a, b, c, d] = this.points;
const res = Vector.sub(b, a).mult(3 * Math.pow(1 - t, 2));
res.add(
Vector.add(
Vector.sub(c, b).mult(6 * (1 - t) * t),
Vector.sub(d, c).mult(3 * Math.pow(t, 2)),
),
);
return res;
}
doesIntersectCircle(x: number, y: number, r: number) {
const v = new Vector(x, y);
const samples = 25;
const resolution = 1 / samples;
let distance = Infinity;
let t;
for (let i = 0; i < samples - 1; i++) {
const a = this.getPointAtT(i * resolution);
const b = this.getPointAtT((i + 1) * resolution);
const ac = Vector.sub(v, a);
const ab = Vector.sub(b, a);
const d = Vector.add(Vector.vectorProjection(ac, ab), a);
const ad = Vector.sub(d, a);
const k = Math.abs(ab.x) > Math.abs(ab.y) ? ad.x / ab.x : ad.y / ab.y;
let dist;
if (k <= 0.0) {
dist = Vector.hypot2(v, a);
} else if (k >= 1.0) {
dist = Vector.hypot2(v, b);
}
dist = Vector.hypot2(v, d);
if (dist < distance) {
distance = dist;
t = i * resolution;
}
}
if (distance < r) return t;
return false;
}
intersectsCircle(circleCenter: Point, radius: number): boolean {
const numSegments = 100; // Initial number of segments
const minResolution = 10; // Minimum resolution to ensure accuracy
for (let i = 0; i < numSegments; i++) {
const t1 = i / numSegments;
const t2 = (i + 1) / numSegments;
const segmentStart = this.getPointAtT(t1);
const segmentEnd = this.getPointAtT(t2);
const segmentLength = Math.sqrt(
(segmentEnd.x - segmentStart.x) ** 2 +
(segmentEnd.y - segmentStart.y) ** 2,
);
// Dynamically adjust resolution based on segment length
const resolution = Math.max(
minResolution,
Math.ceil(numSegments * (segmentLength / radius)),
);
for (let j = 0; j <= resolution; j++) {
const t = j / resolution;
const point = this.getPointAtT(t);
const distance = Math.sqrt(
(point.x - circleCenter.x) ** 2 + (point.y - circleCenter.y) ** 2,
);
if (distance <= radius) {
return true; // Intersection detected
}
}
}
return false; // No intersection found
}
calculateApproxLength(resolution = 25) {
const stepSize = 1 / resolution;
const points: Vector[] = [];
for (let i = 0; i <= resolution; i++) {
const current = stepSize * i;
points.push(this.getPointAtT(current));
}
this.length =
points.reduce((acc: { prev?: Vector; length: number }, cur) => {
const prev = acc.prev;
acc.prev = cur;
if (!prev) return acc;
acc.length += cur.dist(prev);
return acc;
}, { prev: undefined, length: 0 }).length;
return this.length;
}
calculateEvenlySpacedPoints(spacing: number, resolution = 1) {
const points: Vector[] = [];
points.push(this.points[0]);
let prev = points[0];
let distSinceLastEvenPoint = 0;
let t = 0;
const div = Math.ceil(this.length * resolution * 10);
while (t < 1) {
t += 1 / div;
const point = this.getPointAtT(t);
distSinceLastEvenPoint += prev.dist(point);
if (distSinceLastEvenPoint >= spacing) {
const overshoot = distSinceLastEvenPoint - spacing;
const evenPoint = Vector.add(
point,
Vector.sub(point, prev).normalize().mult(overshoot),
);
distSinceLastEvenPoint = overshoot;
points.push(evenPoint);
prev = evenPoint;
}
prev = point;
}
return points;
}
private _aabb?: axisAlignedBoundingBox;
get AABB() {
if (!this._aabb) {
this._aabb = this.recalculateAABB();
}
return this._aabb;
}
recalculateAABB(): axisAlignedBoundingBox {
const numPoints = 100; // You can adjust the number of points based on your needs
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
let maxY = -Infinity;
for (let i = 0; i < numPoints; i++) {
const t = i / numPoints;
const point = this.getPointAtT(t);
minX = Math.min(minX, point.x);
minY = Math.min(minY, point.y);
maxX = Math.max(maxX, point.x);
maxY = Math.max(maxY, point.y);
}
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
}
}

View File

@@ -218,22 +218,20 @@ export class Vector implements Point {
doodler.dot(this, { weight: 2, color: color || "red" });
}
draw() {
draw(origin?: Point) {
if (!doodler) return;
const startPoint = new Vector();
doodler.dot(new Vector(), { weight: 4, color: "orange" });
const startPoint = origin ? new Vector(origin) : new Vector();
doodler.line(
startPoint,
startPoint.copy().add(this.copy().normalize().mult(700)),
);
doodler.line(
startPoint,
startPoint.copy().sub(this.copy().normalize().mult(700)),
startPoint.copy().add(this.copy().normalize().mult(100)),
);
}
normal(v: Vector) {
normal(): Vector;
normal(v: Vector): Vector;
normal(v?: Vector) {
if (!v) return new Vector(-this.y, this.x);
const dx = v.x - this.x;
const dy = v.y - this.y;