@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
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
// packages/asset-loader/src/AssetLoader.ts
|
|
2
|
+
import * as THREE from "three";
|
|
3
|
+
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
4
|
+
import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader.js";
|
|
5
|
+
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader.js";
|
|
6
|
+
var AssetLoader = class extends THREE.EventDispatcher {
|
|
7
|
+
constructor() {
|
|
8
|
+
super();
|
|
9
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
10
|
+
this.placeholder = null;
|
|
11
|
+
this.loadedAsset = null;
|
|
12
|
+
this.lowResAsset = null;
|
|
13
|
+
this.errorColor = 16729156;
|
|
14
|
+
this.errorOpacity = 0.5;
|
|
15
|
+
this.gltfLoader = new GLTFLoader();
|
|
16
|
+
this.fbxLoader = new FBXLoader();
|
|
17
|
+
this.objLoader = new OBJLoader();
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create a placeholder cube with shader effect
|
|
21
|
+
*/
|
|
22
|
+
createPlaceholder(size, color = 5227511, opacity = 0.3) {
|
|
23
|
+
const [width, height, depth] = size;
|
|
24
|
+
const geometry = new THREE.BoxGeometry(width, height, depth);
|
|
25
|
+
const material = new THREE.ShaderMaterial({
|
|
26
|
+
side: THREE.DoubleSide,
|
|
27
|
+
depthWrite: false,
|
|
28
|
+
blending: THREE.AdditiveBlending,
|
|
29
|
+
transparent: true,
|
|
30
|
+
uniforms: {
|
|
31
|
+
color: { value: new THREE.Color(color) },
|
|
32
|
+
opacity: { value: opacity },
|
|
33
|
+
fillProgress: { value: 0 },
|
|
34
|
+
time: { value: 0 },
|
|
35
|
+
isError: { value: 0 }
|
|
36
|
+
},
|
|
37
|
+
vertexShader: `
|
|
38
|
+
varying vec3 vPosition;
|
|
39
|
+
void main() {
|
|
40
|
+
vPosition = position;
|
|
41
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
42
|
+
}
|
|
43
|
+
`,
|
|
44
|
+
fragmentShader: `
|
|
45
|
+
uniform vec3 color;
|
|
46
|
+
uniform float opacity;
|
|
47
|
+
uniform float fillProgress;
|
|
48
|
+
uniform float time;
|
|
49
|
+
uniform float isError;
|
|
50
|
+
varying vec3 vPosition;
|
|
51
|
+
|
|
52
|
+
void main() {
|
|
53
|
+
float normalizedY = (vPosition.y + 1.0) / 2.0; // Normalize from -1,1 to 0,1
|
|
54
|
+
float alpha = opacity;
|
|
55
|
+
|
|
56
|
+
// Create fill-up effect
|
|
57
|
+
if (normalizedY > fillProgress) {
|
|
58
|
+
alpha *= 0.3; // Reduce opacity for unfilled parts
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Error state effects
|
|
62
|
+
if (isError > 0.5) {
|
|
63
|
+
// Add pulsing effect for error state
|
|
64
|
+
float pulse = 0.5 + 0.5 * sin(time * 4.0);
|
|
65
|
+
alpha *= (0.3 + 0.4 * pulse);
|
|
66
|
+
|
|
67
|
+
// Add error pattern
|
|
68
|
+
float stripe = sin(vPosition.y * 20.0 + time * 2.0);
|
|
69
|
+
alpha *= (0.7 + 0.3 * step(0.0, stripe));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Add edge glow
|
|
73
|
+
vec3 viewDirection = normalize(cameraPosition - vPosition);
|
|
74
|
+
float edgeIntensity = pow(1.0 - abs(dot(viewDirection, normalize(vPosition))), 2.0);
|
|
75
|
+
|
|
76
|
+
vec3 finalColor = color + edgeIntensity * 0.5;
|
|
77
|
+
gl_FragColor = vec4(finalColor, alpha);
|
|
78
|
+
}
|
|
79
|
+
`
|
|
80
|
+
});
|
|
81
|
+
const mesh = new THREE.Mesh(geometry, material);
|
|
82
|
+
this.positionAssetAtBottomCenter(mesh);
|
|
83
|
+
return mesh;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Update placeholder fill progress based on loading progress
|
|
87
|
+
*/
|
|
88
|
+
updatePlaceholder(progress) {
|
|
89
|
+
if (this.placeholder && this.placeholder instanceof THREE.Mesh) {
|
|
90
|
+
const material = this.placeholder.material;
|
|
91
|
+
if (material.uniforms && material.uniforms.fillProgress) {
|
|
92
|
+
material.uniforms.fillProgress.value = progress;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Set placeholder to error state with configurable color and opacity
|
|
98
|
+
*/
|
|
99
|
+
setPlaceholderError() {
|
|
100
|
+
if (this.placeholder && this.placeholder instanceof THREE.Mesh) {
|
|
101
|
+
const material = this.placeholder.material;
|
|
102
|
+
if (material.uniforms) {
|
|
103
|
+
if (material.uniforms.color) {
|
|
104
|
+
material.uniforms.color.value = new THREE.Color(this.errorColor);
|
|
105
|
+
}
|
|
106
|
+
if (material.uniforms.opacity) {
|
|
107
|
+
material.uniforms.opacity.value = this.errorOpacity;
|
|
108
|
+
}
|
|
109
|
+
if (material.uniforms.fillProgress) {
|
|
110
|
+
material.uniforms.fillProgress.value = 0;
|
|
111
|
+
}
|
|
112
|
+
if (material.uniforms.isError) {
|
|
113
|
+
material.uniforms.isError.value = 1;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Update placeholder animation time (call this in your render loop)
|
|
120
|
+
*/
|
|
121
|
+
updatePlaceholderAnimation(deltaTime) {
|
|
122
|
+
if (this.placeholder && this.placeholder instanceof THREE.Mesh) {
|
|
123
|
+
const material = this.placeholder.material;
|
|
124
|
+
if (material.uniforms && material.uniforms.time) {
|
|
125
|
+
material.uniforms.time.value += deltaTime;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Reposition an asset so that its bottom-center sits at the local origin.
|
|
131
|
+
*/
|
|
132
|
+
positionAssetAtBottomCenter(object) {
|
|
133
|
+
object.updateMatrixWorld(true);
|
|
134
|
+
const boundingBox = new THREE.Box3().setFromObject(object);
|
|
135
|
+
if (boundingBox.isEmpty()) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const center = boundingBox.getCenter(new THREE.Vector3());
|
|
139
|
+
const bottomCenter = new THREE.Vector3(
|
|
140
|
+
center.x,
|
|
141
|
+
boundingBox.min.y + 0.01,
|
|
142
|
+
center.z
|
|
143
|
+
);
|
|
144
|
+
if (!Number.isFinite(bottomCenter.x) || !Number.isFinite(bottomCenter.y) || !Number.isFinite(bottomCenter.z)) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
object.position.sub(bottomCenter);
|
|
148
|
+
object.userData.bottomCenterOffset = bottomCenter;
|
|
149
|
+
object.updateMatrixWorld(true);
|
|
150
|
+
}
|
|
151
|
+
ensureBottomCenterOffset(object) {
|
|
152
|
+
const offset = object.userData.bottomCenterOffset;
|
|
153
|
+
if (offset instanceof THREE.Vector3) {
|
|
154
|
+
return offset;
|
|
155
|
+
}
|
|
156
|
+
if (offset && typeof offset === "object") {
|
|
157
|
+
const {
|
|
158
|
+
x = 0,
|
|
159
|
+
y = 0,
|
|
160
|
+
z = 0
|
|
161
|
+
} = offset;
|
|
162
|
+
const normalized = new THREE.Vector3(x ?? 0, y ?? 0, z ?? 0);
|
|
163
|
+
object.userData.bottomCenterOffset = normalized;
|
|
164
|
+
return normalized;
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
normalizeBottomCenterData(object) {
|
|
169
|
+
const hasOffset = this.ensureBottomCenterOffset(object) !== null;
|
|
170
|
+
object.children.forEach((child) => this.normalizeBottomCenterData(child));
|
|
171
|
+
return hasOffset;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Load an asset with the specified options
|
|
175
|
+
*/
|
|
176
|
+
async load(options) {
|
|
177
|
+
const {
|
|
178
|
+
type,
|
|
179
|
+
url,
|
|
180
|
+
size,
|
|
181
|
+
lowResUrl,
|
|
182
|
+
enableCaching = true,
|
|
183
|
+
placeholderColor = 5227511,
|
|
184
|
+
placeholderOpacity = 0.8,
|
|
185
|
+
errorColor = 16729156,
|
|
186
|
+
errorOpacity = 0.5
|
|
187
|
+
} = options;
|
|
188
|
+
if (enableCaching && this.cache.has(url)) {
|
|
189
|
+
const cachedClone = this.cache.get(url).clone(true);
|
|
190
|
+
const hasOffset = this.normalizeBottomCenterData(cachedClone);
|
|
191
|
+
if (!hasOffset) {
|
|
192
|
+
this.positionAssetAtBottomCenter(cachedClone);
|
|
193
|
+
} else {
|
|
194
|
+
cachedClone.updateMatrixWorld(true);
|
|
195
|
+
}
|
|
196
|
+
this.loadedAsset = cachedClone;
|
|
197
|
+
this.dispatchEvent({ type: "loaded", asset: cachedClone });
|
|
198
|
+
return cachedClone;
|
|
199
|
+
}
|
|
200
|
+
this.errorColor = errorColor;
|
|
201
|
+
this.errorOpacity = errorOpacity;
|
|
202
|
+
if (size) {
|
|
203
|
+
this.placeholder = this.createPlaceholder(
|
|
204
|
+
size,
|
|
205
|
+
placeholderColor,
|
|
206
|
+
placeholderOpacity
|
|
207
|
+
);
|
|
208
|
+
this.dispatchEvent({
|
|
209
|
+
type: "placeholderCreated",
|
|
210
|
+
placeholder: this.placeholder
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
if (lowResUrl) {
|
|
215
|
+
const lowRes = await this.loadModel(type, lowResUrl, true);
|
|
216
|
+
this.lowResAsset = lowRes;
|
|
217
|
+
this.dispatchEvent({ type: "lowResLoaded", lowRes });
|
|
218
|
+
}
|
|
219
|
+
const asset = await this.loadModel(type, url, false);
|
|
220
|
+
this.loadedAsset = asset;
|
|
221
|
+
if (enableCaching) {
|
|
222
|
+
const cacheEntry = asset.clone(true);
|
|
223
|
+
const hasOffset = this.normalizeBottomCenterData(cacheEntry);
|
|
224
|
+
if (!hasOffset) {
|
|
225
|
+
this.positionAssetAtBottomCenter(cacheEntry);
|
|
226
|
+
} else {
|
|
227
|
+
cacheEntry.updateMatrixWorld(true);
|
|
228
|
+
}
|
|
229
|
+
this.cache.set(url, cacheEntry);
|
|
230
|
+
}
|
|
231
|
+
this.dispatchEvent({ type: "loaded", asset });
|
|
232
|
+
return asset;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
this.setPlaceholderError();
|
|
235
|
+
this.dispatchEvent({ type: "error", error });
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Load a model based on type
|
|
241
|
+
*/
|
|
242
|
+
loadModel(type, url, isLowRes) {
|
|
243
|
+
return new Promise((resolve, reject) => {
|
|
244
|
+
const onProgress = (event) => {
|
|
245
|
+
const percentage = event.loaded / event.total * 100;
|
|
246
|
+
this.dispatchEvent({
|
|
247
|
+
type: "progress",
|
|
248
|
+
loaded: event.loaded,
|
|
249
|
+
total: event.total,
|
|
250
|
+
percentage
|
|
251
|
+
});
|
|
252
|
+
if (!isLowRes) {
|
|
253
|
+
this.updatePlaceholder(percentage / 100);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
const onError = (error) => {
|
|
257
|
+
reject(error);
|
|
258
|
+
};
|
|
259
|
+
switch (type) {
|
|
260
|
+
case "gltf":
|
|
261
|
+
this.gltfLoader.load(
|
|
262
|
+
url,
|
|
263
|
+
(gltf) => {
|
|
264
|
+
const scene = gltf.scene;
|
|
265
|
+
this.positionAssetAtBottomCenter(scene);
|
|
266
|
+
resolve(scene);
|
|
267
|
+
},
|
|
268
|
+
onProgress,
|
|
269
|
+
onError
|
|
270
|
+
);
|
|
271
|
+
break;
|
|
272
|
+
case "fbx":
|
|
273
|
+
this.fbxLoader.load(
|
|
274
|
+
url,
|
|
275
|
+
(fbx) => {
|
|
276
|
+
this.positionAssetAtBottomCenter(fbx);
|
|
277
|
+
resolve(fbx);
|
|
278
|
+
},
|
|
279
|
+
onProgress,
|
|
280
|
+
onError
|
|
281
|
+
);
|
|
282
|
+
break;
|
|
283
|
+
case "obj":
|
|
284
|
+
this.objLoader.load(
|
|
285
|
+
url,
|
|
286
|
+
(obj) => {
|
|
287
|
+
this.positionAssetAtBottomCenter(obj);
|
|
288
|
+
resolve(obj);
|
|
289
|
+
},
|
|
290
|
+
onProgress,
|
|
291
|
+
onError
|
|
292
|
+
);
|
|
293
|
+
break;
|
|
294
|
+
default:
|
|
295
|
+
reject(new Error(`Unsupported asset type: ${type}`));
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Get the placeholder object
|
|
301
|
+
*/
|
|
302
|
+
getPlaceholder() {
|
|
303
|
+
return this.placeholder;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Get the loaded asset
|
|
307
|
+
*/
|
|
308
|
+
getAsset() {
|
|
309
|
+
return this.loadedAsset;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Get the low-res asset
|
|
313
|
+
*/
|
|
314
|
+
getLowResAsset() {
|
|
315
|
+
return this.lowResAsset;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Clear the cache
|
|
319
|
+
*/
|
|
320
|
+
clearCache() {
|
|
321
|
+
this.cache.clear();
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Remove an item from cache
|
|
325
|
+
*/
|
|
326
|
+
removeFromCache(url) {
|
|
327
|
+
this.cache.delete(url);
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Get cache size
|
|
331
|
+
*/
|
|
332
|
+
getCacheSize() {
|
|
333
|
+
return this.cache.size;
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
export {
|
|
338
|
+
AssetLoader
|
|
339
|
+
};
|
|
340
|
+
//# sourceMappingURL=chunk-L4VIIJZD.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../packages/asset-loader/src/AssetLoader.ts"],"sourcesContent":["import * as THREE from 'three'\nimport { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'\nimport { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js'\nimport { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'\n\n// Event types for asset loading\ninterface AssetLoaderEventMap {\n progress: { loaded: number; total: number; percentage: number }\n loaded: { asset: THREE.Object3D }\n error: { error: Error }\n placeholderCreated: { placeholder: THREE.Object3D }\n lowResLoaded: { lowRes: THREE.Object3D }\n}\n\nexport type AssetType = 'gltf' | 'fbx' | 'obj'\n\nexport interface AssetLoaderOptions {\n type: AssetType\n url: string\n size?: [number, number, number] // Optional size for placeholder\n lowResUrl?: string // Optional low-res model URL\n enableCaching?: boolean\n placeholderColor?: number\n placeholderOpacity?: number\n errorColor?: number // Color to use when loading fails\n errorOpacity?: number // Opacity to use when loading fails\n}\n\nexport class AssetLoader extends THREE.EventDispatcher<AssetLoaderEventMap> {\n private cache: Map<string, THREE.Object3D> = new Map()\n private gltfLoader: GLTFLoader\n private fbxLoader: FBXLoader\n private objLoader: OBJLoader\n private placeholder: THREE.Object3D | null = null\n private loadedAsset: THREE.Object3D | null = null\n private lowResAsset: THREE.Object3D | null = null\n private errorColor: number = 0xff4444\n private errorOpacity: number = 0.5\n\n constructor() {\n super()\n this.gltfLoader = new GLTFLoader()\n this.fbxLoader = new FBXLoader()\n this.objLoader = new OBJLoader()\n }\n\n /**\n * Create a placeholder cube with shader effect\n */\n private createPlaceholder(\n size: [number, number, number],\n color: number = 0x4fc3f7,\n opacity: number = 0.3\n ): THREE.Object3D {\n const [width, height, depth] = size\n const geometry = new THREE.BoxGeometry(width, height, depth)\n\n // Custom shader material with fill-up effect\n const material = new THREE.ShaderMaterial({\n side: THREE.DoubleSide,\n depthWrite: false,\n blending: THREE.AdditiveBlending,\n transparent: true,\n uniforms: {\n color: { value: new THREE.Color(color) },\n opacity: { value: opacity },\n fillProgress: { value: 0.0 },\n time: { value: 0.0 },\n isError: { value: 0.0 },\n },\n vertexShader: `\n varying vec3 vPosition;\n void main() {\n vPosition = position;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n `,\n fragmentShader: `\n uniform vec3 color;\n uniform float opacity;\n uniform float fillProgress;\n uniform float time;\n uniform float isError;\n varying vec3 vPosition;\n \n void main() {\n float normalizedY = (vPosition.y + 1.0) / 2.0; // Normalize from -1,1 to 0,1\n float alpha = opacity;\n \n // Create fill-up effect\n if (normalizedY > fillProgress) {\n alpha *= 0.3; // Reduce opacity for unfilled parts\n }\n \n // Error state effects\n if (isError > 0.5) {\n // Add pulsing effect for error state\n float pulse = 0.5 + 0.5 * sin(time * 4.0);\n alpha *= (0.3 + 0.4 * pulse);\n \n // Add error pattern\n float stripe = sin(vPosition.y * 20.0 + time * 2.0);\n alpha *= (0.7 + 0.3 * step(0.0, stripe));\n }\n \n // Add edge glow\n vec3 viewDirection = normalize(cameraPosition - vPosition);\n float edgeIntensity = pow(1.0 - abs(dot(viewDirection, normalize(vPosition))), 2.0);\n \n vec3 finalColor = color + edgeIntensity * 0.5;\n gl_FragColor = vec4(finalColor, alpha);\n }\n `,\n })\n\n const mesh = new THREE.Mesh(geometry, material)\n this.positionAssetAtBottomCenter(mesh)\n return mesh\n }\n\n /**\n * Update placeholder fill progress based on loading progress\n */\n private updatePlaceholder(progress: number) {\n if (this.placeholder && this.placeholder instanceof THREE.Mesh) {\n const material = this.placeholder.material as THREE.ShaderMaterial\n if (material.uniforms && material.uniforms.fillProgress) {\n material.uniforms.fillProgress.value = progress\n }\n }\n }\n\n /**\n * Set placeholder to error state with configurable color and opacity\n */\n private setPlaceholderError() {\n if (this.placeholder && this.placeholder instanceof THREE.Mesh) {\n const material = this.placeholder.material as THREE.ShaderMaterial\n if (material.uniforms) {\n // Change to error color\n if (material.uniforms.color) {\n material.uniforms.color.value = new THREE.Color(this.errorColor)\n }\n // Set error opacity\n if (material.uniforms.opacity) {\n material.uniforms.opacity.value = this.errorOpacity\n }\n // Set fill progress to indicate failure\n if (material.uniforms.fillProgress) {\n material.uniforms.fillProgress.value = 0.0\n }\n // Enable error state\n if (material.uniforms.isError) {\n material.uniforms.isError.value = 1.0\n }\n }\n }\n }\n\n /**\n * Update placeholder animation time (call this in your render loop)\n */\n public updatePlaceholderAnimation(deltaTime: number) {\n if (this.placeholder && this.placeholder instanceof THREE.Mesh) {\n const material = this.placeholder.material as THREE.ShaderMaterial\n if (material.uniforms && material.uniforms.time) {\n material.uniforms.time.value += deltaTime\n }\n }\n }\n\n /**\n * Reposition an asset so that its bottom-center sits at the local origin.\n */\n private positionAssetAtBottomCenter(object: THREE.Object3D) {\n object.updateMatrixWorld(true)\n\n const boundingBox = new THREE.Box3().setFromObject(object)\n if (boundingBox.isEmpty()) {\n return\n }\n\n const center = boundingBox.getCenter(new THREE.Vector3())\n const bottomCenter = new THREE.Vector3(\n center.x,\n boundingBox.min.y + 0.01,\n center.z\n )\n\n if (\n !Number.isFinite(bottomCenter.x) ||\n !Number.isFinite(bottomCenter.y) ||\n !Number.isFinite(bottomCenter.z)\n ) {\n return\n }\n\n object.position.sub(bottomCenter)\n object.userData.bottomCenterOffset = bottomCenter\n object.updateMatrixWorld(true)\n }\n\n private ensureBottomCenterOffset(\n object: THREE.Object3D\n ): THREE.Vector3 | null {\n const offset = object.userData.bottomCenterOffset\n if (offset instanceof THREE.Vector3) {\n return offset\n }\n\n if (offset && typeof offset === 'object') {\n const {\n x = 0,\n y = 0,\n z = 0,\n } = offset as Partial<Record<'x' | 'y' | 'z', number>>\n const normalized = new THREE.Vector3(x ?? 0, y ?? 0, z ?? 0)\n object.userData.bottomCenterOffset = normalized\n return normalized\n }\n\n return null\n }\n\n private normalizeBottomCenterData(object: THREE.Object3D): boolean {\n const hasOffset = this.ensureBottomCenterOffset(object) !== null\n object.children.forEach((child) => this.normalizeBottomCenterData(child))\n return hasOffset\n }\n\n /**\n * Load an asset with the specified options\n */\n async load(options: AssetLoaderOptions): Promise<THREE.Object3D> {\n const {\n type,\n url,\n size,\n lowResUrl,\n enableCaching = true,\n placeholderColor = 0x4fc3f7,\n placeholderOpacity = 0.8,\n errorColor = 0xff4444,\n errorOpacity = 0.5,\n } = options\n\n // Check cache first\n if (enableCaching && this.cache.has(url)) {\n const cachedClone = this.cache.get(url)!.clone(true)\n const hasOffset = this.normalizeBottomCenterData(cachedClone)\n\n if (!hasOffset) {\n this.positionAssetAtBottomCenter(cachedClone)\n } else {\n cachedClone.updateMatrixWorld(true)\n }\n\n this.loadedAsset = cachedClone\n this.dispatchEvent({ type: 'loaded', asset: cachedClone })\n return cachedClone\n }\n\n // Store error styling options\n this.errorColor = errorColor\n this.errorOpacity = errorOpacity\n\n // Create placeholder if size is provided\n if (size) {\n this.placeholder = this.createPlaceholder(\n size,\n placeholderColor,\n placeholderOpacity\n )\n this.dispatchEvent({\n type: 'placeholderCreated',\n placeholder: this.placeholder,\n })\n }\n\n try {\n // Load low-res model first if provided\n if (lowResUrl) {\n const lowRes = await this.loadModel(type, lowResUrl, true)\n this.lowResAsset = lowRes\n this.dispatchEvent({ type: 'lowResLoaded', lowRes })\n }\n\n // Load main asset\n const asset = await this.loadModel(type, url, false)\n this.loadedAsset = asset\n\n // Cache if enabled\n if (enableCaching) {\n const cacheEntry = asset.clone(true)\n const hasOffset = this.normalizeBottomCenterData(cacheEntry)\n if (!hasOffset) {\n this.positionAssetAtBottomCenter(cacheEntry)\n } else {\n cacheEntry.updateMatrixWorld(true)\n }\n this.cache.set(url, cacheEntry)\n }\n\n this.dispatchEvent({ type: 'loaded', asset })\n return asset\n } catch (error) {\n // Set placeholder to error state\n this.setPlaceholderError()\n this.dispatchEvent({ type: 'error', error: error as Error })\n throw error\n }\n }\n\n /**\n * Load a model based on type\n */\n private loadModel(\n type: AssetType,\n url: string,\n isLowRes: boolean\n ): Promise<THREE.Object3D> {\n return new Promise((resolve, reject) => {\n const onProgress = (event: ProgressEvent) => {\n const percentage = (event.loaded / event.total) * 100\n this.dispatchEvent({\n type: 'progress',\n loaded: event.loaded,\n total: event.total,\n percentage,\n })\n\n // Update placeholder fill\n if (!isLowRes) {\n this.updatePlaceholder(percentage / 100)\n }\n }\n\n const onError = (error: unknown) => {\n reject(error)\n }\n\n switch (type) {\n case 'gltf':\n this.gltfLoader.load(\n url,\n (gltf) => {\n const scene = gltf.scene\n this.positionAssetAtBottomCenter(scene)\n resolve(scene)\n },\n onProgress,\n onError\n )\n break\n\n case 'fbx':\n this.fbxLoader.load(\n url,\n (fbx) => {\n this.positionAssetAtBottomCenter(fbx)\n resolve(fbx)\n },\n onProgress,\n onError\n )\n break\n\n case 'obj':\n this.objLoader.load(\n url,\n (obj) => {\n this.positionAssetAtBottomCenter(obj)\n resolve(obj)\n },\n onProgress,\n onError\n )\n break\n\n default:\n reject(new Error(`Unsupported asset type: ${type}`))\n }\n })\n }\n\n /**\n * Get the placeholder object\n */\n getPlaceholder(): THREE.Object3D | null {\n return this.placeholder\n }\n\n /**\n * Get the loaded asset\n */\n getAsset(): THREE.Object3D | null {\n return this.loadedAsset\n }\n\n /**\n * Get the low-res asset\n */\n getLowResAsset(): THREE.Object3D | null {\n return this.lowResAsset\n }\n\n /**\n * Clear the cache\n */\n clearCache() {\n this.cache.clear()\n }\n\n /**\n * Remove an item from cache\n */\n removeFromCache(url: string) {\n this.cache.delete(url)\n }\n\n /**\n * Get cache size\n */\n getCacheSize(): number {\n return this.cache.size\n }\n}\n"],"mappings":"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","names":[]}
|