diff --git a/bundle.js b/bundle.js index 9b401a2..cbecb5c 100644 --- a/bundle.js +++ b/bundle.js @@ -2,9 +2,6 @@ // deno-lint-ignore-file // This code was bundled using `deno bundle` and it's not recommended to edit it manually -const axisAlignedContains = (aa1, aa2)=>{ - return aa1.x < aa2.x && aa1.y < aa2.y && aa1.x + aa1.w > aa2.x + aa2.w && aa1.y + aa1.h > aa2.y + aa2.h; -}; const Constants = { TWO_PI: Math.PI * 2 }; @@ -74,7 +71,7 @@ class Vector { if (arguments.length === 1 && typeof v !== "number") { this.x -= v.x; this.y -= v.y; - this.z -= v.z; + this.z -= v.z || 0; } else if (arguments.length === 2) { this.x -= v; this.y -= y ?? 0; @@ -118,7 +115,7 @@ class Vector { return this; } dist(v) { - const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z; + const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - (v.z || 0); return Math.sqrt(dx * dx + dy * dy + dz * dz); } dot(v, y, z) { @@ -293,86 +290,45 @@ class OriginVector extends Vector { return new OriginVector(origin, v); } } -const satCollision = (s1, s2)=>{ - const shape1 = s1.points.map((p)=>new Vector(p).add(s1.center)); - const shape2 = s2.points.map((p)=>new Vector(p).add(s2.center)); - if (shape1.length < 2 || shape2.length < 2) { - throw "Insufficient shape data in satCollision"; +const satCollisionCircle = (s, c)=>{ + const shape = s.points.map((p)=>new Vector(p).add(s.center)); + if (shape.length < 2) { + throw "Insufficient shape data in satCollisionCircle"; } - new Vector(s1.center).sub(s2.center); - for(let i = 0; i < shape1.length; i++){ - const axis = shape1[i].normal(shape1.at(i - 1)); - let [p1min, p1minDot] = Vector.vectorProjectionAndDot(shape1[0], axis); - let [p1max, p1maxDot] = [ - p1min, - p1minDot - ]; - for (const point of shape1){ - const [projected, dot] = Vector.vectorProjectionAndDot(point, axis); - p1min = dot < p1minDot ? projected : p1min; + for(let i = 0; i < shape.length; i++){ + const axis = shape[i].normal(shape.at(i - 1)); + let [_, p1minDot] = Vector.vectorProjectionAndDot(shape[0], axis); + let p1maxDot = p1minDot; + for (const point of shape){ + const [_, dot] = Vector.vectorProjectionAndDot(point, axis); p1minDot = Math.min(dot, p1minDot); - p1max = dot > p1maxDot ? projected : p1max; p1maxDot = Math.max(dot, p1maxDot); } - let [p2min, p2minDot] = Vector.vectorProjectionAndDot(shape2[0], axis); - let [p2max, p2maxDot] = [ - p2min, - p2minDot - ]; - for (const point of shape2){ - const [projected, dot] = Vector.vectorProjectionAndDot(point, axis); - p2min = dot < p2minDot ? projected : p2min; - p2minDot = Math.min(dot, p2minDot); - p2max = dot > p2maxDot ? projected : p2max; - p2maxDot = Math.max(dot, p2maxDot); - } + const [__, circleDot] = Vector.vectorProjectionAndDot(new Vector(c.center), axis); + const p2minDot = circleDot - c.radius; + const p2maxDot = circleDot + c.radius; if (p1minDot - p2maxDot > 0 || p2minDot - p1maxDot > 0) { return false; } - [ - axis, - p1max, - p1min, - p2max, - p2min - ]; } - for(let i = 0; i < shape2.length; i++){ - const axis = shape2[i].normal(shape2.at(i - 1)); - let [p1min, p1minDot] = Vector.vectorProjectionAndDot(shape2[0], axis); - let [p1max, p1maxDot] = [ - p1min, - p1minDot - ]; - for (const point of shape2){ - const [projected, dot] = Vector.vectorProjectionAndDot(point, axis); - p1min = dot < p1minDot ? projected : p1min; - p1minDot = Math.min(dot, p1minDot); - p1max = dot > p1maxDot ? projected : p1max; - p1maxDot = Math.max(dot, p1maxDot); - } - let [p2min, p2minDot] = Vector.vectorProjectionAndDot(shape1[0], axis); - let [p2max, p2maxDot] = [ - p2min, - p2minDot - ]; - for (const point of shape1){ - const [projected, dot] = Vector.vectorProjectionAndDot(point, axis); - p2min = dot < p2minDot ? projected : p2min; - p2minDot = Math.min(dot, p2minDot); - p2max = dot > p2maxDot ? projected : p2max; - p2maxDot = Math.max(dot, p2maxDot); - } - if (p1minDot - p2maxDot > 0 || p2minDot - p1maxDot > 0) { - return false; - } - [ - axis, - p1max, - p1min, - p2max, - p2min - ]; + const center = new Vector(c.center); + let nearest = shape[0]; + for (const p of shape){ + if (center.dist(p) < center.dist(nearest)) nearest = p; + } + const axis = center.sub(nearest); + let [_, p1minDot] = Vector.vectorProjectionAndDot(shape[0], axis); + let p1maxDot = p1minDot; + for (const point of shape){ + const [_, dot] = Vector.vectorProjectionAndDot(point, axis); + p1minDot = Math.min(dot, p1minDot); + p1maxDot = Math.max(dot, p1maxDot); + } + const [__, circleDot] = Vector.vectorProjectionAndDot(new Vector(c.center), axis); + const p2minDot = circleDot - c.radius; + const p2maxDot = circleDot + c.radius; + if (p1minDot - p2maxDot > 0 || p2minDot - p1maxDot > 0) { + return false; } return true; }; @@ -1048,7 +1004,16 @@ const poly = new Polygon([ y: 25 } ]); -const poly2 = Polygon.createPolygon(5, 75); +const poly2 = new Polygon([ + { + x: -250, + y: -25 + }, + { + x: 25, + y: 250 + } +]); poly2.center = p.copy().add(100, 100); poly.center.add(p); doodler.createLayer((c)=>{ @@ -1059,7 +1024,7 @@ doodler.createLayer((c)=>{ }); } } - const color = satCollision(poly, poly2) ? "red" : "aqua"; + const color = satCollisionCircle(poly2, poly.circularHitbox) ? "red" : "aqua"; poly.draw(color); poly2.draw(color); const [gamepad] = navigator.getGamepads(); @@ -1068,10 +1033,6 @@ doodler.createLayer((c)=>{ const leftY = gamepad.axes[1]; const rightX = gamepad.axes[2]; const rightY = gamepad.axes[3]; - if (axisAlignedContains(poly2.aaHitbox, poly.aaHitbox)) { - poly.center.add(new Vector(Math.min(Math.max(rightX - 0.05, 0), rightX + 0.05), Math.min(Math.max(rightY - 0.05, 0), rightY + 0.05)).mult(10)); - poly2.center.add(new Vector(Math.min(Math.max(leftX - 0.05, 0), leftX + 0.05), Math.min(Math.max(leftY - 0.05, 0), leftY + 0.05)).mult(10)); - } poly.center.add(new Vector(Math.min(Math.max(leftX - 0.05, 0), leftX + 0.05), Math.min(Math.max(leftY - 0.05, 0), leftY + 0.05)).mult(10)); poly2.center.add(new Vector(Math.min(Math.max(rightX - 0.05, 0), rightX + 0.05), Math.min(Math.max(rightY - 0.05, 0), rightY + 0.05)).mult(10)); } diff --git a/collision/sat.ts b/collision/sat.ts index 84383d1..0df3ffd 100644 --- a/collision/sat.ts +++ b/collision/sat.ts @@ -1,5 +1,6 @@ import { Polygon } from "../geometry/polygon.ts"; import { Point, Vector } from "../geometry/vector.ts"; +import { CircleLike } from "./circular.ts"; export const satCollision = (s1: Polygon, s2: Polygon) => { const shape1 = s1.points.map((p) => new Vector(p).add(s1.center)); @@ -8,98 +9,105 @@ export const satCollision = (s1: Polygon, s2: Polygon) => { if (shape1.length < 2 || shape2.length < 2) { throw "Insufficient shape data in satCollision"; } - const offset = new Vector(s1.center).sub(s2.center); - // Take one side of the polygon and find the normal - let last: Vector[] = []; for (let i = 0; i < shape1.length; i++) { const axis = shape1[i].normal(shape1.at(i - 1)!); - // axis.draw(); - let [p1min, p1minDot] = Vector.vectorProjectionAndDot(shape1[0], axis); - let [p1max, p1maxDot] = [p1min, p1minDot]; + let [_, p1minDot] = Vector.vectorProjectionAndDot(shape1[0], axis); + let p1maxDot = p1minDot; for (const point of shape1) { - const [projected, dot] = Vector.vectorProjectionAndDot(point, axis); - // projected.drawDot(); - p1min = dot < p1minDot ? projected : p1min; + const [_, dot] = Vector.vectorProjectionAndDot(point, axis); p1minDot = Math.min(dot, p1minDot); - p1max = dot > p1maxDot ? projected : p1max; p1maxDot = Math.max(dot, p1maxDot); } - let [p2min, p2minDot] = Vector.vectorProjectionAndDot(shape2[0], axis); - let [p2max, p2maxDot] = [p2min, p2minDot]; + let [__, p2minDot] = Vector.vectorProjectionAndDot(shape2[0], axis); + let p2maxDot = p2minDot; for (const point of shape2) { - const [projected, dot] = Vector.vectorProjectionAndDot(point, axis); - // projected.drawDot(); - p2min = dot < p2minDot ? projected : p2min; + const [_, dot] = Vector.vectorProjectionAndDot(point, axis); p2minDot = Math.min(dot, p2minDot); - p2max = dot > p2maxDot ? projected : p2max; p2maxDot = Math.max(dot, p2maxDot); } - // const scale = axis.dot(offset); - // p1max += scale; - // p1min += scale; - - // let p2min = axis.dot(shape1[0]); - // let p2max = p2min; - // for (const point of shape2) { - // const dot = point.dot(axis); - // p2max = Math.max(p2max, dot); - // p2min = Math.min(p2min, dot); - // Vector.vectorProjection(point, axis).drawDot(); - // } - if (p1minDot - p2maxDot > 0 || p2minDot - p1maxDot > 0) { - // axis.draw(); - // p1max.drawDot("blue"); - // p1min.drawDot("lime"); - // p2max.drawDot("blue"); - // p2min.drawDot("lime"); return false; } - last = [axis, p1max, p1min, p2max, p2min]; } for (let i = 0; i < shape2.length; i++) { const axis = shape2[i].normal(shape2.at(i - 1)!); - // axis.draw(); - let [p1min, p1minDot] = Vector.vectorProjectionAndDot(shape2[0], axis); - let [p1max, p1maxDot] = [p1min, p1minDot]; + let [_, p1minDot] = Vector.vectorProjectionAndDot(shape2[0], axis); + let p1maxDot = p1minDot; for (const point of shape2) { - const [projected, dot] = Vector.vectorProjectionAndDot(point, axis); - // projected.drawDot(); - p1min = dot < p1minDot ? projected : p1min; + const [_, dot] = Vector.vectorProjectionAndDot(point, axis); + // p1min = dot < p1minDot ? projected : p1min; p1minDot = Math.min(dot, p1minDot); - p1max = dot > p1maxDot ? projected : p1max; + // p1max = dot > p1maxDot ? projected : p1max; p1maxDot = Math.max(dot, p1maxDot); } - let [p2min, p2minDot] = Vector.vectorProjectionAndDot(shape1[0], axis); - let [p2max, p2maxDot] = [p2min, p2minDot]; + let [__, p2minDot] = Vector.vectorProjectionAndDot(shape1[0], axis); + let p2maxDot = p2minDot; for (const point of shape1) { - const [projected, dot] = Vector.vectorProjectionAndDot(point, axis); - // projected.drawDot(); - p2min = dot < p2minDot ? projected : p2min; + const [_, dot] = Vector.vectorProjectionAndDot(point, axis); + // p2min = dot < p2minDot ? projected : p2min; p2minDot = Math.min(dot, p2minDot); - p2max = dot > p2maxDot ? projected : p2max; + // p2max = dot > p2maxDot ? projected : p2max; p2maxDot = Math.max(dot, p2maxDot); } if (p1minDot - p2maxDot > 0 || p2minDot - p1maxDot > 0) { - // axis.draw(); - // p1max.drawDot("blue"); - // p1min.drawDot("lime"); - // p2max.drawDot("blue"); - // p2min.drawDot("lime"); return false; } - last = [axis, p1max, p1min, p2max, p2min]; } - // for (const [i, l] of last.entries()) { - // if (i === 0) { - // l.draw(); - // } else { - // l.drawDot(); - // } - // } - + return true; +}; + +export const satCollisionCircle = (s: Polygon, c: CircleLike) => { + const shape = s.points.map((p) => new Vector(p).add(s.center)); + + if (shape.length < 2) { + throw "Insufficient shape data in satCollisionCircle"; + } + for (let i = 0; i < shape.length; i++) { + const axis = shape[i].normal(shape.at(i - 1)!); + let [_, p1minDot] = Vector.vectorProjectionAndDot(shape[0], axis); + let p1maxDot = p1minDot; + for (const point of shape) { + const [_, dot] = Vector.vectorProjectionAndDot(point, axis); + p1minDot = Math.min(dot, p1minDot); + p1maxDot = Math.max(dot, p1maxDot); + } + const [__, circleDot] = Vector.vectorProjectionAndDot( + new Vector(c.center), + axis, + ); + const p2minDot = circleDot - c.radius; + const p2maxDot = circleDot + c.radius; + + if (p1minDot - p2maxDot > 0 || p2minDot - p1maxDot > 0) { + return false; + } + } + const center = new Vector(c.center); + let nearest = shape[0]; + for (const p of shape) { + if (center.dist(p) < center.dist(nearest)) nearest = p; + } + const axis = center.sub(nearest); + let [_, p1minDot] = Vector.vectorProjectionAndDot(shape[0], axis); + let p1maxDot = p1minDot; + for (const point of shape) { + const [_, dot] = Vector.vectorProjectionAndDot(point, axis); + p1minDot = Math.min(dot, p1minDot); + p1maxDot = Math.max(dot, p1maxDot); + } + const [__, circleDot] = Vector.vectorProjectionAndDot( + new Vector(c.center), + axis, + ); + const p2minDot = circleDot - c.radius; + const p2maxDot = circleDot + c.radius; + + if (p1minDot - p2maxDot > 0 || p2minDot - p1maxDot > 0) { + return false; + } + return true; }; diff --git a/geometry/vector.ts b/geometry/vector.ts index fbf0287..a04988b 100644 --- a/geometry/vector.ts +++ b/geometry/vector.ts @@ -89,11 +89,12 @@ export class Vector implements Point { sub(x: number, y: number, z: number): Vector; sub(x: number, y: number): Vector; sub(v: Vector): Vector; - sub(v: Vector | number, y?: number, z?: number) { + sub(v: Point): Vector; + sub(v: Vector | Point | number, y?: number, z?: number) { if (arguments.length === 1 && typeof v !== "number") { this.x -= v.x; this.y -= v.y; - this.z -= v.z; + this.z -= v.z || 0; } else if (arguments.length === 2) { // 2D Vector this.x -= v as number; @@ -137,10 +138,10 @@ export class Vector implements Point { this.y = s * prev_x + c * this.y; return this; } - dist(v: Vector) { + dist(v: Vector | Point) { const dx = this.x - v.x, dy = this.y - v.y, - dz = this.z - v.z; + dz = this.z - (v.z || 0); return Math.sqrt(dx * dx + dy * dy + dz * dz); } dot(x: number, y: number, z: number): number; diff --git a/main.ts b/main.ts index 90ac638..ccddfe2 100644 --- a/main.ts +++ b/main.ts @@ -2,7 +2,7 @@ import { axisAlignedCollision, axisAlignedContains } from "./collision/aa.ts"; import { circularCollision } from "./collision/circular.ts"; -import { satCollision } from "./collision/sat.ts"; +import { satCollision, satCollisionCircle } from "./collision/sat.ts"; import { Polygon } from "./geometry/polygon.ts"; import { initializeDoodler, Vector } from "./mod.ts"; // import { ZoomableDoodler } from "./zoomableCanvas.ts"; @@ -39,9 +39,12 @@ const poly = new Polygon([ ]); // poly.center = p.copy(); -const poly2 = Polygon.createPolygon(5, 75); -poly2.center = p.copy().add(100, 100); +const poly2 = new Polygon([ + { x: -250, y: -25 }, + { x: 25, y: 250 }, +]); +poly2.center = p.copy().add(100, 100); poly.center.add(p); doodler.createLayer((c) => { @@ -50,9 +53,9 @@ doodler.createLayer((c) => { doodler.drawSquare(new Vector(i, j), 50, { color: "#00000010" }); } } - const color = satCollision( - poly, + const color = satCollisionCircle( poly2, + poly.circularHitbox, ) ? "red" : "aqua"; @@ -73,20 +76,20 @@ doodler.createLayer((c) => { const rightX = gamepad.axes[2]; const rightY = gamepad.axes[3]; - if (axisAlignedContains(poly2.aaHitbox, poly.aaHitbox)) { - poly.center.add( - new Vector( - Math.min(Math.max(rightX - deadzone, 0), rightX + deadzone), - Math.min(Math.max(rightY - deadzone, 0), rightY + deadzone), - ).mult(10), - ); - poly2.center.add( - new Vector( - Math.min(Math.max(leftX - deadzone, 0), leftX + deadzone), - Math.min(Math.max(leftY - deadzone, 0), leftY + deadzone), - ).mult(10), - ); - } + // if (axisAlignedContains(poly2.aaHitbox, poly.aaHitbox)) { + // poly.center.add( + // new Vector( + // Math.min(Math.max(rightX - deadzone, 0), rightX + deadzone), + // Math.min(Math.max(rightY - deadzone, 0), rightY + deadzone), + // ).mult(10), + // ); + // poly2.center.add( + // new Vector( + // Math.min(Math.max(leftX - deadzone, 0), leftX + deadzone), + // Math.min(Math.max(leftY - deadzone, 0), leftY + deadzone), + // ).mult(10), + // ); + // } poly.center.add( new Vector(