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