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;