import React, { useState, useEffect, useRef } from 'react'; import { Stage, Layer, Circle, Text, Line } from 'react-konva'; 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 const G = 1e-8; // Gravitational constant for simulation const EARTH_MASS = 5.972e6; // Scaled down mass of Earth for simulation const ASTEROID_DENSITY = 2000; // Density in kg/m^3 const CANVAS_WIDTH = window.innerWidth; 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({ name: '', size: 1, speed: 0.05, mass: 1, angle: Math.PI / 9, x: 100, y: CANVAS_HEIGHT / 2 - 100, }); const [trajectoryPoints, setTrajectoryPoints] = useState([]); const [forecastPoints, setForecastPoints] = useState([]); const animationRef = useRef(); const asteroidsData = useRef([]); const [isSimulating, setIsSimulating] = useState(false); 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 () => { try { const response = await axios.get('http://localhost:8080/api/sentry/'); asteroidsData.current = response.data.near_earth_objects; setAsteroidData(0); // Set initial asteroid } catch (error) { console.error('Error fetching asteroid data:', error); } }; fetchAsteroids(); }, []); const estimateMass = (diameter) => { const radius = diameter / 2; const volume = (4 / 3) * Math.PI * Math.pow(radius, 3); return volume * ASTEROID_DENSITY; }; const setAsteroidData = (index) => { const asteroidData = asteroidsData.current[index]; console.log('Asteroid Data:', asteroidData); const size = asteroidData.estimated_diameter.kilometers.estimated_diameter_max; const mass = estimateMass(size); const speed = asteroidData.close_approach_data[0].relative_velocity.kilometers_per_second / 1000; setAsteroid({ name: asteroidData.name, size: size, speed: speed, mass: mass, angle: Math.PI / 9, x: 100, y: CANVAS_HEIGHT / 2 - 100, }); 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 calculateForecastPath = (size, speed, angle, mass) => { let x = 100; let y = CANVAS_HEIGHT / 2 - 100; let vx = speed * Math.cos(angle); let vy = speed * Math.sin(angle); const points = []; let iterationCount = 0; const maxIterations = 2000; // Extended limit to allow for longer forecast while (true) { const dx = EARTH_X - x; const dy = EARTH_Y - y; const distance = Math.sqrt(dx * dx + dy * dy); // Check if the asteroid hits Earth if (distance <= EARTH_RADIUS_KM * EARTH_DISPLAY_SCALE + size * ASTEROID_DISPLAY_SCALE) { break; } // Apply gravitational force const force = (G * EARTH_MASS * mass) / (distance * distance); const ax = force * (dx / distance) / mass; const ay = force * (dy / distance) / mass; vx += ax * timeStep; vy += ay * timeStep; x += vx * timeStep; y += vy * timeStep; points.push(x, y); iterationCount++; if (iterationCount >= maxIterations) { break; } // Stop the loop if the asteroid goes off screen 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) { break; } } 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; let vx = speed * Math.cos(angle); let vy = speed * Math.sin(angle); const updateFrame = () => { const dx = EARTH_X - x; const dy = EARTH_Y - y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance <= EARTH_RADIUS_KM * EARTH_DISPLAY_SCALE + asteroid.size * ASTEROID_DISPLAY_SCALE) { setSimulationResult('Asteroid collided with Earth!'); setIsSimulating(false); return; } // Apply gravitational force const force = (G * EARTH_MASS * mass) / (distance * distance); const ax = force * (dx / distance) / mass; const ay = force * (dy / distance) / mass; vx += ax * timeStep; vy += ay * timeStep; 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]); animationRef.current = window.requestAnimationFrame(updateFrame); }; setIsSimulating(true); setSimulationResult(''); animationRef.current = window.requestAnimationFrame(updateFrame); }; const handleSimulate = () => { if (isSimulating) { setIsSimulating(false); window.cancelAnimationFrame(animationRef.current); setSimulationResult('Simulation stopped.'); } else { simulate(); } }; const handleTimeStepChange = (event, newValue) => { setTimeStep(newValue); }; const handleAngleChange = (event, newValue) => { setAsteroid((prev) => ({ ...prev, angle: newValue })); calculateForecastPath(asteroid.size, asteroid.speed, newValue, asteroid.mass); }; return ( <> Asteroid Collision Scenario {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 )} Time Step: {timeStep} Initial Angle: {(asteroid.angle * 180 / Math.PI).toFixed(2)}° {trajectoryPoints.length > 0 && ( )} {forecastPoints.length > 0 && ( )} {rocket.active && ( )} {rocket.trajectory.length > 0 && ( )} ); } export default Scenario;