@vctrl/hooks 0.0.9-4 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc +12 -0
- package/.eslintrc.json +18 -0
- package/LICENSE +0 -0
- package/README.md +209 -0
- package/package.json +1 -1
- package/project.json +16 -0
- package/src/use-load-model/event-system.ts +29 -0
- package/src/use-load-model/file-type-hooks/use-load-binary.ts +74 -0
- package/src/use-load-model/file-type-hooks/use-load-gltf.ts +115 -0
- package/src/use-load-model/loaders/create-gltf-loader.ts +20 -0
- package/{use-load-model/loaders/create-usdz-loader.d.ts → src/use-load-model/loaders/create-usdz-loader.ts} +7 -1
- package/src/use-load-model/model-context.tsx +18 -0
- package/src/use-load-model/state.ts +33 -0
- package/src/use-load-model/types.ts +53 -0
- package/src/use-load-model/use-load-model.ts +111 -0
- package/src/use-load-model/utils/array-buffer-to-base64.ts +20 -0
- package/src/use-load-model/utils/read-directory.ts +31 -0
- package/tsconfig.json +21 -0
- package/tsconfig.lib.json +25 -0
- package/vite.config.ts +59 -0
- package/index.cjs.js +0 -1
- package/index.es.js +0 -8
- package/model-context-ClI5AGc8.js +0 -19136
- package/model-context-mPTTbSMx.cjs +0 -3849
- package/use-load-model/event-system.d.ts +0 -7
- package/use-load-model/file-type-hooks/use-load-binary.d.ts +0 -11
- package/use-load-model/file-type-hooks/use-load-gltf.d.ts +0 -5
- package/use-load-model/loaders/create-gltf-loader.d.ts +0 -3
- package/use-load-model/model-context.d.ts +0 -20
- package/use-load-model/state.d.ts +0 -11
- package/use-load-model/types.d.ts +0 -48
- package/use-load-model/use-load-model.d.ts +0 -11
- package/use-load-model/utils/array-buffer-to-base64.d.ts +0 -2
- package/use-load-model/utils/read-directory.d.ts +0 -8
- package/use-load-model.cjs.js +0 -1
- package/use-load-model.es.js +0 -8
- /package/{index.d.ts → src/index.ts} +0 -0
- /package/{use-load-model/file-type-hooks/index.d.ts → src/use-load-model/file-type-hooks/index.ts} +0 -0
- /package/{use-load-model/index.d.ts → src/use-load-model/index.ts} +0 -0
- /package/{use-load-model/loaders/index.d.ts → src/use-load-model/loaders/index.ts} +0 -0
- /package/{use-load-model/utils/index.d.ts → src/use-load-model/utils/index.ts} +0 -0
package/.babelrc
ADDED
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": ["plugin:@nx/react", "../../.eslintrc.json"],
|
|
3
|
+
"ignorePatterns": ["!**/*"],
|
|
4
|
+
"overrides": [
|
|
5
|
+
{
|
|
6
|
+
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
|
7
|
+
"rules": {}
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"files": ["*.ts", "*.tsx"],
|
|
11
|
+
"rules": {}
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"files": ["*.js", "*.jsx"],
|
|
15
|
+
"rules": {}
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
package/LICENSE
ADDED
|
File without changes
|
package/README.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# vctrl/hooks
|
|
2
|
+
|
|
3
|
+
[
|
|
4
|
+
](https://github.com/Vectreal/vectreal-core/actions/workflows/version-release.yaml)
|
|
5
|
+
[](https://www.npmjs.com/package/@vctrl/hooks)
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
The package provides a powerful hook for loading various 3D model file formats, along with a React context for easy state management and an event system for handling different stages of the model loading process.
|
|
12
|
+
|
|
13
|
+
## Table of contents
|
|
14
|
+
|
|
15
|
+
- [vctrl/hooks](#vctrlhooks)
|
|
16
|
+
- [Overview](#overview)
|
|
17
|
+
- [Table of contents](#table-of-contents)
|
|
18
|
+
- [Features](#features)
|
|
19
|
+
- [Installation](#installation)
|
|
20
|
+
- [Usage](#usage)
|
|
21
|
+
- [API Reference](#api-reference)
|
|
22
|
+
- [useLoadModel](#useloadmodel)
|
|
23
|
+
- [Returns](#returns)
|
|
24
|
+
- [ModelContext](#modelcontext)
|
|
25
|
+
- [Event System](#event-system)
|
|
26
|
+
- [Events](#events)
|
|
27
|
+
- [Supported File Types](#supported-file-types)
|
|
28
|
+
- [File Loading Process](#file-loading-process)
|
|
29
|
+
- [State Management](#state-management)
|
|
30
|
+
- [Integration with Three.js](#integration-with-threejs)
|
|
31
|
+
- [Development](#development)
|
|
32
|
+
- [License](#license)
|
|
33
|
+
- [Contributing](#contributing)
|
|
34
|
+
- [Support](#support)
|
|
35
|
+
|
|
36
|
+
## Features
|
|
37
|
+
|
|
38
|
+
- Direct 3D model file loading (supports GLTF, GLB, and USDZ formats)
|
|
39
|
+
- React context for state management (ModelContext)
|
|
40
|
+
- Event system for handling loading progress and completion
|
|
41
|
+
- Three.js integration
|
|
42
|
+
- TypeScript support
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
To install the package, use npm or yarn:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install @vctrl/hooks
|
|
50
|
+
# or
|
|
51
|
+
yarn add @vctrl/hooks
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
The main hook exported by this package is `useLoadModel`. Here's a basic example of how to use it:
|
|
57
|
+
|
|
58
|
+
```jsx
|
|
59
|
+
import React from 'react';
|
|
60
|
+
import { useLoadModel } from '@vctrl/hooks/use-load-model';
|
|
61
|
+
|
|
62
|
+
function ModelLoader() {
|
|
63
|
+
const { handleFileUpload, file, progress, isLoading } = useLoadModel();
|
|
64
|
+
|
|
65
|
+
const onFileChange = (event) => {
|
|
66
|
+
const files = Array.from(event.target.files);
|
|
67
|
+
handleFileUpload(files);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div>
|
|
72
|
+
<input type="file" onChange={onFileChange} />
|
|
73
|
+
{isLoading && <p>Loading: {progress}%</p>}
|
|
74
|
+
{file && <p>Model loaded: {file.name}</p>}
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
> Multiple files can be handled, e.g. when uploading a `.gltf` model, its `.bin` file and the relavant image texture files as `.jpeg`/`.png` (Other texture file formats have not been tested).
|
|
81
|
+
|
|
82
|
+
## API Reference
|
|
83
|
+
|
|
84
|
+
### useLoadModel
|
|
85
|
+
|
|
86
|
+
The main hook for loading and managing 3D model files.
|
|
87
|
+
|
|
88
|
+
#### Returns
|
|
89
|
+
|
|
90
|
+
- `file`: The loaded file object of type `ModelFile | null`.
|
|
91
|
+
- `isLoading`: A boolean indicating whether a file is currently being loaded.
|
|
92
|
+
- `progress`: A number between 0 and 100 representing the loading progress.
|
|
93
|
+
- `handleFileUpload`: A function to handle file upload. It accepts an array of `File` objects or a mixed array of `File` objects and directory entries.
|
|
94
|
+
- `on`: A function to subscribe to events.
|
|
95
|
+
- `off`: A function to unsubscribe from events.
|
|
96
|
+
|
|
97
|
+
### ModelContext
|
|
98
|
+
|
|
99
|
+
A React context that provides the state and functions from `useLoadModel` to child components. It's implemented in `model-context.tsx`.
|
|
100
|
+
|
|
101
|
+
```jsx
|
|
102
|
+
import { ModelProvider, useModelContext } from '@vctrl/hooks/use-load-model';
|
|
103
|
+
|
|
104
|
+
function App() {
|
|
105
|
+
return (
|
|
106
|
+
<ModelProvider>
|
|
107
|
+
<ModelConsumer />
|
|
108
|
+
</ModelProvider>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function ModelConsumer() {
|
|
113
|
+
const { file, isLoading, progress, handleFileUpload } = useModelContext();
|
|
114
|
+
// Use the context values
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
> When using the React Context, only load models with the `useModelContext` hook.
|
|
119
|
+
|
|
120
|
+
### Event System
|
|
121
|
+
|
|
122
|
+
The package includes a custom event system for handling various stages of the model loading process. The event system is implemented in `event-system.ts` and provides three main methods:
|
|
123
|
+
|
|
124
|
+
- `emit<T extends EventTypes>(event: T, data: EventData[T]): void`
|
|
125
|
+
- `on<T extends EventTypes>(event: T, handler: EventHandler<T>): void`
|
|
126
|
+
- `off<T extends EventTypes>(event: T, handler: EventHandler<T>): void`
|
|
127
|
+
|
|
128
|
+
#### Events
|
|
129
|
+
|
|
130
|
+
You can subscribe to these events using the `on` method:
|
|
131
|
+
|
|
132
|
+
- `'UPLOAD_PROGRESS'`: Emitted with the current progress (0-100) during file upload.
|
|
133
|
+
- `'UPLOAD_COMPLETE'`: Emitted with the loaded file object when the upload is complete.
|
|
134
|
+
- `'MULTIPLE_3D_MODELS'`: Emitted if multiple supported 3D model files are detected in the upload.
|
|
135
|
+
- `'UNSUPPORTED_FILE_TYPE'`: Emitted if an unsupported file type is uploaded.
|
|
136
|
+
|
|
137
|
+
Example usage:
|
|
138
|
+
|
|
139
|
+
```javascript
|
|
140
|
+
const { on, off } = useLoadModel();
|
|
141
|
+
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
const handleProgress = (progress) => {
|
|
144
|
+
console.log(`Upload progress: ${progress}%`);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
on('UPLOAD_PROGRESS', handleProgress);
|
|
148
|
+
|
|
149
|
+
return () => off('UPLOAD_PROGRESS', handleProgress);
|
|
150
|
+
}, [on, off]);
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Supported File Types
|
|
154
|
+
|
|
155
|
+
The package currently supports the following 3D model file formats:
|
|
156
|
+
|
|
157
|
+
- GLTF (.gltf)
|
|
158
|
+
- GLB (.glb)
|
|
159
|
+
- USDZ (.usdz)
|
|
160
|
+
|
|
161
|
+
These are defined in the `ModelFileTypes` enum in `types.ts`.
|
|
162
|
+
|
|
163
|
+
## File Loading Process
|
|
164
|
+
|
|
165
|
+
The file loading process, particularly for GLTF files, is handled by the `useLoadGltf` hook in `use-load-gltf.ts`. This hook performs the following steps:
|
|
166
|
+
|
|
167
|
+
1. Parses the GLTF file content.
|
|
168
|
+
2. Embeds external resources (buffers and images) into the GLTF content.
|
|
169
|
+
3. Uses Three.js GLTFLoader to parse the modified GLTF content.
|
|
170
|
+
4. Dispatches actions to update the state with the loaded model.
|
|
171
|
+
|
|
172
|
+
The loading process includes progress updates, which are communicated through the event system.
|
|
173
|
+
|
|
174
|
+
## State Management
|
|
175
|
+
|
|
176
|
+
The package uses a reducer pattern for state management, implemented in `state.ts`. The state includes:
|
|
177
|
+
|
|
178
|
+
- `file`: The currently loaded model file.
|
|
179
|
+
- `isFileLoading`: A boolean indicating if a file is being loaded.
|
|
180
|
+
- `progress`: The current loading progress.
|
|
181
|
+
- `supportedFileTypes`: An array of supported file types.
|
|
182
|
+
|
|
183
|
+
Actions for updating the state are defined in the `Action` type in `types.ts`.
|
|
184
|
+
|
|
185
|
+
## Integration with Three.js
|
|
186
|
+
|
|
187
|
+
The package integrates with Three.js for handling 3D model rendering. The loaded models are compatible with Three.js scene rendering, with the model stored as a Three.js `Object3D` in the `ModelFile` interface.
|
|
188
|
+
|
|
189
|
+
## Development
|
|
190
|
+
|
|
191
|
+
This package is part of a monorepo workspace managed with Nx. To contribute or modify the package:
|
|
192
|
+
|
|
193
|
+
1. Clone the monorepo
|
|
194
|
+
2. Install dependencies: `npm install` or `yarn install`
|
|
195
|
+
3. Make your changes
|
|
196
|
+
4. Build the package: `nx build vctrl/hooks`
|
|
197
|
+
5. Test your changes: `nx test vctrl/hooks`
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
Please refer to the LICENSE file in the package root for licensing information.
|
|
202
|
+
|
|
203
|
+
## Contributing
|
|
204
|
+
|
|
205
|
+
Contributions are welcome! Please read the contributing guidelines in the [vectreal-core](https://github.com/vectreal/vectreal-core) monorepo before submitting pull requests.
|
|
206
|
+
|
|
207
|
+
## Support
|
|
208
|
+
|
|
209
|
+
For issues, feature requests, or questions, please file an issue in the GitHub repository.
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.0.9
|
|
2
|
+
"version": "0.0.9",
|
|
3
3
|
"name": "@vctrl/hooks",
|
|
4
4
|
"description": "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.",
|
|
5
5
|
"bugs": {
|
package/project.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vctrl/hooks",
|
|
3
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "packages/hooks/src",
|
|
5
|
+
"projectType": "library",
|
|
6
|
+
"tags": [],
|
|
7
|
+
"targets": {
|
|
8
|
+
"build": {
|
|
9
|
+
"executor": "@nx/vite:build",
|
|
10
|
+
"options": {
|
|
11
|
+
"outputPath": "dist/packages/vctrl/hooks",
|
|
12
|
+
"configFile": "packages/hooks/vite.config.ts"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { EventData, EventHandler, EventTypes } from './types';
|
|
2
|
+
|
|
3
|
+
const listeners = {} as Record<EventTypes, EventHandler<EventTypes>[]>;
|
|
4
|
+
const eventSystem = {
|
|
5
|
+
emit<T extends EventTypes>(event: T, data: EventData[T]): void {
|
|
6
|
+
const handlers = listeners[event];
|
|
7
|
+
if (handlers) {
|
|
8
|
+
handlers.forEach((handler) => handler(data));
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
on<T extends EventTypes>(event: T, handler: EventHandler<T>): void {
|
|
13
|
+
if (!listeners[event]) {
|
|
14
|
+
listeners[event] = [];
|
|
15
|
+
}
|
|
16
|
+
listeners[event].push(handler as EventHandler<EventTypes>);
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
off<T extends EventTypes>(event: T, handler: EventHandler<T>): void {
|
|
20
|
+
const handlers = listeners[event];
|
|
21
|
+
if (handlers) {
|
|
22
|
+
listeners[event] = handlers.filter(
|
|
23
|
+
(h) => h !== handler,
|
|
24
|
+
) as EventHandler<EventTypes>[];
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default eventSystem;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Action, ModelFileTypes } from '../types';
|
|
4
|
+
import { createGltfLoader, createUsdzLoader } from '../loaders';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Hook to load binary files
|
|
8
|
+
*
|
|
9
|
+
* @param dispatch - The dispatch function from the useReducer hook
|
|
10
|
+
* @returns An object containing the loadBinary function
|
|
11
|
+
*/
|
|
12
|
+
function useLoadBinary(dispatch: React.Dispatch<Action>) {
|
|
13
|
+
/**
|
|
14
|
+
* Function to read binary files
|
|
15
|
+
*
|
|
16
|
+
* @param file - The binary file to read
|
|
17
|
+
* @param fileType - The type of file to read
|
|
18
|
+
* @param onProgress - The function to call when progress is made
|
|
19
|
+
* @returns {void}
|
|
20
|
+
*/
|
|
21
|
+
const loadBinary = useCallback(
|
|
22
|
+
(file: File, fileType: ModelFileTypes, onProgress: () => void) => {
|
|
23
|
+
const reader = new FileReader();
|
|
24
|
+
|
|
25
|
+
reader.onload = async (event: ProgressEvent<FileReader>) => {
|
|
26
|
+
if (event.target?.result) {
|
|
27
|
+
const arrayBuffer = event.target.result as ArrayBuffer;
|
|
28
|
+
|
|
29
|
+
if (fileType === ModelFileTypes.glb) {
|
|
30
|
+
const gltfLoader = createGltfLoader();
|
|
31
|
+
|
|
32
|
+
gltfLoader.parse(arrayBuffer, '', (gltf) => {
|
|
33
|
+
dispatch({
|
|
34
|
+
type: 'SET_FILE',
|
|
35
|
+
payload: {
|
|
36
|
+
model: gltf.scene,
|
|
37
|
+
type: ModelFileTypes.glb,
|
|
38
|
+
name: file.name,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
} else if (fileType === ModelFileTypes.usdz) {
|
|
43
|
+
const usdzLoader = createUsdzLoader();
|
|
44
|
+
const model = usdzLoader.parse(arrayBuffer);
|
|
45
|
+
|
|
46
|
+
dispatch({
|
|
47
|
+
type: 'SET_FILE',
|
|
48
|
+
payload: {
|
|
49
|
+
model: model,
|
|
50
|
+
type: ModelFileTypes.usdz,
|
|
51
|
+
name: file.name,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
onProgress(); // Call progress callback when binary file is loaded
|
|
57
|
+
dispatch({ type: 'SET_FILE_LOADING', payload: false });
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
reader.onerror = (error) => {
|
|
62
|
+
console.error('Error reading file:', error);
|
|
63
|
+
dispatch({ type: 'SET_FILE_LOADING', payload: false });
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
reader.readAsArrayBuffer(file);
|
|
67
|
+
},
|
|
68
|
+
[dispatch],
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return { loadBinary };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export default useLoadBinary;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Action, ModelFileTypes, ReducedGltf } from '../types';
|
|
4
|
+
import { arrayBufferToBase64 } from '../utils';
|
|
5
|
+
import { createGltfLoader } from '../loaders';
|
|
6
|
+
|
|
7
|
+
function useLoadGltf(dispatch: React.Dispatch<Action>) {
|
|
8
|
+
const embedExternalResources = useCallback(
|
|
9
|
+
async (
|
|
10
|
+
gltfContent: ReducedGltf,
|
|
11
|
+
otherFiles: File[],
|
|
12
|
+
onProgress: (progress: number) => void,
|
|
13
|
+
) => {
|
|
14
|
+
const fileMap = new Map(otherFiles.map((file) => [file.name, file]));
|
|
15
|
+
const totalFiles =
|
|
16
|
+
(gltfContent.buffers?.length || 0) + (gltfContent.images?.length || 0);
|
|
17
|
+
let processedFiles = 0;
|
|
18
|
+
|
|
19
|
+
const updateProgress = () => {
|
|
20
|
+
processedFiles++;
|
|
21
|
+
onProgress((processedFiles / totalFiles) * 100);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Embed buffers
|
|
25
|
+
if (gltfContent.buffers) {
|
|
26
|
+
for (let i = 0; i < gltfContent.buffers.length; i++) {
|
|
27
|
+
const buffer = gltfContent.buffers[i];
|
|
28
|
+
if (!buffer.uri || buffer.uri.startsWith('data:')) {
|
|
29
|
+
updateProgress();
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const fileName = buffer.uri.split('/').pop() || '';
|
|
34
|
+
const file = fileMap.get(fileName);
|
|
35
|
+
if (file) {
|
|
36
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
37
|
+
const base64 = await arrayBufferToBase64(arrayBuffer);
|
|
38
|
+
buffer.uri = `data:application/octet-stream;base64,${base64}`;
|
|
39
|
+
}
|
|
40
|
+
updateProgress();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Embed images
|
|
45
|
+
if (gltfContent.images) {
|
|
46
|
+
for (let i = 0; i < gltfContent.images.length; i++) {
|
|
47
|
+
const image = gltfContent.images[i];
|
|
48
|
+
if (!image.uri || image.uri.startsWith('data:')) {
|
|
49
|
+
updateProgress();
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const fileName = image.uri.split('/').pop() || '';
|
|
54
|
+
const file = fileMap.get(fileName);
|
|
55
|
+
if (file) {
|
|
56
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
57
|
+
const base64 = await arrayBufferToBase64(arrayBuffer);
|
|
58
|
+
const mimeType = file.type || 'image/png';
|
|
59
|
+
image.uri = `data:${mimeType};base64,${base64}`;
|
|
60
|
+
}
|
|
61
|
+
updateProgress();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return gltfContent;
|
|
66
|
+
},
|
|
67
|
+
[],
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const loadGltf = useCallback(
|
|
71
|
+
(
|
|
72
|
+
gltfFile: File,
|
|
73
|
+
otherFiles: File[],
|
|
74
|
+
onProgress: (progress: number) => void,
|
|
75
|
+
) => {
|
|
76
|
+
const reader = new FileReader();
|
|
77
|
+
reader.onload = async (e) => {
|
|
78
|
+
const gltfContent = JSON.parse(e.target?.result as string);
|
|
79
|
+
onProgress(10); // Initial progress after parsing GLTF
|
|
80
|
+
|
|
81
|
+
const modifiedGLTF = await embedExternalResources(
|
|
82
|
+
gltfContent,
|
|
83
|
+
otherFiles,
|
|
84
|
+
(embeddingProgress) => {
|
|
85
|
+
// Map embedding progress to 10-90% range
|
|
86
|
+
onProgress(10 + embeddingProgress * 0.8);
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const gltfLoader = createGltfLoader();
|
|
91
|
+
|
|
92
|
+
gltfLoader.parse(JSON.stringify(modifiedGLTF), '', (gltf) => {
|
|
93
|
+
dispatch({
|
|
94
|
+
type: 'SET_FILE',
|
|
95
|
+
payload: {
|
|
96
|
+
model: gltf.scene,
|
|
97
|
+
type: ModelFileTypes.gltf,
|
|
98
|
+
name: gltfFile.name,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
onProgress(100); // Final progress
|
|
104
|
+
dispatch({ type: 'SET_FILE_LOADING', payload: false });
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
reader.readAsText(gltfFile);
|
|
108
|
+
},
|
|
109
|
+
[dispatch, embedExternalResources],
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
return { loadGltf };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export default useLoadGltf;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { WebGLRenderer } from 'three';
|
|
2
|
+
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
|
3
|
+
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
|
4
|
+
import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader';
|
|
5
|
+
|
|
6
|
+
function createGltfLoader() {
|
|
7
|
+
const dracoLoader = new DRACOLoader();
|
|
8
|
+
dracoLoader.setDecoderPath('/draco/');
|
|
9
|
+
|
|
10
|
+
const ktxLoader = new KTX2Loader();
|
|
11
|
+
ktxLoader.setTranscoderPath('/draco/');
|
|
12
|
+
|
|
13
|
+
const gltfLoader = new GLTFLoader()
|
|
14
|
+
.setDRACOLoader(dracoLoader)
|
|
15
|
+
.setKTX2Loader(ktxLoader.detectSupport(new WebGLRenderer()));
|
|
16
|
+
|
|
17
|
+
return gltfLoader;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default createGltfLoader;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
import useLoadModel from './use-load-model';
|
|
3
|
+
|
|
4
|
+
const ModelContext = createContext({} as ReturnType<typeof useLoadModel>);
|
|
5
|
+
|
|
6
|
+
const ModelProvider = ({ children }: React.PropsWithChildren) => {
|
|
7
|
+
const value = useLoadModel();
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<ModelContext.Provider value={value}>{children}</ModelContext.Provider>
|
|
11
|
+
);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const useModelContext = () => {
|
|
15
|
+
return useContext(ModelContext);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export { ModelContext, ModelProvider, useModelContext };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Action, ModelFileTypes, State } from './types';
|
|
2
|
+
|
|
3
|
+
// Initial state
|
|
4
|
+
export const initialState: State = {
|
|
5
|
+
file: null,
|
|
6
|
+
isFileLoading: false,
|
|
7
|
+
progress: 0,
|
|
8
|
+
supportedFileTypes: Object.values(ModelFileTypes),
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Reducer function for the useReadModelFiles hook
|
|
13
|
+
*
|
|
14
|
+
* @param state - The current state of the reducer
|
|
15
|
+
* @param action - The action to be performed
|
|
16
|
+
* @returns The updated state
|
|
17
|
+
*/
|
|
18
|
+
function reducer(state: State, action: Action): State {
|
|
19
|
+
switch (action.type) {
|
|
20
|
+
case 'SET_FILE':
|
|
21
|
+
return { ...state, file: action.payload };
|
|
22
|
+
case 'SET_FILE_LOADING':
|
|
23
|
+
return { ...state, isFileLoading: action.payload };
|
|
24
|
+
case 'SET_PROGRESS':
|
|
25
|
+
return { ...state, progress: action.payload };
|
|
26
|
+
case 'RESET_STATE':
|
|
27
|
+
return { ...initialState };
|
|
28
|
+
default:
|
|
29
|
+
return state;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default reducer;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Object3D } from 'three';
|
|
2
|
+
|
|
3
|
+
export enum ModelFileTypes {
|
|
4
|
+
gltf = 'gltf',
|
|
5
|
+
glb = 'glb',
|
|
6
|
+
usdz = 'usdz',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type InputFileOrDirectory = (File | FileSystemDirectoryHandle)[];
|
|
10
|
+
|
|
11
|
+
export interface ReducedGltf {
|
|
12
|
+
images: { name: string; uri: string }[];
|
|
13
|
+
buffers: { bytelength: string; uri: string }[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ModelFile {
|
|
17
|
+
model: Object3D;
|
|
18
|
+
type: ModelFileTypes;
|
|
19
|
+
name: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Updated State interface
|
|
23
|
+
export interface State {
|
|
24
|
+
file: ModelFile | null;
|
|
25
|
+
isFileLoading: boolean;
|
|
26
|
+
progress: number;
|
|
27
|
+
supportedFileTypes: ModelFileTypes[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Updated Action types
|
|
31
|
+
export type Action =
|
|
32
|
+
| { type: 'SET_FILE'; payload: ModelFile }
|
|
33
|
+
| { type: 'SET_FILE_LOADING'; payload: boolean }
|
|
34
|
+
| { type: 'SET_PROGRESS'; payload: number }
|
|
35
|
+
| { type: 'RESET_STATE' };
|
|
36
|
+
|
|
37
|
+
// Updated Event types
|
|
38
|
+
export type EventTypes =
|
|
39
|
+
| 'MULTIPLE_3D_MODELS'
|
|
40
|
+
| 'UNSUPPORTED_FILE_TYPE'
|
|
41
|
+
| 'UPLOAD_PROGRESS'
|
|
42
|
+
| 'UPLOAD_COMPLETE';
|
|
43
|
+
|
|
44
|
+
// Updated event data types
|
|
45
|
+
export type EventData = {
|
|
46
|
+
MULTIPLE_3D_MODELS: File[];
|
|
47
|
+
UNSUPPORTED_FILE_TYPE: File[];
|
|
48
|
+
UPLOAD_PROGRESS: number;
|
|
49
|
+
UPLOAD_COMPLETE: State['file'];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Updated EventHandler type
|
|
53
|
+
export type EventHandler<T extends EventTypes> = (data: EventData[T]) => void;
|