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(); }