diff --git a/backend/.env.sample b/backend/.env.sample index cc3ccc3..8530af4 100644 --- a/backend/.env.sample +++ b/backend/.env.sample @@ -3,4 +3,4 @@ SENTRY_API_KEY=<"Your NASA Sentry API Key"> OPENAI_API_KEY=<"Your OpenAI API Key"> HOST_CLIENT=<"Your FrontEnd URL"> ALLOWED_HOSTS=<"Your Server URL"> -DEBUG=<"True for Deployment | False for Development"> \ No newline at end of file +DEBUG=<"True for Deployment | False for Development"> diff --git a/backend/.gitignore b/backend/.gitignore index c66ba3a..b741592 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,2 +1,3 @@ .env -.venv \ No newline at end of file +.venv +/__pycache__/ \ No newline at end of file diff --git a/frontend/src/pages/scenario.jsx b/frontend/src/pages/scenario.jsx index ee37a8f..3f13923 100644 --- a/frontend/src/pages/scenario.jsx +++ b/frontend/src/pages/scenario.jsx @@ -1,9 +1,8 @@ import React, { useState, useEffect, useRef } from 'react'; import { Stage, Layer, Circle, Text, Line } from 'react-konva'; -import { Slider, Button } from '@mui/material'; +import { Slider, Button, MenuItem, Select, Typography, Box } from '@mui/material'; import axios from 'axios'; - const EARTH_RADIUS_KM = 6371; // Earth's radius in kilometers const EARTH_DISPLAY_SCALE = 0.01; // Display scale for Earth const ASTEROID_DISPLAY_SCALE = 0.3; // Display scale for asteroids @@ -16,6 +15,7 @@ const CANVAS_HEIGHT = window.innerHeight; const EARTH_X = CANVAS_WIDTH - 100; const EARTH_Y = CANVAS_HEIGHT / 2; const SIMULATION_AREA_MULTIPLIER = 2; // Extend the simulation area +const ROCKET_SPEED = 0.025; // Reduced speed of the rocket function Scenario() { const [asteroid, setAsteroid] = useState({ @@ -35,11 +35,21 @@ function Scenario() { const [simulationResult, setSimulationResult] = useState(''); const [strategy, setStrategy] = useState(''); // State for selected strategy const [timeStep, setTimeStep] = useState(100); // State for time step + const [rocket, setRocket] = useState({ + active: false, + x: EARTH_X, + y: EARTH_Y, + trajectory: [], + vx: 0, + vy: 0, + }); + const [rocketDistance, setRocketDistance] = useState(10000); + const [collisionOccurred, setCollisionOccurred] = useState(false); useEffect(() => { - const fetchAsteroids = async (self, request) => { + const fetchAsteroids = async () => { try { - const response = await axios.get(`http://localhost:8000/api/sentry/`); + const response = await axios.get('http://localhost:8080/api/sentry/'); asteroidsData.current = response.data.near_earth_objects; setAsteroidData(0); // Set initial asteroid } catch (error) { @@ -72,23 +82,15 @@ function Scenario() { }); setTrajectoryPoints([]); calculateForecastPath(size, speed, Math.PI / 9, mass); // Calculate the forecast path once + setRocket({ active: false, x: EARTH_X, y: EARTH_Y, trajectory: [], vx: 0, vy: 0 }); + setRocketDistance(10000); + setCollisionOccurred(false); }; const handleAsteroidSelect = (event) => { setAsteroidData(event.target.value); }; - const applyDeflectionStrategy = (vx, vy) => { - if (strategy === 'Nuclear Detonation') { - vx += 0.5; // Example effect - } else if (strategy === 'Kinetic Impact') { - vx += 0.25; // Example effect - } else if (strategy === 'Gravity Tractor') { - vy -= 0.1; // Example effect - } - return { vx, vy }; - }; - const calculateForecastPath = (size, speed, angle, mass) => { let x = 100; let y = CANVAS_HEIGHT / 2 - 100; @@ -135,6 +137,20 @@ function Scenario() { setForecastPoints(points); }; + const applyVelocityImpulse = (asteroid, strategy, rocketDx, rocketDy) => { + let impulseX = 0; + let impulseY = 0; + const angle = Math.atan2(rocketDy, rocketDx); + if (strategy === 'Nuclear Detonation') { + impulseX = 0.01 * Math.cos(angle); + impulseY = 0.01 * Math.sin(angle); + } else if (strategy === 'Kinetic Impact') { + impulseX = 0.005 * Math.cos(angle); + impulseY = 0.005 * Math.sin(angle); + } + return { vx: asteroid.vx + impulseX, vy: asteroid.vy + impulseY }; + }; + const simulate = () => { let { x, y, speed, angle, mass } = asteroid; @@ -142,8 +158,6 @@ function Scenario() { let vy = speed * Math.sin(angle); const updateFrame = () => { - console.log(`Asteroid position: (${x}, ${y}), velocity: (${vx}, ${vy})`); - const dx = EARTH_X - x; const dy = EARTH_Y - y; const distance = Math.sqrt(dx * dx + dy * dy); @@ -162,19 +176,57 @@ function Scenario() { vx += ax * timeStep; vy += ay * timeStep; - ({ vx, vy } = applyDeflectionStrategy(vx, vy)); - x += vx * timeStep; y += vy * timeStep; + // Update rocket position + setRocket((prev) => { + const rocketDx = x - prev.x; + const rocketDy = y - prev.y; + const rocketDistance = Math.sqrt(rocketDx * rocketDx + rocketDy * rocketDy); + const rocketAngle = Math.atan2(rocketDy, rocketDx); + + const rocketVX = ROCKET_SPEED * Math.cos(rocketAngle); + const rocketVY = ROCKET_SPEED * Math.sin(rocketAngle); + + const newX = prev.x + rocketVX * timeStep; + const newY = prev.y + rocketVY * timeStep; + + if (rocketDistance <= asteroid.size * ASTEROID_DISPLAY_SCALE && !collisionOccurred) { + const newVelocity = applyVelocityImpulse({ vx, vy }, strategy, rocketDx, rocketDy); + vx = newVelocity.vx; + vy = newVelocity.vy; + setSimulationResult('Rocket intercepted the asteroid!'); + setCollisionOccurred(true); // Mark collision occurred + return { ...prev, active: false, x: newX, y: newY, vx: 0, vy: 0 }; // Stop the rocket + } + + return { + ...prev, + x: newX, + y: newY, + vx: prev.active ? rocketVX : 0, // Stop velocity if inactive + vy: prev.active ? rocketVY : 0, // Stop velocity if inactive + trajectory: [...prev.trajectory, newX, newY], + }; + }); + + setRocketDistance(rocketDistance); + + if (rocketDistance <= asteroid.size * ASTEROID_DISPLAY_SCALE && !collisionOccurred) { + setSimulationResult('Rocket intercepted the asteroid!'); + setIsSimulating(false); + return; + } + if (y > CANVAS_HEIGHT * SIMULATION_AREA_MULTIPLIER || x > CANVAS_WIDTH * SIMULATION_AREA_MULTIPLIER || y < -CANVAS_HEIGHT * SIMULATION_AREA_MULTIPLIER || x < -CANVAS_WIDTH * SIMULATION_AREA_MULTIPLIER) { setSimulationResult('Asteroid missed Earth!'); setIsSimulating(false); return; } - setAsteroid(prev => ({ ...prev, x, y })); - setTrajectoryPoints(prev => [...prev, x, y]); + setAsteroid((prev) => ({ ...prev, x, y })); + setTrajectoryPoints((prev) => [...prev, x, y]); animationRef.current = window.requestAnimationFrame(updateFrame); }; @@ -184,8 +236,13 @@ function Scenario() { }; const handleSimulate = () => { - if (isSimulating) return; - simulate(); + if (isSimulating) { + setIsSimulating(false); + window.cancelAnimationFrame(animationRef.current); + setSimulationResult('Simulation stopped.'); + } else { + simulate(); + } }; const handleTimeStepChange = (event, newValue) => { @@ -193,51 +250,67 @@ function Scenario() { }; const handleAngleChange = (event, newValue) => { - setAsteroid(prev => ({ ...prev, angle: newValue })); + setAsteroid((prev) => ({ ...prev, angle: newValue })); calculateForecastPath(asteroid.size, asteroid.speed, newValue, asteroid.mass); }; return ( <> -
-

Asteroid Collision Scenario

- - + + + Asteroid Collision Scenario + + + + + + + {simulationResult && ( -
-

Simulation Result

-

{simulationResult}

-
+ + Simulation Result + {simulationResult} + )} {asteroid.name && ( -
-

Asteroid Information

-

Name: {asteroid.name}

-

Size: {asteroid.size.toFixed(3)} kilometers

-

Speed: {(asteroid.speed * 1000).toFixed(3)} km/s

-

Mass: {asteroid.mass.toExponential(3)} kg

-
+ + Asteroid Information + Name: {asteroid.name} + Size: {asteroid.size.toFixed(3)} kilometers + Speed: {(asteroid.speed * 1000).toFixed(3)} km/s + Mass: {asteroid.mass.toExponential(3)} kg + )} -
-

Time Step: {timeStep}

+ + Time Step: {timeStep} -
-
-

Initial Angle: {(asteroid.angle * 180 / Math.PI).toFixed(2)}°

+ + + Initial Angle: {(asteroid.angle * 180 / Math.PI).toFixed(2)}° -
-
+ + @@ -270,6 +343,12 @@ function Scenario() { {forecastPoints.length > 0 && ( )} + {rocket.active && ( + + )} + {rocket.trajectory.length > 0 && ( + + )}