Интеграция DebugDraw с Canvas 2D

Для работы с визуализацией физических тел через Canvas 2D сначала необходимо создать базовую сцену Oimo.js. Ключевые элементы включают мир (World), тела (RigidBody) и геометрии (Shape).

import * as OIMO from "oimo";

const world = new OIMO.World({
    timestep: 1/60,
    iterations: 8,
    broadphase: 2, // 1 = brute force, 2 = sweep & prune
    worldscale: 1,
    random: true,
    info: false
});

Параметр timestep определяет шаг симуляции, iterations — количество итераций решения столкновений, broadphase отвечает за алгоритм предварительной проверки столкновений.

Для добавления тел используется конструктор RigidBody:

const box = world.add({
    type: "box",
    size: [2, 2, 2],
    pos: [0, 10, 0],
    move: true,
    density: 1
});

Здесь type задаёт форму тела, size — размеры, pos — начальное положение, move указывает на подвижность объекта, density влияет на массу.


Подключение DebugDraw для Canvas 2D

DebugDraw в Oimo.js позволяет визуализировать все физические объекты и их столкновения в удобной форме. Для интеграции с Canvas 2D необходимо создать специальный класс отрисовки.

class DebugDraw2D {
    constructor(canvas, world) {
        this.canvas = canvas;
        this.ctx = canvas.getContext("2d");
        this.world = world;
    }

    clear() {
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    }

    draw() {
        this.clear();
        const bodies = this.world.getRigidBodies();
        for (let i = 0; i < bodies.length; i++) {
            this.drawBody(bodies[i]);
        }
    }

    drawBody(body) {
        const shapes = body.shapes;
        for (let i = 0; i < shapes.length; i++) {
            this.drawShape(shapes[i], body);
        }
    }

    drawShape(shape, body) {
        const pos = shape.getPosition();
        const size = shape.getHalfExtents ? shape.getHalfExtents() : [1,1,1];

        this.ctx.save();
        this.ctx.translate(pos[0] + this.canvas.width/2, -pos[1] + this.canvas.height/2);
        this.ctx.fillStyle = "rgba(0,150,255,0.5)";

        if(shape.type === "box") {
            this.ctx.fillRect(-size[0], -size[1], size[0]*2, size[1]*2);
        } else if(shape.type === "sphere") {
            this.ctx.beginPath();
            this.ctx.arc(0, 0, size[0], 0, Math.PI*2);
            this.ctx.fill();
        }

        this.ctx.restore();
    }
}

Ключевые моменты реализации:

  • clear() — очищает канвас перед каждой отрисовкой.
  • drawBody() — проходит по всем телам сцены и вызывает drawShape().
  • drawShape() — визуализирует разные типы геометрий: коробки, сферы и при необходимости другие формы.
  • Смещение и масштаб: центр канваса переносится в середину, ось Y инвертируется для соответствия координатам физического мира.

Синхронизация физики и отрисовки

Для правильного отображения необходимо запускать цикл обновления физики и рендеринга.

const canvas = document.getElementById("canvas");
const debugDraw = new DebugDraw2D(canvas, world);

function animate() {
    requestAnimationFrame(animate);
    world.step();
    debugDraw.draw();
}

animate();

world.step() обновляет физическую симуляцию с учётом текущего времени, после чего debugDraw.draw() визуализирует все тела.


Визуализация столкновений и векторов силы

DebugDraw можно расширить для отображения контактных точек и векторов силы:

drawContacts() {
    const contacts = this.world.getContacts();
    this.ctx.strokeStyle = "red";
    contacts.forEach(contact => {
        const pos = contact.position;
        this.ctx.beginPath();
        this.ctx.arc(pos[0] + this.canvas.width/2, -pos[1] + this.canvas.height/2, 2, 0, Math.PI*2);
        this.ctx.stroke();
    });
}

Это позволяет наблюдать реальные точки соприкосновения объектов, что особенно полезно при отладке сложных взаимодействий.


Масштабирование и трансформация канваса

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

this.ctx.translate(this.canvas.width/2, this.canvas.height/2);
this.ctx.scale(10, -10);
  • translate смещает центр координат в центр окна.
  • scale увеличивает видимые размеры объектов и инвертирует ось Y, чтобы верхняя часть канваса соответствовала положительной координате Y в физическом мире.

Добавление динамических объектов и сцены с гравитацией

Гравитацию задают через параметры мира:

const world = new OIMO.World({
    gravity: [0, -9.8, 0]
});

Для динамических объектов важно указывать move: true. При необходимости можно создавать падающие коробки и шары, наблюдая их столкновения через DebugDraw:

for(let i = 0; i < 5; i++) {
    world.add({
        type: "sphere",
        size: [1],
        pos: [i*2-4, 20, 0],
        move: true,
        density: 1
    });
}

Отрисовка будет автоматически обновляться при каждом шаге симуляции, демонстрируя физическое поведение объектов.


Настройка цветовой схемы и прозрачности

Canvas позволяет выделять объекты разными цветами для удобства отладки:

this.ctx.fillStyle = shape.type === "box" ? "rgba(0,150,255,0.5)" : "rgba(255,100,0,0.5)";

Прозрачность позволяет видеть наложение объектов и их взаимное пересечение, а различие цветов помогает различать типы тел.


Вывод

Использование DebugDraw с Canvas 2D в Oimo.js обеспечивает наглядную отладку физической симуляции, позволяет визуализировать объекты, столкновения и силы, а также тестировать поведение сложных динамических сцен. Правильное масштабирование и трансформация канваса делают работу с 2D-представлением интуитивной и наглядной.