Сериализация и десериализация состояния мира

В физическом движке Oimo.js сериализация и десериализация представляют собой процессы сохранения и восстановления состояния физического мира. Эти механизмы необходимы для сохранения игры, создания контрольных точек (checkpoints) или передачи состояния между клиентом и сервером.

Основные понятия

  • Сериализация — преобразование состояния объектов мира в формат, который можно сохранить или передать (например, JSON).
  • Десериализация — обратный процесс: восстановление объектов мира из сохранённого формата.

В контексте Oimo.js состояние мира включает:

  • Тела (RigidBody): позиции, ориентации, линейные и угловые скорости.
  • Фиксаторы (Constraint): соединения тел, их параметры.
  • Физические параметры (World): гравитация, шаг интеграции, коэффициенты силы трения и упругости.

Сериализация мира

Oimo.js не предоставляет встроенного метода world.toJSON(), поэтому сериализация требует ручного обхода объектов. Общая схема:

const serializedWorld = {
    bodies: [],
    constraints: [],
    settings: {
        gravity: [world.gravity.x, world.gravity.y, world.gravity.z],
        timestep: world.timeStep
    }
};

world.bodies.forEach(body => {
    serializedWorld.bodies.push({
        name: body.name,
        type: body.type, // dynamic, static, kinematic
        position: [body.position.x, body.position.y, body.position.z],
        rotation: [body.quaternion.x, body.quaternion.y, body.quaternion.z, body.quaternion.w],
        linearVelocity: [body.linearVelocity.x, body.linearVelocity.y, body.linearVelocity.z],
        angularVelocity: [body.angularVelocity.x, body.angularVelocity.y, body.angularVelocity.z],
        shapes: body.shapes.map(shape => ({
            type: shape.type,
            size: shape.size,
            friction: shape.friction,
            restitution: shape.restitution
        }))
    });
});

world.constraints.forEach(constraint => {
    serializedWorld.constraints.push({
        type: constraint.type,
        bodyA: constraint.bodyA.name,
        bodyB: constraint.bodyB.name,
        anchorA: [constraint.anchorA.x, constraint.anchorA.y, constraint.anchorA.z],
        anchorB: [constraint.anchorB.x, constraint.anchorB.y, constraint.anchorB.z],
        // дополнительные параметры конкретного типа
    });
});

const jsonString = JSON.stringify(serializedWorld);

Особенности:

  • Для сохранения связей между телами и фиксирующими объектами необходимо использовать уникальные идентификаторы (name или id).
  • Состояние каждого тела включает не только позицию и ориентацию, но и текущие скорости для корректного продолжения симуляции.
  • Физические параметры мира (gravity, timestep) обеспечивают одинаковое поведение при восстановлении.

Десериализация мира

Процесс восстановления требует создания нового мира и последовательного добавления тел и ограничений. Пример:

const parsed = JSON.parse(jsonString);
const newWorld = new OIMO.World();
newWorld.gravity = new OIMO.Vec3(...parsed.settings.gravity);
newWorld.timeStep = parsed.settings.timestep;

const bodyMap = {};

parsed.bodies.forEach(b => {
    const body = new OIMO.RigidBody({
        type: b.type,
        position: new OIMO.Vec3(...b.position),
        rotation: new OIMO.Quat(...b.rotation)
    });
    
    b.shapes.forEach(s => {
        body.addShape(new OIMO.Shape({
            type: s.type,
            size: s.size,
            friction: s.friction,
            restitution: s.restitution
        }));
    });

    body.linearVelocity = new OIMO.Vec3(...b.linearVelocity);
    body.angularVelocity = new OIMO.Vec3(...b.angularVelocity);
    newWorld.addRigidBody(body);
    bodyMap[b.name] = body;
});

parsed.constraints.forEach(c => {
    const constraint = new OIMO.Constraint({
        type: c.type,
        bodyA: bodyMap[c.bodyA],
        bodyB: bodyMap[c.bodyB],
        anchorA: new OIMO.Vec3(...c.anchorA),
        anchorB: new OIMO.Vec3(...c.anchorB)
    });
    newWorld.addConstraint(constraint);
});

Ключевые моменты:

  • Создание карты bodyMap обеспечивает правильную привязку ограничений к телам.
  • Каждое тело и его формы должны точно соответствовать сохранённой структуре.
  • Восстановление скоростей позволяет симуляции продолжаться без скачков и артефактов.

Оптимизация сериализации

  1. Минимизация данных: можно не сохранять статические тела или тела вне поля зрения, если симуляция не требует их состояния.
  2. Бинарный формат: для больших сцен JSON может быть заменён на более компактный бинарный формат.
  3. Инкрементальные сохранения: сохранять изменения только для движущихся объектов между кадрами для снижения объёма данных.

Особенности работы с Oimo.js

  • Quaternion vs Euler: Oimo.js использует кватернионы для вращений. Хранение в виде кватернионов исключает проблемы с угловыми граничными условиями.
  • Типы тел: dynamic — полностью физически активное, static — неподвижное, kinematic — управляемое программно. При сериализации важно сохранять тип, иначе восстановление нарушит логику взаимодействия.
  • Формы тел: одна RigidBody может содержать несколько форм. Каждая форма должна иметь свои параметры.
  • Совместимость версий: структура объектов Oimo.js может меняться между версиями. При сериализации для долгосрочного хранения важно фиксировать версию движка.

Сериализация и десериализация в Oimo.js позволяют создавать сложные физические сцены с возможностью сохранения и восстановления их точного состояния, что является критически важным для игр, симуляций и научных приложений.