diff options
Diffstat (limited to 'site/pure.mjs')
| -rw-r--r-- | site/pure.mjs | 262 |
1 files changed, 262 insertions, 0 deletions
diff --git a/site/pure.mjs b/site/pure.mjs new file mode 100644 index 0000000..43f7213 --- /dev/null +++ b/site/pure.mjs @@ -0,0 +1,262 @@ +const NUM_POINTS = 40; + +// thanks vihart +const TAU = 2 * Math.PI; + +// no more than 1% of world size per tick. +const MAX_SPEED = 0.01; + +// size of rendered points in proportion to canvas. +const POINT_RADIUS = 0.01; + +class Point { + x = Math.random(); + y = Math.random(); + #heading = Math.random() * TAU; + speed = Math.random() * MAX_SPEED; + color = randColor(); + + constructor(x = Math.random(), y = Math.random()) { + this.x = x; + this.y = y; + } + + equalPos(other) { + return this.x === other.x && this.y === other.y; + } + + dot(other) { + return this.x * other.x + this.y * other.y; + } + + get mag() { + return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2)); + } + + #speedX; + get speedX() { + if (this.#speedX === undefined) { + this.#speedX = this.speed * Math.cos(this.heading); + } + return this.#speedX; + } + + #speedY; + get speedY() { + if (this.#speedY === undefined) { + this.#speedY = this.speed * Math.sin(this.heading); + } + return this.#speedY; + } + + get heading() { + return this.#heading; + } + + set heading(val) { + this.#heading = val; + this.#speedX = this.#speedY = undefined; + } + + toString() { + return `(${this.x.toFixed(2)}, ${this.y.toFixed(2)})` + } +} + +class Line { + constructor(p1, p2) { + this.p1 = p1; + this.p2 = p2; + } + + get vec() { + return new Point(this.p2.x - this.p1.x, this.p2.y - this.p1.y) + } + + angleBetween(other) { + // console.debug(' angleBetween', this.toString(), other.toString()); + const v1 = this.vec; + const v2 = other.vec; + // console.debug(` dot of ${v1} * ${v2}`, v1.dot(v2)); + // console.debug(` mag of ${v1} * ${v2}`, v1.mag * v2.mag); + const theta = Math.acos(v1.dot(v2) / (v1.mag * v2.mag)); + // console.debug(` theta is`, 360 * theta / TAU); + return theta; + } + angleBetween2(other) { + console.debug('angleBetween', + `(${this.p1.x}, ${this.p1.y})-(${this.p2.x}, ${this.p2.y})`, + `(${other.p1.x}, ${other.p1.y})-(${other.p2.x}, ${other.p2.y})`, + ); + return Math.atan(Math.abs((this.slope - other.slope) / (1 + this.slope * other.slope))) + } + + get slope() { + return (this.p2.y - this.p1.y) / (this.p2.x - this.p1.x); + } + + toString() { + return `${this.p1}-${this.p2}`; + } +} + +// assume points is sorted min first. +function findPoly3(points) { + // console.debug('findPoly()', ...points.map(p => { return {x: p.x, y: p.y} })); + + let lastLine = new Line(new Point(0, 0), new Point(1, 0)); + // console.debug('horiz slope', lastLine.slope); + + // bail out counter. shouldn't be necessary, but who knows. + let xxx = 100; + let res = [points[0]]; + do { + // last result is always a point on the edge. + // const p1 = res[res.length-1]; + // console.debug(`-- checking lines from ${p1}`); + let last = res[res.length-1]; + let minTheta = TAU; + let minP = points[0]; + for (let i = 0; i < points.length; i++) { + if (!last.equalPos(points[i])) { + let l2 = new Line(last, points[i]); + let theta = lastLine.angleBetween(l2); + // console.debug('+++ l2 slope', l2.slope, theta, 360 * theta / TAU); + if (theta < minTheta) { + // console.debug(' -- min angle') + minTheta = theta; + minP = points[i]; + } + } + } + lastLine = new Line(res[res.length-1], minP); + res.push(minP); + // console.debug('term', xxx, res[0].x, res[0].y, res[res.length-1].x, res[res.length-1].y) + } while (xxx-- > 0 && !res[0].equalPos(res[res.length-1])); + if (xxx === 0) { + alert('couldn\'t find your polycule'); + } + + // console.debug('findPoly res', res); + return res; +} + +const points = [...Array(NUM_POINTS)].map(_ => new Point()); +// const points = [ +// new Point(0.5, 0.1), +// new Point(0.75, 0.2), +// new Point(0.9, 0.9), +// new Point(0.2, 0.5), +// new Point(0.6, 0.4), // should be inside +// ]; + +function randColor() { + const [r, g, b] = [...Array(3)].map(_ => Math.random() * 255); + return `rgb(${r} ${g} ${b})` +} + +function renderPoints(ctx, points) { + points.forEach(p => { + ctx.fillStyle = p.color; + ctx.beginPath(); + ctx.arc(p.x, p.y, POINT_RADIUS, 0, TAU); + ctx.fill(); + + ctx.strokeStyle = p.color; + ctx.lineWidth = 0.005; + ctx.beginPath(); + ctx.moveTo(p.x, p.y); + ctx.lineTo(p.x + p.speedX * 3, p.y + p.speedY * 3); + ctx.stroke(); + }); +} + +function movePoints(points) { + points.forEach(p => { + p.x += p.speedX; + p.y += p.speedY; + }); +} + +function bouncePoints(points) { + let didBounce = false; + points.forEach(p => { + const x = p.x + p.speedX; + const y = p.y + p.speedY; + + if (x < 0) { + p.heading = Math.PI - p.heading; + didBounce = true; + } else if (x >= 1) { + p.heading = Math.PI - p.heading; + didBounce = true; + } else if (y < 0) { + p.heading = -1 * p.heading; + didBounce = true; + } else if (y >= 1) { + p.heading = -1 * p.heading; + didBounce = true; + } + }); + return didBounce; +} + +async function loaded() { + const canvas = document.querySelector('canvas'); + const ctx = canvas.getContext('2d'); + console.debug('canvas:', canvas, 'ctx', ctx); + ctx.scale(canvas.width, canvas.height); + + const fps = document.querySelector('#fps'); + + const goButton = document.querySelector('button'); + let paused = true; + goButton.onclick = e => { + paused = !paused; + if (paused) { + e.target.textContent = 'go'; + } else { + e.target.textContent = 'pause'; + self.requestAnimationFrame(render); + } + }; + + let lastTime = document.timeline.currentTime; + let interCount = 0; + function render(t) { + if (t > lastTime) { + fps.textContent = Math.floor(1_000 * interCount / (t - lastTime)); + lastTime = t; + interCount = 0; + } else { + interCount++; + } + + ctx.clearRect(0, 0, canvas.width, canvas.height); + + renderPoints(ctx, points); + if (bouncePoints(points)) { + //goButton.onclick({ target: goButton }); + } + movePoints(points); + + points.sort((a, b) => a.y > b.y); + const polyPoints = findPoly3(points); + + ctx.lineWidth = 0.005; + ctx.beginPath(); + ctx.moveTo(polyPoints[0].x, polyPoints[0].y); + polyPoints.forEach(p => ctx.lineTo(p.x, p.y)); + ctx.strokeStyle = 'blue'; + ctx.stroke(); + + if (!paused) { + self.requestAnimationFrame(render); + } + } + + //goButton.onclick({ target: goButton }); + render(document.timeline.currentTime); +} + +document.addEventListener('DOMContentLoaded', loaded); |
