Иерархия сцены и дерево объектов

В Three.js сцена представлена объектом THREE.Scene, который служит корневым контейнером для всех визуальных элементов: камер, источников света, моделей, мешей и вспомогательных объектов. Каждый объект сцены является экземпляром класса THREE.Object3D или его наследников, таких как THREE.Mesh, THREE.Group, THREE.Camera, THREE.Light.

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

const scene = new THREE.Scene();
const group = new THREE.Group();
const mesh = new THREE.Mesh(
    new THREE.BoxGeometry(1, 1, 1),
    new THREE.MeshStandardMaterial({ color: 0xff0000 })
);

group.add(mesh);
scene.add(group);

В этом примере mesh является дочерним объектом group, а group — дочерним объектом scene. Любые преобразования, применённые к group, автоматически влияют на все её дочерние объекты.


Преобразования объектов и наследование трансформаций

Каждый объект в дереве имеет свойства position, rotation, scale, которые определяют его положение, ориентацию и масштаб относительно родителя. Эти свойства формируют локальную систему координат объекта.

  • position — вектор THREE.Vector3, задающий смещение объекта относительно родителя.
  • rotation — объект THREE.Euler, определяющий углы вращения вокруг осей X, Y, Z.
  • scale — вектор THREE.Vector3, задающий масштаб по каждой оси.

Если объект не имеет родителя, его трансформации считаются глобальными относительно сцены. Если объект вложен, глобальное положение вычисляется с учётом всех родителей:

mesh.position.set(1, 0, 0);
group.position.set(2, 0, 0);

scene.add(group);
// Глобальная позиция mesh = (1+2, 0, 0) = (3, 0, 0)

Трансформации наследуются и при вращении: вращение родителя вращает все дочерние объекты вместе с ним.


Управление иерархией: методы добавления и удаления объектов

add() и remove() — основные методы для построения и изменения иерархии:

group.add(mesh); // Добавление дочернего объекта
group.remove(mesh); // Удаление объекта из группы

Метод attach() позволяет перенести объект из одного родителя к другому с сохранением глобальной позиции:

scene.add(mesh);
group.attach(mesh); // mesh теперь дочерний объект group, позиция не изменится

Свойство children хранит массив всех дочерних объектов объекта:

console.log(group.children); // [mesh]

Свойство parent указывает на родительский объект:

console.log(mesh.parent === group); // true

Использование групп и вспомогательных объектов

THREE.Group — удобный способ объединять несколько объектов для совместных трансформаций или управления логикой. Группы могут содержать как меши, так и другие группы, создавая сложные иерархические структуры.

const solarSystem = new THREE.Group();
const earthOrbit = new THREE.Group();
const earth = new THREE.Mesh(
    new THREE.SphereGeometry(1, 32, 32),
    new THREE.MeshStandardMaterial({ color: 0x0000ff })
);

earthOrbit.add(earth);
solarSystem.add(earthOrbit);
scene.add(solarSystem);

В этом примере вращение earthOrbit вокруг своей оси моделирует орбитальное движение Земли, не затрагивая положение других планет. Такой подход позволяет легко создавать сложные механизмы и симуляции.


Сценарии обхода дерева объектов

Three.js предоставляет методы для рекурсивного обхода:

  • traverse(callback) — вызывает функцию для текущего объекта и всех его потомков:
scene.traverse((obj) => {
    console.log(obj.type, obj.name);
});
  • traverseVisible(callback) — аналогично, но только для видимых объектов.
  • getObjectByName(name) — поиск объекта по имени во всём дереве.
  • getObjectById(id) — поиск объекта по уникальному идентификатору.

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


Локальные и глобальные координаты

Методы localToWorld(vector) и worldToLocal(vector) преобразуют координаты объекта между локальной системой и системой сцены:

const localPos = new THREE.Vector3(1, 0, 0);
const globalPos = mesh.localToWorld(localPos.clone());

const convertedBack = mesh.worldToLocal(globalPos.clone());
console.log(convertedBack); // (1, 0, 0)

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


Именование и идентификация объектов

Каждому объекту можно задать уникальное имя через свойство name:

mesh.name = "cube1";
const found = scene.getObjectByName("cube1");

Каждый объект также имеет уникальный идентификатор id, присваиваемый автоматически, что облегчает управление сценой при динамическом создании множества объектов.


Влияние иерархии на производительность

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

  • Группировать статические объекты в THREE.Group и использовать matrixAutoUpdate = false.
  • Минимизировать количество уровней вложенности для объектов с динамическими трансформациями.
  • Использовать InstancedMesh для повторяющихся объектов, если требуется большое количество одинаковых мешей.

Иерархия света и камер

Свет и камеры также являются объектами сцены и поддерживают иерархию:

const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
const light = new THREE.PointLight(0xffffff, 1);
const cameraHolder = new THREE.Group();

cameraHolder.add(camera);
cameraHolder.add(light);
scene.add(cameraHolder);

Перемещение или вращение cameraHolder одновременно изменяет положение камеры и источника света, что упрощает настройку сложных сцен.


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