@vctrl/hooks
Version:
vctrl/hooks is a React hooks package designed to simplify 3D model loading and management within React applications. It's part of the vectreal-core ecosystem and is primarily used in the vctrl/viewer React component and the official website application.
244 lines (176 loc) • 12.3 kB
Markdown
# @vctrl/hooks
[](https://www.npmjs.com/package/@vctrl/hooks)
Browser-side React hooks for loading, optimizing, and exporting 3D models. The runtime counterpart to [`@vctrl/core`](https://vectreal.com/docs/packages/core), built for React apps that need to handle 3D files directly in the browser.
---
## Installation
```bash
npm install @vctrl/hooks
# or
pnpm add @vctrl/hooks
```
---
## Hooks overview
| Hook | Import path | Description |
| ------------------ | --------------------------------- | ------------------------------------------------------------------------------- |
| `useLoadModel` | `@vctrl/hooks/use-load-model` | Load and parse GLTF, GLB, and USDZ files from file lists or dropped directories |
| `useOptimizeModel` | `@vctrl/hooks/use-optimize-model` | Run glTF-Transform optimizations on loaded models |
| `useExportModel` | `@vctrl/hooks/use-export-model` | Export the current scene to GLB or glTF |
---
## `useLoadModel`
Loads 3D files and exposes the parsed Three.js `Object3D` scene.
```tsx
import { ModelProvider, useLoadModel } from '@vctrl/hooks/use-load-model'
function Uploader() {
const { load, file, isFileLoading } = useLoadModel()
const handleDrop = (e: React.DragEvent) => {
e.preventDefault()
void load(Array.from(e.dataTransfer.files))
}
if (isFileLoading) return <p>Loading...</p>
if (file?.model) return <p>Model loaded: {file.name}</p>
return (
<div onDrop={handleDrop} onDragOver={(e) => e.preventDefault()}>
Drop a file
</div>
)
}
export default function App() {
return (
<ModelProvider>
<Uploader />
</ModelProvider>
)
}
```
### Context and direct usage
`useLoadModel` can be used in two ways:
1. Context mode: wrap your app with `ModelProvider`, then consume with `useModelContext()` anywhere in that tree.
2. Direct mode: call `useLoadModel()` outside a provider to manage a local model state.
### Return values
| Value | Type | Description |
| -------------------------- | ------------------------------------ | ----------------------------------------------------------- |
| `file` | `ModelFile \| null` | Loaded file metadata and Three.js model |
| `isFileLoading` | `boolean` | True while loading and parsing |
| `progress` | `number` | Progress value from 0 to 100 |
| `load(filesOrDirectories)` | `Promise<void>` | Load files or dropped `FileSystemDirectoryHandle` entries |
| `loadFromData(options)` | `Promise<SceneLoadResult>` | Load already-resolved server scene payload |
| `loadFromServer(options)` | `Promise<SceneLoadResult>` | Fetch and load scene from an API endpoint |
| `reset` | `() => void` | Clear the current model |
| `on` / `off` | Event helpers | Subscribe and unsubscribe to loader lifecycle events |
| `optimizer` | `OptimizerIntegrationReturn \| null` | Populated when the hook is called with `useOptimizeModel()` |
### `useLoadModel` events
`on` and `off` support these typed events:
| Event | Payload |
| ---------------------- | ------------------- |
| `multiple-models` | `File[]` |
| `not-loaded-files` | `File[]` |
| `load-start` | `null` |
| `load-progress` | `number` |
| `load-complete` | `ModelFile \| null` |
| `load-reset` | `null` |
| `load-error` | `Error \| unknown` |
| `server-load-start` | `string` |
| `server-load-complete` | `SceneLoadResult` |
| `server-load-error` | `Error \| unknown` |
### Scene loading option types
`loadFromServer(options)` uses:
| Field | Type | Description |
| --------------- | --------------- | --------------------------------------------- |
| `sceneId` | `string` | Scene identifier to fetch |
| `serverOptions` | `ServerOptions` | Endpoint, auth, and header configuration |
| `applySettings` | `boolean` | Whether scene settings are applied after load |
`loadFromData(options)` uses:
| Field | Type | Description |
| --------------- | --------------------- | -------------------------------------------------------------- |
| `sceneId` | `string \| undefined` | Optional scene identifier |
| `sceneData` | `ServerSceneData` | Already-resolved payload containing glTF, settings, and assets |
| `applySettings` | `boolean` | Whether scene settings are applied after load |
---
## `useOptimizeModel`
Runs mesh simplification, deduplication, quantization, and normals optimization using [glTF-Transform](https://gltf-transform.dev), plus texture compression via browser-native OffscreenCanvas encoding. Every pass runs on the calling thread: this package spawns no Web Worker. If you need the work off the main thread, run the hook inside a worker you own, as the Vectreal Platform does.
```tsx
import { useOptimizeModel } from '@vctrl/hooks/use-optimize-model'
import { useLoadModel } from '@vctrl/hooks/use-load-model'
function Optimizer() {
const optimizer = useOptimizeModel()
const { file, optimizer: integrated } = useLoadModel(optimizer)
const handleOptimize = async () => {
if (!file?.model || !integrated) return
await integrated.applyOptimization(integrated.simplifyOptimization, {
ratio: 0.6,
error: 0.001
})
}
return (
<button onClick={handleOptimize} disabled={optimizer.loading}>
{optimizer.loading ? 'Optimizing...' : 'Optimize model'}
</button>
)
}
```
### Key API surface
| Method / State | Type | Description |
| ------------------------------------ | ------------------------------------------------------------------ | ---------------------------------------------------------------------- |
| `load(model)` | `(model: Object3D) => Promise<void>` | Load a Three.js scene into the optimizer |
| `loadFromServerSceneData(sceneData)` | `Promise<void>` | Initialize optimizer from a server scene payload |
| `simplifyOptimization(options?)` | `Promise<void>` | Simplify mesh geometry |
| `dedupOptimization(options?)` | `Promise<void>` | Deduplicate model data |
| `quantizeOptimization(options?)` | `Promise<void>` | Quantize vertex attributes |
| `normalsOptimization(options?)` | `Promise<void>` | Recompute or normalize normals |
| `texturesOptimization(options?)` | `Promise<void>` | Run texture compression flow |
| `getModel()` | `Promise<Uint8Array \| null>` | Export the current optimized model as GLB binary |
| `report` / `info` | Objects | Optimization metrics and derived stats |
| `loading` / `error` | State | Optimization status |
> `applyOptimization(fn, opts?)` is **not** returned by `useOptimizeModel` directly. It becomes available on the `optimizer` object returned by `useLoadModel(optimizer)` when you pass an optimizer instance in; it runs an optimization step and syncs the result back into loader state (see the example above).
### Optimization option types
`useOptimizeModel` methods map directly to `@vctrl/core/model-optimizer` option types:
| Method | Option fields |
| ---------------------- | ------------------------------------------------------------------------------------------------------------- |
| `simplifyOptimization` | `ratio?: number`, `error?: number` |
| `dedupOptimization` | `textures?: boolean`, `materials?: boolean`, `meshes?: boolean`, `accessors?: boolean` |
| `quantizeOptimization` | `quantizePosition?: number`, `quantizeNormal?: number`, `quantizeColor?: number`, `quantizeTexcoord?: number` |
| `normalsOptimization` | `overwrite?: boolean` |
| `texturesOptimization` | `resize?: [number, number]`, `targetFormat?: 'webp' \| 'jpeg' \| 'png'`, `quality?: number` |
Texture compression runs browser-native via `OffscreenCanvas`. No server call is made.
---
## `useExportModel`
Exports a `ModelFile` to a downloadable file. The hook takes optional `onSaved` and `onError` callbacks; the model itself is passed to the export method as an argument, not read from `ModelProvider` context.
```tsx
import { useExportModel } from '@vctrl/hooks/use-export-model'
import type { ModelFile } from '@vctrl/hooks/use-load-model'
function ExportButton({ file }: { file: ModelFile | null }) {
const { handleThreeGltfExport } = useExportModel()
return (
<button onClick={() => void handleThreeGltfExport(file, true)}>
Download GLB
</button>
)
}
```
### Methods
| Method | Description |
| -------------------------------------------------------------- | ------------------------------------------------------------------- |
| `handleThreeGltfExport(file, binary)` | Export a loaded Three.js model to `.glb` or a zipped `.gltf` bundle |
| `handleDocumentGltfExport(document, file, binary?, download?)` | Export from a glTF-Transform `Document` |
`binary = true` writes `.glb`; `binary = false` writes a zipped `.gltf` package.
---
## Additional exports
- `reconstructGltfFiles` from `@vctrl/hooks`
- `ModelProvider` and `useModelContext` from `@vctrl/hooks/use-load-model`
- Shared types such as `ModelFile`, `SceneLoadResult`, and `ServerSceneData`
---
## Peer dependencies
| Package | Version |
| ------- | -------------- |
| `react` | `^18 \|\| ^19` |
| `three` | `^0.177` |
---
## Related docs
- [Uploading Models](https://vectreal.com/docs/guides/upload)
- [Optimizing & Configuring](https://vectreal.com/docs/guides/optimize)
- [@vctrl/viewer](https://vectreal.com/docs/packages/viewer)
---
## Source
The full source and README live in [packages/hooks](https://github.com/Vectreal/vectreal-platform/tree/main/packages/hooks).
## License
AGPL-3.0-only. See [LICENSE.md](https://github.com/Vectreal/vectreal-platform/blob/main/packages/hooks/LICENSE.md).