Skip to content

Instantly share code, notes, and snippets.

@jaames
Created November 27, 2022 19:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jaames/8567d051c493a8b5a59e3dac39518c10 to your computer and use it in GitHub Desktop.
Save jaames/8567d051c493a8b5a59e3dac39518c10 to your computer and use it in GitHub Desktop.
projective shape tool demo
export const dist = (x1: number, y1: number, x2: number, y2: number) => Math.sqrt(Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2));
export class LineSegment {
x0: number = 0;
y0: number = 0;
x1: number = 0;
y1: number = 0;
constructor (x0: number, y0: number, x1: number, y1: number) {
this.x0 = x0;
this.y0 = y0;
this.x1 = x1;
this.y1 = y1;
}
setStart(x: number, y: number) {
this.x0 = x;
this.y0 = y;
}
setEnd(x: number, y: number) {
this.x1 = x;
this.y1 = y;
}
pointAt(t: number) {
const dx = this.x1 - this.x0;
const dy = this.y1 - this.y0;
return {x: this.x0 + t * dx, y: this.y0 + t * dy};
}
offsetAt(x: number, y: number) {
const dx = this.x1 - this.x0;
const dy = this.y1 - this.y0;
const lenSquared = dx * dx + dy * dy;
if (lenSquared === 0)
return 1;
const t = ((x - this.x0) * dx + (y - this.y0) * dy) / lenSquared;
if (t > 1)
return 1;
if (t < 0)
return 0;
return t;
}
projectPoint(x: number, y: number) {
return this.pointAt(this.offsetAt(x, y));
}
distanceTo(x: number, y: number) {
const local = this.projectPoint(x, y);
return dist(local.x, local.y, x, y);
}
};
export class Canvas {
el: HTMLCanvasElement;
ctx: CanvasRenderingContext2D;
paintingCanvas: HTMLCanvasElement;
paintingCtx: CanvasRenderingContext2D;
lineGuide = new LineSegment(100, 100, 250, 250);
isMouseDown = false;
lastMouseX = -1;
lastMouseY = -1;
currMouseX = -1;
currMouseY = -1;
mouseAction: 'moveLineStart' | 'moveLineEnd' | 'paint';
constructor(parent: Element) {
const canvas = document.createElement('canvas');
const dpi = devicePixelRatio;
canvas.width = window.innerWidth * dpi;
canvas.height = window.innerHeight * dpi;
canvas.style.width = `${ canvas.width / dpi }px`;
canvas.style.height = `${ canvas.height / dpi }px`;
parent.appendChild(canvas);
const painting = document.createElement('canvas');
painting.width = canvas.width;
painting.height = canvas.height;
this.paintingCanvas = painting;
this.paintingCtx = painting.getContext('2d')!;
const ctx = canvas.getContext('2d')!;
ctx.scale(dpi, dpi);
canvas.addEventListener('mousedown', (e) => this.onMouseDown(e.clientX, e.clientY));
canvas.addEventListener('mouseup', (e) => this.onMouseUp(e.clientX, e.clientY));
canvas.addEventListener('mousemove', (e) => this.onMouseMove(e.clientX, e.clientY));
this.el = canvas;
this.ctx = ctx;
this.update();
}
update = () => {
const ctx = this.ctx;
ctx.clearRect(0, 0, this.el.width, this.el.height);
const lineGuide = this.lineGuide;
ctx.lineWidth = 32;
ctx.lineCap = 'round';
ctx.strokeStyle = '#f002';
ctx.beginPath();
ctx.moveTo(lineGuide.x0, lineGuide.y0);
ctx.lineTo(lineGuide.x1, lineGuide.y1);
ctx.stroke();
ctx.drawImage(this.paintingCanvas, 0, 0);
if (!(this.isMouseDown && this.mouseAction === 'paint')) {
ctx.lineWidth = 9;
ctx.strokeStyle = '#fff';
ctx.beginPath();
ctx.arc(lineGuide.x0, lineGuide.y0, 8, 0, 360);
ctx.stroke();
ctx.beginPath();
ctx.arc(lineGuide.x1, lineGuide.y1, 8, 0, 360);
ctx.stroke();
ctx.lineWidth = 3;
ctx.strokeStyle = '#000';
ctx.beginPath();
ctx.arc(lineGuide.x0, lineGuide.y0, 8, 0, 360);
ctx.stroke();
ctx.beginPath();
ctx.arc(lineGuide.x1, lineGuide.y1, 8, 0, 360);
ctx.stroke();
}
if (this.isMouseDown && this.mouseAction === 'paint') {
this.paintingCtx.fillStyle = '#000';
this.paintingCtx.beginPath();
this.paintingCtx.arc(this.currMouseX, this.currMouseY, 20, 0, 360);
this.paintingCtx.fill();
this.lastMouseX = this.currMouseX;
this.lastMouseY = this.currMouseY;
}
requestAnimationFrame(this.update);
}
paintAt(mx: number, my: number) {
const {x, y} = this.lineGuide.projectPoint(mx, my);
this.currMouseX = x;
this.currMouseY = y;
}
onMouseDown(x: number, y: number) {
this.isMouseDown = true;
if (dist(x, y, this.lineGuide.x0, this.lineGuide.y0) < 40)
this.mouseAction = 'moveLineStart';
else if (dist(x, y, this.lineGuide.x1, this.lineGuide.y1) < 40)
this.mouseAction = 'moveLineEnd';
else {
this.mouseAction = 'paint';
this.paintAt(x, y);
}
}
onMouseUp(x: number, y: number) {
this.isMouseDown = false;
this.paintAt(x, y);
}
onMouseMove(x: number, y: number) {
if (!this.isMouseDown)
return;
if (this.mouseAction === 'moveLineStart')
this.lineGuide.setStart(x, y);
else if (this.mouseAction === 'moveLineEnd')
this.lineGuide.setEnd(x, y);
else
this.paintAt(x, y);
}
}
new Canvas(document.body);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment