Fixes circular SAT

This commit is contained in:
Emmaline Autumn 2023-11-05 01:41:21 -06:00
parent e70787260a
commit da77aa10bb
7 changed files with 174 additions and 104 deletions

152
bundle.js
View File

@ -707,7 +707,7 @@ class Doodler {
document.body.append(canvas); document.body.append(canvas);
} }
this.bg = bg || "white"; this.bg = bg || "white";
this.framerate = framerate || 60; this.framerate = framerate;
canvas.width = fillScreen ? document.body.clientWidth : width; canvas.width = fillScreen ? document.body.clientWidth : width;
canvas.height = fillScreen ? document.body.clientHeight : height; canvas.height = fillScreen ? document.body.clientHeight : height;
if (fillScreen) { if (fillScreen) {
@ -735,10 +735,17 @@ class Doodler {
lastFrameAt = 0; lastFrameAt = 0;
startDrawLoop() { startDrawLoop() {
this.lastFrameAt = Date.now(); this.lastFrameAt = Date.now();
this.timer = setInterval(()=>this.draw(), 1000 / this.framerate); if (this.framerate) {
this.timer = setInterval(()=>this.draw(Date.now()), 1000 / this.framerate);
} else {
const cb = (t)=>{
this.draw(t);
requestAnimationFrame(cb);
};
requestAnimationFrame(cb);
}
} }
draw() { draw(time) {
const time = Date.now();
const frameTime = time - this.lastFrameAt; const frameTime = time - this.lastFrameAt;
this.ctx.clearRect(0, 0, this.width, this.height); this.ctx.clearRect(0, 0, this.width, this.height);
this.ctx.fillStyle = this.bg; this.ctx.fillStyle = this.bg;
@ -1033,6 +1040,7 @@ class ZoomableDoodler extends Doodler {
y: 0 y: 0
}; };
maxScale = 4; maxScale = 4;
minScale = 1;
constructor(options, postInit){ constructor(options, postInit){
super(options, postInit); super(options, postInit);
this._canvas.addEventListener("wheel", (e)=>{ this._canvas.addEventListener("wheel", (e)=>{
@ -1180,7 +1188,7 @@ class ZoomableDoodler extends Doodler {
}, scaleBy); }, scaleBy);
} }
scaleAt(p, scaleBy) { scaleAt(p, scaleBy) {
this.scale = Math.min(Math.max(this.scale * scaleBy, 1), this.maxScale); this.scale = Math.min(Math.max(this.scale * scaleBy, this.minScale), this.maxScale);
this.origin.x = p.x - (p.x - this.origin.x) * scaleBy; this.origin.x = p.x - (p.x - this.origin.x) * scaleBy;
this.origin.y = p.y - (p.y - this.origin.y) * scaleBy; this.origin.y = p.y - (p.y - this.origin.y) * scaleBy;
this.constrainOrigin(); this.constrainOrigin();
@ -1205,12 +1213,12 @@ class ZoomableDoodler extends Doodler {
this.origin.x = Math.min(Math.max(this.origin.x, -this._canvas.width * this.scale + this._canvas.width), 0); this.origin.x = Math.min(Math.max(this.origin.x, -this._canvas.width * this.scale + this._canvas.width), 0);
this.origin.y = Math.min(Math.max(this.origin.y, -this._canvas.height * this.scale + this._canvas.height), 0); this.origin.y = Math.min(Math.max(this.origin.y, -this._canvas.height * this.scale + this._canvas.height), 0);
} }
draw() { draw(time) {
this.ctx.setTransform(this.scale, 0, 0, this.scale, this.origin.x, this.origin.y); this.ctx.setTransform(this.scale, 0, 0, this.scale, this.origin.x, this.origin.y);
this.animateZoom(); this.animateZoom();
this.ctx.fillStyle = this.bg; this.ctx.fillStyle = this.bg;
this.ctx.fillRect(0, 0, this.width / this.scale, this.height / this.scale); this.ctx.fillRect(0, 0, this.width / this.scale, this.height / this.scale);
super.draw(); super.draw(time);
} }
getTouchOffset(p) { getTouchOffset(p) {
const { x, y } = this._canvas.getBoundingClientRect(); const { x, y } = this._canvas.getBoundingClientRect();
@ -1269,54 +1277,6 @@ const init = (opt, zoomable, postInit)=>{
window.doodler = zoomable ? new ZoomableDoodler(opt, postInit) : new Doodler(opt, postInit); window.doodler = zoomable ? new ZoomableDoodler(opt, postInit) : new Doodler(opt, postInit);
window.doodler.init(); window.doodler.init();
}; };
function satCollisionSpline(p, spline) {
for(let i = 0; i < 100; i++){
const t1 = i / 100;
const t2 = (i + 1) / 100;
const segmentStart = spline.getPointAtT(t1);
const segmentEnd = spline.getPointAtT(t2);
if (segmentIntersectsPolygon(p, segmentStart, segmentEnd)) {
return true;
}
}
return false;
}
function segmentIntersectsPolygon(p, start, end) {
const edges = p.getEdges();
for (const edge of edges){
const axis = edge.copy().normal().normalize();
const proj1 = projectPolygonOntoAxis(p, axis);
const proj2 = projectSegmentOntoAxis(start, end, axis);
if (!overlap(proj1, proj2)) {
return false;
}
}
return true;
}
function projectPolygonOntoAxis(p, axis) {
let min = Infinity;
let max = -Infinity;
for (const point of p.points){
const dotProduct = point.copy().add(p.center).dot(axis);
min = Math.min(min, dotProduct);
max = Math.max(max, dotProduct);
}
return {
min,
max
};
}
function projectSegmentOntoAxis(start, end, axis) {
const dotProductStart = start.dot(axis);
const dotProductEnd = end.dot(axis);
return {
min: Math.min(dotProductStart, dotProductEnd),
max: Math.max(dotProductStart, dotProductEnd)
};
}
function overlap(proj1, proj2) {
return proj1.min <= proj2.max && proj1.max >= proj2.min;
}
class Polygon { class Polygon {
points; points;
center; center;
@ -1342,7 +1302,12 @@ class Polygon {
center.div(this.points.length); center.div(this.points.length);
return center; return center;
} }
get circularHitbox() { _circularBoundingBox;
get circularBoundingBox() {
this._circularBoundingBox = this.calculateCircularBoundingBox();
return this._circularBoundingBox;
}
calculateCircularBoundingBox() {
let greatestDistance = 0; let greatestDistance = 0;
for (const p of this.points){ for (const p of this.points){
greatestDistance = Math.max(p.copy().add(this.center).dist(this.center), greatestDistance); greatestDistance = Math.max(p.copy().add(this.center).dist(this.center), greatestDistance);
@ -1354,9 +1319,7 @@ class Polygon {
} }
_aabb; _aabb;
get AABB() { get AABB() {
if (!this._aabb) { this._aabb = this.recalculateAABB();
this._aabb = this.recalculateAABB();
}
return this._aabb; return this._aabb;
} }
recalculateAABB() { recalculateAABB() {
@ -1371,8 +1334,8 @@ class Polygon {
biggestY = Math.max(temp.y, biggestY); biggestY = Math.max(temp.y, biggestY);
} }
return { return {
x: smallestX, x: smallestX + this.center.x,
y: smallestY, y: smallestY + this.center.y,
w: biggestX - smallestX, w: biggestX - smallestX,
h: biggestY - smallestY h: biggestY - smallestY
}; };
@ -1407,9 +1370,49 @@ class Polygon {
for (const point of this.points){ for (const point of this.points){
if (p.dist(point) < p.dist(nearest)) nearest = point; if (p.dist(point) < p.dist(nearest)) nearest = point;
} }
return nearest; return nearest.copy().add(this.center);
} }
} }
function satCollisionCircle(p, circle) {
for (const edge of p.getEdges()){
const axis = edge.copy().normal().normalize();
const proj1 = projectPolygonOntoAxis(p, axis);
const proj2 = projectCircleOntoAxis(circle, axis);
if (!overlap(proj1, proj2)) return false;
}
const center = new Vector(circle.center);
const nearest = p.getNearestPoint(center);
const axis = nearest.copy().sub(center).normalize();
const proj1 = projectPolygonOntoAxis(p, axis);
const proj2 = projectCircleOntoAxis(circle, axis);
if (!overlap(proj1, proj2)) return false;
return true;
}
function projectPolygonOntoAxis(p, axis) {
let min = Infinity;
let max = -Infinity;
for (const point of p.points){
const dotProduct = point.copy().add(p.center).dot(axis);
min = Math.min(min, dotProduct);
max = Math.max(max, dotProduct);
}
return {
min,
max
};
}
function projectCircleOntoAxis(c, axis) {
const dot = new Vector(c.center).dot(axis);
const min = dot - c.radius;
const max = dot + c.radius;
return {
min,
max
};
}
function overlap(proj1, proj2) {
return proj1.min <= proj2.max && proj1.max >= proj2.min;
}
class SplineSegment { class SplineSegment {
points; points;
length; length;
@ -1594,10 +1597,11 @@ init({
}, true, (ctx)=>{ }, true, (ctx)=>{
ctx.imageSmoothingEnabled = false; ctx.imageSmoothingEnabled = false;
}); });
doodler.minScale = .1;
const img = new Image(); const img = new Image();
img.src = "./pixel fire.gif"; img.src = "./pixel fire.gif";
const p = new Vector(); const p = new Vector(500, 500);
const gif = new GIFAnimation("./fire-joypixels.gif", p, .5); new GIFAnimation("./fire-joypixels.gif", p, .5);
const spline = new SplineSegment([ const spline = new SplineSegment([
new Vector({ new Vector({
x: -25, x: -25,
@ -1616,10 +1620,11 @@ const spline = new SplineSegment([
y: 25 y: 25
}).mult(10).add(p) }).mult(10).add(p)
]); ]);
const poly = Polygon.createPolygon(4);
const poly2 = Polygon.createPolygon(4); const poly2 = Polygon.createPolygon(4);
poly2.center = p.copy().add(100, 100); poly2.center = p.copy().add(100, 100);
doodler.createLayer((c, i, t)=>{ doodler.createLayer((c, i, t)=>{
gif.draw(t); c.translate(500, 500);
for(let i = 0; i < c.canvas.width; i += 50){ for(let i = 0; i < c.canvas.width; i += 50){
for(let j = 0; j < c.canvas.height; j += 50){ for(let j = 0; j < c.canvas.height; j += 50){
doodler.drawSquare(new Vector(i, j), 50, { doodler.drawSquare(new Vector(i, j), 50, {
@ -1627,19 +1632,22 @@ doodler.createLayer((c, i, t)=>{
}); });
} }
} }
poly2.circularHitbox; const intersects = satCollisionCircle(poly, poly2.circularBoundingBox);
const intersects = satCollisionSpline(poly2, spline);
const color = intersects ? "red" : "aqua"; const color = intersects ? "red" : "aqua";
spline.draw(color); spline.draw(color);
poly.draw(color);
poly2.draw(color); poly2.draw(color);
const [gamepad] = navigator.getGamepads(); const [gamepad] = navigator.getGamepads();
if (gamepad) { if (gamepad) {
gamepad.axes[0]; const leftX = gamepad.axes[0];
gamepad.axes[1]; const leftY = gamepad.axes[1];
const rightX = gamepad.axes[2]; const rightX = gamepad.axes[2];
const rightY = gamepad.axes[3]; const rightY = gamepad.axes[3];
let mMulti = 10; let lMulti = 10;
const mod = new Vector(Math.min(Math.max(rightX - 0.05, 0), rightX + 0.05), Math.min(Math.max(rightY - 0.05, 0), rightY + 0.05)); const lMod = new Vector(Math.min(Math.max(leftX - 0.05, 0), leftX + 0.05), Math.min(Math.max(leftY - 0.05, 0), leftY + 0.05));
poly2.center.add(mod.mult(mMulti)); poly.center.add(lMod.mult(lMulti));
let rMulti = 10;
const rMod = new Vector(Math.min(Math.max(rightX - 0.05, 0), rightX + 0.05), Math.min(Math.max(rightY - 0.05, 0), rightY + 0.05));
poly2.center.add(rMod.mult(rMulti));
} }
}); });

View File

@ -49,7 +49,7 @@ export class Doodler {
private layers: layer[] = []; private layers: layer[] = [];
protected bg: string; protected bg: string;
private framerate: number; private framerate?: number;
get width() { get width() {
return this.ctx.canvas.width; return this.ctx.canvas.width;
@ -77,7 +77,7 @@ export class Doodler {
} }
this.bg = bg || "white"; this.bg = bg || "white";
this.framerate = framerate || 60; this.framerate = framerate;
canvas.width = fillScreen ? document.body.clientWidth : width; canvas.width = fillScreen ? document.body.clientWidth : width;
canvas.height = fillScreen ? document.body.clientHeight : height; canvas.height = fillScreen ? document.body.clientHeight : height;
@ -113,11 +113,21 @@ export class Doodler {
private lastFrameAt = 0; private lastFrameAt = 0;
private startDrawLoop() { private startDrawLoop() {
this.lastFrameAt = Date.now(); this.lastFrameAt = Date.now();
this.timer = setInterval(() => this.draw(), 1000 / this.framerate); if (this.framerate) {
this.timer = setInterval(
() => this.draw(Date.now()),
1000 / this.framerate,
);
} else {
const cb = (t: number) => {
this.draw(t);
requestAnimationFrame(cb);
};
requestAnimationFrame(cb);
}
} }
protected draw() { protected draw(time: number) {
const time = Date.now();
const frameTime = time - this.lastFrameAt; const frameTime = time - this.lastFrameAt;
this.ctx.clearRect(0, 0, this.width, this.height); this.ctx.clearRect(0, 0, this.width, this.height);
this.ctx.fillStyle = this.bg; this.ctx.fillStyle = this.bg;
@ -254,6 +264,13 @@ export class Doodler {
: this.ctx.drawImage(img, at.x, at.y); : this.ctx.drawImage(img, at.x, at.y);
} }
/**
* @description This method is VERY expensive and should be used sparingly - O(n^2) where n is weight. Beyond that, it doesn't work with transparency correctly since the image is overlaid multiple times in drawing and the resulting transparency is dependent on the weight provided
*
* @param img
* @param at
* @param style
*/
drawImageWithOutline(img: HTMLImageElement, at: Vector, style?: IStyle): void; drawImageWithOutline(img: HTMLImageElement, at: Vector, style?: IStyle): void;
drawImageWithOutline( drawImageWithOutline(
img: HTMLImageElement, img: HTMLImageElement,

View File

@ -1,6 +1,7 @@
import { Polygon } from "../geometry/polygon.ts"; import { Polygon } from "../geometry/polygon.ts";
import { SplineSegment } from "../geometry/spline.ts"; import { SplineSegment } from "../geometry/spline.ts";
import { Vector } from "../geometry/vector.ts"; import { Vector } from "../geometry/vector.ts";
import { axisAlignedBoundingBox } from "./aa.ts";
import { CircleLike } from "./circular.ts"; import { CircleLike } from "./circular.ts";
export function satCollisionSpline(p: Polygon, spline: SplineSegment): boolean { export function satCollisionSpline(p: Polygon, spline: SplineSegment): boolean {
@ -48,13 +49,25 @@ export function satCollisionCircle(p: Polygon, circle: CircleLike): boolean {
} }
const center = new Vector(circle.center); const center = new Vector(circle.center);
const nearest = p.getNearestPoint(center); const nearest = p.getNearestPoint(center);
const axis = nearest.copy().normal(center).normalize(); const axis = nearest.copy().sub(center).normalize();
const proj1 = projectPolygonOntoAxis(p, axis); const proj1 = projectPolygonOntoAxis(p, axis);
const proj2 = projectCircleOntoAxis(circle, axis); const proj2 = projectCircleOntoAxis(circle, axis);
if (!overlap(proj1, proj2)) return false; if (!overlap(proj1, proj2)) return false;
return true; return true;
} }
export function satCollisionAABBCircle(
aabb: axisAlignedBoundingBox,
circle: CircleLike,
): boolean {
const p = new Polygon([
{ x: aabb.x, y: aabb.y },
{ x: aabb.x + aabb.w, y: aabb.y },
{ x: aabb.x + aabb.w, y: aabb.y + aabb.h },
{ x: aabb.x, y: aabb.y + aabb.h },
]);
return satCollisionCircle(p, circle);
}
function segmentIntersectsPolygon( function segmentIntersectsPolygon(
p: Polygon, p: Polygon,

View File

@ -33,7 +33,14 @@ export class Polygon {
return center; return center;
} }
get circularHitbox(): CircleLike { _circularBoundingBox?: CircleLike;
get circularBoundingBox(): CircleLike {
this._circularBoundingBox = this.calculateCircularBoundingBox();
return this._circularBoundingBox;
}
private calculateCircularBoundingBox() {
let greatestDistance = 0; let greatestDistance = 0;
for (const p of this.points) { for (const p of this.points) {
greatestDistance = Math.max( greatestDistance = Math.max(
@ -50,13 +57,11 @@ export class Polygon {
_aabb?: axisAlignedBoundingBox; _aabb?: axisAlignedBoundingBox;
get AABB(): axisAlignedBoundingBox { get AABB(): axisAlignedBoundingBox {
if (!this._aabb) { this._aabb = this.recalculateAABB();
this._aabb = this.recalculateAABB();
}
return this._aabb; return this._aabb;
} }
recalculateAABB(): axisAlignedBoundingBox { private recalculateAABB(): axisAlignedBoundingBox {
let smallestX, biggestX, smallestY, biggestY; let smallestX, biggestX, smallestY, biggestY;
smallestX = smallestX =
smallestY = smallestY =
@ -74,8 +79,8 @@ export class Polygon {
} }
return { return {
x: smallestX, x: smallestX + this.center.x,
y: smallestY, y: smallestY + this.center.y,
w: biggestX - smallestX, w: biggestX - smallestX,
h: biggestY - smallestY, h: biggestY - smallestY,
}; };
@ -118,6 +123,6 @@ export class Polygon {
for (const point of this.points) { for (const point of this.points) {
if (p.dist(point) < p.dist(nearest)) nearest = point; if (p.dist(point) < p.dist(nearest)) nearest = point;
} }
return nearest; return nearest.copy().add(this.center);
} }
} }

View File

@ -8,7 +8,7 @@
<title>Doodler</title> <title>Doodler</title>
<style> <style>
* { * {
image-rendering: pixelated; /* image-rendering: pixelated; */
margin: 0; margin: 0;
} }

51
main.ts
View File

@ -7,7 +7,11 @@ import { handleGIF } from "./processing/gif.ts";
import { ZoomableDoodler } from "./zoomableCanvas.ts"; import { ZoomableDoodler } from "./zoomableCanvas.ts";
import { axisAlignedCollision, axisAlignedContains } from "./collision/aa.ts"; import { axisAlignedCollision, axisAlignedContains } from "./collision/aa.ts";
import { circularCollision } from "./collision/circular.ts"; import { circularCollision } from "./collision/circular.ts";
import { satCollisionSpline } from "./collision/sat.ts"; import {
satCollisionAABBCircle,
satCollisionCircle,
satCollisionSpline,
} from "./collision/sat.ts";
import { Polygon } from "./geometry/polygon.ts"; import { Polygon } from "./geometry/polygon.ts";
import { SplineSegment } from "./geometry/spline.ts"; import { SplineSegment } from "./geometry/spline.ts";
// import { ZoomableDoodler } from "./zoomableCanvas.ts"; // import { ZoomableDoodler } from "./zoomableCanvas.ts";
@ -22,10 +26,11 @@ initializeDoodler(
true, true,
(ctx) => { (ctx) => {
ctx.imageSmoothingEnabled = false; ctx.imageSmoothingEnabled = false;
// ctx.translate(1200, 600);
}, },
); );
(doodler as ZoomableDoodler).minScale = .1;
// const movingVector = new Vector(100, 300); // const movingVector = new Vector(100, 300);
// let angleMultiplier = 0; // let angleMultiplier = 0;
// const v = new Vector(30, 30); // const v = new Vector(30, 30);
@ -33,7 +38,7 @@ initializeDoodler(
const img = new Image(); const img = new Image();
img.src = "./pixel fire.gif"; img.src = "./pixel fire.gif";
const p = new Vector(); const p = new Vector(500, 500);
const gif = new GIFAnimation("./fire-joypixels.gif", p, .5); const gif = new GIFAnimation("./fire-joypixels.gif", p, .5);
const spline = new SplineSegment([ const spline = new SplineSegment([
@ -44,25 +49,26 @@ const spline = new SplineSegment([
]); ]);
// poly.center = p.copy(); // poly.center = p.copy();
const poly = Polygon.createPolygon(4);
const poly2 = Polygon.createPolygon(4); const poly2 = Polygon.createPolygon(4);
poly2.center = p.copy().add(100, 100); poly2.center = p.copy().add(100, 100);
// poly.center.add(p); // poly.center.add(p);
doodler.createLayer((c, i, t) => { doodler.createLayer((c, i, t) => {
gif.draw(t); // gif.draw(t);
// c.translate(1200, 600); c.translate(500, 500);
for (let i = 0; i < c.canvas.width; i += 50) { for (let i = 0; i < c.canvas.width; i += 50) {
for (let j = 0; j < c.canvas.height; j += 50) { for (let j = 0; j < c.canvas.height; j += 50) {
doodler.drawSquare(new Vector(i, j), 50, { color: "#00000010" }); doodler.drawSquare(new Vector(i, j), 50, { color: "#00000010" });
} }
} }
const cir = poly2.circularHitbox; // const cir = poly2.circularHitbox;
// const t = spline.getPointsWithinRadius( // const t = spline.getPointsWithinRadius(
// new Vector(cir.center), // new Vector(cir.center),
// cir.radius, // cir.radius,
// ).map((t) => t[0]); // ).map((t) => t[0]);
const intersects = satCollisionSpline(poly2, spline); const intersects = satCollisionCircle(poly, poly2.circularBoundingBox);
const color = intersects ? "red" : "aqua"; const color = intersects ? "red" : "aqua";
// const point = spline.getPointAtT(t || 0); // const point = spline.getPointAtT(t || 0);
@ -80,6 +86,7 @@ doodler.createLayer((c, i, t) => {
spline.draw(color); spline.draw(color);
poly.draw(color);
poly2.draw(color); poly2.draw(color);
// poly2.center.add(Vector.random2D()); // poly2.center.add(Vector.random2D());
@ -112,21 +119,37 @@ doodler.createLayer((c, i, t) => {
// Math.min(Math.max(leftY - deadzone, 0), leftY + deadzone), // Math.min(Math.max(leftY - deadzone, 0), leftY + deadzone),
// ).mult(10), // ).mult(10),
// ); // );
let mMulti = 10; let lMulti = 10;
const mod = new Vector( const lMod = new Vector(
Math.min(Math.max(leftX - deadzone, 0), leftX + deadzone),
Math.min(Math.max(leftY - deadzone, 0), leftY + deadzone),
);
// let future = new Vector(cir.center).add(mod.copy().mult(lMulti--));
// while (spline.intersectsCircle(future, cir.radius)) {
// // if (lMulti === 0) {
// // lMulti = 1;
// // break;
// // }
// future = new Vector(cir.center).add(mod.copy().mult(lMulti--));
// }
poly.center.add(
lMod.mult(lMulti),
);
let rMulti = 10;
const rMod = new Vector(
Math.min(Math.max(rightX - deadzone, 0), rightX + deadzone), Math.min(Math.max(rightX - deadzone, 0), rightX + deadzone),
Math.min(Math.max(rightY - deadzone, 0), rightY + deadzone), Math.min(Math.max(rightY - deadzone, 0), rightY + deadzone),
); );
// let future = new Vector(cir.center).add(mod.copy().mult(mMulti--)); // let future = new Vector(cir.center).add(mod.copy().mult(rMulti--));
// while (spline.intersectsCircle(future, cir.radius)) { // while (spline.intersectsCircle(future, cir.radius)) {
// // if (mMulti === 0) { // // if (rMulti === 0) {
// // mMulti = 1; // // rMulti = 1;
// // break; // // break;
// // } // // }
// future = new Vector(cir.center).add(mod.copy().mult(mMulti--)); // future = new Vector(cir.center).add(mod.copy().mult(rMulti--));
// } // }
poly2.center.add( poly2.center.add(
mod.mult(mMulti), rMod.mult(rMulti),
); );
// (doodler as ZoomableDoodler).moveOrigin({ x: -rigthX * 5, y: -rigthY * 5 }); // (doodler as ZoomableDoodler).moveOrigin({ x: -rigthX * 5, y: -rigthY * 5 });

View File

@ -28,6 +28,7 @@ export class ZoomableDoodler extends Doodler {
scaleAround: Point = { x: 0, y: 0 }; scaleAround: Point = { x: 0, y: 0 };
maxScale = 4; maxScale = 4;
minScale = 1;
constructor(options: DoodlerOptions, postInit?: postInit) { constructor(options: DoodlerOptions, postInit?: postInit) {
super(options, postInit); super(options, postInit);
@ -190,7 +191,10 @@ export class ZoomableDoodler extends Doodler {
}, scaleBy); }, scaleBy);
} }
scaleAt(p: Point, scaleBy: number) { scaleAt(p: Point, scaleBy: number) {
this.scale = Math.min(Math.max(this.scale * scaleBy, 1), this.maxScale); this.scale = Math.min(
Math.max(this.scale * scaleBy, this.minScale),
this.maxScale,
);
this.origin.x = p.x - (p.x - this.origin.x) * scaleBy; this.origin.x = p.x - (p.x - this.origin.x) * scaleBy;
this.origin.y = p.y - (p.y - this.origin.y) * scaleBy; this.origin.y = p.y - (p.y - this.origin.y) * scaleBy;
this.constrainOrigin(); this.constrainOrigin();
@ -228,7 +232,7 @@ export class ZoomableDoodler extends Doodler {
); );
} }
draw() { draw(time: number) {
this.ctx.setTransform( this.ctx.setTransform(
this.scale, this.scale,
0, 0,
@ -240,7 +244,7 @@ export class ZoomableDoodler extends Doodler {
this.animateZoom(); this.animateZoom();
this.ctx.fillStyle = this.bg; this.ctx.fillStyle = this.bg;
this.ctx.fillRect(0, 0, this.width / this.scale, this.height / this.scale); this.ctx.fillRect(0, 0, this.width / this.scale, this.height / this.scale);
super.draw(); super.draw(time);
} }
getTouchOffset(p: Point) { getTouchOffset(p: Point) {