@tonybfox/threejs-tools 1.0.0
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/README.md +321 -0
- package/dist/asset-loader/index.cjs +376 -0
- package/dist/asset-loader/index.cjs.map +1 -0
- package/dist/asset-loader/index.d.mts +101 -0
- package/dist/asset-loader/index.d.ts +101 -0
- package/dist/asset-loader/index.mjs +7 -0
- package/dist/asset-loader/index.mjs.map +1 -0
- package/dist/camera/index.cjs +313 -0
- package/dist/camera/index.cjs.map +1 -0
- package/dist/camera/index.d.mts +82 -0
- package/dist/camera/index.d.ts +82 -0
- package/dist/camera/index.mjs +7 -0
- package/dist/camera/index.mjs.map +1 -0
- package/dist/chunk-5DP6WDB3.mjs +1161 -0
- package/dist/chunk-5DP6WDB3.mjs.map +1 -0
- package/dist/chunk-BJKSICFA.mjs +1579 -0
- package/dist/chunk-BJKSICFA.mjs.map +1 -0
- package/dist/chunk-BYRZCHE7.mjs +277 -0
- package/dist/chunk-BYRZCHE7.mjs.map +1 -0
- package/dist/chunk-EIROAPF7.mjs +387 -0
- package/dist/chunk-EIROAPF7.mjs.map +1 -0
- package/dist/chunk-EQDOX34V.mjs +164 -0
- package/dist/chunk-EQDOX34V.mjs.map +1 -0
- package/dist/chunk-IIAZ2WJJ.mjs +405 -0
- package/dist/chunk-IIAZ2WJJ.mjs.map +1 -0
- package/dist/chunk-L4VIIJZD.mjs +340 -0
- package/dist/chunk-L4VIIJZD.mjs.map +1 -0
- package/dist/chunk-P35QJCOG.mjs +339 -0
- package/dist/chunk-P35QJCOG.mjs.map +1 -0
- package/dist/chunk-R64RVBRM.mjs +394 -0
- package/dist/chunk-R64RVBRM.mjs.map +1 -0
- package/dist/compass/index.cjs +375 -0
- package/dist/compass/index.cjs.map +1 -0
- package/dist/compass/index.d.mts +58 -0
- package/dist/compass/index.d.ts +58 -0
- package/dist/compass/index.mjs +7 -0
- package/dist/compass/index.mjs.map +1 -0
- package/dist/grid/index.cjs +200 -0
- package/dist/grid/index.cjs.map +1 -0
- package/dist/grid/index.d.mts +43 -0
- package/dist/grid/index.d.ts +43 -0
- package/dist/grid/index.mjs +7 -0
- package/dist/grid/index.mjs.map +1 -0
- package/dist/index.cjs +5049 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.mjs +47 -0
- package/dist/index.mjs.map +1 -0
- package/dist/measurements/index.cjs +1198 -0
- package/dist/measurements/index.cjs.map +1 -0
- package/dist/measurements/index.d.mts +449 -0
- package/dist/measurements/index.d.ts +449 -0
- package/dist/measurements/index.mjs +9 -0
- package/dist/measurements/index.mjs.map +1 -0
- package/dist/sunlight/index.cjs +441 -0
- package/dist/sunlight/index.cjs.map +1 -0
- package/dist/sunlight/index.d.mts +92 -0
- package/dist/sunlight/index.d.ts +92 -0
- package/dist/sunlight/index.mjs +7 -0
- package/dist/sunlight/index.mjs.map +1 -0
- package/dist/terrain/index.cjs +423 -0
- package/dist/terrain/index.cjs.map +1 -0
- package/dist/terrain/index.d.mts +219 -0
- package/dist/terrain/index.d.ts +219 -0
- package/dist/terrain/index.mjs +7 -0
- package/dist/terrain/index.mjs.map +1 -0
- package/dist/transform-controls/index.cjs +1587 -0
- package/dist/transform-controls/index.cjs.map +1 -0
- package/dist/transform-controls/index.d.mts +162 -0
- package/dist/transform-controls/index.d.ts +162 -0
- package/dist/transform-controls/index.mjs +13 -0
- package/dist/transform-controls/index.mjs.map +1 -0
- package/dist/view-helper/index.cjs +430 -0
- package/dist/view-helper/index.cjs.map +1 -0
- package/dist/view-helper/index.d.mts +75 -0
- package/dist/view-helper/index.d.ts +75 -0
- package/dist/view-helper/index.mjs +7 -0
- package/dist/view-helper/index.mjs.map +1 -0
- package/package.json +124 -0
package/README.md
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
# ThreeJS Tools
|
|
2
|
+
|
|
3
|
+
A collection of utilities and tools for Three.js development, organized as a monorepo with separate packages for different functionalities. The root `@tonybfox/threejs-tools` package now bundles every tool so you can install a single npm package while still importing submodules (for example `@tonybfox/threejs-tools/camera` or `@tonybfox/threejs-tools/terrain`).
|
|
4
|
+
|
|
5
|
+
## 📦 Packages
|
|
6
|
+
|
|
7
|
+
- **[@tonybfox/threejs-camera](./packages/camera/)** - Camera utilities and controls
|
|
8
|
+
- **[@tonybfox/threejs-grid](./packages/grid/)** - Infinite grid component
|
|
9
|
+
- **[@tonybfox/threejs-measurements](./packages/measurements/)** - Measurement tools for 3D scenes
|
|
10
|
+
- **[@tonybfox/threejs-asset-loader](./packages/asset-loader/)** - Universal asset loader with progress tracking and caching
|
|
11
|
+
|
|
12
|
+
## 🛠️ Prerequisites
|
|
13
|
+
|
|
14
|
+
Make sure you have the following installed on your system:
|
|
15
|
+
|
|
16
|
+
- **Node.js** (version 16 or higher)
|
|
17
|
+
- **pnpm** (recommended package manager)
|
|
18
|
+
|
|
19
|
+
If you don't have pnpm installed, you can install it globally:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g pnpm
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 🚀 Installation
|
|
26
|
+
|
|
27
|
+
1. **Clone the repository:**
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
git clone https://github.com/tonybfox/threejs-tools.git
|
|
31
|
+
cd threejs-tools
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
2. **Install dependencies:**
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pnpm install
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
This will install all dependencies for the root workspace and all packages.
|
|
41
|
+
|
|
42
|
+
## 🔨 Building
|
|
43
|
+
|
|
44
|
+
### Build the publishable bundle
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pnpm build
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Generates the aggregated `dist/` folder that is published to npm (includes CommonJS, ESM, and type declarations for every tool).
|
|
51
|
+
|
|
52
|
+
### Build individual workspace packages
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pnpm build:packages
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Builds each package inside `packages/` – useful for local development and examples.
|
|
59
|
+
|
|
60
|
+
### Build everything including examples
|
|
61
|
+
|
|
62
|
+
To build both packages and examples:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pnpm build:all
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Build only examples
|
|
69
|
+
|
|
70
|
+
To build just the examples:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pnpm build:examples
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Clean build artifacts
|
|
77
|
+
|
|
78
|
+
To clean all build outputs:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pnpm clean
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 🎮 Running Examples
|
|
85
|
+
|
|
86
|
+
The project includes interactive examples demonstrating each package's functionality.
|
|
87
|
+
|
|
88
|
+
### Development Mode
|
|
89
|
+
|
|
90
|
+
1. **Install dependencies and build packages:**
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pnpm install
|
|
94
|
+
pnpm build:packages
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
2. **Start the development server:**
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
cd examples
|
|
101
|
+
pnpm dev
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
3. **Open your browser** and navigate to `http://localhost:5173`
|
|
105
|
+
|
|
106
|
+
### Production Preview
|
|
107
|
+
|
|
108
|
+
To preview the built examples:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
cd examples
|
|
112
|
+
pnpm build
|
|
113
|
+
pnpm preview
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## 📁 Examples Overview
|
|
117
|
+
|
|
118
|
+
### 🔧 Grid Package Example
|
|
119
|
+
|
|
120
|
+
- **Location:** `/examples/grid/`
|
|
121
|
+
- **Features:**
|
|
122
|
+
- Interactive InfiniteGrid component
|
|
123
|
+
- Adjustable grid size and divisions
|
|
124
|
+
- Multiple grid instances
|
|
125
|
+
- Grid positioning and rotation
|
|
126
|
+
- **Controls:** Use the control panel to modify grid properties
|
|
127
|
+
|
|
128
|
+
### 📷 Camera Package Example
|
|
129
|
+
|
|
130
|
+
- **Location:** `/examples/camera/`
|
|
131
|
+
- **Features:**
|
|
132
|
+
- Camera position presets (Front, Side, Top, Isometric)
|
|
133
|
+
- Smooth camera animations
|
|
134
|
+
- Orbit animation mode
|
|
135
|
+
- Perspective vs Orthographic camera switching
|
|
136
|
+
- Real-time camera info display
|
|
137
|
+
- **Controls:** Click buttons to change camera views and modes
|
|
138
|
+
|
|
139
|
+
### 📏 Measurements Package Example
|
|
140
|
+
|
|
141
|
+
- **Location:** `/examples/measurements/`
|
|
142
|
+
- **Features:**
|
|
143
|
+
- Distance measurement tool
|
|
144
|
+
- Angle measurement (3-point)
|
|
145
|
+
- Area calculation tool
|
|
146
|
+
- Grid snap functionality
|
|
147
|
+
- Unit switching (meters/feet)
|
|
148
|
+
- Interactive labels
|
|
149
|
+
- **Usage:** Select a tool and click in the 3D scene to measure
|
|
150
|
+
|
|
151
|
+
### 📦 Asset Loader Package Example
|
|
152
|
+
|
|
153
|
+
- **Location:** `/examples/asset-loader/`
|
|
154
|
+
- **Features:**
|
|
155
|
+
- Universal loader for GLTF, FBX, and OBJ formats
|
|
156
|
+
- Visual placeholder with shader fill effect
|
|
157
|
+
- Real-time progress tracking
|
|
158
|
+
- Asset caching for performance
|
|
159
|
+
- Optional low-res model loading
|
|
160
|
+
- Event-driven architecture
|
|
161
|
+
- **Usage:** Use the control panel to create placeholders and see the loading system in action
|
|
162
|
+
|
|
163
|
+
### 🎮 Interactive Features
|
|
164
|
+
|
|
165
|
+
Each example includes:
|
|
166
|
+
|
|
167
|
+
- **Mouse Controls:** Orbit, zoom, and pan with OrbitControls
|
|
168
|
+
- **Responsive Design:** Automatic window resize handling
|
|
169
|
+
- **Visual Feedback:** Hover effects and interactive elements
|
|
170
|
+
- **Real-time Updates:** Live parameter adjustment
|
|
171
|
+
- **Performance Monitoring:** Optional FPS and render statistics
|
|
172
|
+
|
|
173
|
+
### 🛠 Example Development
|
|
174
|
+
|
|
175
|
+
#### Shared Utilities
|
|
176
|
+
|
|
177
|
+
The `/examples/shared/utils.js` file contains common utilities for:
|
|
178
|
+
|
|
179
|
+
- Scene setup and configuration
|
|
180
|
+
- Object creation helpers
|
|
181
|
+
- UI component helpers
|
|
182
|
+
- Performance monitoring
|
|
183
|
+
|
|
184
|
+
#### Adding New Examples
|
|
185
|
+
|
|
186
|
+
1. Create a new folder in `/examples/`
|
|
187
|
+
2. Add `index.html` and `main.js` files
|
|
188
|
+
3. Update `vite.config.js` to include the new example
|
|
189
|
+
4. Update the main `index.html` with a link to your example
|
|
190
|
+
|
|
191
|
+
#### Configuration
|
|
192
|
+
|
|
193
|
+
Examples use Vite for development with:
|
|
194
|
+
|
|
195
|
+
- ES modules support
|
|
196
|
+
- Hot module replacement
|
|
197
|
+
- Multi-page application setup
|
|
198
|
+
- Three.js optimization
|
|
199
|
+
- Workspace linking for package imports
|
|
200
|
+
|
|
201
|
+
## 📦 Publishing to npm
|
|
202
|
+
|
|
203
|
+
Steps to publish the unified `threejs-tools` package:
|
|
204
|
+
|
|
205
|
+
1. Ensure you are logged in: `pnpm login`
|
|
206
|
+
2. Build the bundle: `pnpm build`
|
|
207
|
+
3. Publish from the repository root: `pnpm publish --access public`
|
|
208
|
+
|
|
209
|
+
The published package exposes:
|
|
210
|
+
|
|
211
|
+
- `@tonybfox/threejs-tools` – combined exports (tree-shakeable)
|
|
212
|
+
- `@tonybfox/threejs-tools/<tool>` – direct submodule imports for each tool (`camera`, `terrain`, `measurements`, etc.)
|
|
213
|
+
|
|
214
|
+
## 🏗️ Development Workflow
|
|
215
|
+
|
|
216
|
+
### Working on packages
|
|
217
|
+
|
|
218
|
+
1. **Make changes** to any package in the `packages/` directory
|
|
219
|
+
2. **Build the packages:**
|
|
220
|
+
```bash
|
|
221
|
+
pnpm build
|
|
222
|
+
```
|
|
223
|
+
3. **Test your changes** in the examples:
|
|
224
|
+
```bash
|
|
225
|
+
cd examples
|
|
226
|
+
pnpm dev
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The examples use workspace references (`workspace:*`), so your local changes will be automatically reflected.
|
|
230
|
+
|
|
231
|
+
### Adding new packages
|
|
232
|
+
|
|
233
|
+
1. Create a new directory in `packages/`
|
|
234
|
+
2. Add a `package.json` with the `@tonybfox/threejs-*` naming convention
|
|
235
|
+
3. The workspace will automatically include it
|
|
236
|
+
|
|
237
|
+
## 📝 Code Formatting
|
|
238
|
+
|
|
239
|
+
This project uses Prettier for code formatting:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
# Format all files
|
|
243
|
+
pnpm format
|
|
244
|
+
|
|
245
|
+
# Check formatting without making changes
|
|
246
|
+
pnpm format:check
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## 🏛️ Project Structure
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
threejs-tools/
|
|
253
|
+
├── packages/ # Library packages
|
|
254
|
+
│ ├── camera/ # Camera utilities
|
|
255
|
+
│ ├── core/ # Core functionality
|
|
256
|
+
│ ├── grid/ # Grid components
|
|
257
|
+
│ └── measurements/ # Measurement tools
|
|
258
|
+
├── examples/ # Interactive examples
|
|
259
|
+
│ ├── camera/ # Camera example
|
|
260
|
+
│ ├── grid/ # Grid example
|
|
261
|
+
│ ├── measurements/ # Measurements example
|
|
262
|
+
│ └── shared/ # Shared example utilities
|
|
263
|
+
├── package.json # Root workspace configuration
|
|
264
|
+
├── pnpm-workspace.yaml # pnpm workspace definition
|
|
265
|
+
└── tsconfig.json # TypeScript configuration
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## 🔧 Scripts Reference
|
|
269
|
+
|
|
270
|
+
| Script | Description |
|
|
271
|
+
| --------------------- | -------------------------------------- |
|
|
272
|
+
| `pnpm build` | Build all packages (excludes examples) |
|
|
273
|
+
| `pnpm build:all` | Build packages and examples |
|
|
274
|
+
| `pnpm build:examples` | Build only examples |
|
|
275
|
+
| `pnpm clean` | Clean all build artifacts |
|
|
276
|
+
| `pnpm format` | Format all files with Prettier |
|
|
277
|
+
| `pnpm format:check` | Check code formatting |
|
|
278
|
+
|
|
279
|
+
## 🚨 Troubleshooting
|
|
280
|
+
|
|
281
|
+
### Common Issues
|
|
282
|
+
|
|
283
|
+
1. **Package not found:** Run `pnpm build` from root directory
|
|
284
|
+
2. **Vite not starting:** Check Node.js version compatibility (20.18+)
|
|
285
|
+
3. **Import errors:** Ensure packages are built and workspace is properly configured
|
|
286
|
+
4. **WebGL errors:** Check browser WebGL support
|
|
287
|
+
|
|
288
|
+
### Performance Tips
|
|
289
|
+
|
|
290
|
+
- Use the built-in performance monitor in examples
|
|
291
|
+
- Test with different device capabilities
|
|
292
|
+
- Monitor memory usage with large scenes
|
|
293
|
+
- Use appropriate LOD for complex geometries
|
|
294
|
+
|
|
295
|
+
## 🤝 Contributing
|
|
296
|
+
|
|
297
|
+
1. Fork the repository
|
|
298
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
299
|
+
3. Make your changes
|
|
300
|
+
4. Run tests and formatting (`pnpm format`)
|
|
301
|
+
5. Build packages (`pnpm build`)
|
|
302
|
+
6. Test examples (`cd examples && pnpm dev`)
|
|
303
|
+
7. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
304
|
+
8. Push to the branch (`git push origin feature/amazing-feature`)
|
|
305
|
+
9. Open a Pull Request
|
|
306
|
+
|
|
307
|
+
## 📄 License
|
|
308
|
+
|
|
309
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
310
|
+
|
|
311
|
+
## 🐛 Issues & Support
|
|
312
|
+
|
|
313
|
+
If you encounter any issues or have questions:
|
|
314
|
+
|
|
315
|
+
1. Check existing [GitHub Issues](https://github.com/tonybfox/threejs-tools/issues)
|
|
316
|
+
2. Create a new issue with detailed information
|
|
317
|
+
3. Include steps to reproduce and expected behavior
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
Made with ❤️ for the Three.js community
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// packages/asset-loader/src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
AssetLoader: () => AssetLoader
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(src_exports);
|
|
36
|
+
|
|
37
|
+
// packages/asset-loader/src/AssetLoader.ts
|
|
38
|
+
var THREE = __toESM(require("three"));
|
|
39
|
+
var import_GLTFLoader = require("three/examples/jsm/loaders/GLTFLoader.js");
|
|
40
|
+
var import_FBXLoader = require("three/examples/jsm/loaders/FBXLoader.js");
|
|
41
|
+
var import_OBJLoader = require("three/examples/jsm/loaders/OBJLoader.js");
|
|
42
|
+
var AssetLoader = class extends THREE.EventDispatcher {
|
|
43
|
+
constructor() {
|
|
44
|
+
super();
|
|
45
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
46
|
+
this.placeholder = null;
|
|
47
|
+
this.loadedAsset = null;
|
|
48
|
+
this.lowResAsset = null;
|
|
49
|
+
this.errorColor = 16729156;
|
|
50
|
+
this.errorOpacity = 0.5;
|
|
51
|
+
this.gltfLoader = new import_GLTFLoader.GLTFLoader();
|
|
52
|
+
this.fbxLoader = new import_FBXLoader.FBXLoader();
|
|
53
|
+
this.objLoader = new import_OBJLoader.OBJLoader();
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Create a placeholder cube with shader effect
|
|
57
|
+
*/
|
|
58
|
+
createPlaceholder(size, color = 5227511, opacity = 0.3) {
|
|
59
|
+
const [width, height, depth] = size;
|
|
60
|
+
const geometry = new THREE.BoxGeometry(width, height, depth);
|
|
61
|
+
const material = new THREE.ShaderMaterial({
|
|
62
|
+
side: THREE.DoubleSide,
|
|
63
|
+
depthWrite: false,
|
|
64
|
+
blending: THREE.AdditiveBlending,
|
|
65
|
+
transparent: true,
|
|
66
|
+
uniforms: {
|
|
67
|
+
color: { value: new THREE.Color(color) },
|
|
68
|
+
opacity: { value: opacity },
|
|
69
|
+
fillProgress: { value: 0 },
|
|
70
|
+
time: { value: 0 },
|
|
71
|
+
isError: { value: 0 }
|
|
72
|
+
},
|
|
73
|
+
vertexShader: `
|
|
74
|
+
varying vec3 vPosition;
|
|
75
|
+
void main() {
|
|
76
|
+
vPosition = position;
|
|
77
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
78
|
+
}
|
|
79
|
+
`,
|
|
80
|
+
fragmentShader: `
|
|
81
|
+
uniform vec3 color;
|
|
82
|
+
uniform float opacity;
|
|
83
|
+
uniform float fillProgress;
|
|
84
|
+
uniform float time;
|
|
85
|
+
uniform float isError;
|
|
86
|
+
varying vec3 vPosition;
|
|
87
|
+
|
|
88
|
+
void main() {
|
|
89
|
+
float normalizedY = (vPosition.y + 1.0) / 2.0; // Normalize from -1,1 to 0,1
|
|
90
|
+
float alpha = opacity;
|
|
91
|
+
|
|
92
|
+
// Create fill-up effect
|
|
93
|
+
if (normalizedY > fillProgress) {
|
|
94
|
+
alpha *= 0.3; // Reduce opacity for unfilled parts
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Error state effects
|
|
98
|
+
if (isError > 0.5) {
|
|
99
|
+
// Add pulsing effect for error state
|
|
100
|
+
float pulse = 0.5 + 0.5 * sin(time * 4.0);
|
|
101
|
+
alpha *= (0.3 + 0.4 * pulse);
|
|
102
|
+
|
|
103
|
+
// Add error pattern
|
|
104
|
+
float stripe = sin(vPosition.y * 20.0 + time * 2.0);
|
|
105
|
+
alpha *= (0.7 + 0.3 * step(0.0, stripe));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Add edge glow
|
|
109
|
+
vec3 viewDirection = normalize(cameraPosition - vPosition);
|
|
110
|
+
float edgeIntensity = pow(1.0 - abs(dot(viewDirection, normalize(vPosition))), 2.0);
|
|
111
|
+
|
|
112
|
+
vec3 finalColor = color + edgeIntensity * 0.5;
|
|
113
|
+
gl_FragColor = vec4(finalColor, alpha);
|
|
114
|
+
}
|
|
115
|
+
`
|
|
116
|
+
});
|
|
117
|
+
const mesh = new THREE.Mesh(geometry, material);
|
|
118
|
+
this.positionAssetAtBottomCenter(mesh);
|
|
119
|
+
return mesh;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Update placeholder fill progress based on loading progress
|
|
123
|
+
*/
|
|
124
|
+
updatePlaceholder(progress) {
|
|
125
|
+
if (this.placeholder && this.placeholder instanceof THREE.Mesh) {
|
|
126
|
+
const material = this.placeholder.material;
|
|
127
|
+
if (material.uniforms && material.uniforms.fillProgress) {
|
|
128
|
+
material.uniforms.fillProgress.value = progress;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Set placeholder to error state with configurable color and opacity
|
|
134
|
+
*/
|
|
135
|
+
setPlaceholderError() {
|
|
136
|
+
if (this.placeholder && this.placeholder instanceof THREE.Mesh) {
|
|
137
|
+
const material = this.placeholder.material;
|
|
138
|
+
if (material.uniforms) {
|
|
139
|
+
if (material.uniforms.color) {
|
|
140
|
+
material.uniforms.color.value = new THREE.Color(this.errorColor);
|
|
141
|
+
}
|
|
142
|
+
if (material.uniforms.opacity) {
|
|
143
|
+
material.uniforms.opacity.value = this.errorOpacity;
|
|
144
|
+
}
|
|
145
|
+
if (material.uniforms.fillProgress) {
|
|
146
|
+
material.uniforms.fillProgress.value = 0;
|
|
147
|
+
}
|
|
148
|
+
if (material.uniforms.isError) {
|
|
149
|
+
material.uniforms.isError.value = 1;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Update placeholder animation time (call this in your render loop)
|
|
156
|
+
*/
|
|
157
|
+
updatePlaceholderAnimation(deltaTime) {
|
|
158
|
+
if (this.placeholder && this.placeholder instanceof THREE.Mesh) {
|
|
159
|
+
const material = this.placeholder.material;
|
|
160
|
+
if (material.uniforms && material.uniforms.time) {
|
|
161
|
+
material.uniforms.time.value += deltaTime;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Reposition an asset so that its bottom-center sits at the local origin.
|
|
167
|
+
*/
|
|
168
|
+
positionAssetAtBottomCenter(object) {
|
|
169
|
+
object.updateMatrixWorld(true);
|
|
170
|
+
const boundingBox = new THREE.Box3().setFromObject(object);
|
|
171
|
+
if (boundingBox.isEmpty()) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const center = boundingBox.getCenter(new THREE.Vector3());
|
|
175
|
+
const bottomCenter = new THREE.Vector3(
|
|
176
|
+
center.x,
|
|
177
|
+
boundingBox.min.y + 0.01,
|
|
178
|
+
center.z
|
|
179
|
+
);
|
|
180
|
+
if (!Number.isFinite(bottomCenter.x) || !Number.isFinite(bottomCenter.y) || !Number.isFinite(bottomCenter.z)) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
object.position.sub(bottomCenter);
|
|
184
|
+
object.userData.bottomCenterOffset = bottomCenter;
|
|
185
|
+
object.updateMatrixWorld(true);
|
|
186
|
+
}
|
|
187
|
+
ensureBottomCenterOffset(object) {
|
|
188
|
+
const offset = object.userData.bottomCenterOffset;
|
|
189
|
+
if (offset instanceof THREE.Vector3) {
|
|
190
|
+
return offset;
|
|
191
|
+
}
|
|
192
|
+
if (offset && typeof offset === "object") {
|
|
193
|
+
const {
|
|
194
|
+
x = 0,
|
|
195
|
+
y = 0,
|
|
196
|
+
z = 0
|
|
197
|
+
} = offset;
|
|
198
|
+
const normalized = new THREE.Vector3(x ?? 0, y ?? 0, z ?? 0);
|
|
199
|
+
object.userData.bottomCenterOffset = normalized;
|
|
200
|
+
return normalized;
|
|
201
|
+
}
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
normalizeBottomCenterData(object) {
|
|
205
|
+
const hasOffset = this.ensureBottomCenterOffset(object) !== null;
|
|
206
|
+
object.children.forEach((child) => this.normalizeBottomCenterData(child));
|
|
207
|
+
return hasOffset;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Load an asset with the specified options
|
|
211
|
+
*/
|
|
212
|
+
async load(options) {
|
|
213
|
+
const {
|
|
214
|
+
type,
|
|
215
|
+
url,
|
|
216
|
+
size,
|
|
217
|
+
lowResUrl,
|
|
218
|
+
enableCaching = true,
|
|
219
|
+
placeholderColor = 5227511,
|
|
220
|
+
placeholderOpacity = 0.8,
|
|
221
|
+
errorColor = 16729156,
|
|
222
|
+
errorOpacity = 0.5
|
|
223
|
+
} = options;
|
|
224
|
+
if (enableCaching && this.cache.has(url)) {
|
|
225
|
+
const cachedClone = this.cache.get(url).clone(true);
|
|
226
|
+
const hasOffset = this.normalizeBottomCenterData(cachedClone);
|
|
227
|
+
if (!hasOffset) {
|
|
228
|
+
this.positionAssetAtBottomCenter(cachedClone);
|
|
229
|
+
} else {
|
|
230
|
+
cachedClone.updateMatrixWorld(true);
|
|
231
|
+
}
|
|
232
|
+
this.loadedAsset = cachedClone;
|
|
233
|
+
this.dispatchEvent({ type: "loaded", asset: cachedClone });
|
|
234
|
+
return cachedClone;
|
|
235
|
+
}
|
|
236
|
+
this.errorColor = errorColor;
|
|
237
|
+
this.errorOpacity = errorOpacity;
|
|
238
|
+
if (size) {
|
|
239
|
+
this.placeholder = this.createPlaceholder(
|
|
240
|
+
size,
|
|
241
|
+
placeholderColor,
|
|
242
|
+
placeholderOpacity
|
|
243
|
+
);
|
|
244
|
+
this.dispatchEvent({
|
|
245
|
+
type: "placeholderCreated",
|
|
246
|
+
placeholder: this.placeholder
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
if (lowResUrl) {
|
|
251
|
+
const lowRes = await this.loadModel(type, lowResUrl, true);
|
|
252
|
+
this.lowResAsset = lowRes;
|
|
253
|
+
this.dispatchEvent({ type: "lowResLoaded", lowRes });
|
|
254
|
+
}
|
|
255
|
+
const asset = await this.loadModel(type, url, false);
|
|
256
|
+
this.loadedAsset = asset;
|
|
257
|
+
if (enableCaching) {
|
|
258
|
+
const cacheEntry = asset.clone(true);
|
|
259
|
+
const hasOffset = this.normalizeBottomCenterData(cacheEntry);
|
|
260
|
+
if (!hasOffset) {
|
|
261
|
+
this.positionAssetAtBottomCenter(cacheEntry);
|
|
262
|
+
} else {
|
|
263
|
+
cacheEntry.updateMatrixWorld(true);
|
|
264
|
+
}
|
|
265
|
+
this.cache.set(url, cacheEntry);
|
|
266
|
+
}
|
|
267
|
+
this.dispatchEvent({ type: "loaded", asset });
|
|
268
|
+
return asset;
|
|
269
|
+
} catch (error) {
|
|
270
|
+
this.setPlaceholderError();
|
|
271
|
+
this.dispatchEvent({ type: "error", error });
|
|
272
|
+
throw error;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Load a model based on type
|
|
277
|
+
*/
|
|
278
|
+
loadModel(type, url, isLowRes) {
|
|
279
|
+
return new Promise((resolve, reject) => {
|
|
280
|
+
const onProgress = (event) => {
|
|
281
|
+
const percentage = event.loaded / event.total * 100;
|
|
282
|
+
this.dispatchEvent({
|
|
283
|
+
type: "progress",
|
|
284
|
+
loaded: event.loaded,
|
|
285
|
+
total: event.total,
|
|
286
|
+
percentage
|
|
287
|
+
});
|
|
288
|
+
if (!isLowRes) {
|
|
289
|
+
this.updatePlaceholder(percentage / 100);
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
const onError = (error) => {
|
|
293
|
+
reject(error);
|
|
294
|
+
};
|
|
295
|
+
switch (type) {
|
|
296
|
+
case "gltf":
|
|
297
|
+
this.gltfLoader.load(
|
|
298
|
+
url,
|
|
299
|
+
(gltf) => {
|
|
300
|
+
const scene = gltf.scene;
|
|
301
|
+
this.positionAssetAtBottomCenter(scene);
|
|
302
|
+
resolve(scene);
|
|
303
|
+
},
|
|
304
|
+
onProgress,
|
|
305
|
+
onError
|
|
306
|
+
);
|
|
307
|
+
break;
|
|
308
|
+
case "fbx":
|
|
309
|
+
this.fbxLoader.load(
|
|
310
|
+
url,
|
|
311
|
+
(fbx) => {
|
|
312
|
+
this.positionAssetAtBottomCenter(fbx);
|
|
313
|
+
resolve(fbx);
|
|
314
|
+
},
|
|
315
|
+
onProgress,
|
|
316
|
+
onError
|
|
317
|
+
);
|
|
318
|
+
break;
|
|
319
|
+
case "obj":
|
|
320
|
+
this.objLoader.load(
|
|
321
|
+
url,
|
|
322
|
+
(obj) => {
|
|
323
|
+
this.positionAssetAtBottomCenter(obj);
|
|
324
|
+
resolve(obj);
|
|
325
|
+
},
|
|
326
|
+
onProgress,
|
|
327
|
+
onError
|
|
328
|
+
);
|
|
329
|
+
break;
|
|
330
|
+
default:
|
|
331
|
+
reject(new Error(`Unsupported asset type: ${type}`));
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Get the placeholder object
|
|
337
|
+
*/
|
|
338
|
+
getPlaceholder() {
|
|
339
|
+
return this.placeholder;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Get the loaded asset
|
|
343
|
+
*/
|
|
344
|
+
getAsset() {
|
|
345
|
+
return this.loadedAsset;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Get the low-res asset
|
|
349
|
+
*/
|
|
350
|
+
getLowResAsset() {
|
|
351
|
+
return this.lowResAsset;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Clear the cache
|
|
355
|
+
*/
|
|
356
|
+
clearCache() {
|
|
357
|
+
this.cache.clear();
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Remove an item from cache
|
|
361
|
+
*/
|
|
362
|
+
removeFromCache(url) {
|
|
363
|
+
this.cache.delete(url);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Get cache size
|
|
367
|
+
*/
|
|
368
|
+
getCacheSize() {
|
|
369
|
+
return this.cache.size;
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
373
|
+
0 && (module.exports = {
|
|
374
|
+
AssetLoader
|
|
375
|
+
});
|
|
376
|
+
//# sourceMappingURL=index.cjs.map
|