Two.js — это мощная библиотека для создания двумерной графики на основе HTML5 Canvas, SVG и WebGL. Несмотря на то, что она не предоставляет встроенный полноценный физический движок, она позволяет реализовывать физические симуляции через интеграцию с внешними библиотеками или собственную логику на JavaScript. Физические симуляции в Two.js включают управление объектами с учетом сил, массы, гравитации, столкновений и трения.
В Two.js каждый визуальный элемент создается как объект
Two.Shape или его производные (Two.Circle,
Two.Rectangle, Two.Path). Для физической
симуляции каждому объекту обычно добавляют следующие свойства:
mass — масса объекта, влияет на инерцию.velocity — вектор скорости {x, y}.acceleration — вектор ускорения
{x, y}.forces — список приложенных сил, которые накапливаются
каждый кадр.Пример расширения объекта круга физическими свойствами:
const circle = two.makeCircle(100, 100, 50);
circle.mass = 2;
circle.velocity = { x: 0, y: 0 };
circle.acceleration = { x: 0, y: 0 };
circle.forces = [];
Силы в симуляции представлены векторными величинами. Чтобы изменить движение объекта, силы суммируются, и на их основе вычисляется ускорение через второй закон Ньютона:
[ = m = ]
Пример добавления силы гравитации:
function applyGravity(shape, gravity = 0.98) {
shape.forces.push({ x: 0, y: gravity * shape.mass });
}
function computeAcceleration(shape) {
let ax = 0;
let ay = 0;
shape.forces.forEach(force => {
ax += force.x / shape.mass;
ay += force.y / shape.mass;
});
shape.acceleration.x = ax;
shape.acceleration.y = ay;
shape.forces = [];
}
После вычисления ускорения, положение и скорость объектов обновляются через численное интегрирование. Наиболее простой метод — явный метод Эйлера:
function updateMotion(shape, deltaTime) {
shape.velocity.x += shape.acceleration.x * deltaTime;
shape.velocity.y += shape.acceleration.y * deltaTime;
shape.translation.x += shape.velocity.x * deltaTime;
shape.translation.y += shape.velocity.y * deltaTime;
}
deltaTime — время между кадрами в секундах. Для более
точных симуляций можно использовать методы Рунге-Кутты.
Для столкновений объектов нужно определять пересечение их границ. Для примера с кругами проверка расстояния между центрами:
function checkCollision(circle1, circle2) {
const dx = circle2.translation.x - circle1.translation.x;
const dy = circle2.translation.y - circle1.translation.y;
const distance = Math.sqrt(dx * dx + dy * dy);
return distance < (circle1.radius + circle2.radius);
}
При обнаружении столкновения можно использовать импульсную реакцию:
[ ’ = - (1 + e) ]
где e — коэффициент упругости, n — нормаль
между объектами, v и u — скорости объектов до
столкновения.
Простейшее отражение вдоль нормали:
function resolveCollision(circle1, circle2, restitution = 0.9) {
const dx = circle2.translation.x - circle1.translation.x;
const dy = circle2.translation.y - circle1.translation.y;
const distance = Math.sqrt(dx * dx + dy * dy);
const nx = dx / distance;
const ny = dy / distance;
const relativeVelocityX = circle1.velocity.x - circle2.velocity.x;
const relativeVelocityY = circle1.velocity.y - circle2.velocity.y;
const dot = relativeVelocityX * nx + relativeVelocityY * ny;
const impulse = (-(1 + restitution) * dot) / (1 / circle1.mass + 1 / circle2.mass);
circle1.velocity.x += (impulse * nx) / circle1.mass;
circle1.velocity.y += (impulse * ny) / circle1.mass;
circle2.velocity.x -= (impulse * nx) / circle2.mass;
circle2.velocity.y -= (impulse * ny) / circle2.mass;
}
Для реалистичного движения объектов можно учитывать сопротивление
воздуха или трение. Простейший способ — уменьшать скорость на каждом
кадре на коэффициент damping:
function applyDamping(shape, damping = 0.98) {
shape.velocity.x *= damping;
shape.velocity.y *= damping;
}
Для трения о поверхность применяется сила, противоположная направлению движения:
function applyFriction(shape, mu = 0.1) {
const speed = Math.sqrt(shape.velocity.x**2 + shape.velocity.y**2);
if(speed > 0){
shape.forces.push({
x: -mu * shape.mass * (shape.velocity.x / speed),
y: -mu * shape.mass * (shape.velocity.y / speed)
});
}
}
Движение всех объектов обновляется внутри анимационного цикла Two.js:
two.bind('update', function(frameCount, deltaTime) {
deltaTime = deltaTime / 1000; // преобразование в секунды
shapes.forEach(shape => {
applyGravity(shape);
applyFriction(shape);
computeAcceleration(shape);
updateMotion(shape, deltaTime);
});
// Проверка столкновений
for(let i = 0; i < shapes.length; i++){
for(let j = i+1; j < shapes.length; j++){
if(checkCollision(shapes[i], shapes[j])){
resolveCollision(shapes[i], shapes[j]);
}
}
}
}).play();
Такой подход позволяет реализовать простую физическую среду, где объекты подчиняются законам Ньютона, сталкиваются между собой и реагируют на силы.
Для более сложных симуляций Two.js легко сочетается с библиотеками, такими как:
Объекты Two.js при этом служат только визуальной оболочкой, а физика
рассчитывается движком. Синхронизация происходит через обновление
shape.translation в каждом кадре по координатам физического
тела.
В более продвинутых симуляциях можно моделировать соединенные объекты:
DistanceConstraint — фиксированное расстояние между
двумя объектами.Spring — пружинное соединение с коэффициентами
жесткости и демпфирования.В Two.js их реализация возможна через прямое управление координатами объектов на основе рассчитанных сил и импульсов.
requestAnimationFrame (что автоматически
делает Two.js) для оптимизации рендеринга.update,
предварительно рассчитывая постоянные величины.Эти принципы позволяют создавать плавные и реалистичные физические симуляции даже в браузере, используя Two.js.