@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.
Files changed (80) hide show
  1. package/README.md +321 -0
  2. package/dist/asset-loader/index.cjs +376 -0
  3. package/dist/asset-loader/index.cjs.map +1 -0
  4. package/dist/asset-loader/index.d.mts +101 -0
  5. package/dist/asset-loader/index.d.ts +101 -0
  6. package/dist/asset-loader/index.mjs +7 -0
  7. package/dist/asset-loader/index.mjs.map +1 -0
  8. package/dist/camera/index.cjs +313 -0
  9. package/dist/camera/index.cjs.map +1 -0
  10. package/dist/camera/index.d.mts +82 -0
  11. package/dist/camera/index.d.ts +82 -0
  12. package/dist/camera/index.mjs +7 -0
  13. package/dist/camera/index.mjs.map +1 -0
  14. package/dist/chunk-5DP6WDB3.mjs +1161 -0
  15. package/dist/chunk-5DP6WDB3.mjs.map +1 -0
  16. package/dist/chunk-BJKSICFA.mjs +1579 -0
  17. package/dist/chunk-BJKSICFA.mjs.map +1 -0
  18. package/dist/chunk-BYRZCHE7.mjs +277 -0
  19. package/dist/chunk-BYRZCHE7.mjs.map +1 -0
  20. package/dist/chunk-EIROAPF7.mjs +387 -0
  21. package/dist/chunk-EIROAPF7.mjs.map +1 -0
  22. package/dist/chunk-EQDOX34V.mjs +164 -0
  23. package/dist/chunk-EQDOX34V.mjs.map +1 -0
  24. package/dist/chunk-IIAZ2WJJ.mjs +405 -0
  25. package/dist/chunk-IIAZ2WJJ.mjs.map +1 -0
  26. package/dist/chunk-L4VIIJZD.mjs +340 -0
  27. package/dist/chunk-L4VIIJZD.mjs.map +1 -0
  28. package/dist/chunk-P35QJCOG.mjs +339 -0
  29. package/dist/chunk-P35QJCOG.mjs.map +1 -0
  30. package/dist/chunk-R64RVBRM.mjs +394 -0
  31. package/dist/chunk-R64RVBRM.mjs.map +1 -0
  32. package/dist/compass/index.cjs +375 -0
  33. package/dist/compass/index.cjs.map +1 -0
  34. package/dist/compass/index.d.mts +58 -0
  35. package/dist/compass/index.d.ts +58 -0
  36. package/dist/compass/index.mjs +7 -0
  37. package/dist/compass/index.mjs.map +1 -0
  38. package/dist/grid/index.cjs +200 -0
  39. package/dist/grid/index.cjs.map +1 -0
  40. package/dist/grid/index.d.mts +43 -0
  41. package/dist/grid/index.d.ts +43 -0
  42. package/dist/grid/index.mjs +7 -0
  43. package/dist/grid/index.mjs.map +1 -0
  44. package/dist/index.cjs +5049 -0
  45. package/dist/index.cjs.map +1 -0
  46. package/dist/index.d.mts +13 -0
  47. package/dist/index.d.ts +13 -0
  48. package/dist/index.mjs +47 -0
  49. package/dist/index.mjs.map +1 -0
  50. package/dist/measurements/index.cjs +1198 -0
  51. package/dist/measurements/index.cjs.map +1 -0
  52. package/dist/measurements/index.d.mts +449 -0
  53. package/dist/measurements/index.d.ts +449 -0
  54. package/dist/measurements/index.mjs +9 -0
  55. package/dist/measurements/index.mjs.map +1 -0
  56. package/dist/sunlight/index.cjs +441 -0
  57. package/dist/sunlight/index.cjs.map +1 -0
  58. package/dist/sunlight/index.d.mts +92 -0
  59. package/dist/sunlight/index.d.ts +92 -0
  60. package/dist/sunlight/index.mjs +7 -0
  61. package/dist/sunlight/index.mjs.map +1 -0
  62. package/dist/terrain/index.cjs +423 -0
  63. package/dist/terrain/index.cjs.map +1 -0
  64. package/dist/terrain/index.d.mts +219 -0
  65. package/dist/terrain/index.d.ts +219 -0
  66. package/dist/terrain/index.mjs +7 -0
  67. package/dist/terrain/index.mjs.map +1 -0
  68. package/dist/transform-controls/index.cjs +1587 -0
  69. package/dist/transform-controls/index.cjs.map +1 -0
  70. package/dist/transform-controls/index.d.mts +162 -0
  71. package/dist/transform-controls/index.d.ts +162 -0
  72. package/dist/transform-controls/index.mjs +13 -0
  73. package/dist/transform-controls/index.mjs.map +1 -0
  74. package/dist/view-helper/index.cjs +430 -0
  75. package/dist/view-helper/index.cjs.map +1 -0
  76. package/dist/view-helper/index.d.mts +75 -0
  77. package/dist/view-helper/index.d.ts +75 -0
  78. package/dist/view-helper/index.mjs +7 -0
  79. package/dist/view-helper/index.mjs.map +1 -0
  80. 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