Created
November 27, 2022 19:49
-
-
Save jaames/8567d051c493a8b5a59e3dac39518c10 to your computer and use it in GitHub Desktop.
projective shape tool demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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