245 lines
7.7 KiB
JavaScript
245 lines
7.7 KiB
JavaScript
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();
|