Напиши на JavaScript модуль для three.js r186, который строит 3D-сцену по описанию в JSON. Модуль экспортирует одну функцию и ничего не импортирует: объект `THREE` (то, что экспортирует пакет `three`) передаётся аргументом. ## Интерфейс ```js export function buildScene(THREE, spec) { // ... return { scene, camera }; } ``` `scene` это `THREE.Scene`, `camera` это `THREE.PerspectiveCamera` или `THREE.OrthographicCamera`. Камеру в сцену добавлять не нужно. Перед возвратом у сцены и камеры должны быть актуальны мировые матрицы, а у камеры матрица проекции. ## Описание сцены ```js { "background": "#101820", // цвет фона: scene.background = new THREE.Color(...); по умолчанию "#000000" "camera": { ... }, // камера, см. ниже "nodes": [ { ... }, { ... } ] // узлы верхнего уровня, у каждого могут быть "children" } ``` ## Узлы Каждый узел становится объектом three.js с `name` из описания и добавляется к родителю (узлы верхнего уровня к сцене) в порядке описания. Общие поля всех узлов: - `name`: уникальное имя, записывается в `object.name`. - `type`: тип узла, см. ниже. - `position`: `[x, y, z]`, по умолчанию `[0, 0, 0]`. - `rotation`: углы `[x, y, z]` **в градусах**, по умолчанию нули. `rotationOrder`: порядок поворотов Эйлера, одна из строк `"XYZ"`, `"XZY"`, `"YXZ"`, `"YZX"`, `"ZXY"`, `"ZYX"`, по умолчанию `"XYZ"`. - `scale`: число (одинаковый масштаб по всем осям) или `[x, y, z]`, по умолчанию 1. Значения могут быть отрицательными. - `lookAt`: точка `[x, y, z]` в мировых координатах. Если она задана, узел поворачивается методом `Object3D.lookAt` к этой точке, а `rotation` не используется. Все `lookAt` применяются после того, как построено всё дерево, в порядке описания (родитель раньше потомков), и каждый учитывает текущие мировые преобразования родителей. - `visible`: `false` делает объект невидимым, по умолчанию видимый. - `children`: дочерние узлы. Типы узлов: - `group`: `THREE.Group`. - Примитивы, каждый становится `THREE.Mesh` со своей геометрией three.js: - `box`: `size: [w, h, d]` → `BoxGeometry(w, h, d)`; - `sphere`: `radius`, `widthSegments` (по умолчанию 32), `heightSegments` (по умолчанию 16) → `SphereGeometry`; - `cylinder`: `radiusTop`, `radiusBottom`, `height`, `radialSegments` (по умолчанию 32) → `CylinderGeometry`; - `torus`: `radius`, `tube`, `radialSegments` (по умолчанию 16), `tubularSegments` (по умолчанию 48) → `TorusGeometry`; - `plane`: `size: [w, h]` → `PlaneGeometry(w, h)`; - `extrude`: `points` (контур `[[x, y], ...]` в плоскости XY, не меньше трёх точек), `holes` (необязательный список отверстий, каждое тоже список точек), `depth` → `ExtrudeGeometry` с `bevelEnabled: false` и `steps: 1`. - `instances`: `THREE.InstancedMesh`. Поле `geometry` описывает геометрию одного экземпляра так же, как примитив (`{"type": "box", "size": [...]}`, допустимы типы `box`, `sphere`, `cylinder`, `torus`, `plane`), `items` это список экземпляров, у каждого необязательные `position`, `rotation` (градусы, порядок всегда `"XYZ"`) и `scale` (число или тройка). Число экземпляров равно длине `items`, может быть нулём. Матрица экземпляра собирается из его позиции, поворота и масштаба. - Источники света: - `ambient`: `THREE.AmbientLight(color, intensity)`; - `directional`: `THREE.DirectionalLight(color, intensity)`, поле `target: [x, y, z]` (по умолчанию начало координат) задаёт позицию `light.target`, а сам `target` добавляется в сцену; - `point`: `THREE.PointLight(color, intensity, distance, decay)`, по умолчанию `distance` 0 и `decay` 2. У света `color` по умолчанию `"#ffffff"`, `intensity` по умолчанию 1. ## Материал У примитивов и `instances` материал `THREE.MeshStandardMaterial`: - `color`: цвет из описания, по умолчанию `"#ffffff"`; - `opacity`: из поля `opacity`, по умолчанию 1; `transparent: true`, если `opacity < 1`; - `side`: `THREE.DoubleSide`, если у узла `doubleSided: true`, иначе `THREE.FrontSide`. ## Цвета Цвет задаётся строкой `#rgb` или `#rrggbb` в пространстве sRGB, как в CSS, и превращается в `THREE.Color` стандартным способом three.js. Любая другая строка считается ошибкой. ## Камера - `"type": "perspective"` (по умолчанию): `fov` (по умолчанию 50), `aspect` (по умолчанию 1), `near` (0.1), `far` (1000). - `"type": "orthographic"`: `left`, `right`, `top`, `bottom`, `near` (0.1), `far` (1000). - Общие поля: `position` (по умолчанию `[0, 0, 10]`), `lookAt` (по умолчанию `[0, 0, 0]`), необязательный `up: [x, y, z]`, который задаётся до `lookAt`. ## Ошибки Неизвестный тип узла или геометрии экземпляров, неверная строка цвета, `extrude` меньше чем из трёх точек и неизвестный `rotationOrder` должны выбрасывать исключение. ## Производительность Сцена из 3000 объектов с `lookAt`, цепочка из 1500 вложенных групп с `lookAt` у каждого объекта и `instances` из 50 000 экземпляров должны строиться не дольше 2 секунд каждая. ## Формат ответа Ответь ТОЛЬКО кодом модуля в одном блоке ```javascript.