184 lines
7.0 KiB
JavaScript
184 lines
7.0 KiB
JavaScript
document.addEventListener('DOMContentLoaded', () => {
|
||
const canvas = document.getElementById('simulationCanvas');
|
||
if (!canvas) return;
|
||
|
||
const ctx = canvas.getContext('2d');
|
||
const launchButton = document.getElementById('launchButton');
|
||
const accelerationInput = document.getElementById('acceleration');
|
||
const burnTimeInput = document.getElementById('burnTime');
|
||
const angleInput = document.getElementById('angle');
|
||
const resultMessage = document.getElementById('result-message');
|
||
|
||
// --- Параметры симуляции ---
|
||
const GRAVITY = 9.8;
|
||
const PIXELS_PER_METER = 0.01;
|
||
const TIME_STEP = 1.0;
|
||
|
||
let rocket = {};
|
||
let simulationState = {};
|
||
|
||
function initializeState() {
|
||
return {
|
||
animationFrameId: null,
|
||
elapsedTime: 0,
|
||
acceleration: 0,
|
||
burnTime: 0,
|
||
angleRad: 0,
|
||
};
|
||
}
|
||
|
||
function initializeRocket() {
|
||
return {
|
||
x: 30,
|
||
y: canvas.height - 45,
|
||
width: 10,
|
||
height: 25,
|
||
x_meters: 0,
|
||
y_meters: 0,
|
||
vx: 0, // Скорость по X в м/с
|
||
vy: 0, // Скорость по Y в м/с
|
||
};
|
||
}
|
||
|
||
function drawEarth() {
|
||
ctx.fillStyle = '#2c5c23';
|
||
ctx.beginPath();
|
||
ctx.arc(canvas.width / 2, canvas.height + canvas.height - 50, canvas.height, 0, Math.PI, true);
|
||
ctx.fill();
|
||
}
|
||
|
||
function drawRocket() {
|
||
ctx.save();
|
||
ctx.translate(rocket.x, rocket.y);
|
||
|
||
// Угол поворота ракеты зависит от вектора скорости
|
||
// Но до старта (скорость 0) она стоит вертикально
|
||
const angle = (rocket.vx === 0 && rocket.vy === 0)
|
||
? -Math.PI / 2
|
||
: Math.atan2(rocket.vy, rocket.vx);
|
||
|
||
// Поворачиваем ракету. Наша модель "смотрит" вправо, поэтому доп. вращение не нужно.
|
||
ctx.rotate(angle);
|
||
|
||
// Рисуем пламя, если двигатель работает
|
||
if (simulationState.elapsedTime > 0 && simulationState.elapsedTime <= simulationState.burnTime) {
|
||
ctx.fillStyle = `rgba(255, ${Math.random() * 150 + 100}, 0, 0.8)`;
|
||
ctx.beginPath();
|
||
const flameLength = rocket.height * (1.5 + Math.random() * 0.5);
|
||
ctx.moveTo(-rocket.width / 2, 0);
|
||
ctx.lineTo(-rocket.width / 2 - flameLength, rocket.width / 2);
|
||
ctx.lineTo(-rocket.width / 2 - flameLength, -rocket.width / 2);
|
||
ctx.closePath();
|
||
ctx.fill();
|
||
}
|
||
|
||
// Корпус ракеты (перерисовываем, чтобы был поверх пламени)
|
||
// Модель ракеты теперь "смотрит" вправо (по оси X)
|
||
ctx.fillStyle = '#d0d0d0';
|
||
ctx.beginPath();
|
||
ctx.moveTo(rocket.height / 2, 0);
|
||
ctx.lineTo(-rocket.height / 2, -rocket.width / 2);
|
||
ctx.lineTo(-rocket.height / 2, rocket.width / 2);
|
||
ctx.closePath();
|
||
ctx.fill();
|
||
|
||
ctx.restore();
|
||
}
|
||
|
||
function resetSimulation() {
|
||
if (simulationState.animationFrameId) {
|
||
cancelAnimationFrame(simulationState.animationFrameId);
|
||
}
|
||
simulationState = initializeState();
|
||
rocket = initializeRocket();
|
||
resultMessage.innerHTML = ' ';
|
||
resultMessage.className = 'alert alert-secondary';
|
||
clearCanvas();
|
||
drawEarth();
|
||
drawRocket();
|
||
}
|
||
|
||
function clearCanvas() {
|
||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||
}
|
||
|
||
function animate() {
|
||
simulationState.elapsedTime += TIME_STEP;
|
||
|
||
// 1. Обновляем физику
|
||
// Если двигатель работает, применяем ускорение
|
||
if (simulationState.elapsedTime <= simulationState.burnTime) {
|
||
const currentAccelerationX = simulationState.acceleration * Math.cos(simulationState.angleRad);
|
||
const currentAccelerationY = simulationState.acceleration * Math.sin(simulationState.angleRad);
|
||
rocket.vx += currentAccelerationX * TIME_STEP;
|
||
rocket.vy += currentAccelerationY * TIME_STEP;
|
||
}
|
||
|
||
// Всегда применяем гравитацию (она действует на ось Y)
|
||
rocket.vy -= GRAVITY * TIME_STEP;
|
||
|
||
// Обновляем позицию в метрах
|
||
rocket.x_meters += rocket.vx * TIME_STEP;
|
||
rocket.y_meters += rocket.vy * TIME_STEP;
|
||
|
||
// 2. Обновляем позицию для отрисовки в пикселях
|
||
const groundY = canvas.height - 45;
|
||
rocket.x = 30 + (rocket.x_meters * PIXELS_PER_METER);
|
||
rocket.y = groundY - (rocket.y_meters * PIXELS_PER_METER);
|
||
|
||
// 3. Отрисовываем сцену
|
||
clearCanvas();
|
||
drawEarth();
|
||
drawRocket();
|
||
|
||
// 4. Проверяем условия завершения
|
||
const hasCrashed = rocket.y_meters < 0 && simulationState.elapsedTime > 1;
|
||
const outOfBounds = rocket.x > canvas.width + rocket.height;
|
||
|
||
if (hasCrashed || outOfBounds) {
|
||
rocket.y_meters = 0;
|
||
rocket.y = groundY;
|
||
clearCanvas();
|
||
drawEarth();
|
||
drawRocket();
|
||
|
||
resultMessage.innerHTML = outOfBounds
|
||
? "<strong>Неудача.</strong> Ракета улетела за пределы видимости."
|
||
: "<strong>Неудача.</strong> Ракета упала обратно на Землю.";
|
||
resultMessage.className = 'alert alert-danger';
|
||
|
||
cancelAnimationFrame(simulationState.animationFrameId);
|
||
simulationState.animationFrameId = null;
|
||
return;
|
||
}
|
||
|
||
simulationState.animationFrameId = requestAnimationFrame(animate);
|
||
}
|
||
|
||
launchButton.addEventListener('click', () => {
|
||
const accelValue = parseFloat(accelerationInput.value);
|
||
const burnTimeValue = parseFloat(burnTimeInput.value);
|
||
const angleValue = parseFloat(angleInput.value);
|
||
|
||
if (isNaN(accelValue) || isNaN(burnTimeValue) || isNaN(angleValue) || accelValue <= 0 || burnTimeValue <= 0 || angleValue < 0 || angleValue > 90) {
|
||
resultMessage.innerHTML = 'Пожалуйста, введите корректные значения (ускорение > 0, время > 0, угол 0-90).';
|
||
resultMessage.className = 'alert alert-warning';
|
||
return;
|
||
}
|
||
|
||
resetSimulation();
|
||
|
||
simulationState.acceleration = accelValue;
|
||
simulationState.burnTime = burnTimeValue;
|
||
simulationState.angleRad = angleValue * (Math.PI / 180);
|
||
|
||
resultMessage.innerHTML = "Запуск...";
|
||
resultMessage.className = 'alert alert-info';
|
||
|
||
if (!simulationState.animationFrameId) {
|
||
simulationState.animationFrameId = requestAnimationFrame(animate);
|
||
}
|
||
});
|
||
|
||
resetSimulation();
|
||
}); |