refactors SAT to be less wet, adds spline and spline collision using SAT
This commit is contained in:
@@ -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
246
geometry/spline.ts
Normal 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 };
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
||||
|
Reference in New Issue
Block a user