// Main JavaScript for Baseball Projectile Simulator let mainCanvas, trajectoryCanvas; let currentAngle = 45; let currentVelocity = 50; let currentTime = 0; let isPlaying = false; let animationId = null; let shotHistory = []; const gravity = 9.81; function setup() { mainCanvas = createCanvas(600, 400); mainCanvas.parent('main-canvas'); trajectoryCanvas = createGraphics(600, 200); const canvas = document.createElement('canvas'); canvas.width = 600; canvas.height = 200; canvas.id = 'trajectory-display'; document.getElementById('trajectory-canvas').appendChild(canvas); setupControls(); drawTrajectoryPreview(); noLoop(); } function draw() { drawMainSimulation(); drawTrajectoryPreview(); if (isPlaying) { currentTime += 0.05; updateTimeSlider(); if (currentTime >= 20) { isPlaying = false; noLoop(); } else { loop(); } } } function drawMainSimulation() { background(44, 62, 80); fill(139, 195, 74); rect(0, 350, width, 50); const angleRad = currentAngle * Math.PI / 180; const time = currentTime; const posX = currentVelocity * Math.cos(angleRad) * time; const posY = 350 - (currentVelocity * Math.sin(angleRad) * time - 0.5 * gravity * time * time); document.getElementById('pos-x').textContent = posX.toFixed(2) + ' m'; document.getElementById('pos-y').textContent = Math.max(0, 350 - posY).toFixed(2) + ' m'; document.getElementById('vel-x').textContent = (currentVelocity * Math.cos(angleRad)).toFixed(2) + ' m/s'; document.getElementById('vel-y').textContent = (currentVelocity * Math.sin(angleRad) - gravity * time).toFixed(2) + ' m/s'; stroke(255, 255, 255, 100); strokeWeight(1); noFill(); beginShape(); for (let t = 0; t <= time; t += 0.1) { const x = currentVelocity * Math.cos(angleRad) * t; const y = 350 - (currentVelocity * Math.sin(angleRad) * t - 0.5 * gravity * t * t); if (y <= 350) { vertex(x + 50, y); } } endShape(); if (posY <= 350 && posX >= 0) { fill(255, 87, 34); stroke(255); strokeWeight(2); ellipse(posX + 50, posY, 20, 20); stroke(255); strokeWeight(1); line(posX + 50 - 10, posY, posX + 50 + 10, posY); line(posX + 50, posY - 10, posX + 50, posY + 10); } fill(120); rect(40, 340, 20, 10); fill(180); ellipse(50, 350, 30, 30); } function drawTrajectoryPreview() { trajectoryCanvas.background(236, 240, 241); const angleRad = currentAngle * Math.PI / 180; const scaleX = 600 / 200; const scaleY = 200 / 100; trajectoryCanvas.fill(139, 195, 74); trajectoryCanvas.rect(0, 180, 600, 20); const totalTime = (2 * currentVelocity * Math.sin(angleRad)) / gravity; const maxRange = currentVelocity * Math.cos(angleRad) * totalTime; trajectoryCanvas.stroke(41, 128, 185); trajectoryCanvas.strokeWeight(2); trajectoryCanvas.noFill(); trajectoryCanvas.beginShape(); for (let t = 0; t <= totalTime; t += 0.1) { const x = currentVelocity * Math.cos(angleRad) * t; const y = currentVelocity * Math.sin(angleRad) * t - 0.5 * gravity * t * t; if (y >= 0) { const plotX = (x / maxRange) * 500 + 50; const plotY = 180 - (y * scaleY); trajectoryCanvas.vertex(plotX, plotY); } } trajectoryCanvas.endShape(); if (currentTime <= totalTime) { const currentX = currentVelocity * Math.cos(angleRad) * currentTime; const currentY = currentVelocity * Math.sin(angleRad) * currentTime - 0.5 * gravity * currentTime * currentTime; if (currentY >= 0) { const markerX = (currentX / maxRange) * 500 + 50; const markerY = 180 - (currentY * scaleY); trajectoryCanvas.fill(231, 76, 60); trajectoryCanvas.stroke(255); trajectoryCanvas.strokeWeight(2); trajectoryCanvas.ellipse(markerX, markerY, 10, 10); } } trajectoryCanvas.fill(0); trajectoryCanvas.noStroke(); trajectoryCanvas.textAlign(LEFT); trajectoryCanvas.textSize(12); trajectoryCanvas.text(`Max Height: ${(Math.pow(currentVelocity * Math.sin(angleRad), 2) / (2 * gravity)).toFixed(1)}m`, 10, 20); trajectoryCanvas.text(`Total Range: ${maxRange.toFixed(1)}m`, 10, 40); trajectoryCanvas.text(`Flight Time: ${totalTime.toFixed(1)}s`, 10, 60); const displayCanvas = document.getElementById('trajectory-display'); const ctx = displayCanvas.getContext('2d'); ctx.clearRect(0, 0, 600, 200); ctx.drawImage(trajectoryCanvas.elt, 0, 0); } function setupControls() { const angleSlider = document.getElementById('angle'); const velocitySlider = document.getElementById('velocity'); const timeSlider = document.getElementById('time'); angleSlider.addEventListener('input', (e) => { currentAngle = parseInt(e.target.value); document.getElementById('angle-value').textContent = currentAngle + '°'; currentTime = 0; updateTimeSlider(); redraw(); }); velocitySlider.addEventListener('input', (e) => { currentVelocity = parseInt(e.target.value); document.getElementById('velocity-value').textContent = currentVelocity + ' m/s'; currentTime = 0; updateTimeSlider(); redraw(); }); timeSlider.addEventListener('input', (e) => { currentTime = parseFloat(e.target.value); document.getElementById('time-value').textContent = currentTime.toFixed(1) + ' s'; isPlaying = false; redraw(); }); document.getElementById('play-btn').addEventListener('click', () => { isPlaying = true; loop(); }); document.getElementById('pause-btn').addEventListener('click', () => { isPlaying = false; noLoop(); }); document.getElementById('reset-btn').addEventListener('click', () => { currentTime = 0; isPlaying = false; updateTimeSlider(); noLoop(); redraw(); }); document.getElementById('shoot-btn').addEventListener('click', () => { addToHistory(); redraw(); }); } function updateTimeSlider() { const timeSlider = document.getElementById('time'); const timeValue = document.getElementById('time-value'); timeSlider.value = currentTime; timeValue.textContent = currentTime.toFixed(1) + ' s'; } function addToHistory() { const angleRad = currentAngle * Math.PI / 180; const totalTime = (2 * currentVelocity * Math.sin(angleRad)) / gravity; const maxRange = currentVelocity * Math.cos(angleRad) * totalTime; const maxHeight = Math.pow(currentVelocity * Math.sin(angleRad), 2) / (2 * gravity); const shot = { angle: currentAngle, velocity: currentVelocity, timestamp: new Date().toLocaleTimeString(), maxHeight: maxHeight.toFixed(1), totalRange: maxRange.toFixed(1) }; shotHistory.unshift(shot); updateHistoryList(); } function updateHistoryList() { const historyList = document.getElementById('history-list'); historyList.innerHTML = ''; shotHistory.forEach((shot, index) => { const item = document.createElement('div'); item.className = 'history-item'; item.innerHTML = ` Shot ${shotHistory.length - index} | Angle: ${shot.angle}° | Velocity: ${shot.velocity} m/s | Max Height: ${shot.maxHeight} m | Range: ${shot.totalRange} m | ${shot.timestamp} `; item.addEventListener('click', () => { currentAngle = shot.angle; currentVelocity = shot.velocity; currentTime = 0; updateTimeSlider(); document.getElementById('angle').value = currentAngle; document.getElementById('velocity').value = currentVelocity; document.getElementById('angle-value').textContent = currentAngle + '°'; document.getElementById('velocity-value').textContent = currentVelocity + ' m/s'; redraw(); }); historyList.appendChild(item); }); }