# `@huggingface/usd-web-viewer`

[OpenUSD](https://openusd.org) в браузере: композиция USD в WebAssembly, построенная на [openusd](https://github.com/mxpv/openusd) (чистый Rust), с рендерингом через [three.js](https://threejs.org). Читает `.usd`, `.usda`, `.usdc` и `.usdz`; загрузка выполняется в Web Worker.

```sh
npm install @huggingface/usd-web-viewer three
```

## Использование

Готовый просмотрщик (рендерер, освещение, орбитальные элементы управления):

```js
import { createViewer } from '@huggingface/usd-web-viewer';

const viewer = createViewer(document.getElementById('viewer')); // a container element
const { info } = await viewer.load(
  'https://huggingface.co/datasets/Robotiq-Official/simready-assets/resolve/main/Robotiq_2F_85/simready_usd/Robotiq_2F_85.usda',
);
```

Или в собственную сцену three.js:

```js
import { loadUsd } from '@huggingface/usd-web-viewer';

const { root, complete, dispose } = await loadUsd(url);
scene.add(root); // a THREE.Group, Y-up, in meters
await complete; // textures stream in after the geometry
```

## Физика и суставы

`info` также сообщает о том, что авторы сцены для симуляции (UsdPhysics) и её метки UsdSemantics, как они были созданы: ничего не оценивается. Массы указаны в килограммах, плотности в кг/м³, инерция в кг·м², пределы и положения шарниров в градусах или метрах; положения и матрицы находятся в пространстве сцены, как и сетки под `root`.

```js
const { info, joints, setJointPositions, resetJointPositions, getJointPositions, getWorldMatrix } = await viewer.load(url);

info.physics.bodies; // rigid bodies: mass, center of mass, inertia, colliders, bodies fixed to them
info.physics.colliders; // shape, approximation (convexHull, sdf, ...), physics material
info.physics.materials; // density, friction, restitution
info.physics.joints; // revolute, prismatic, spherical, distance, fixed and generic joints with their limits and drives
info.physics.articulationRoots; // where articulations (bodies solved together, like a robot) start
info.physics.frames; // Xforms under a body, often points with no mesh, like a gripper's tip
info.labels; // e.g. { path: '/RootNode', taxonomy: 'wikidata_class', values: ['cabinet'] }
info.variantSelections; // e.g. { prim: '/Robotiq_2F_85', set: 'Physics', selection: 'Physx_parallel_grip' }
info.dependencies; // every layer and texture the stage read, with its size

joints; // the joints setJointPositions can move, e.g. to build sliders
setJointPositions({ '/Robotiq_2F_85/Robotiq_2F_85/Joints/finger_joint': 40 }); // revolute and prismatic joints, any number at once; what hangs from them and mimic joints follow
getJointPositions(); // where every revolute and prismatic joint is now
getWorldMatrix('/SO101/gripper/gripper_frame_link'); // where a body, frame or mesh under a body is now and which way it faces, as a THREE.Matrix4 in the scene; null if it is none of those
resetJointPositions(); // back to the authored pose
```

Позирование чисто кинематическое (без учёта столкновений и гравитации) и достаточно дёшево, чтобы выполняться на каждом кадре при перетаскивании ползунка или записи.

## Параметры

`loadUsd(url, options)` и `viewer.load(url, options)` принимают:

| Параметр                         | По умолчанию             |                                                                                 |
| ------------------------------ | ------------------- | ------------------------------------------------------------------------------- |
| `textures`                     | `'preview'`         | `'none'`, `'preview'` (базовый цвет, небольшие карты данных, без карт нормалей) или `'full'` |
| `maxTextureSize`               | `1024`              | Длина длинной стороны декодированных текстур, в пикселях                                        |
| `signal`                       |                     | Сигнал `AbortSignal`; загрузка отклоняется с ошибкой `UsdLoadError` с кодом `aborted`      |
| `onProgress`                   |                     | Вызывается с указанием этапа и общей `fraction` от 0 до 1                     |
| `files`                        |                     | Загружайте удалённые или добавленные файлы вместо сети (см. ниже)                 |
| `wasmUrl`, `workerUrl`         | рядом с пакетом | Откуда загружаются WASM-бинарник и скрипт воркера                     |
| `maxConcurrentFetches`         | `16`                | Запросы, выполняемые одновременно                                                      |
| `maxLayerBytes`, `maxLayers`   | 768 МБ, 1024       | Ограничения на загрузку слоёв                                                           |
| `maxTextureBytes`              | 512 МБ             | Ограничение на загрузку текстур                                                          |
| `maxTriangles`, `maxInstances` | 20M, 100,000        | Что рисуется максимум                                                           |

`createViewer(container, options)` принимает `background` (по умолчанию прозрачный), `onContextLost` и `onContextRestored` (вызываются при перезагрузке последней отображаемой модели).

Сбои представлены в виде `UsdLoadError` с `code` (`aborted`, `fetch`, `compose`, `worker`, `webgl`); всё, что не удалось отобразить корректно, перечислено в `info.warnings`. Для каждого параметра, ошибки и предупреждения определён тип.

## Загрузка недоверенных файлов

- Запросы отправляются так же, как запросы `<model-viewer>`: куки — только для собственного источника страницы (`credentials: 'same-origin'`), без пользовательских заголовков и без referrer.
- Слои и текстуры загружаются только по http(s).
- Разбор выполняется на Rust, скомпилированном в WebAssembly, в Web Worker.
- Каждая загрузка ограничена: `maxLayerBytes` (768 МиБ), `maxLayers` (1024), `maxTextureBytes` (512 МиБ), `maxTriangles` (20 млн) и `maxInstances` (100 000); изображения размером до 16384 пикс. по стороне и 8192 x 8192 пикс. по площади; примитивы вложенностью до 256 уровней; записи `.usdz` размером до 512 МиБ каждая и 1 ГиБ суммарно, хранящиеся без сжатия.
- Файлы с некорректным форматом приводят к ошибке загрузки `UsdLoadError`.

## Локальные файлы

Чтобы отобразить файлы, перетасканные на страницу или выбранные с помощью `<input type="file" webkitdirectory>`, передайте их в параметре `files`, используя в качестве ключей их пути внутри пакета, и загрузите ключ корневого слоя. Относительные пути в файлах разрешаются относительно друг друга; для них ничего не запрашивается.

```js
const files = new Map([...input.files].map((file) => [file.webkitRelativePath || file.name, file]));
await viewer.load('chair/simready_usd/sm_chair.usd', { files });
```

## Сборщики

Рабочий процесс и WASM-бинарник находятся с помощью `new URL(..., import.meta.url)`, что Vite, webpack и другие сборщики разрешают и выдают. При использовании dev-сервера Vite исключите пакет из предварительной сборки зависимостей:

```js
// vite.config.js
export default { optimizeDeps: { exclude: ['@huggingface/usd-web-viewer'] } };
```

Лицензия MIT. Лицензии Rust-пакетов, скомпилированных в WASM-бинарник, приведены в `THIRD_PARTY_LICENSES`.

