diff --git a/.vscode/settings.json b/.vscode/settings.json index 25c1934..736d3dc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -21,5 +21,6 @@ "titleBar.inactiveBackground": "#33005599", "titleBar.inactiveForeground": "#e7e7e799" }, - "peacock.remoteColor": "330055" + "peacock.remoteColor": "330055", + "liveServer.settings.port": 5501 } \ No newline at end of file diff --git a/bundle.js b/bundle.js index 8eaab4e..973ca78 100644 --- a/bundle.js +++ b/bundle.js @@ -6,6 +6,9 @@ const Constants = { TWO_PI: Math.PI * 2 }; const map = (value, x1, y1, x2, y2)=>(value - x1) * (y2 - x2) / (y1 - x1) + x2; +const Constants1 = { + TWO_PI: Math.PI * 2 +}; class Vector { x; y; @@ -169,10 +172,12 @@ class Vector { copy() { return new Vector(this.x, this.y, this.z); } - drawDot(ctx) { - ctx.beginPath(); - ctx.arc(this.x, this.y, 2, 0, Constants.TWO_PI); - ctx.fill(); + drawDot() { + if (!doodler) return; + doodler.dot(this, { + weight: 2, + color: 'red' + }); } static fromAngle(angle, v) { if (v === undefined || v === null) { @@ -186,7 +191,7 @@ class Vector { return Vector.fromAngle(Math.random() * (Math.PI * 2), v); } static random3D(v) { - const angle = Math.random() * Constants.TWO_PI; + const angle = Math.random() * Constants1.TWO_PI; const vz = Math.random() * 2 - 1; const mult = Math.sqrt(1 - vz * vz); const vx = mult * Math.cos(angle); @@ -232,6 +237,230 @@ class Vector { return Vector.dot(Vector.sub(a, b), Vector.sub(a, b)); } } +const init = (opt)=>{ + if (window.doodler) throw 'Doodler has already been initialized in this window'; + window.doodler = new Doodler(opt); + window.doodler.init(); +}; +class Doodler { + ctx; + _canvas; + layers = []; + bg; + framerate; + get width() { + return this.ctx.canvas.width; + } + get height() { + return this.ctx.canvas.height; + } + draggables = []; + constructor({ width , height , canvas , bg , framerate }){ + if (!canvas) { + canvas = document.createElement('canvas'); + document.body.append(canvas); + } + this.bg = bg || 'white'; + this.framerate = framerate || 60; + canvas.width = width; + canvas.height = height; + this._canvas = canvas; + const ctx = canvas.getContext('2d'); + console.log(ctx); + if (!ctx) throw 'Unable to initialize Doodler: Canvas context not found'; + this.ctx = ctx; + } + init() { + this._canvas.addEventListener('mousedown', (e)=>this.onClick(e)); + this._canvas.addEventListener('mouseup', (e)=>this.offClick(e)); + this._canvas.addEventListener('mousemove', (e)=>{ + const rect = this._canvas.getBoundingClientRect(); + this.mouseX = e.clientX - rect.left; + this.mouseY = e.clientY - rect.top; + for (const d of this.draggables.filter((d)=>d.beingDragged)){ + d.point.add(e.movementX, e.movementY); + } + }); + this.startDrawLoop(); + } + timer; + startDrawLoop() { + this.timer = setInterval(()=>this.draw(), 1000 / this.framerate); + } + draw() { + this.ctx.fillStyle = this.bg; + this.ctx.fillRect(0, 0, this.width, this.height); + for (const [i, l] of (this.layers || []).entries()){ + l(this.ctx, i); + } + this.drawUI(); + } + createLayer(layer) { + this.layers.push(layer); + } + deleteLayer(layer) { + this.layers = this.layers.filter((l)=>l !== layer); + } + moveLayer(layer, index) { + let temp = this.layers.filter((l)=>l !== layer); + temp = [ + ...temp.slice(0, index), + layer, + ...temp.slice(index) + ]; + this.layers = temp; + } + line(start, end, style) { + this.setStyle(style); + this.ctx.beginPath(); + this.ctx.moveTo(start.x, start.y); + this.ctx.lineTo(end.x, end.y); + this.ctx.stroke(); + } + dot(at, style) { + this.setStyle({ + ...style, + weight: 1 + }); + this.ctx.beginPath(); + this.ctx.arc(at.x, at.y, style?.weight || 1, 0, Constants1.TWO_PI); + this.ctx.fill(); + } + drawCircle(at, radius, style) { + this.setStyle(style); + this.ctx.beginPath(); + this.ctx.arc(at.x, at.y, radius, 0, Constants1.TWO_PI); + this.ctx.stroke(); + } + fillCircle(at, radius, style) { + this.setStyle(style); + this.ctx.beginPath(); + this.ctx.arc(at.x, at.y, radius, 0, Constants1.TWO_PI); + this.ctx.fill(); + } + drawRect(at, width, height, style) { + this.setStyle(style); + this.ctx.strokeRect(at.x, at.y, width, height); + } + fillRect(at, width, height, style) { + this.setStyle(style); + this.ctx.fillRect(at.x, at.y, width, height); + } + drawSquare(at, size, style) { + this.drawRect(at, size, size, style); + } + fillSquare(at, size, style) { + this.fillRect(at, size, size, style); + } + drawCenteredRect(at, width, height, style) { + this.ctx.save(); + this.ctx.translate(-width / 2, -height / 2); + this.drawRect(at, width, height, style); + this.ctx.restore(); + } + fillCenteredRect(at, width, height, style) { + this.ctx.save(); + this.ctx.translate(-width / 2, -height / 2); + this.fillRect(at, width, height, style); + this.ctx.restore(); + } + drawCenteredSquare(at, size, style) { + this.drawCenteredRect(at, size, size, style); + } + fillCenteredSquare(at, size, style) { + this.fillCenteredRect(at, size, size, style); + } + drawBezier(a, b, c, d, style) { + this.setStyle(style); + this.ctx.beginPath(); + this.ctx.moveTo(a.x, a.y); + this.ctx.bezierCurveTo(b.x, b.y, c.x, c.y, d.x, d.y); + this.ctx.stroke(); + } + drawRotated(origin, angle, cb) { + this.ctx.save(); + this.ctx.translate(origin.x, origin.y); + this.ctx.rotate(angle); + this.ctx.translate(-origin.x, -origin.y); + cb(); + this.ctx.restore(); + } + setStyle(style) { + const ctx = this.ctx; + ctx.fillStyle = style?.color || style?.fillColor || 'black'; + ctx.strokeStyle = style?.color || style?.strokeColor || 'black'; + ctx.lineWidth = style?.weight || 1; + } + mouseX = 0; + mouseY = 0; + registerDraggable(point, radius, style) { + if (this.draggables.find((d)=>d.point === point)) return; + const id = this.addUIElement('circle', point, radius, { + fillColor: '#5533ff50', + strokeColor: '#5533ff50' + }); + this.draggables.push({ + point, + radius, + style, + id + }); + } + unregisterDraggable(point) { + for (const d of this.draggables){ + if (d.point === point) { + this.removeUIElement(d.id); + } + } + this.draggables = this.draggables.filter((d)=>d.point !== point); + } + onClick(e) { + for (const d of this.draggables){ + if (d.point.dist(new Vector(this.mouseX, this.mouseY)) <= d.radius) { + d.beingDragged = true; + } else d.beingDragged = false; + } + } + offClick(e) { + for (const d of this.draggables){ + d.beingDragged = false; + } + } + uiElements = new Map(); + uiDrawing = { + rectangle: (...args)=>{ + !args[3].noFill && this.fillRect(args[0], args[1], args[2], args[3]); + !args[3].noStroke && this.drawRect(args[0], args[1], args[2], args[3]); + }, + square: (...args)=>{ + !args[2].noFill && this.fillSquare(args[0], args[1], args[2]); + !args[2].noStroke && this.drawSquare(args[0], args[1], args[2]); + }, + circle: (...args)=>{ + !args[2].noFill && this.fillCircle(args[0], args[1], args[2]); + !args[2].noStroke && this.drawCircle(args[0], args[1], args[2]); + } + }; + drawUI() { + for (const [shape, ...args] of this.uiElements.values()){ + this.uiDrawing[shape].apply(null, args); + } + } + addUIElement(shape, ...args) { + const id = crypto.randomUUID(); + for (const arg of args){ + delete arg.color; + } + this.uiElements.set(id, [ + shape, + ...args + ]); + return id; + } + removeUIElement(id) { + this.uiElements.delete(id); + } +} class ComplexPath { points = []; radius = 50; @@ -266,23 +495,19 @@ class ComplexPath { class PathSegment { points; ctx; + length; constructor(points){ this.points = points; + this.length = this.calculateApproxLength(100); } setContext(ctx) { this.ctx = ctx; } draw() { - if (!this.ctx) return; - const ctx = this.ctx; - ctx.save(); - ctx.beginPath(); - ctx.moveTo(this.points[0].x, this.points[0].y); - ctx.bezierCurveTo(this.points[1].x, this.points[1].y, this.points[2].x, this.points[2].y, this.points[3].x, this.points[3].y); - ctx.strokeStyle = '#ffffff50'; - ctx.lineWidth = 2; - ctx.stroke(); - ctx.restore(); + const [a, b, c, d] = this.points; + doodler.drawBezier(a, b, c, d, { + strokeColor: '#ffffff50' + }); } getPointAtT(t) { const [a, b, c, d] = this.points; @@ -363,6 +588,24 @@ class PathSegment { if (distance < r) return t; return false; } + calculateApproxLength(resolution = 25) { + const stepSize = 1 / resolution; + const points = []; + for(let i = 0; i <= resolution; i++){ + const current = stepSize * i; + points.push(this.getPointAtT(current)); + } + return points.reduce((acc, cur)=>{ + const prev = acc.prev; + acc.prev = cur; + if (!prev) return acc; + acc.length += cur.dist(prev); + return acc; + }, { + prev: undefined, + length: 0 + }).length; + } } class Mover { position; @@ -423,6 +666,11 @@ class Mover { if (this.position.y < 0) this.position.y = this.ctx.canvas.height; } draw() { + doodler.drawRotated(this.position, this.velocity.heading() || 0, ()=>{ + doodler.fillCenteredRect(this.position, this.boundingBox.size.x, this.boundingBox.size.y, { + fillColor: 'white' + }); + }); if (!this.ctx) return; this.ctx.fillStyle = 'white'; this.ctx.save(); @@ -650,15 +898,12 @@ class Track extends PathSegment { ]; } getNearestPoint(p) { - let [closest, closestDistance, closestT] = this.getClosestPoint(p); - let mostValid = this; + let [closest, closestDistance] = this.getClosestPoint(p); if (this.next !== this) { const [point, distance, t] = this.next.getClosestPoint(p); if (distance < closestDistance) { closest = point; closestDistance = distance; - mostValid = this.next; - t; } } if (this.prev !== this) { @@ -666,8 +911,6 @@ class Track extends PathSegment { if (distance1 < closestDistance) { closest = point1; closestDistance = distance1; - mostValid = this.next; - t1; } } return closest; @@ -678,9 +921,8 @@ class Track extends PathSegment { } draw() { super.draw(); - if (this.ctx && this.editable) for (const e of this.points){ - this.ctx.fillStyle = 'blue'; - e.drawDot(this.ctx); + if (this.editable) for (const e of this.points){ + e.drawDot(); } } } @@ -776,57 +1018,37 @@ const generateSquareTrack = ()=>{ eighth ]); }; -const drawLine = (ctx, x1, y1, x2, y2)=>{ - ctx.beginPath(); - ctx.moveTo(x1, y1); - ctx.lineTo(x2, y2); - ctx.stroke(); -}; -const hello = ()=>{ - console.log('HELLO WORLD'); -}; -hello(); -const canvas = document.createElement('canvas'); -canvas.height = 400; -canvas.width = 400; -document.body.append(canvas); -const ctx = canvas.getContext('2d'); -const clear = ()=>{ - ctx.fillStyle = 'black'; - ctx.fillRect(0, 0, canvas.width, canvas.height); -}; -setInterval(()=>{ - draw(); -}, 1000 / 60); +init({ + width: 400, + height: 400, + bg: '#333' +}); const path = generateSquareTrack(); -path.setContext(ctx); let t = 0; let currentSeg = 0; const trains = Array(1).fill(null).map((_, i)=>new Train(path.segments[i % path.segments.length], 5)); -for (const train of trains){ - train.setContext(ctx); -} -function draw() { - clear(); +doodler.createLayer(()=>{ path.draw(); for (const train of trains){ train.move(); } - ctx.strokeStyle = 'red'; - ctx.lineWidth = 4; const seg = path.segments[currentSeg]; + const tMod = 1 / seg.length; const start = seg.getPointAtT(t); const tan = seg.tangent(t).normalize().mult(25); - drawLine(ctx, start.x, start.y, start.x + tan.x, start.y + tan.y); - t += .01; + doodler.line(start, new Vector(start.x + tan.x, start.y + tan.y), { + color: 'red' + }); + t += tMod; if (t > 1) { t -= 1; currentSeg = (currentSeg + 1) % path.segments.length; } -} +}); document.addEventListener('keyup', (e)=>{ if (e.key === 'd') { console.log(trains); + console.log(path.segments.reduce((a, b)=>a + b.calculateApproxLength(1000), 0)); } if (e.key === 'ArrowUp') { for (const train of trains){ @@ -841,6 +1063,10 @@ document.addEventListener('keyup', (e)=>{ if (e.key === 'e') { for (const t of path.segments){ t.editable = !t.editable; + for (const p of t.points){ + if (t.editable) doodler.registerDraggable(p, 10); + else doodler.unregisterDraggable(p); + } } } }); diff --git a/deno.jsonc b/deno.jsonc index 3770318..b4a894c 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -12,6 +12,6 @@ }, "imports": { "drawing": "./drawing/index.ts", - "doodler": "https://git.cyborggrizzly.com/emma/doodler/raw/branch/main/mod.ts" + "doodler": "https://git.cyborggrizzly.com/emma/doodler/raw/tag/0.0.3a/mod.ts" } } \ No newline at end of file diff --git a/drawing/circle.ts b/drawing/circle.ts index 1245aea..2991b6b 100644 --- a/drawing/circle.ts +++ b/drawing/circle.ts @@ -1,5 +1,5 @@ import { Constants } from "../math/constants.ts"; -import { Vector } from "../math/vector.ts"; +import { Vector } from "doodler"; const circle = (ctx: CanvasRenderingContext2D, center: Vector, radius: number) => { ctx.beginPath(); diff --git a/main.ts b/main.ts index a00b623..362c0dc 100644 --- a/main.ts +++ b/main.ts @@ -1,48 +1,26 @@ import { lerp } from "./math/lerp.ts"; import { ComplexPath, PathSegment } from "./math/path.ts"; -import { Vector } from "./math/vector.ts"; import { Mover } from "./physics/mover.ts"; import { Train } from "./train.ts"; import { fillCircle, drawCircle } from 'drawing'; import { generateSquareTrack } from "./track.ts"; import { drawLine } from "./drawing/line.ts"; -import { hello } from 'doodler'; +import { initializeDoodler, Vector } from 'doodler'; -hello(); -const canvas = document.createElement('canvas'); -canvas.height = 400; -canvas.width = 400; -document.body.append(canvas); -const ctx = canvas.getContext('2d')!; // for (const mover of trains) { // mover.setContext(ctx); // mover.velocity.add(Vector.random2D()) // } -const clear = () => { - ctx.fillStyle = 'black'; - ctx.fillRect(0, 0, canvas.width, canvas.height) -} +initializeDoodler({ + width: 400, + height: 400, + bg: '#333' +}); -const fps = 60; - -setInterval(() => { - // for (const train of trains) { - // train.move(); - // } - draw(); -}, 1000 / fps); - - -// const path = new PathSegment([new Vector(20, 20), new Vector(200, 100), new Vector(200, 300), new Vector(20, 380)]); const path = generateSquareTrack(); -path.setContext(ctx); -// const train = new Train(path.segments[0], 4); -// train.setContext(ctx); -// train.velocity.x = -1; -// train.velocity.y = 1; const controls = { ArrowUp: false, @@ -53,91 +31,38 @@ const controls = { let t = 0; let currentSeg = 0; +const speed = 1; const trainCount = 1; const trains = Array(trainCount).fill(null).map((_, i) => new Train(path.segments[i % path.segments.length], 5)); -for (const train of trains) { - train.setContext(ctx); - // train.maxSpeed = Math.random() * 5 + 1 -} -function draw() { - clear(); +doodler.createLayer(() => { path.draw(); - - // for (const control in controls) { - // if (controls.hasOwnProperty(control)) { - // const isActive = controls[control as keyof typeof controls]; - // if (isActive) { - // const force = getSteeringForce(train, control); - // train.applyForce(force); - // } - // } - - // if (Object.values(controls).every(c => !c)) { - // train.acceleration.set(0, 0) - // } - // } - - // train.follow(path) + for (const train of trains) { train.move(); } - // ctx.strokeStyle = 'orange'; - - ctx.strokeStyle = 'red'; - ctx.lineWidth = 4; + + // ctx.strokeStyle = 'red'; + // ctx.lineWidth = 4; const seg = path.segments[currentSeg]; + const tMod = speed/seg.length; const start = seg.getPointAtT(t); const tan = seg.tangent(t).normalize().mult(25); - drawLine(ctx, start.x, start.y, start.x + tan.x, start.y + tan.y); - - t += .01; + doodler.line(start, new Vector(start.x + tan.x, start.y + tan.y), {color: 'red'}); + + t += tMod; if (t > 1) { t -= 1; currentSeg = (currentSeg + 1) % path.segments.length; } -} - -// let wKeydown =false - -// document.addEventListener('keydown', e => { -// if (e.key === 'w' && !wKeydown) { -// wKeydown = true; -// for (const train of trains) { -// train.acceleration.add(.1, 0); -// } -// } -// }); -// document.addEventListener('keyup', e => { -// if (e.key === 'w') { -// wKeydown = false; -// for (const train of trains) { -// train.acceleration.sub(.1, 0); -// } -// } -// }); -// let sKeydown = false; -// document.addEventListener('keydown', e => { -// if (e.key === 's' && !sKeydown) { -// sKeydown = true; -// for (const train of trains) { -// train.acceleration.sub(.1, 0); -// } -// } -// }); -// document.addEventListener('keyup', e => { -// if (e.key === 's') { -// sKeydown = false; -// for (const train of trains) { -// train.acceleration.add(.1, 0); -// } -// } -// }); + // path.segments.forEach(s => s.calculateApproxLength(10000)) +}) document.addEventListener('keyup', e => { if (e.key === 'd') { console.log(trains) + console.log(path.segments.reduce((a,b) => a + b.calculateApproxLength(1000), 0)) } if (e.key === 'ArrowUp') { @@ -154,6 +79,12 @@ document.addEventListener('keyup', e => { if (e.key === 'e') { for (const t of path.segments) { t.editable = !t.editable; + for (const p of t.points) { + if (t.editable) + doodler.registerDraggable(p, 10) + else + doodler.unregisterDraggable(p) + } } } }) diff --git a/math/lerp.ts b/math/lerp.ts index f6acaeb..41faba0 100644 --- a/math/lerp.ts +++ b/math/lerp.ts @@ -1,4 +1,4 @@ -import { Vector } from "./vector.ts"; +import { Vector } from "doodler"; export const lerp = (a: number, b: number, t: number) => { return (a*t) + (b*(1-t)); diff --git a/math/path.ts b/math/path.ts index 16ae52c..3b3023f 100644 --- a/math/path.ts +++ b/math/path.ts @@ -1,4 +1,4 @@ -import { Vector } from "./vector.ts"; +import { Vector } from "doodler"; export class ComplexPath { @@ -42,8 +42,11 @@ export class PathSegment { points: [Vector, Vector, Vector, Vector] ctx?: CanvasRenderingContext2D; + length: number; + constructor(points: [Vector, Vector, Vector, Vector]) { this.points = points; + this.length = this.calculateApproxLength(100); } setContext(ctx: CanvasRenderingContext2D) { @@ -51,26 +54,30 @@ export class PathSegment { } draw() { - if (!this.ctx) return; - const ctx = this.ctx; + const [a,b,c,d] = this.points; + doodler.drawBezier(a,b,c,d, { + strokeColor: '#ffffff50' + }) + // if (!this.ctx) return; + // const ctx = this.ctx; - ctx.save(); - ctx.beginPath(); - ctx.moveTo(this.points[0].x, this.points[0].y); + // ctx.save(); + // ctx.beginPath(); + // ctx.moveTo(this.points[0].x, this.points[0].y); - ctx.bezierCurveTo( - this.points[1].x, - this.points[1].y, - this.points[2].x, - this.points[2].y, - this.points[3].x, - this.points[3].y, - ); + // ctx.bezierCurveTo( + // this.points[1].x, + // this.points[1].y, + // this.points[2].x, + // this.points[2].y, + // this.points[3].x, + // this.points[3].y, + // ); - ctx.strokeStyle = '#ffffff50'; - ctx.lineWidth = 2; - ctx.stroke(); - ctx.restore(); + // ctx.strokeStyle = '#ffffff50'; + // ctx.lineWidth = 2; + // ctx.stroke(); + // ctx.restore(); } getPointAtT(t: number) { @@ -169,4 +176,20 @@ export class PathSegment { return false; } + + 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)) + } + return 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 + } } diff --git a/math/vector.ts b/math/vector.ts deleted file mode 100644 index f18c8af..0000000 --- a/math/vector.ts +++ /dev/null @@ -1,273 +0,0 @@ -import { Constants } from "./constants.ts"; - -export class Vector { - x: number; - y: number; - z: number; - - constructor(x = 0, y = 0, z = 0) { - this.x = x; - this.y = y; - this.z = z; - } - - set(x: number, y: number, z?: number): void; - set(v: Vector): void; - 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, - (v as Vector).y || (v as Array)[1] || 0, - (v as Vector).z || (v as Array)[2] || 0); - } else { - this.x = v as number; - this.y = y || 0; - this.z = z || 0; - } - } - get() { - return new Vector(this.x, this.y, this.z); - } - mag() { - const x = this.x, - y = this.y, - z = this.z; - return Math.sqrt(x * x + y * y + z * z); - } - magSq() { - const x = this.x, - y = this.y, - z = this.z; - return (x * x + y * y + z * z); - } - setMag(len: number): void; - setMag(v: Vector, len: number): Vector - setMag(v_or_len: Vector | number, len?: number) { - if (len === undefined) { - len = v_or_len as number; - this.normalize(); - this.mult(len); - } else { - const v = v_or_len as Vector; - v.normalize(); - v.mult(len); - return v; - } - } - add(x: number, y: number, z: number): void; - add(x: number, y: number): void; - add(v: Vector): void; - add(v: Vector | number, y?: number, z?: number) { - if (arguments.length === 1 && typeof v !== 'number') { - this.x += v.x; - this.y += v.y; - this.z += v.z; - } else if (arguments.length === 2) { - // 2D Vector - this.x += v as number; - this.y += y ?? 0; - } else { - this.x += v as number; - this.y += y ?? 0; - this.z += z ?? 0; - } - } - sub(x: number, y: number, z: number): void; - sub(x: number, y: number): void; - sub(v: Vector): void; - sub(v: Vector | number, y?: number, z?: number) { - if (arguments.length === 1 && typeof v !== 'number') { - this.x -= v.x; - this.y -= v.y; - this.z -= v.z; - } else if (arguments.length === 2) { - // 2D Vector - this.x -= v as number; - this.y -= y ?? 0; - } else { - this.x -= v as number; - this.y -= y ?? 0; - this.z -= z ?? 0; - } - } - mult(v: number | Vector) { - if (typeof v === 'number') { - this.x *= v; - this.y *= v; - this.z *= v; - } else { - this.x *= v.x; - this.y *= v.y; - this.z *= v.z; - } - return this; - } - div(v: number | Vector) { - if (typeof v === 'number') { - this.x /= v; - this.y /= v; - this.z /= v; - } else { - this.x /= v.x; - this.y /= v.y; - this.z /= v.z; - } - } - rotate(angle: number) { - const prev_x = this.x; - const c = Math.cos(angle); - const s = Math.sin(angle); - this.x = c * this.x - s * this.y; - this.y = s * prev_x + c * this.y; - } - dist(v: Vector) { - const dx = this.x - v.x, - dy = this.y - v.y, - dz = this.z - v.z; - return Math.sqrt(dx * dx + dy * dy + dz * dz); - } - 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); - } - 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); - } - lerp(x: number, y: number, z: number): void; - lerp(v: Vector, amt: number): void; - lerp(v_or_x: Vector | number, amt_or_y: number, z?: number, amt?: number) { - const lerp_val = (start: number, stop: number, amt: number) => { - return start + (stop - start) * amt; - }; - let x, y: number; - if (arguments.length === 2 && typeof v_or_x !== 'number') { - // given vector and amt - amt = amt_or_y; - x = v_or_x.x; - y = v_or_x.y; - z = v_or_x.z; - } else { - // given x, y, z and amt - x = v_or_x as number; - y = amt_or_y; - } - this.x = lerp_val(this.x, x, amt!); - this.y = lerp_val(this.y, y, amt!); - this.z = lerp_val(this.z, z!, amt!); - } - normalize() { - const m = this.mag(); - if (m > 0) { - this.div(m); - } - return this; - } - limit(high: number) { - if (this.mag() > high) { - this.normalize(); - this.mult(high); - } - } - heading() { - return (-Math.atan2(-this.y, this.x)); - } - heading2D() { - return this.heading(); - } - toString() { - return "[" + this.x + ", " + this.y + ", " + this.z + "]"; - } - array() { - return [this.x, this.y, this.z]; - } - - copy() { - return new Vector(this.x, this.y, this.z); - } - - drawDot(ctx: CanvasRenderingContext2D) { - // ctx.fillStyle = 'red' - ctx.beginPath(); - ctx.arc(this.x, this.y, 2, 0, Constants.TWO_PI); - ctx.fill(); - } - - static fromAngle(angle: number, v?: Vector) { - if (v === undefined || v === null) { - v = new Vector(); - } - v.x = Math.cos(angle); - v.y = Math.sin(angle); - return v; - } - - static random2D(v?: Vector) { - return Vector.fromAngle(Math.random() * (Math.PI * 2), v); - } - - static random3D(v: Vector) { - const angle = Math.random() * Constants.TWO_PI; - const vz = Math.random() * 2 - 1; - const mult = Math.sqrt(1 - vz * vz); - const vx = mult * Math.cos(angle); - const vy = mult * Math.sin(angle); - if (v === undefined || v === null) { - v = new Vector(vx, vy, vz); - } else { - v.set(vx, vy, vz); - } - return v; - } - - static dist(v1: Vector, v2: Vector) { - return v1.dist(v2); - } - - static dot(v1: Vector, v2: Vector) { - return v1.dot(v2); - } - - static cross(v1: Vector, v2: Vector) { - return v1.cross(v2); - } - - static add(v1: Vector, v2: Vector) { - return new Vector(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z); - } - - static sub(v1: Vector, v2: Vector) { - return new Vector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); - } - - static angleBetween(v1: Vector, v2: Vector) { - return Math.acos(v1.dot(v2) / Math.sqrt(v1.magSq() * v2.magSq())); - } - - 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; - } - - static vectorProjection(v1: Vector, v2: Vector) { - v2 = v2.copy(); - v2.normalize(); - const sp = v1.dot(v2); - v2.mult(sp); - return v2; - } - - static hypot2(a: Vector, b: Vector) { - return Vector.dot(Vector.sub(a,b), Vector.sub(a,b)) - } -} diff --git a/physics/follower.ts b/physics/follower.ts index 1c7744a..5127fd5 100644 --- a/physics/follower.ts +++ b/physics/follower.ts @@ -1,7 +1,7 @@ import { Constants } from "../math/constants.ts"; import { map } from "../math/lerp.ts"; import { ComplexPath, PathSegment } from "../math/path.ts"; -import { Vector } from "../math/vector.ts"; +import { Vector } from "doodler"; import { Mover } from "./mover.ts"; export class diff --git a/physics/mover.ts b/physics/mover.ts index 2152973..2aeeaf2 100644 --- a/physics/mover.ts +++ b/physics/mover.ts @@ -1,4 +1,4 @@ -import { Vector } from "../math/vector.ts"; +import { Vector } from "doodler"; export class Mover { position: Vector; @@ -81,6 +81,9 @@ export class Mover { } draw() { + doodler.drawRotated(this.position, this.velocity.heading() || 0, () => { + doodler.fillCenteredRect(this.position, this.boundingBox.size.x, this.boundingBox.size.y, {fillColor: 'white'}) + }); if (!this.ctx) return; this.ctx.fillStyle = 'white' diff --git a/track.ts b/track.ts index 415d5ec..2124c81 100644 --- a/track.ts +++ b/track.ts @@ -1,5 +1,5 @@ import { PathSegment } from "./math/path.ts"; -import { Vector } from "./math/vector.ts"; +import { Vector } from "doodler"; import { Train } from "./train.ts"; export class Track extends PathSegment { @@ -57,17 +57,13 @@ export class Track extends PathSegment { } getNearestPoint(p: Vector) { - let [closest, closestDistance, closestT] = this.getClosestPoint(p); - // deno-lint-ignore no-this-alias - let mostValid: Track = this; + let [closest, closestDistance] = this.getClosestPoint(p); if (this.next !== this) { const [point, distance, t] = this.next.getClosestPoint(p); if (distance < closestDistance) { closest = point; closestDistance = distance; - mostValid = this.next; - closestT = t; } } if (this.prev !== this) { @@ -75,8 +71,6 @@ export class Track extends PathSegment { if (distance < closestDistance) { closest = point; closestDistance = distance; - mostValid = this.next; - closestT = t; } } @@ -91,10 +85,9 @@ export class Track extends PathSegment { draw(): void { super.draw(); - if (this.ctx && this.editable) + if (this.editable) for (const e of this.points) { - this.ctx.fillStyle = 'blue'; - e.drawDot(this.ctx); + e.drawDot(); } } } diff --git a/train.ts b/train.ts index 284f17d..308243a 100644 --- a/train.ts +++ b/train.ts @@ -1,6 +1,6 @@ import { drawLine } from "./drawing/line.ts"; import { ComplexPath, PathSegment } from "./math/path.ts"; -import { Vector } from "./math/vector.ts"; +import { Vector } from "doodler"; import { Follower } from "./physics/follower.ts"; import { Mover } from "./physics/mover.ts"; import { Track } from "./track.ts";