Реализация пружины без встроенного класса Spring

Cannon.js — это физический движок для JavaScript, предназначенный для симуляции твердых тел, столкновений и ограничений. Он предоставляет набор классов для создания динамических объектов, их физических свойств и взаимодействий. Основные компоненты включают:

  • World — пространство, в котором происходят все симуляции.
  • Body — физическое тело с массой, положением, ориентацией и скоростью.
  • Shape — геометрическая форма тела (Box, Sphere, Plane и др.).
  • Constraint — связи между телами (например, DistanceConstraint, HingeConstraint).

Для создания пружины можно обойтись без встроенного класса Spring, используя базовые физические принципы: силу упругости, демпфирование и обновление тела на каждом шаге симуляции.


Принцип работы пружины

Пружина описывается законом Гука:

[ F = -k x]

где:

    1. — сила, действующая на тело,
    1. — коэффициент жесткости пружины,
    1. — отклонение тела от точки равновесия.

Для стабилизации колебаний добавляется демпфирование:

[ F_{} = -c v]

где:

    1. — коэффициент демпфирования,
    1. — скорость тела вдоль направления пружины.

Суммарная сила, прикладываемая к телу:

[ F_{} = F + F_{}]

Эта сила применяется к телу на каждом шаге симуляции через метод applyForce или applyImpulse.


Создание пружины между двумя телами

Для реализации пружины между телами bodyA и bodyB нужно вычислять:

  1. Вектор между телами:
const delta = new CANNON.Vec3();
bodyB.position.vsub(bodyA.position, delta); // delta = bodyB - bodyA
  1. Текущее растяжение пружины:
const currentLength = delta.length();
const extension = currentLength - restLength; // restLength — длина пружины в покое
  1. Направление силы:
const forceDirection = delta.unit(); // нормализованный вектор
  1. Сила упругости и демпфирования:
const relativeVelocity = new CANNON.Vec3();
bodyB.velocity.vsub(bodyA.velocity, relativeVelocity);
const dampingForce = relativeVelocity.dot(forceDirection) * damping; // c * v
const springForce = -springConstant * extension - dampingForce;
  1. Применение силы к телам:
const force = forceDirection.scale(springForce);
bodyA.applyForce(force);
bodyB.applyForce(force.negate());

Эта процедура повторяется на каждом шаге симуляции. Для интеграции с Cannon.js используется событие postStep или обычный вызов внутри основного цикла обновления мира:

world.addEventListener('postStep', function() {
    applySpring(bodyA, bodyB, restLength, springConstant, damping);
});

Пружина с якорем в пространстве

Если пружина закреплена к фиксированной точке (anchor), алгоритм упрощается:

  1. Вычисляется вектор от точки к телу:
const delta = new CANNON.Vec3();
body.position.vsub(anchor, delta);
  1. Сила определяется как:
const extension = delta.length() - restLength;
const forceDirection = delta.unit();
const dampingForce = body.velocity.dot(forceDirection) * damping;
const springForce = -springConstant * extension - dampingForce;
const force = forceDirection.scale(springForce);
body.applyForce(force);

Фиксированная точка может быть представлена как CANNON.Vec3, и тело реагирует на силу точно так же, как если бы это была пружина между двумя динамическими телами.


Настройка параметров пружины

Коэффициент жесткости (springConstant, k):

  • Большие значения делают пружину более «жесткой», вызывая быстрые колебания.
  • Малые значения создают мягкие, медленные колебания.

Демпфирование (damping, c):

  • Высокое демпфирование быстро гасит колебания.
  • Низкое демпфирование оставляет пружину почти без сопротивления.

Длина покоя (restLength):

  • Определяет положение равновесия пружины.
  • Изменение restLength во время симуляции позволяет имитировать растяжение или сжатие.

Применение к цепям и мягким телам

Использование описанной реализации позволяет строить:

  • Цепочки тел: несколько тел соединены последовательно пружинами.
  • Сетки мягких тел: каждая вершина соединена пружинами с соседними, создавая эффект ткани или сетки.
  • Подвесные конструкции: один конец закреплен в пространстве, другой свободно движется.

Для стабильности симуляции рекомендуется использовать небольшие шаги времени (dt) и корректировать коэффициенты демпфирования в зависимости от массы тел:

[ c ]

где (m) — масса тела.


Пример функции applySpring

function applySpring(bodyA, bodyB, restLength, k, damping) {
    const delta = new CANNON.Vec3();
    bodyB.position.vsub(bodyA.position, delta);

    const currentLength = delta.length();
    const extension = currentLength - restLength;
    const forceDirection = delta.unit();

    const relativeVelocity = new CANNON.Vec3();
    bodyB.velocity.vsub(bodyA.velocity, relativeVelocity);
    const dampingForce = relativeVelocity.dot(forceDirection) * damping;

    const springForce = -k * extension - dampingForce;
    const force = forceDirection.scale(springForce);

    bodyA.applyForce(force);
    bodyB.applyForce(force.negate());
}

Эта функция позволяет гибко настраивать пружины, объединять их в сложные конструкции и использовать как основу для создания динамических симуляций без встроенного класса Spring Cannon.js.