diff --git a/.vscode/settings.json b/.vscode/settings.json index fe50924..be43d98 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,25 +1,28 @@ { "workbench.colorCustomizations": { - "activityBar.activeBackground": "#d816d8", - "activityBar.background": "#d816d8", + "activityBar.activeBackground": "#2f7c47", + "activityBar.background": "#2f7c47", "activityBar.foreground": "#e7e7e7", "activityBar.inactiveForeground": "#e7e7e799", - "activityBarBadge.background": "#caca15", - "activityBarBadge.foreground": "#15202b", + "activityBarBadge.background": "#422c74", + "activityBarBadge.foreground": "#e7e7e7", "commandCenter.border": "#e7e7e799", - "sash.hoverBorder": "#d816d8", - "statusBar.background": "#aa11aa", + "sash.hoverBorder": "#2f7c47", + "statusBar.background": "#215732", "statusBar.foreground": "#e7e7e7", - "statusBarItem.hoverBackground": "#d816d8", - "statusBarItem.remoteBackground": "#aa11aa", + "statusBarItem.hoverBackground": "#2f7c47", + "statusBarItem.remoteBackground": "#215732", "statusBarItem.remoteForeground": "#e7e7e7", - "titleBar.activeBackground": "#aa11aa", + "titleBar.activeBackground": "#215732", "titleBar.activeForeground": "#e7e7e7", - "titleBar.inactiveBackground": "#aa11aa99", + "titleBar.inactiveBackground": "#21573299", "titleBar.inactiveForeground": "#e7e7e799" }, - "peacock.remoteColor": "aa11aa", + "peacock.remoteColor": "#215732", "deno.enable": true, "deno.unstable": true, - "liveServer.settings.port": 5501 + "liveServer.settings.port": 5501, + "cSpell.words": [ + "deadzone" + ] } \ No newline at end of file diff --git a/bundle.js b/bundle.js index 98e3b92..95dbaac 100644 --- a/bundle.js +++ b/bundle.js @@ -2,6 +2,9 @@ // deno-lint-ignore-file // This code was bundled using `deno bundle` and it's not recommended to edit it manually +const axisAlignedCollision = (aa1, aa2)=>{ + return aa1.x < aa2.x + aa2.w && aa1.x + aa1.w > aa2.x && aa1.y < aa2.y + aa2.h && aa1.y + aa1.h > aa2.y; +}; const Constants = { TWO_PI: Math.PI * 2 }; @@ -10,9 +13,15 @@ class Vector { y; z; constructor(x = 0, y = 0, z = 0){ - this.x = x; - this.y = y; - this.z = z; + if (typeof x === "number") { + this.x = x; + this.y = y; + this.z = z; + } else { + this.x = x.x; + this.y = x.y || y; + this.z = x.z || z; + } } set(v, y, z) { if (arguments.length === 1 && typeof v !== "number") { @@ -47,7 +56,7 @@ class Vector { } } add(v, y, z) { - if (arguments.length === 1 && typeof v !== 'number') { + if (arguments.length === 1 && typeof v !== "number") { this.x += v.x; this.y += v.y; this.z += v.z; @@ -62,7 +71,7 @@ class Vector { return this; } sub(v, y, z) { - if (arguments.length === 1 && typeof v !== 'number') { + if (arguments.length === 1 && typeof v !== "number") { this.x -= v.x; this.y -= v.y; this.z -= v.z; @@ -77,7 +86,7 @@ class Vector { return this; } mult(v) { - if (typeof v === 'number') { + if (typeof v === "number") { this.x *= v; this.y *= v; this.z *= v; @@ -89,7 +98,7 @@ class Vector { return this; } div(v) { - if (typeof v === 'number') { + if (typeof v === "number") { this.x /= v; this.y /= v; this.z /= v; @@ -113,7 +122,7 @@ class Vector { return Math.sqrt(dx * dx + dy * dy + dz * dz); } dot(v, y, z) { - if (arguments.length === 1 && typeof v !== 'number') { + if (arguments.length === 1 && typeof v !== "number") { return this.x * v.x + this.y * v.y + this.z * v.z; } return this.x * v + this.y * y + this.z * z; @@ -127,7 +136,7 @@ class Vector { return start + (stop - start) * amt; }; let x, y; - if (arguments.length === 2 && typeof v_or_x !== 'number') { + if (arguments.length === 2 && typeof v_or_x !== "number") { amt = amt_or_y; x = v_or_x.x; y = v_or_x.y; @@ -174,13 +183,28 @@ class Vector { copy() { return new Vector(this.x, this.y, this.z); } - drawDot() { + drawDot(color) { if (!doodler) return; doodler.dot(this, { weight: 2, - color: 'red' + color: color || "red" }); } + draw() { + if (!doodler) return; + const startPoint = new Vector(); + doodler.dot(new Vector(), { + weight: 4, + color: "orange" + }); + doodler.line(startPoint, startPoint.copy().add(this.copy().normalize().mult(700))); + doodler.line(startPoint, startPoint.copy().sub(this.copy().normalize().mult(700))); + } + normal(v) { + const dx = v.x - this.x; + const dy = v.y - this.y; + return new Vector(-dy, dx); + } static fromAngle(angle, v) { if (v === undefined || v === null) { v = new Vector(); @@ -224,9 +248,9 @@ class Vector { return Math.acos(v1.dot(v2) / Math.sqrt(v1.magSq() * v2.magSq())); } static lerp(v1, v2, amt) { - const retval = new Vector(v1.x, v1.y, v1.z); - retval.lerp(v2, amt); - return retval; + const val = new Vector(v1.x, v1.y, v1.z); + val.lerp(v2, amt); + return val; } static vectorProjection(v1, v2) { v2 = v2.copy(); @@ -235,6 +259,16 @@ class Vector { v2.mult(sp); return v2; } + static vectorProjectionAndDot(v1, v2) { + v2 = v2.copy(); + v2.normalize(); + const sp = v1.dot(v2); + v2.mult(sp); + return [ + v2, + sp + ]; + } static hypot2(a, b) { return Vector.dot(Vector.sub(a, b), Vector.sub(a, b)); } @@ -618,7 +652,7 @@ class ZoomableDoodler extends Doodler { e.preventDefault(); this.dragging = false; }); - this._canvas.addEventListener("mouseleave", (e)=>{ + this._canvas.addEventListener("mouseleave", (_e)=>{ this.dragging = false; }); this._canvas.addEventListener("mousemove", (e)=>{ @@ -831,50 +865,131 @@ const init = (opt, zoomable, postInit)=>{ window.doodler = zoomable ? new ZoomableDoodler(opt, postInit) : new Doodler(opt, postInit); window.doodler.init(); }; +class Polygon { + points; + center; + constructor(points){ + this.points = points.map((p)=>new Vector(p)); + this.center = this.calcCenter(); + } + draw(color) { + for(let i = 0; i < this.points.length; i++){ + const p1 = this.points[i]; + const p2 = this.points.at(i - this.points.length + 1); + doodler.line(p1.copy().add(this.center), p2.copy().add(this.center), { + color + }); + const { x, y, w, h } = this.aaHitbox(); + doodler.drawRect(new Vector(x, y), w, h, { + color: "lime" + }); + } + } + calcCenter() { + if (!this.points.length) return new Vector(); + const center = new Vector(); + for (const point of this.points){ + center.add(point); + } + center.div(this.points.length); + return center; + } + circularHitbox() { + let greatestDistance = 0; + for (const p of this.points){ + greatestDistance = Math.max(p.copy().add(this.center).dist(this.center), greatestDistance); + } + return { + center: this.center.copy(), + radius: greatestDistance + }; + } + aaHitbox() { + let smallestX, biggestX, smallestY, biggestY; + smallestX = smallestY = Infinity; + biggestX = biggestY = -Infinity; + for (const p of this.points){ + const temp = p.copy().add(this.center); + smallestX = Math.min(temp.x, smallestX); + biggestX = Math.max(temp.x, biggestX); + smallestY = Math.min(temp.y, smallestY); + biggestY = Math.max(temp.y, biggestY); + } + return { + x: smallestX, + y: smallestY, + w: biggestX - smallestX, + h: biggestY - smallestY + }; + } + static createPolygon(sides = 3, radius = 100) { + sides = Math.round(sides); + if (sides < 3) { + throw "You need at least 3 sides for a polygon"; + } + const poly = new Polygon([]); + const rotangle = Math.PI * 2 / sides; + let angle = 0; + for(let i = 0; i < sides; i++){ + angle = i * rotangle + (Math.PI - rotangle) * 0.5; + const pt = new Vector(Math.cos(angle) * radius, Math.sin(angle) * radius); + poly.points.push(pt); + } + poly.center = poly.calcCenter(); + return poly; + } +} init({ - width: 400, - height: 400 + width: 2400, + height: 1200, + bg: "#333" }, true, (ctx)=>{ ctx.imageSmoothingEnabled = false; }); -new Vector(100, 300); -const v = new Vector(30, 30); -doodler.registerDraggable(v, 20); const img = new Image(); img.src = "./skeleton.png"; img.hidden; document.body.append(img); -const p = new Vector(200, 200); -doodler.createLayer(()=>{ +const p = new Vector(500, 500); +const poly = new Polygon([ + { + x: -50, + y: -25 + }, + { + x: 25, + y: -50 + }, + { + x: 25, + y: 25 + }, + { + x: -25, + y: 25 + } +]); +const poly2 = Polygon.createPolygon(5, 75); +poly2.center = p.copy(); +poly.center.add(p); +doodler.createLayer((c)=>{ + for(let i = 0; i < c.canvas.width; i += 50){ + for(let j = 0; j < c.canvas.height; j += 50){ + doodler.drawSquare(new Vector(i, j), 50, { + color: "#00000010" + }); + } + } + const color = axisAlignedCollision(poly.aaHitbox(), poly2.aaHitbox()) ? "red" : "black"; + poly.draw(color); + poly2.draw(color); const [gamepad] = navigator.getGamepads(); if (gamepad) { const leftX = gamepad.axes[0]; const leftY = gamepad.axes[1]; - p.add(Math.min(Math.max(leftX - 0.04, 0), leftX + 0.04), Math.min(Math.max(leftY - 0.04, 0), leftY + 0.04)); - const rigthX = gamepad.axes[2]; - const rigthY = gamepad.axes[3]; - doodler.moveOrigin({ - x: -rigthX * 5, - y: -rigthY * 5 - }); - if (gamepad.buttons[7].value) { - doodler.scaleAt({ - x: 200, - y: 200 - }, 1 + gamepad.buttons[7].value / 5); - } - if (gamepad.buttons[6].value) { - doodler.scaleAt({ - x: 200, - y: 200 - }, 1 - gamepad.buttons[6].value / 5); - } - } - doodler.drawImageWithOutline(img, p); -}); -document.addEventListener("keyup", (e)=>{ - e.preventDefault(); - if (e.key === " ") { - doodler.unregisterDraggable(v); + 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)); + const rightX = gamepad.axes[2]; + const rightY = gamepad.axes[3]; + 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/aa.ts b/collision/aa.ts new file mode 100644 index 0000000..7387e69 --- /dev/null +++ b/collision/aa.ts @@ -0,0 +1,16 @@ +import { Point } from "../geometry/vector.ts"; + +export type axisAlignedBoundingBox = { + w: number; + h: number; +} & Point; + +export const axisAlignedCollision = ( + aa1: axisAlignedBoundingBox, + aa2: axisAlignedBoundingBox, +) => { + return aa1.x < aa2.x + aa2.w && + aa1.x + aa1.w > aa2.x && + aa1.y < aa2.y + aa2.h && + aa1.y + aa1.h > aa2.y; +}; diff --git a/collision/circular.ts b/collision/circular.ts new file mode 100644 index 0000000..a430ba6 --- /dev/null +++ b/collision/circular.ts @@ -0,0 +1,15 @@ +import { Point } from "../geometry/vector.ts"; +import { Vector } from "../mod.ts"; + +export type CircleLike = { + center: Point; + radius: number; +}; + +export const circularCollision = (c1: CircleLike, c2: CircleLike) => { + const center1 = new Vector(c1.center); + const center2 = new Vector(c2.center); + const maxDist = c1.radius + c2.radius; + + return Vector.dist(center1, center2) < maxDist; +}; diff --git a/collision/sat.ts b/collision/sat.ts new file mode 100644 index 0000000..84383d1 --- /dev/null +++ b/collision/sat.ts @@ -0,0 +1,105 @@ +import { Polygon } from "../geometry/polygon.ts"; +import { Point, Vector } from "../geometry/vector.ts"; + +export const satCollision = (s1: Polygon, s2: Polygon) => { + 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 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]; + for (const point of shape1) { + const [projected, dot] = Vector.vectorProjectionAndDot(point, axis); + // projected.drawDot(); + 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(shape2[0], axis); + let [p2max, p2maxDot] = [p2min, p2minDot]; + for (const point of shape2) { + const [projected, dot] = Vector.vectorProjectionAndDot(point, axis); + // projected.drawDot(); + p2min = dot < p2minDot ? projected : p2min; + 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]; + for (const point of shape2) { + const [projected, dot] = Vector.vectorProjectionAndDot(point, axis); + // projected.drawDot(); + 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); + // projected.drawDot(); + 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) { + // 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; +}; diff --git a/deno.jsonc b/deno.jsonc index c24fed6..993cb74 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -2,13 +2,13 @@ "compilerOptions": { "lib": [ "DOM", - "es2015" + "es2023" ], "types": [ "./global.d.ts" ] }, "tasks": { - "dev" : "deno bundle --watch main.ts bundle.js" + "dev": "deno bundle --watch main.ts bundle.js" } } \ No newline at end of file diff --git a/geometry/polygon.ts b/geometry/polygon.ts new file mode 100644 index 0000000..d2927a1 --- /dev/null +++ b/geometry/polygon.ts @@ -0,0 +1,97 @@ +import { axisAlignedBoundingBox } from "../collision/aa.ts"; +import { CircleLike } from "../collision/circular.ts"; +import { Vector } from "../mod.ts"; +import { Point } from "./vector.ts"; + +export class Polygon { + points: Vector[]; + center: Vector; + + constructor(points: Point[]) { + this.points = points.map((p) => new Vector(p)); + this.center = this.calcCenter(); + } + + draw(color?: string) { + for (let i = 0; i < this.points.length; i++) { + const p1 = this.points[i]; + const p2 = this.points.at(i - this.points.length + 1)!; + doodler.line(p1.copy().add(this.center), p2.copy().add(this.center), { + color, + }); + const { x, y, w, h } = this.aaHitbox(); + doodler.drawRect(new Vector(x, y), w, h, { color: "lime" }); + } + } + + calcCenter() { + if (!this.points.length) return new Vector(); + const center = new Vector(); + + for (const point of this.points) { + center.add(point); + } + center.div(this.points.length); + return center; + } + + circularHitbox(): CircleLike { + let greatestDistance = 0; + for (const p of this.points) { + greatestDistance = Math.max( + p.copy().add(this.center).dist(this.center), + greatestDistance, + ); + } + + return { + center: this.center.copy(), + radius: greatestDistance, + }; + } + + aaHitbox(): axisAlignedBoundingBox { + let smallestX, biggestX, smallestY, biggestY; + smallestX = + smallestY = + Infinity; + biggestX = + biggestY = + -Infinity; + + for (const p of this.points) { + const temp = p.copy().add(this.center); + smallestX = Math.min(temp.x, smallestX); + biggestX = Math.max(temp.x, biggestX); + smallestY = Math.min(temp.y, smallestY); + biggestY = Math.max(temp.y, biggestY); + } + + return { + x: smallestX, + y: smallestY, + w: biggestX - smallestX, + h: biggestY - smallestY, + }; + } + + static createPolygon(sides = 3, radius = 100) { + sides = Math.round(sides); + if (sides < 3) { + throw "You need at least 3 sides for a polygon"; + } + + const poly = new Polygon([]); + // figure out the angles required + const rotangle = (Math.PI * 2) / sides; + let angle = 0; + // loop through and generate each point + for (let i = 0; i < sides; i++) { + angle = (i * rotangle) + ((Math.PI - rotangle) * 0.5); + const pt = new Vector(Math.cos(angle) * radius, Math.sin(angle) * radius); + poly.points.push(pt); + } + poly.center = poly.calcCenter(); + return poly; + } +} diff --git a/geometry/vector.ts b/geometry/vector.ts index 9d15ed4..fbf0287 100644 --- a/geometry/vector.ts +++ b/geometry/vector.ts @@ -7,10 +7,19 @@ export class Vector implements Point { y: number; z: number; - constructor(x = 0, y = 0, z = 0) { - this.x = x; - this.y = y; - this.z = z; + constructor(); + constructor(p: Point); + constructor(x: number, y: number, z?: number); + constructor(x: number | Point = 0, y = 0, z = 0) { + if (typeof x === "number") { + this.x = x; + this.y = y; + this.z = z; + } else { + this.x = x.x; + this.y = x.y || y; + this.z = x.z || z; + } } set(x: number, y: number, z?: number): void; @@ -18,9 +27,11 @@ export class Vector implements Point { set(v: [number, number, number]): void; set(v: Vector | [number, number, number] | number, y?: number, z?: number) { if (arguments.length === 1 && typeof v !== "number") { - this.set((v as Vector).x || (v as Array)[0] || 0, + this.set( + (v as Vector).x || (v as Array)[0] || 0, (v as Vector).y || (v as Array)[1] || 0, - (v as Vector).z || (v as Array)[2] || 0); + (v as Vector).z || (v as Array)[2] || 0, + ); } else { this.x = v as number; this.y = y || 0; @@ -43,7 +54,7 @@ export class Vector implements Point { return (x * x + y * y + z * z); } setMag(len: number): void; - setMag(v: Vector, len: number): Vector + setMag(v: Vector, len: number): Vector; setMag(v_or_len: Vector | number, len?: number) { if (len === undefined) { len = v_or_len as number; @@ -60,7 +71,7 @@ export class Vector implements Point { add(x: number, y: number): Vector; add(v: Vector): Vector; add(v: Vector | number, y?: number, z?: number) { - if (arguments.length === 1 && typeof v !== 'number') { + if (arguments.length === 1 && typeof v !== "number") { this.x += v.x; this.y += v.y; this.z += v.z; @@ -79,7 +90,7 @@ export class Vector implements Point { sub(x: number, y: number): Vector; sub(v: Vector): Vector; sub(v: Vector | number, y?: number, z?: number) { - if (arguments.length === 1 && typeof v !== 'number') { + if (arguments.length === 1 && typeof v !== "number") { this.x -= v.x; this.y -= v.y; this.z -= v.z; @@ -95,7 +106,7 @@ export class Vector implements Point { return this; } mult(v: number | Vector) { - if (typeof v === 'number') { + if (typeof v === "number") { this.x *= v; this.y *= v; this.z *= v; @@ -107,7 +118,7 @@ export class Vector implements Point { return this; } div(v: number | Vector) { - if (typeof v === 'number') { + if (typeof v === "number") { this.x /= v; this.y /= v; this.z /= v; @@ -135,18 +146,16 @@ export class Vector implements Point { dot(x: number, y: number, z: number): number; dot(v: Vector): number; dot(v: Vector | number, y?: number, z?: number) { - if (arguments.length === 1 && typeof v !== 'number') { - return (this.x * v.x + this.y * v.y + this.z * v.z); + if (arguments.length === 1 && typeof v !== "number") { + return (this.x * v.x) + (this.y * v.y) + (this.z * v.z); } - return (this.x * (v as number) + this.y * y! + this.z * z!); + return (this.x * (v as number)) + (this.y * y!) + (this.z * z!); } cross(v: Vector) { const x = this.x, y = this.y, z = this.z; - return new Vector(y * v.z - v.y * z, - z * v.x - v.z * x, - x * v.y - v.x * y); + return new Vector(y * v.z - v.y * z, z * v.x - v.z * x, x * v.y - v.x * y); } lerp(x: number, y: number, z: number): void; lerp(v: Vector, amt: number): void; @@ -155,7 +164,7 @@ export class Vector implements Point { return start + (stop - start) * amt; }; let x, y: number; - if (arguments.length === 2 && typeof v_or_x !== 'number') { + if (arguments.length === 2 && typeof v_or_x !== "number") { // given vector and amt amt = amt_or_y; x = v_or_x.x; @@ -202,10 +211,32 @@ export class Vector implements Point { return new Vector(this.x, this.y, this.z); } - drawDot() { + drawDot(color?: string) { if (!doodler) return; - doodler.dot(this, {weight: 2, color: 'red'}); + doodler.dot(this, { weight: 2, color: color || "red" }); + } + + draw() { + if (!doodler) return; + + const startPoint = new Vector(); + doodler.dot(new Vector(), { weight: 4, color: "orange" }); + doodler.line( + startPoint, + startPoint.copy().add(this.copy().normalize().mult(700)), + ); + doodler.line( + startPoint, + startPoint.copy().sub(this.copy().normalize().mult(700)), + ); + } + + normal(v: Vector) { + const dx = v.x - this.x; + const dy = v.y - this.y; + + return new Vector(-dy, dx); } static fromAngle(angle: number, v?: Vector) { @@ -261,9 +292,9 @@ export class Vector implements Point { static lerp(v1: Vector, v2: Vector, amt: number) { // non-static lerp mutates object, but this version returns a new vector - const retval = new Vector(v1.x, v1.y, v1.z); - retval.lerp(v2, amt); - return retval; + const val = new Vector(v1.x, v1.y, v1.z); + val.lerp(v2, amt); + return val; } static vectorProjection(v1: Vector, v2: Vector) { @@ -273,9 +304,16 @@ export class Vector implements Point { v2.mult(sp); return v2; } + static vectorProjectionAndDot(v1: Vector, v2: Vector): [Vector, number] { + v2 = v2.copy(); + v2.normalize(); + const sp = v1.dot(v2); + v2.mult(sp); + return [v2, sp]; + } static hypot2(a: Vector, b: Vector) { - return Vector.dot(Vector.sub(a, b), Vector.sub(a, b)) + return Vector.dot(Vector.sub(a, b), Vector.sub(a, b)); } } @@ -284,9 +322,9 @@ export class OriginVector extends Vector { get halfwayPoint() { return { - x: (this.mag()/2 * Math.sin(this.heading())) + this.origin.x, - y: (this.mag()/2 * Math.cos(this.heading())) + this.origin.y - } + x: (this.mag() / 2 * Math.sin(this.heading())) + this.origin.x, + y: (this.mag() / 2 * Math.cos(this.heading())) + this.origin.y, + }; } constructor(origin: Point, p: Point) { diff --git a/main.ts b/main.ts index cbdefcd..400be28 100644 --- a/main.ts +++ b/main.ts @@ -1,59 +1,106 @@ /// +import { axisAlignedCollision } from "./collision/aa.ts"; +import { circularCollision } from "./collision/circular.ts"; +import { satCollision } from "./collision/sat.ts"; +import { Polygon } from "./geometry/polygon.ts"; import { initializeDoodler, Vector } from "./mod.ts"; -import { ZoomableDoodler } from "./zoomableCanvas.ts"; +// import { ZoomableDoodler } from "./zoomableCanvas.ts"; initializeDoodler( { - width: 400, - height: 400, + width: 2400, + height: 1200, + bg: "#333", }, true, (ctx) => { ctx.imageSmoothingEnabled = false; + // ctx.translate(1200, 600); }, ); -const movingVector = new Vector(100, 300); -let angleMultiplier = 0; -const v = new Vector(30, 30); -doodler.registerDraggable(v, 20); +// const movingVector = new Vector(100, 300); +// let angleMultiplier = 0; +// const v = new Vector(30, 30); +// doodler.registerDraggable(v, 20); const img = new Image(); img.src = "./skeleton.png"; img.hidden; document.body.append(img); -const p = new Vector(200, 200); +const p = new Vector(500, 500); -doodler.createLayer(() => { +const poly = new Polygon([ + { x: -50, y: -25 }, + { x: 25, y: -50 }, + { x: 25, y: 25 }, + { x: -25, y: 25 }, +]); +// poly.center = p.copy(); + +const poly2 = Polygon.createPolygon(5, 75); +poly2.center = p.copy(); + +poly.center.add(p); + +doodler.createLayer((c) => { + for (let i = 0; i < c.canvas.width; i += 50) { + for (let j = 0; j < c.canvas.height; j += 50) { + doodler.drawSquare(new Vector(i, j), 50, { color: "#00000010" }); + } + } + const color = axisAlignedCollision( + poly.aaHitbox(), + poly2.aaHitbox(), + ) + ? "red" + : "black"; + + // console.log(satCollision( + // )); + + poly.draw(color); + + poly2.draw(color); + + // poly2.center.add(Vector.random2D()); const [gamepad] = navigator.getGamepads(); - const deadzone = 0.04; + const deadzone = 0.05; if (gamepad) { const leftX = gamepad.axes[0]; const leftY = gamepad.axes[1]; - p.add( - Math.min(Math.max(leftX - deadzone, 0), leftX + deadzone), - Math.min(Math.max(leftY - deadzone, 0), leftY + deadzone), + poly.center.add( + new Vector( + Math.min(Math.max(leftX - deadzone, 0), leftX + deadzone), + Math.min(Math.max(leftY - deadzone, 0), leftY + deadzone), + ).mult(10), ); - const rigthX = gamepad.axes[2]; - const rigthY = gamepad.axes[3]; - (doodler as ZoomableDoodler).moveOrigin({ x: -rigthX * 5, y: -rigthY * 5 }); + const rightX = gamepad.axes[2]; + const rightY = gamepad.axes[3]; + poly2.center.add( + new Vector( + Math.min(Math.max(rightX - deadzone, 0), rightX + deadzone), + Math.min(Math.max(rightY - deadzone, 0), rightY + deadzone), + ).mult(10), + ); + // (doodler as ZoomableDoodler).moveOrigin({ x: -rigthX * 5, y: -rigthY * 5 }); - if (gamepad.buttons[7].value) { - (doodler as ZoomableDoodler).scaleAt( - { x: 200, y: 200 }, - 1 + (gamepad.buttons[7].value / 5), - ); - } - if (gamepad.buttons[6].value) { - (doodler as ZoomableDoodler).scaleAt( - { x: 200, y: 200 }, - 1 - (gamepad.buttons[6].value / 5), - ); - } + // if (gamepad.buttons[7].value) { + // (doodler as ZoomableDoodler).scaleAt( + // { x: 200, y: 200 }, + // 1 + (gamepad.buttons[7].value / 5), + // ); + // } + // if (gamepad.buttons[6].value) { + // (doodler as ZoomableDoodler).scaleAt( + // { x: 200, y: 200 }, + // 1 - (gamepad.buttons[6].value / 5), + // ); + // } } - doodler.drawImageWithOutline(img, p); + // doodler.drawImageWithOutline(img, p); // doodler.line(new Vector(100, 100), new Vector(200, 200)) // doodler.dot(new Vector(300, 300)) // doodler.fillCircle(movingVector, 6, { color: 'red' }); @@ -88,9 +135,9 @@ doodler.createLayer(() => { // }); }); -document.addEventListener("keyup", (e) => { - e.preventDefault(); - if (e.key === " ") { - doodler.unregisterDraggable(v); - } -}); +// document.addEventListener("keyup", (e) => { +// e.preventDefault(); +// if (e.key === " ") { +// doodler.unregisterDraggable(v); +// } +// }); diff --git a/zoomableCanvas.ts b/zoomableCanvas.ts index 35a01ce..0b88503 100644 --- a/zoomableCanvas.ts +++ b/zoomableCanvas.ts @@ -54,7 +54,7 @@ export class ZoomableDoodler extends Doodler { e.preventDefault(); this.dragging = false; }); - this._canvas.addEventListener("mouseleave", (e) => { + this._canvas.addEventListener("mouseleave", (_e) => { this.dragging = false; }); this._canvas.addEventListener("mousemove", (e) => {