Библиотека Oimo.js — это физический движок для трёхмерных твёрдых тел. Он реализует столкновения, импульсную динамику, ограничения, трение и упругость. Однако полноценная гидродинамика (Navier–Stokes, давление, вязкость, поверхностное натяжение) в движке отсутствует.
Следовательно, моделирование жидкостей возможно только через частичную эмуляцию с использованием:
Подход строится на принципе: жидкость не рассчитывается как сплошная среда, а имитируется через воздействие сил на твёрдые тела.
В основе лежит объект мира:
const world = new OIMO.World({
gravity: [0, -9.8, 0],
timestep: 1/60,
iterations: 8,
broadphase: 2
});
Гравитация задаётся глобально, но жидкостная эмуляция требует локального переопределения сил для отдельных тел.
Тело создаётся так:
const body = world.add({
type: 'box',
size: [1, 1, 1],
pos: [0, 5, 0],
move: true,
density: 1
});
Все жидкостные эффекты реализуются через методы:
body.applyForce(forceVec, positionVec)body.applyImpulse(positionVec, impulseVec)linearDampingangularDampingСила Архимеда:
[ F = {жидкости} V{погружённой части} g]
В Oimo.js нет автоматического вычисления объёма погружённой части, поэтому рассчитывается:
Создаётся логическая область (например, бассейн):
const waterLevel = 0;
const waterDensity = 2;
Каждый кадр выполняется проверка положения тела:
function applyBuoyancy(body) {
const position = body.getPosition();
const halfHeight = 0.5;
const depth = waterLevel - (position.y - halfHeight);
if (depth > 0) {
const submergedRatio = Math.min(depth / (halfHeight * 2), 1);
const volume = 1; // для куба 1x1x1
const buoyancyForce = waterDensity * volume * 9.8 * submergedRatio;
body.applyForce([0, buoyancyForce, 0], position);
}
}
Жидкость создаёт сопротивление, пропорциональное скорости:
[ F_{drag} = -k v]
Реализация:
function applyDrag(body) {
const velocity = body.getLinearVelocity();
const dragCoefficient = 2;
const dragForce = [
-velocity.x * dragCoefficient,
-velocity.y * dragCoefficient,
-velocity.z * dragCoefficient
];
body.applyForce(dragForce, body.getPosition());
}
Альтернативный способ — использование встроенного демпфирования:
body.linearDamping = 0.9;
body.angularDamping = 0.9;
Разница:
Другой подход — представить жидкость как множество мелких сфер.
for (let i = 0; i < 200; i++) {
world.add({
type: 'sphere',
size: [0.2],
pos: [
Math.random()*2 - 1,
5 + Math.random()*2,
Math.random()*2 - 1
],
move: true,
density: 0.2,
friction: 0,
restitution: 0
});
}
Особенности:
iterations,Такой метод создаёт эффект «вязкой массы», но не даёт реалистичных волн.
Вязкость достигается добавлением силы, направленной к центру соседних частиц.
Простейшая модель:
function applyViscosity(body, neighbors) {
const pos = body.getPosition();
const k = 0.5;
neighbors.forEach(n => {
const nPos = n.getPosition();
const dx = nPos.x - pos.x;
const dy = nPos.y - pos.y;
const dz = nPos.z - pos.z;
const distance = Math.sqrt(dx*dx + dy*dy + dz*dz);
if (distance < 1) {
body.applyForce([
dx * k,
dy * k,
dz * k
], pos);
}
});
}
Это создаёт эффект притяжения и сглаживания движения.
Для имитации волн используется передача импульса от одного тела к соседнему:
body.applyImpulse(
body.getPosition(),
[0, 5, 0]
);
Если импульс передаётся группе частиц, возникает волнообразное движение.
Для бассейна с фиксированными стенками:
move: false),Простейшая аппроксимация:
Логика:
Это создаёт тенденцию жидкости сохранять компактную форму.
При большом числе частиц необходимо:
timestep,sleep: true для покоящихся объектов,broadphase сложность.Пример ограничения скорости:
const maxSpeed = 10;
const v = body.getLinearVelocity();
const speed = Math.sqrt(v.x*v.x + v.y*v.y + v.z*v.z);
if (speed > maxSpeed) {
body.setLinearVelocity([
v.x / speed * maxSpeed,
v.y / speed * maxSpeed,
v.z / speed * maxSpeed
]);
}
Практически применимая схема:
Такой гибрид:
Тем не менее, для игровых задач и интерактивных сцен частичная эмуляция через силы позволяет создать убедительную иллюзию жидкости в рамках возможностей Oimo.js.