diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..6f3a291 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "liveServer.settings.port": 5501 +} \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..afaa5b6 --- /dev/null +++ b/index.html @@ -0,0 +1,67 @@ + + + + + + Projectile Motion Simulator + + + +
+

Projectile Motion Simulator

+ +
+
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+
+

Initial Velocity

+

v₀ = 0 m/s

+
+
+

Maximum Height

+

h_max = 0 m

+
+
+

Time of Flight

+

t = 0 s

+
+
+

Range

+

R = 0 m

+
+
+ +
+
+ + Projectile +
+
+ + Velocity Vectors +
+
+ + +
+
+ + + + + \ No newline at end of file diff --git a/javascript.svg b/javascript.svg new file mode 100644 index 0000000..f9abb2b --- /dev/null +++ b/javascript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/projectile.js b/projectile.js new file mode 100644 index 0000000..677f650 --- /dev/null +++ b/projectile.js @@ -0,0 +1,53 @@ +const g = 9.81; // Acceleration due to gravity (m/s²) + +class ProjectileMotion { + constructor(mass, angle, force) { + this.mass = mass; + this.angle = angle * (Math.PI / 180); // Convert to radians + this.force = force; + this.initialVelocity = this.force / this.mass; + } + + calculateInitialVelocities() { + return { + vx: this.initialVelocity * Math.cos(this.angle), + vy: this.initialVelocity * Math.sin(this.angle) + }; + } + + calculateMaxHeight() { + const { vy } = this.calculateInitialVelocities(); + return (vy * vy) / (2 * g); + } + + calculateTimeOfFlight() { + const { vy } = this.calculateInitialVelocities(); + return (2 * vy) / g; + } + + calculateRange() { + const { vx } = this.calculateInitialVelocities(); + return vx * this.calculateTimeOfFlight(); + } + + getPositionAtTime(t) { + const { vx, vy } = this.calculateInitialVelocities(); + return { + x: vx * t, + y: vy * t - (0.5 * g * t * t) + }; + } + + getTrajectoryPoints() { + const points = []; + const totalTime = this.calculateTimeOfFlight(); + const steps = 100; + const dt = totalTime / steps; + + for (let t = 0; t <= totalTime; t += dt) { + points.push(this.getPositionAtTime(t)); + } + + return points; + } +} \ No newline at end of file diff --git a/script.js b/script.js new file mode 100644 index 0000000..7581e40 --- /dev/null +++ b/script.js @@ -0,0 +1,24 @@ +// Initialize visualizer +const visualizer = new ProjectileVisualizer('trajectory'); + +// Setup event listeners +document.getElementById('calculate').addEventListener('click', () => { + const mass = parseFloat(document.getElementById('mass').value); + const angle = parseFloat(document.getElementById('angle').value); + const force = parseFloat(document.getElementById('force').value); + + const projectile = new ProjectileMotion(mass, angle, force); + + // Update results + document.getElementById('initial-velocity').textContent = + projectile.initialVelocity.toFixed(2); + document.getElementById('max-height').textContent = + projectile.calculateMaxHeight().toFixed(2); + document.getElementById('time-of-flight').textContent = + projectile.calculateTimeOfFlight().toFixed(2); + document.getElementById('range').textContent = + projectile.calculateRange().toFixed(2); + + // Start animation + visualizer.animate(projectile); +}); \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..45e5119 --- /dev/null +++ b/style.css @@ -0,0 +1,128 @@ +:root { + --primary-color: #2563eb; + --background-color: #f8fafc; + --text-color: #1e293b; + --border-color: #e2e8f0; + } + + body { + margin: 0; + font-family: system-ui, -apple-system, sans-serif; + background-color: var(--background-color); + color: var(--text-color); + line-height: 1.5; + } + + .container { + max-width: 1200px; + margin: 0 auto; + padding: 2rem; + } + + h1 { + text-align: center; + color: var(--primary-color); + margin-bottom: 2rem; + } + + .simulator { + background: white; + border-radius: 1rem; + padding: 2rem; + box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1); + } + + .inputs { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1.5rem; + margin-bottom: 2rem; + } + + .input-group { + display: flex; + flex-direction: column; + gap: 0.5rem; + } + + label { + font-weight: 500; + color: var(--text-color); + } + + input { + padding: 0.5rem; + border: 1px solid var(--border-color); + border-radius: 0.5rem; + font-size: 1rem; + } + + button { + background-color: var(--primary-color); + color: white; + border: none; + padding: 0.75rem 1.5rem; + border-radius: 0.5rem; + cursor: pointer; + font-size: 1rem; + font-weight: 500; + transition: background-color 0.2s; + } + + button:hover { + background-color: #1d4ed8; + } + + .results { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1.5rem; + margin-bottom: 2rem; + padding: 1rem; + background-color: var(--background-color); + border-radius: 0.5rem; + } + + .result-group { + text-align: center; + } + + .result-group h3 { + margin: 0; + color: var(--primary-color); + font-size: 1.1rem; + } + + .result-group p { + margin: 0.5rem 0 0 0; + font-size: 1.1rem; + font-weight: 500; + } + + .legend { + display: flex; + gap: 2rem; + justify-content: center; + margin-bottom: 1rem; + } + + .legend-item { + display: flex; + align-items: center; + gap: 0.5rem; + } + + .legend-color { + width: 20px; + height: 20px; + border-radius: 50%; + } + + canvas { + width: 100%; + height: 400px; + border: 1px solid var(--border-color); + border-radius: 0.5rem; + margin-top: 1rem; + background-color: white; + } \ No newline at end of file diff --git a/visulize.js b/visulize.js new file mode 100644 index 0000000..9e10add --- /dev/null +++ b/visulize.js @@ -0,0 +1,154 @@ +class ProjectileVisualizer { + constructor(canvasId) { + this.canvas = document.getElementById(canvasId); + this.ctx = this.canvas.getContext('2d'); + this.setCanvasSize(); + this.animationId = null; + this.currentTime = 0; + this.projectile = null; + this.scale = 1; + this.padding = 20; + window.addEventListener('resize', () => this.setCanvasSize()); + } + + setCanvasSize() { + this.canvas.width = this.canvas.offsetWidth; + this.canvas.height = this.canvas.offsetHeight; + } + + calculateScale(points) { + const maxX = Math.max(...points.map(p => p.x)); + const maxY = Math.max(...points.map(p => p.y)); + const scaleX = (this.canvas.width - this.padding * 2) / maxX; + const scaleY = (this.canvas.height - this.padding * 2) / maxY; + this.scale = Math.min(scaleX, scaleY); + } + + drawAxes() { + this.ctx.beginPath(); + this.ctx.strokeStyle = '#94a3b8'; + this.ctx.lineWidth = 1; + + // X-axis + this.ctx.moveTo(this.padding, this.canvas.height - this.padding); + this.ctx.lineTo(this.canvas.width - this.padding, this.canvas.height - this.padding); + + // Y-axis + this.ctx.moveTo(this.padding, this.canvas.height - this.padding); + this.ctx.lineTo(this.padding, this.padding); + + // Draw arrows + this.ctx.moveTo(this.canvas.width - this.padding, this.canvas.height - this.padding); + this.ctx.lineTo(this.canvas.width - this.padding - 10, this.canvas.height - this.padding - 5); + this.ctx.moveTo(this.canvas.width - this.padding, this.canvas.height - this.padding); + this.ctx.lineTo(this.canvas.width - this.padding - 10, this.canvas.height - this.padding + 5); + + this.ctx.moveTo(this.padding, this.padding); + this.ctx.lineTo(this.padding - 5, this.padding + 10); + this.ctx.moveTo(this.padding, this.padding); + this.ctx.lineTo(this.padding + 5, this.padding + 10); + + this.ctx.stroke(); + } + + drawTrajectory(points) { + this.ctx.beginPath(); + this.ctx.strokeStyle = '#94a3b8'; + this.ctx.lineWidth = 1; + points.forEach((point, i) => { + const x = point.x * this.scale + this.padding; + const y = this.canvas.height - (point.y * this.scale + this.padding); + if (i === 0) { + this.ctx.moveTo(x, y); + } else { + this.ctx.lineTo(x, y); + } + }); + this.ctx.stroke(); + } + + drawProjectile(x, y) { + const radius = 8; + this.ctx.beginPath(); + this.ctx.fillStyle = '#2563eb'; + this.ctx.arc( + x * this.scale + this.padding, + this.canvas.height - (y * this.scale + this.padding), + radius, + 0, + Math.PI * 2 + ); + this.ctx.fill(); + } + + drawVelocityVectors(x, y, vx, vy) { + const scaledX = x * this.scale + this.padding; + const scaledY = this.canvas.height - (y * this.scale + this.padding); + const vectorScale = 20; + + // Draw velocity vectors + this.ctx.beginPath(); + this.ctx.strokeStyle = '#22c55e'; + this.ctx.lineWidth = 2; + + // Horizontal velocity + this.ctx.moveTo(scaledX, scaledY); + this.ctx.lineTo(scaledX + vx * vectorScale, scaledY); + + // Vertical velocity + this.ctx.moveTo(scaledX, scaledY); + this.ctx.lineTo(scaledX, scaledY - vy * vectorScale); + + // Resultant velocity + this.ctx.moveTo(scaledX, scaledY); + this.ctx.lineTo( + scaledX + vx * vectorScale, + scaledY - vy * vectorScale + ); + + this.ctx.stroke(); + } + + animate(projectile) { + if (this.animationId) { + cancelAnimationFrame(this.animationId); + } + + this.projectile = projectile; + this.currentTime = 0; + const totalTime = projectile.calculateTimeOfFlight(); + const points = projectile.getTrajectoryPoints(); + this.calculateScale(points); + + const animate = () => { + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + + // Draw background elements + this.drawAxes(); + this.drawTrajectory(points); + + // Calculate current position and velocities + const { x, y } = projectile.getPositionAtTime(this.currentTime); + const { vx, vy } = projectile.calculateInitialVelocities(); + const currentVy = vy - g * this.currentTime; + + // Draw velocity vectors and projectile + this.drawVelocityVectors(x, y, vx, currentVy); + this.drawProjectile(x, y); + + if (this.currentTime < totalTime) { + this.currentTime += totalTime / 100; + this.animationId = requestAnimationFrame(animate); + } + }; + + animate(); + } + + stop() { + if (this.animationId) { + cancelAnimationFrame(this.animationId); + this.animationId = null; + } + } + } \ No newline at end of file