diff options
author | sanine <sanine.not@pm.me> | 2024-07-05 16:38:44 -0500 |
---|---|---|
committer | sanine <sanine.not@pm.me> | 2024-07-05 16:38:44 -0500 |
commit | b4227d3c206aa122b9f4e562760a568a99dd623e (patch) | |
tree | a2feda9ba9407e754ef9dfbb9069602f97f99292 /main.js |
basic trajectory plotting
Diffstat (limited to 'main.js')
-rw-r--r-- | main.js | 153 |
1 files changed, 153 insertions, 0 deletions
@@ -0,0 +1,153 @@ +const X_START = '8*cos(t)'; +const Y_START = '8*sin(t)'; +const equations = { + x: math.compile(X_START), + y: math.compile(Y_START), +} + +window.onload = () => { + const root = document.getElementById('root'); + + const launchButton = document.createElement('input'); + launchButton.type = 'button'; + launchButton.value = 'Launch'; + const xeq = document.createElement('input'); + xeq.value = X_START; + xeq.onchange = (e) => { + equations.x = math.compile(e.target.value); + running = false; + path = computePath(equations, 0, 100, 0.1); + render(ctx, path, 0); + }; + xeq.onkeydown = xeq.onchange; + xeq.onkeyup = xeq.onchange; + const yeq = document.createElement('input'); + yeq.value = Y_START; + yeq.onchange = (e) => { + equations.y = math.compile(e.target.value); + running = false; + path = computePath(equations, 0, 100, 0.1); + render(ctx, path, 0); + }; + yeq.onkeydown = yeq.onchange; + yeq.onkeyup = yeq.onchange; + + root.appendChild(xeq); + root.appendChild(yeq); + root.appendChild(launchButton); + + const canvas = document.createElement('canvas'); + canvas.width = 600; + canvas.height = 600; + root.appendChild(canvas); + + const ctx = canvas.getContext('2d'); + ctx.translate(canvas.width/2, canvas.height/2) + ctx.scale(canvas.width/20, -canvas.height/20); + let path = computePath(equations, 0, 100, 0.1); + + let running = false; + const step = (index) => { + render(ctx, path, index); + if (running && index < path.length-1) { + setTimeout(() => step(index+1), 10); + } else { + running = false; + render(ctx, path, 0); + } + } + launchButton.onclick = () => { + running = false; + setTimeout(() => { running=true; step(0); }, 20); + } + render(ctx, path, 0); +} + + +function render(ctx, path, index) { + const { width, height } = ctx.canvas; + ctx.fillStyle = 'black'; + ctx.fillRect(-width, -height, 2*width, 2*height); + const [x0, y0] = path[index]; + const [x1, y1] = path[index+1] || path[index]; + const angle = Math.atan2(y1-y0, x1-x0); + drawGrid(ctx); + drawPath(ctx, path); + drawShip(ctx, [x0, y0], angle); +} + + +function drawShip(ctx, pos, angle) { + const transform = ctx.getTransform(); + const { width, height } = ctx.canvas; + const [x, y] = pos; + ctx.translate(x, y); + ctx.rotate(angle); + ctx.fillStyle = 'white'; + const STEP = 120/Math.min(width,height); + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(-STEP, -STEP); + ctx.lineTo(2*STEP, 0); + ctx.lineTo(-STEP, STEP); + ctx.lineTo(0, 0); + ctx.fill(); + ctx.setTransform(transform); +} + + +function computePath(equations, start, end, step) { + const ts = [...Array(Math.floor((end-start)/step)).keys()].map(k => step * (start+k)) + return ts.map(t => [ equations.x.eval({t}), equations.y.eval({t}) ]); +} + +function drawGrid(ctx) { + ctx.scale(1, -1); + ctx.strokeStyle = 'white'; + ctx.fillStyle = 'white'; + const { width, height } = ctx.canvas; + ctx.font = `${200/Math.max(width, height)}px serif`; + ctx.lineWidth = 4 / Math.max(width, height); + const drawLine = (start, end) => { + ctx.beginPath(); + ctx.moveTo(start[0], start[1]); + ctx.lineTo(end[0], end[1]); + ctx.stroke(); + } + const drawXLabel = (x, y) => { + ctx.textAlign = 'right'; + ctx.textBaseline = 'bottom'; + ctx.fillText(`${x} `, x, y); + } + + const drawYLabel = (x, y) => { + ctx.rotate(Math.PI/2); + ctx.textAlign = 'right'; + ctx.textBaseline = 'bottom'; + ctx.fillText(`${y} `, y, x); + ctx.rotate(-Math.PI/2); + } + + const drawGrid = (start, end, step) => { + [...Array(1+Math.floor((end-start)/step)).keys()].forEach(z => { + z = (step*z)+start; + drawLine([z, start], [z, end]); + drawLine([start, z], [end, z]); + drawXLabel(z, end); + drawYLabel(end, z); + }); + } + drawGrid(-10, 10, 2); + ctx.scale(1, -1); +} + +function drawPath(ctx, path) { + const [ start, ...line ] = path; + const { width, height } = ctx.canvas; + ctx.strokeStyle = 'white'; + ctx.lineWidth = 8 / Math.max(width, height); + ctx.beginPath(); + ctx.moveTo(start[0], start[1]); + line.forEach(p => ctx.lineTo(p[0], p[1])); + ctx.stroke(); +} |