physics-frontend/index.js

245 lines
7.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

let isAnimated = false;
const boardOffset = 20;
const boardColor = "#664F27";
const stats = document.getElementById("stats");
const canvas = document.getElementById("canvas");
const canvasContainer = document.querySelector(".canvas-container");
const ctx = canvas.getContext("2d");
// Все нулевые значения рассчитываются позже
const ball = {
radius: 8,
color: boardColor,
x: 0,
y: 0,
angle: -Math.PI / 2,
velocity: 0
};
const board = {
startX: 0,
startY: 0,
endX: 0,
endY: 0
}
function resizeCanvas() {
// Получаем размеры контейнера
const containerWidth = canvasContainer.offsetWidth;
const containerHeight = canvasContainer.offsetHeight;
// Устанавливаем физические размеры Canvas с учётом масштаба экрана
canvas.width = containerWidth * window.devicePixelRatio;
canvas.height = containerHeight * window.devicePixelRatio;
// Настраиваем масштаб для рендеринга
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
// Устанавливаем CSS размеры Canvas
canvas.style.width = `${containerWidth}px`;
canvas.style.height = `${containerHeight}px`;
}
function updateField() {
ctx.clearRect(0, 0, canvas.width / window.devicePixelRatio, canvas.height / window.devicePixelRatio);
drawBoard();
drawBall();
updateStats();
}
function drawBoard() {
const angle = parseFloat(angleSlider.value);
const length = parseFloat(lengthSlider.value);
board.endX = (canvas.width - boardOffset) / window.devicePixelRatio;
board.endY = (canvas.height - boardOffset) / window.devicePixelRatio;
board.startX = board.endX - length * Math.cos(Math.PI / 180 * angle);
board.startY = board.endY - length * Math.sin(Math.PI / 180 * angle);
if (board.startX - boardOffset < 0) {
board.startX = boardOffset
let newLength = - (board.startX - board.endX) / Math.cos(Math.PI / 180 * angle)
board.startY = board.endY - newLength * Math.sin(Math.PI / 180 * angle);
}
if (board.startY - boardOffset < 0) {
board.startY = boardOffset
let newLength = - (board.startY - board.endY) / Math.sin(Math.PI / 180 * angle)
board.startX = board.endY - newLength * Math.sin(Math.PI / 180 * angle);
}
ctx.beginPath();
ctx.moveTo(board.startX, board.startY);
ctx.lineTo(board.endX, board.endY);
ctx.strokeStyle = boardColor;
ctx.lineWidth = 5;
ctx.stroke();
ctx.closePath();
}
function drawBall() {
let delta = parseFloat(heightSlider.value);
if (!isAnimated) {
ball.x = board.startX + ball.radius / 2;
ball.y = board.startY - ball.radius - delta;
}
if (ball.y <= ball.radius) {
// heightSlider.value = delta + ball.y;
ball.y = ball.radius;
}
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.fillStyle = ball.color;
ctx.fill();
ctx.closePath()
vectorXEnd = ball.x + ball.radius * 3 * Math.cos(ball.angle);
vectorYEnd = ball.y - ball.radius * 3 * Math.sin(ball.angle)
ctx.beginPath();
ctx.moveTo(ball.x, ball.y);
ctx.lineTo(vectorXEnd, vectorYEnd);
//ctx.lineTo(
// ball.x - 20 * Math.cos(ball.angle - Math.PI / 6),
// ball.y - 20 * Math.sin(ball.angle - Math.PI / 6)
//);
//ctx.moveTo(vectorXEnd, vectorYEnd);
//ctx.lineTo(
// ball.x - 20 * Math.cos(ball.angle + Math.PI / 6),
// ball.y - 20 * Math.sin(ball.angle + Math.PI / 6)
//);
ctx.strokeStyle = ball.color;
ctx.lineWidth = 2;
ctx.stroke();
ctx.closePath();
}
function updateStats() {
stats.innerText = `x: ${ball.x.toFixed(1)}\n`+
`y: ${(canvas.height / window.devicePixelRatio - ball.y).toFixed(1)}\n` +
`Угол: ${(ball.angle * 180 / Math.PI).toFixed(0)}\n` +
`Скорость: ${ball.velocity.toFixed(2)}`
}
function syncSliderAndInput(slider, input) {
slider.addEventListener("input", () => {
input.value = parseFloat(slider.value).toFixed(slider.step.includes('.') ? slider.step.split('.')[1].length : 0);
updateField();
});
input.addEventListener("input", () => {
if (input.value == "") return;
const value = parseFloat(input.value);
if (isNaN(value) || value < parseFloat(slider.min) || value > parseFloat(slider.max)) {
input.value = slider.value;
} else {
slider.value = value;
input.value = value;
}
updateField();
});
}
function animateBall() {
if (isAnimated) return;
let xhr = new XMLHttpRequest();
const angle = (Math.PI / 180 * parseFloat(angleSlider.value)).toFixed(3);
const length = (parseFloat(lengthSlider.value) / 100).toFixed(3);
const height = (parseFloat(heightSlider.value) / 100).toFixed(3);
const g = parseFloat(gSlider.value).toFixed(3);
const urlBase = 'https://physics.zzvt.pw';
// const urlBase = 'http://localhost:3000';
const urlPoints = `${urlBase}/api/points?h=${height}&l=${length}&g=10&alpha=${angle}`;
const pointsTable = document.querySelector('#points');
pointsTable.innerHTML = '';
fetch(urlPoints)
.then(r => r.json().then(data => {
data.forEach((value, i) => pointsTable.innerHTML += `<tr><td>${i+1}</td><td>${Number(value.x).toFixed(2)}</td></tr>`)
}));
const url = `${urlBase}/api/interpolation?h=${height}&l=${length}&g=${g}&alpha=${angle}&count=1000`;
xhr.open("GET", url, true);
console.log(`Sent request to ${url}`)
xhr.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
const points = JSON.parse(this.responseText);
startX = ball.x;
startY = ball.y;
startAngle = ball.angle;
isAnimated = true;
for(let i = 0; i < points.length; ++i) {
let point = points[i];
let resetBall = function(){
isAnimated = false;
ball.x = startX;
ball.y = startY;
ball.angle = startAngle;
updateField();
}
if (startY - point.y * 100 > canvas.height / window.devicePixelRatio ||
startX + point.x * 100 > canvas.width / window.devicePixelRatio) {
setTimeout(resetBall, point.time * 1000)
break;
}
setTimeout(function(){
ball.x = startX + point.x * 100;
ball.y = startY - point.y * 100;
ball.angle = point.angle;
ball.velocity = point.velocity;
updateField();
if (i == points.length - 1) resetBall();
}, point.time * 1000)
}
}
}
xhr.send(null);
}
const angleSlider = document.getElementById("slider-angle");
const angleInput = document.getElementById("input-angle");
syncSliderAndInput(angleSlider, angleInput);
const heightSlider = document.getElementById("slider-height");
const heightInput = document.getElementById("input-height");
syncSliderAndInput(heightSlider, heightInput);
const lengthSlider = document.getElementById("slider-length");
const lengthInput = document.getElementById("input-length");
syncSliderAndInput(lengthSlider, lengthInput);
const gSlider = document.getElementById("slider-g");
const gInput = document.getElementById("input-g");
syncSliderAndInput(gSlider, gInput);
window.addEventListener("resize", () => {
resizeCanvas();
updateField();
});
resizeCanvas();
updateField();