@tonybfox/threejs-tools 1.0.2 → 1.0.3
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/dist/asset-loader/index.cjs +27 -6
- package/dist/asset-loader/index.cjs.map +1 -1
- package/dist/asset-loader/index.d.mts +2 -1
- package/dist/asset-loader/index.d.ts +2 -1
- package/dist/asset-loader/index.mjs +1 -1
- package/dist/camera/index.cjs +91 -2
- package/dist/camera/index.cjs.map +1 -1
- package/dist/camera/index.d.mts +22 -1
- package/dist/camera/index.d.ts +22 -1
- package/dist/camera/index.mjs +1 -1
- package/dist/{chunk-WMHEIUXE.mjs → chunk-IEE7DQOU.mjs} +92 -3
- package/dist/chunk-IEE7DQOU.mjs.map +1 -0
- package/dist/{chunk-L4VIIJZD.mjs → chunk-WZ6IGBSE.mjs} +28 -7
- package/dist/chunk-WZ6IGBSE.mjs.map +1 -0
- package/dist/index.cjs +118 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +2 -2
- package/package.json +1 -1
- package/dist/chunk-L4VIIJZD.mjs.map +0 -1
- package/dist/chunk-WMHEIUXE.mjs.map +0 -1
|
@@ -64,6 +64,7 @@ var DualCameraControls = class extends CameraControls {
|
|
|
64
64
|
const initialCamera = initialMode === "orthographic" ? orthographicCamera : perspectiveCamera;
|
|
65
65
|
super(initialCamera, domElement);
|
|
66
66
|
this.updateClock = new THREE.Clock();
|
|
67
|
+
this.externalCamera = null;
|
|
67
68
|
const initialTarget = toVector3(
|
|
68
69
|
options.initialTarget,
|
|
69
70
|
[0, 0, 0],
|
|
@@ -99,9 +100,10 @@ var DualCameraControls = class extends CameraControls {
|
|
|
99
100
|
* Switch to the perspective camera while keeping the current framing.
|
|
100
101
|
*/
|
|
101
102
|
switchToPerspective(enableTransition = false) {
|
|
102
|
-
if (this.activeMode === "perspective") {
|
|
103
|
+
if (this.activeMode === "perspective" && !this.externalCamera) {
|
|
103
104
|
return;
|
|
104
105
|
}
|
|
106
|
+
this.externalCamera = null;
|
|
105
107
|
const target = this.getTarget(tempVec3A);
|
|
106
108
|
const position = this.getPosition(tempVec3B);
|
|
107
109
|
const aspect = resolveAspect(this.renderer, this.domElementRef);
|
|
@@ -133,9 +135,10 @@ var DualCameraControls = class extends CameraControls {
|
|
|
133
135
|
* Switch to the orthographic camera while keeping the current framing.
|
|
134
136
|
*/
|
|
135
137
|
switchToOrthographic(enableTransition = false) {
|
|
136
|
-
if (this.activeMode === "orthographic") {
|
|
138
|
+
if (this.activeMode === "orthographic" && !this.externalCamera) {
|
|
137
139
|
return;
|
|
138
140
|
}
|
|
141
|
+
this.externalCamera = null;
|
|
139
142
|
const target = this.getTarget(tempVec3A);
|
|
140
143
|
const position = this.getPosition(tempVec3B);
|
|
141
144
|
const aspect = resolveAspect(this.renderer, this.domElementRef);
|
|
@@ -173,6 +176,92 @@ var DualCameraControls = class extends CameraControls {
|
|
|
173
176
|
this.switchToPerspective(enableTransition);
|
|
174
177
|
}
|
|
175
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Returns true if currently using an external camera.
|
|
181
|
+
*/
|
|
182
|
+
get isUsingExternalCamera() {
|
|
183
|
+
return this.externalCamera !== null;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Sets an external camera to use with the controls.
|
|
187
|
+
* This allows using a camera created outside of DualCameraControls.
|
|
188
|
+
* Call `clearExternalCamera()` to return to the internal cameras.
|
|
189
|
+
*/
|
|
190
|
+
setCamera(camera, target, enableTransition = false) {
|
|
191
|
+
const previousCamera = this.camera;
|
|
192
|
+
this.externalCamera = camera;
|
|
193
|
+
const aspect = resolveAspect(this.renderer, this.domElementRef);
|
|
194
|
+
if (camera.type === "PerspectiveCamera") {
|
|
195
|
+
;
|
|
196
|
+
camera.aspect = aspect;
|
|
197
|
+
camera.updateProjectionMatrix();
|
|
198
|
+
} else if (camera.type === "OrthographicCamera") {
|
|
199
|
+
const ortho = camera;
|
|
200
|
+
const currentHeight = (ortho.top - ortho.bottom) * 0.5;
|
|
201
|
+
const newHalfWidth = currentHeight * aspect;
|
|
202
|
+
ortho.left = -newHalfWidth;
|
|
203
|
+
ortho.right = newHalfWidth;
|
|
204
|
+
ortho.updateProjectionMatrix();
|
|
205
|
+
}
|
|
206
|
+
this.camera = camera;
|
|
207
|
+
const mode = camera.type === "PerspectiveCamera" ? "perspective" : "orthographic";
|
|
208
|
+
this.activeMode = mode;
|
|
209
|
+
this.updateInputBindingsForMode(mode);
|
|
210
|
+
const targetVec = toVector3(target, [0, 0, 0], tempVec3A);
|
|
211
|
+
void this.setLookAt(
|
|
212
|
+
camera.position.x,
|
|
213
|
+
camera.position.y,
|
|
214
|
+
camera.position.z,
|
|
215
|
+
targetVec.x,
|
|
216
|
+
targetVec.y,
|
|
217
|
+
targetVec.z,
|
|
218
|
+
enableTransition
|
|
219
|
+
);
|
|
220
|
+
this.dispatchEvent({
|
|
221
|
+
type: "externalcamerachange",
|
|
222
|
+
camera,
|
|
223
|
+
previousCamera
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Clears the external camera and returns to using the internal cameras.
|
|
228
|
+
* Will switch to the camera matching the current mode.
|
|
229
|
+
*/
|
|
230
|
+
clearExternalCamera(enableTransition = false) {
|
|
231
|
+
if (!this.externalCamera) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const target = this.getTarget(tempVec3A);
|
|
235
|
+
const position = this.getPosition(tempVec3B);
|
|
236
|
+
this.externalCamera = null;
|
|
237
|
+
const internalCamera = this.activeMode === "orthographic" ? this.orthographicCamera : this.perspectiveCamera;
|
|
238
|
+
internalCamera.position.copy(position);
|
|
239
|
+
internalCamera.quaternion.copy(this.camera.quaternion);
|
|
240
|
+
internalCamera.up.copy(this.camera.up);
|
|
241
|
+
const aspect = resolveAspect(this.renderer, this.domElementRef);
|
|
242
|
+
if (this.activeMode === "orthographic") {
|
|
243
|
+
this.updateOrthographicFrustum(position, target, aspect);
|
|
244
|
+
} else {
|
|
245
|
+
this.perspectiveCamera.aspect = aspect;
|
|
246
|
+
this.perspectiveCamera.updateProjectionMatrix();
|
|
247
|
+
}
|
|
248
|
+
this.camera = internalCamera;
|
|
249
|
+
void this.setLookAt(
|
|
250
|
+
position.x,
|
|
251
|
+
position.y,
|
|
252
|
+
position.z,
|
|
253
|
+
target.x,
|
|
254
|
+
target.y,
|
|
255
|
+
target.z,
|
|
256
|
+
enableTransition
|
|
257
|
+
);
|
|
258
|
+
this.dispatchEvent({
|
|
259
|
+
type: "modechange",
|
|
260
|
+
mode: this.activeMode,
|
|
261
|
+
previousMode: this.activeMode,
|
|
262
|
+
camera: internalCamera
|
|
263
|
+
});
|
|
264
|
+
}
|
|
176
265
|
/**
|
|
177
266
|
* Update camera projection parameters when the viewport size changes.
|
|
178
267
|
*/
|
|
@@ -266,4 +355,4 @@ function resolveAspect(renderer, domElement) {
|
|
|
266
355
|
export {
|
|
267
356
|
DualCameraControls
|
|
268
357
|
};
|
|
269
|
-
//# sourceMappingURL=chunk-
|
|
358
|
+
//# sourceMappingURL=chunk-IEE7DQOU.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../packages/camera/src/DualCameraControls.ts"],"sourcesContent":["import * as THREE from 'three'\nimport CameraControls from 'camera-controls'\n\ntype Vector3Tuple = [number, number, number]\ntype Vector3Like = THREE.Vector3 | Vector3Tuple\n\nexport type CameraMode = 'perspective' | 'orthographic'\n\nexport interface PerspectiveCameraConfig {\n fov?: number\n near?: number\n far?: number\n position?: Vector3Like\n zoom?: number\n}\n\nexport interface OrthographicCameraConfig {\n size?: number\n near?: number\n far?: number\n position?: Vector3Like\n zoom?: number\n}\n\nexport interface DualCameraControlsOptions {\n domElement?: HTMLElement\n initialMode?: CameraMode\n initialTarget?: Vector3Like\n perspective?: PerspectiveCameraConfig\n orthographic?: OrthographicCameraConfig\n}\n\nexport interface ModeChangedEvent {\n type: 'modechange'\n mode: CameraMode\n previousMode: CameraMode\n camera: THREE.PerspectiveCamera | THREE.OrthographicCamera\n}\n\nexport interface ExternalCameraChangedEvent {\n type: 'externalcamerachange'\n camera: THREE.PerspectiveCamera | THREE.OrthographicCamera\n previousCamera: THREE.PerspectiveCamera | THREE.OrthographicCamera\n}\n\nlet controlsInstalled = false\n\nconst tempVec3A = new THREE.Vector3()\nconst tempVec3B = new THREE.Vector3()\nconst tempVec2 = new THREE.Vector2()\n\nfunction ensureCameraControlsInstalled() {\n if (!controlsInstalled) {\n CameraControls.install({ THREE })\n controlsInstalled = true\n }\n}\n\nfunction toVector3(\n value: Vector3Like | undefined,\n fallback: Vector3Tuple,\n target: THREE.Vector3\n) {\n if (!value) {\n target.set(fallback[0], fallback[1], fallback[2])\n return target\n }\n\n if (Array.isArray(value)) {\n target.set(value[0], value[1], value[2])\n return target\n }\n\n target.copy(value)\n return target\n}\n\n/**\n * Camera controls that manage both perspective and orthographic cameras while\n * extending {@link CameraControls}. Provides helpers to toggle between the\n * camera types and keep the framing consistent.\n */\nexport class DualCameraControls extends CameraControls {\n readonly perspectiveCamera: THREE.PerspectiveCamera\n readonly orthographicCamera: THREE.OrthographicCamera\n\n private readonly renderer: THREE.WebGLRenderer\n private readonly domElementRef: HTMLElement\n private activeMode: CameraMode\n private readonly minOrthoHalfHeight: number\n private readonly updateClock = new THREE.Clock()\n private externalCamera:\n | THREE.PerspectiveCamera\n | THREE.OrthographicCamera\n | null = null\n\n constructor(\n renderer: THREE.WebGLRenderer,\n options: DualCameraControlsOptions = {}\n ) {\n ensureCameraControlsInstalled()\n\n const { domElement = renderer.domElement } = options\n const aspect = resolveAspect(renderer, domElement)\n\n const perspectiveConfig = options.perspective ?? {}\n const orthographicConfig = options.orthographic ?? {}\n\n const perspectiveCamera = new THREE.PerspectiveCamera(\n perspectiveConfig.fov ?? 60,\n aspect,\n perspectiveConfig.near ?? 0.1,\n perspectiveConfig.far ?? 2000\n )\n\n perspectiveCamera.position.copy(\n toVector3(perspectiveConfig.position, [12, 12, 12], new THREE.Vector3())\n )\n\n if (perspectiveConfig.zoom !== undefined) {\n perspectiveCamera.zoom = perspectiveConfig.zoom\n perspectiveCamera.updateProjectionMatrix()\n }\n\n const orthoHalfHeight = Math.max(orthographicConfig.size ?? 20, 0.001) * 0.5\n const orthoHalfWidth = orthoHalfHeight * aspect\n\n const orthographicCamera = new THREE.OrthographicCamera(\n -orthoHalfWidth,\n orthoHalfWidth,\n orthoHalfHeight,\n -orthoHalfHeight,\n orthographicConfig.near ?? 0.1,\n orthographicConfig.far ?? 2000\n )\n\n orthographicCamera.position.copy(\n toVector3(orthographicConfig.position, [12, 12, 12], new THREE.Vector3())\n )\n\n if (orthographicConfig.zoom !== undefined) {\n orthographicCamera.zoom = orthographicConfig.zoom\n orthographicCamera.updateProjectionMatrix()\n }\n\n const initialMode = options.initialMode ?? 'perspective'\n const initialCamera =\n initialMode === 'orthographic' ? orthographicCamera : perspectiveCamera\n\n super(initialCamera, domElement)\n\n const initialTarget = toVector3(\n options.initialTarget,\n [0, 0, 0],\n new THREE.Vector3()\n )\n void this.setLookAt(\n initialCamera.position.x,\n initialCamera.position.y,\n initialCamera.position.z,\n initialTarget.x,\n initialTarget.y,\n initialTarget.z,\n false\n )\n\n this.renderer = renderer\n this.domElementRef = domElement\n this.perspectiveCamera = perspectiveCamera\n this.orthographicCamera = orthographicCamera\n this.activeMode = initialMode\n this.minOrthoHalfHeight = orthoHalfHeight\n\n this.updateInputBindingsForMode(initialMode)\n }\n\n get mode(): CameraMode {\n return this.activeMode\n }\n\n /**\n * Returns the currently active camera instance.\n */\n get activeCamera(): THREE.PerspectiveCamera | THREE.OrthographicCamera {\n return this.camera\n }\n\n /**\n * Switch to the perspective camera while keeping the current framing.\n */\n switchToPerspective(enableTransition = false) {\n if (this.activeMode === 'perspective' && !this.externalCamera) {\n return\n }\n\n // Clear external camera if set\n this.externalCamera = null\n\n const target = this.getTarget(tempVec3A)\n const position = this.getPosition(tempVec3B)\n\n const aspect = resolveAspect(this.renderer, this.domElementRef)\n this.perspectiveCamera.aspect = aspect\n this.perspectiveCamera.position.copy(position)\n this.perspectiveCamera.quaternion.copy(this.camera.quaternion)\n this.perspectiveCamera.up.copy(this.camera.up)\n this.perspectiveCamera.updateProjectionMatrix()\n\n this.camera = this.perspectiveCamera\n this.activeMode = 'perspective'\n this.updateInputBindingsForMode('perspective')\n\n void this.setLookAt(\n position.x,\n position.y,\n position.z,\n target.x,\n target.y,\n target.z,\n enableTransition\n )\n\n this.dispatchEvent({\n type: 'modechange',\n mode: this.activeMode,\n previousMode: 'orthographic',\n camera: this.perspectiveCamera,\n } satisfies ModeChangedEvent)\n }\n\n /**\n * Switch to the orthographic camera while keeping the current framing.\n */\n switchToOrthographic(enableTransition = false) {\n if (this.activeMode === 'orthographic' && !this.externalCamera) {\n return\n }\n\n // Clear external camera if set\n this.externalCamera = null\n\n const target = this.getTarget(tempVec3A)\n const position = this.getPosition(tempVec3B)\n\n const aspect = resolveAspect(this.renderer, this.domElementRef)\n this.updateOrthographicFrustum(position, target, aspect)\n\n this.orthographicCamera.position.copy(position)\n this.orthographicCamera.quaternion.copy(this.camera.quaternion)\n this.orthographicCamera.up.copy(this.camera.up)\n this.orthographicCamera.updateProjectionMatrix()\n\n this.camera = this.orthographicCamera\n this.activeMode = 'orthographic'\n this.updateInputBindingsForMode('orthographic')\n\n void this.setLookAt(\n position.x,\n position.y,\n position.z,\n target.x,\n target.y,\n target.z,\n enableTransition\n )\n\n this.dispatchEvent({\n type: 'modechange',\n mode: this.activeMode,\n previousMode: 'perspective',\n camera: this.orthographicCamera,\n } satisfies ModeChangedEvent)\n }\n\n /**\n * Toggles between perspective and orthographic camera modes.\n */\n toggleCameraMode(enableTransition = false) {\n if (this.activeMode === 'perspective') {\n this.switchToOrthographic(enableTransition)\n } else {\n this.switchToPerspective(enableTransition)\n }\n }\n\n /**\n * Returns true if currently using an external camera.\n */\n get isUsingExternalCamera(): boolean {\n return this.externalCamera !== null\n }\n\n /**\n * Sets an external camera to use with the controls.\n * This allows using a camera created outside of DualCameraControls.\n * Call `clearExternalCamera()` to return to the internal cameras.\n */\n setCamera(\n camera: THREE.PerspectiveCamera | THREE.OrthographicCamera,\n target?: Vector3Like,\n enableTransition = false\n ) {\n const previousCamera = this.camera\n this.externalCamera = camera\n\n // Update aspect/frustum for the external camera\n const aspect = resolveAspect(this.renderer, this.domElementRef)\n if (camera.type === 'PerspectiveCamera') {\n ;(camera as THREE.PerspectiveCamera).aspect = aspect\n camera.updateProjectionMatrix()\n } else if (camera.type === 'OrthographicCamera') {\n // Maintain the camera's existing frustum proportions but update aspect\n const ortho = camera as THREE.OrthographicCamera\n const currentHeight = (ortho.top - ortho.bottom) * 0.5\n const newHalfWidth = currentHeight * aspect\n ortho.left = -newHalfWidth\n ortho.right = newHalfWidth\n ortho.updateProjectionMatrix()\n }\n\n this.camera = camera\n const mode: CameraMode =\n camera.type === 'PerspectiveCamera' ? 'perspective' : 'orthographic'\n this.activeMode = mode\n this.updateInputBindingsForMode(mode)\n\n // Set up the camera position and target in the controls\n const targetVec = toVector3(target, [0, 0, 0], tempVec3A)\n void this.setLookAt(\n camera.position.x,\n camera.position.y,\n camera.position.z,\n targetVec.x,\n targetVec.y,\n targetVec.z,\n enableTransition\n )\n\n this.dispatchEvent({\n type: 'externalcamerachange',\n camera,\n previousCamera,\n } satisfies ExternalCameraChangedEvent)\n }\n\n /**\n * Clears the external camera and returns to using the internal cameras.\n * Will switch to the camera matching the current mode.\n */\n clearExternalCamera(enableTransition = false) {\n if (!this.externalCamera) {\n return\n }\n\n const target = this.getTarget(tempVec3A)\n const position = this.getPosition(tempVec3B)\n\n this.externalCamera = null\n\n // Return to the internal camera matching the current mode\n const internalCamera =\n this.activeMode === 'orthographic'\n ? this.orthographicCamera\n : this.perspectiveCamera\n\n internalCamera.position.copy(position)\n internalCamera.quaternion.copy(this.camera.quaternion)\n internalCamera.up.copy(this.camera.up)\n\n const aspect = resolveAspect(this.renderer, this.domElementRef)\n if (this.activeMode === 'orthographic') {\n this.updateOrthographicFrustum(position, target, aspect)\n } else {\n this.perspectiveCamera.aspect = aspect\n this.perspectiveCamera.updateProjectionMatrix()\n }\n\n this.camera = internalCamera\n\n void this.setLookAt(\n position.x,\n position.y,\n position.z,\n target.x,\n target.y,\n target.z,\n enableTransition\n )\n\n this.dispatchEvent({\n type: 'modechange',\n mode: this.activeMode,\n previousMode: this.activeMode,\n camera: internalCamera,\n } satisfies ModeChangedEvent)\n }\n\n /**\n * Update camera projection parameters when the viewport size changes.\n */\n handleResize(width: number, height: number) {\n const aspect = height === 0 ? 1 : width / height\n\n this.perspectiveCamera.aspect = aspect\n this.perspectiveCamera.updateProjectionMatrix()\n\n const target = this.getTarget(tempVec3A)\n const position = this.getPosition(tempVec3B)\n this.updateOrthographicFrustum(position, target, aspect)\n\n if (this.activeMode === 'orthographic') {\n this.camera = this.orthographicCamera\n void this.setLookAt(\n position.x,\n position.y,\n position.z,\n target.x,\n target.y,\n target.z,\n false\n )\n }\n }\n\n /**\n * Moves the camera to a new position and target.\n */\n moveCamera(\n position: Vector3Like,\n target: Vector3Like,\n enableTransition = true\n ) {\n toVector3(position, [0, 0, 0], tempVec3A)\n toVector3(target, [0, 0, 0], tempVec3B)\n\n return this.setLookAt(\n tempVec3A.x,\n tempVec3A.y,\n tempVec3A.z,\n tempVec3B.x,\n tempVec3B.y,\n tempVec3B.z,\n enableTransition\n )\n }\n\n /**\n * Updates the controls using an internally managed clock.\n * Useful when you don't want to pass delta time each frame.\n */\n updateDelta(): ReturnType<CameraControls['update']> {\n const delta = this.updateClock.getDelta()\n return super.update(delta)\n }\n\n private updateInputBindingsForMode(mode: CameraMode) {\n const { ACTION } = CameraControls\n\n if (mode === 'orthographic') {\n this.mouseButtons.left = ACTION.TRUCK\n this.mouseButtons.right = ACTION.ROTATE\n this.mouseButtons.wheel = ACTION.ZOOM\n this.touches.one = ACTION.TOUCH_TRUCK\n this.touches.two = ACTION.TOUCH_ZOOM_TRUCK\n } else {\n this.mouseButtons.left = ACTION.ROTATE\n this.mouseButtons.right = ACTION.TRUCK\n this.mouseButtons.wheel = ACTION.DOLLY\n this.touches.one = ACTION.TOUCH_ROTATE\n this.touches.two = ACTION.TOUCH_DOLLY_TRUCK\n }\n }\n\n private updateOrthographicFrustum(\n position: THREE.Vector3,\n target: THREE.Vector3,\n aspect: number\n ) {\n const distance = Math.max(position.distanceTo(target), 0.001)\n const fov = this.perspectiveCamera.fov\n const halfHeight = Math.max(\n distance * Math.tan(THREE.MathUtils.degToRad(fov * 0.5)),\n this.minOrthoHalfHeight\n )\n const halfWidth = halfHeight * aspect\n\n this.orthographicCamera.left = -halfWidth\n this.orthographicCamera.right = halfWidth\n this.orthographicCamera.top = halfHeight\n this.orthographicCamera.bottom = -halfHeight\n this.orthographicCamera.updateProjectionMatrix()\n }\n}\n\nfunction resolveAspect(\n renderer: THREE.WebGLRenderer,\n domElement: HTMLElement\n): number {\n const size = renderer.getSize(tempVec2)\n if (size.y > 0) {\n return size.x / size.y\n }\n\n const { clientWidth, clientHeight } = domElement\n if (clientHeight > 0) {\n return clientWidth / clientHeight\n }\n\n return 1\n}\n"],"mappings":"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","names":[]}
|
|
@@ -3,6 +3,7 @@ import * as THREE from "three";
|
|
|
3
3
|
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
4
4
|
import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader.js";
|
|
5
5
|
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader.js";
|
|
6
|
+
import { USDLoader } from "three/examples/jsm/loaders/USDLoader.js";
|
|
6
7
|
var AssetLoader = class extends THREE.EventDispatcher {
|
|
7
8
|
constructor() {
|
|
8
9
|
super();
|
|
@@ -15,6 +16,7 @@ var AssetLoader = class extends THREE.EventDispatcher {
|
|
|
15
16
|
this.gltfLoader = new GLTFLoader();
|
|
16
17
|
this.fbxLoader = new FBXLoader();
|
|
17
18
|
this.objLoader = new OBJLoader();
|
|
19
|
+
this.usdLoader = new USDLoader();
|
|
18
20
|
}
|
|
19
21
|
/**
|
|
20
22
|
* Create a placeholder cube with shader effect
|
|
@@ -32,7 +34,9 @@ var AssetLoader = class extends THREE.EventDispatcher {
|
|
|
32
34
|
opacity: { value: opacity },
|
|
33
35
|
fillProgress: { value: 0 },
|
|
34
36
|
time: { value: 0 },
|
|
35
|
-
isError: { value: 0 }
|
|
37
|
+
isError: { value: 0 },
|
|
38
|
+
yMin: { value: -height / 2 },
|
|
39
|
+
yMax: { value: height / 2 }
|
|
36
40
|
},
|
|
37
41
|
vertexShader: `
|
|
38
42
|
varying vec3 vPosition;
|
|
@@ -47,15 +51,17 @@ var AssetLoader = class extends THREE.EventDispatcher {
|
|
|
47
51
|
uniform float fillProgress;
|
|
48
52
|
uniform float time;
|
|
49
53
|
uniform float isError;
|
|
54
|
+
uniform float yMin;
|
|
55
|
+
uniform float yMax;
|
|
50
56
|
varying vec3 vPosition;
|
|
51
57
|
|
|
52
58
|
void main() {
|
|
53
|
-
float normalizedY = (vPosition.y
|
|
59
|
+
float normalizedY = (vPosition.y - yMin) / (yMax - yMin); // Normalize based on actual geometry bounds
|
|
54
60
|
float alpha = opacity;
|
|
55
61
|
|
|
56
62
|
// Create fill-up effect
|
|
57
63
|
if (normalizedY > fillProgress) {
|
|
58
|
-
alpha *= 0.
|
|
64
|
+
alpha *= 0.1; // Reduce opacity for unfilled parts
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
// Error state effects
|
|
@@ -181,7 +187,7 @@ var AssetLoader = class extends THREE.EventDispatcher {
|
|
|
181
187
|
lowResUrl,
|
|
182
188
|
enableCaching = true,
|
|
183
189
|
placeholderColor = 5227511,
|
|
184
|
-
placeholderOpacity = 0.
|
|
190
|
+
placeholderOpacity = 0.4,
|
|
185
191
|
errorColor = 16729156,
|
|
186
192
|
errorOpacity = 0.5
|
|
187
193
|
} = options;
|
|
@@ -242,14 +248,17 @@ var AssetLoader = class extends THREE.EventDispatcher {
|
|
|
242
248
|
loadModel(type, url, isLowRes) {
|
|
243
249
|
return new Promise((resolve, reject) => {
|
|
244
250
|
const onProgress = (event) => {
|
|
245
|
-
|
|
251
|
+
let percentage = -1;
|
|
252
|
+
if (event.lengthComputable && event.total > 0 && event.loaded <= event.total) {
|
|
253
|
+
percentage = event.loaded / event.total * 100;
|
|
254
|
+
}
|
|
246
255
|
this.dispatchEvent({
|
|
247
256
|
type: "progress",
|
|
248
257
|
loaded: event.loaded,
|
|
249
258
|
total: event.total,
|
|
250
259
|
percentage
|
|
251
260
|
});
|
|
252
|
-
if (!isLowRes) {
|
|
261
|
+
if (!isLowRes && percentage >= 0) {
|
|
253
262
|
this.updatePlaceholder(percentage / 100);
|
|
254
263
|
}
|
|
255
264
|
};
|
|
@@ -291,6 +300,18 @@ var AssetLoader = class extends THREE.EventDispatcher {
|
|
|
291
300
|
onError
|
|
292
301
|
);
|
|
293
302
|
break;
|
|
303
|
+
case "usd":
|
|
304
|
+
case "usdz":
|
|
305
|
+
this.usdLoader.load(
|
|
306
|
+
url,
|
|
307
|
+
(usd) => {
|
|
308
|
+
this.positionAssetAtBottomCenter(usd);
|
|
309
|
+
resolve(usd);
|
|
310
|
+
},
|
|
311
|
+
onProgress,
|
|
312
|
+
onError
|
|
313
|
+
);
|
|
314
|
+
break;
|
|
294
315
|
default:
|
|
295
316
|
reject(new Error(`Unsupported asset type: ${type}`));
|
|
296
317
|
}
|
|
@@ -337,4 +358,4 @@ var AssetLoader = class extends THREE.EventDispatcher {
|
|
|
337
358
|
export {
|
|
338
359
|
AssetLoader
|
|
339
360
|
};
|
|
340
|
-
//# sourceMappingURL=chunk-
|
|
361
|
+
//# sourceMappingURL=chunk-WZ6IGBSE.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'\nimport { USDLoader } from 'three/examples/jsm/loaders/USDLoader.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' | 'usd' | 'usdz'\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 usdLoader: USDLoader\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 this.usdLoader = new USDLoader()\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 yMin: { value: -height / 2 },\n yMax: { value: height / 2 },\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 uniform float yMin;\n uniform float yMax;\n varying vec3 vPosition;\n \n void main() {\n float normalizedY = (vPosition.y - yMin) / (yMax - yMin); // Normalize based on actual geometry bounds\n float alpha = opacity;\n \n // Create fill-up effect\n if (normalizedY > fillProgress) {\n alpha *= 0.1; // 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.4,\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 // Handle cases where loaded > total (compressed content mismatch)\n // or total is 0 (unknown content length)\n let percentage = -1 // Default to indeterminate\n\n // Only calculate percentage if lengthComputable is true AND loaded <= total\n // This prevents false 100% when server reports compressed size\n if (\n event.lengthComputable &&\n event.total > 0 &&\n event.loaded <= event.total\n ) {\n percentage = (event.loaded / event.total) * 100\n }\n\n this.dispatchEvent({\n type: 'progress',\n loaded: event.loaded,\n total: event.total,\n percentage,\n })\n\n // Update placeholder fill only with valid percentage\n if (!isLowRes && percentage >= 0) {\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 case 'usd':\n case 'usdz':\n this.usdLoader.load(\n url,\n (usd) => {\n this.positionAssetAtBottomCenter(usd)\n resolve(usd)\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":";AAAA,YAAY,WAAW;AACvB,SAAS,kBAAkB;AAC3B,SAAS,iBAAiB;AAC1B,SAAS,iBAAiB;AAC1B,SAAS,iBAAiB;AAyBnB,IAAM,cAAN,cAAgC,sBAAqC;AAAA,EAY1E,cAAc;AACZ,UAAM;AAZR,SAAQ,QAAqC,oBAAI,IAAI;AAKrD,SAAQ,cAAqC;AAC7C,SAAQ,cAAqC;AAC7C,SAAQ,cAAqC;AAC7C,SAAQ,aAAqB;AAC7B,SAAQ,eAAuB;AAI7B,SAAK,aAAa,IAAI,WAAW;AACjC,SAAK,YAAY,IAAI,UAAU;AAC/B,SAAK,YAAY,IAAI,UAAU;AAC/B,SAAK,YAAY,IAAI,UAAU;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKQ,kBACN,MACA,QAAgB,SAChB,UAAkB,KACF;AAChB,UAAM,CAAC,OAAO,QAAQ,KAAK,IAAI;AAC/B,UAAM,WAAW,IAAU,kBAAY,OAAO,QAAQ,KAAK;AAG3D,UAAM,WAAW,IAAU,qBAAe;AAAA,MACxC,MAAY;AAAA,MACZ,YAAY;AAAA,MACZ,UAAgB;AAAA,MAChB,aAAa;AAAA,MACb,UAAU;AAAA,QACR,OAAO,EAAE,OAAO,IAAU,YAAM,KAAK,EAAE;AAAA,QACvC,SAAS,EAAE,OAAO,QAAQ;AAAA,QAC1B,cAAc,EAAE,OAAO,EAAI;AAAA,QAC3B,MAAM,EAAE,OAAO,EAAI;AAAA,QACnB,SAAS,EAAE,OAAO,EAAI;AAAA,QACtB,MAAM,EAAE,OAAO,CAAC,SAAS,EAAE;AAAA,QAC3B,MAAM,EAAE,OAAO,SAAS,EAAE;AAAA,MAC5B;AAAA,MACA,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOd,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAsClB,CAAC;AAED,UAAM,OAAO,IAAU,WAAK,UAAU,QAAQ;AAC9C,SAAK,4BAA4B,IAAI;AACrC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,kBAAkB,UAAkB;AAC1C,QAAI,KAAK,eAAe,KAAK,uBAA6B,YAAM;AAC9D,YAAM,WAAW,KAAK,YAAY;AAClC,UAAI,SAAS,YAAY,SAAS,SAAS,cAAc;AACvD,iBAAS,SAAS,aAAa,QAAQ;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,sBAAsB;AAC5B,QAAI,KAAK,eAAe,KAAK,uBAA6B,YAAM;AAC9D,YAAM,WAAW,KAAK,YAAY;AAClC,UAAI,SAAS,UAAU;AAErB,YAAI,SAAS,SAAS,OAAO;AAC3B,mBAAS,SAAS,MAAM,QAAQ,IAAU,YAAM,KAAK,UAAU;AAAA,QACjE;AAEA,YAAI,SAAS,SAAS,SAAS;AAC7B,mBAAS,SAAS,QAAQ,QAAQ,KAAK;AAAA,QACzC;AAEA,YAAI,SAAS,SAAS,cAAc;AAClC,mBAAS,SAAS,aAAa,QAAQ;AAAA,QACzC;AAEA,YAAI,SAAS,SAAS,SAAS;AAC7B,mBAAS,SAAS,QAAQ,QAAQ;AAAA,QACpC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKO,2BAA2B,WAAmB;AACnD,QAAI,KAAK,eAAe,KAAK,uBAA6B,YAAM;AAC9D,YAAM,WAAW,KAAK,YAAY;AAClC,UAAI,SAAS,YAAY,SAAS,SAAS,MAAM;AAC/C,iBAAS,SAAS,KAAK,SAAS;AAAA,MAClC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,4BAA4B,QAAwB;AAC1D,WAAO,kBAAkB,IAAI;AAE7B,UAAM,cAAc,IAAU,WAAK,EAAE,cAAc,MAAM;AACzD,QAAI,YAAY,QAAQ,GAAG;AACzB;AAAA,IACF;AAEA,UAAM,SAAS,YAAY,UAAU,IAAU,cAAQ,CAAC;AACxD,UAAM,eAAe,IAAU;AAAA,MAC7B,OAAO;AAAA,MACP,YAAY,IAAI,IAAI;AAAA,MACpB,OAAO;AAAA,IACT;AAEA,QACE,CAAC,OAAO,SAAS,aAAa,CAAC,KAC/B,CAAC,OAAO,SAAS,aAAa,CAAC,KAC/B,CAAC,OAAO,SAAS,aAAa,CAAC,GAC/B;AACA;AAAA,IACF;AAEA,WAAO,SAAS,IAAI,YAAY;AAChC,WAAO,SAAS,qBAAqB;AACrC,WAAO,kBAAkB,IAAI;AAAA,EAC/B;AAAA,EAEQ,yBACN,QACsB;AACtB,UAAM,SAAS,OAAO,SAAS;AAC/B,QAAI,kBAAwB,eAAS;AACnC,aAAO;AAAA,IACT;AAEA,QAAI,UAAU,OAAO,WAAW,UAAU;AACxC,YAAM;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,MACN,IAAI;AACJ,YAAM,aAAa,IAAU,cAAQ,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AAC3D,aAAO,SAAS,qBAAqB;AACrC,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,0BAA0B,QAAiC;AACjE,UAAM,YAAY,KAAK,yBAAyB,MAAM,MAAM;AAC5D,WAAO,SAAS,QAAQ,CAAC,UAAU,KAAK,0BAA0B,KAAK,CAAC;AACxE,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KAAK,SAAsD;AAC/D,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,gBAAgB;AAAA,MAChB,mBAAmB;AAAA,MACnB,qBAAqB;AAAA,MACrB,aAAa;AAAA,MACb,eAAe;AAAA,IACjB,IAAI;AAGJ,QAAI,iBAAiB,KAAK,MAAM,IAAI,GAAG,GAAG;AACxC,YAAM,cAAc,KAAK,MAAM,IAAI,GAAG,EAAG,MAAM,IAAI;AACnD,YAAM,YAAY,KAAK,0BAA0B,WAAW;AAE5D,UAAI,CAAC,WAAW;AACd,aAAK,4BAA4B,WAAW;AAAA,MAC9C,OAAO;AACL,oBAAY,kBAAkB,IAAI;AAAA,MACpC;AAEA,WAAK,cAAc;AACnB,WAAK,cAAc,EAAE,MAAM,UAAU,OAAO,YAAY,CAAC;AACzD,aAAO;AAAA,IACT;AAGA,SAAK,aAAa;AAClB,SAAK,eAAe;AAGpB,QAAI,MAAM;AACR,WAAK,cAAc,KAAK;AAAA,QACtB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,WAAK,cAAc;AAAA,QACjB,MAAM;AAAA,QACN,aAAa,KAAK;AAAA,MACpB,CAAC;AAAA,IACH;AAEA,QAAI;AAEF,UAAI,WAAW;AACb,cAAM,SAAS,MAAM,KAAK,UAAU,MAAM,WAAW,IAAI;AACzD,aAAK,cAAc;AACnB,aAAK,cAAc,EAAE,MAAM,gBAAgB,OAAO,CAAC;AAAA,MACrD;AAGA,YAAM,QAAQ,MAAM,KAAK,UAAU,MAAM,KAAK,KAAK;AACnD,WAAK,cAAc;AAGnB,UAAI,eAAe;AACjB,cAAM,aAAa,MAAM,MAAM,IAAI;AACnC,cAAM,YAAY,KAAK,0BAA0B,UAAU;AAC3D,YAAI,CAAC,WAAW;AACd,eAAK,4BAA4B,UAAU;AAAA,QAC7C,OAAO;AACL,qBAAW,kBAAkB,IAAI;AAAA,QACnC;AACA,aAAK,MAAM,IAAI,KAAK,UAAU;AAAA,MAChC;AAEA,WAAK,cAAc,EAAE,MAAM,UAAU,MAAM,CAAC;AAC5C,aAAO;AAAA,IACT,SAAS,OAAO;AAEd,WAAK,oBAAoB;AACzB,WAAK,cAAc,EAAE,MAAM,SAAS,MAAsB,CAAC;AAC3D,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,UACN,MACA,KACA,UACyB;AACzB,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAM,aAAa,CAAC,UAAyB;AAG3C,YAAI,aAAa;AAIjB,YACE,MAAM,oBACN,MAAM,QAAQ,KACd,MAAM,UAAU,MAAM,OACtB;AACA,uBAAc,MAAM,SAAS,MAAM,QAAS;AAAA,QAC9C;AAEA,aAAK,cAAc;AAAA,UACjB,MAAM;AAAA,UACN,QAAQ,MAAM;AAAA,UACd,OAAO,MAAM;AAAA,UACb;AAAA,QACF,CAAC;AAGD,YAAI,CAAC,YAAY,cAAc,GAAG;AAChC,eAAK,kBAAkB,aAAa,GAAG;AAAA,QACzC;AAAA,MACF;AAEA,YAAM,UAAU,CAAC,UAAmB;AAClC,eAAO,KAAK;AAAA,MACd;AAEA,cAAQ,MAAM;AAAA,QACZ,KAAK;AACH,eAAK,WAAW;AAAA,YACd;AAAA,YACA,CAAC,SAAS;AACR,oBAAM,QAAQ,KAAK;AACnB,mBAAK,4BAA4B,KAAK;AACtC,sBAAQ,KAAK;AAAA,YACf;AAAA,YACA;AAAA,YACA;AAAA,UACF;AACA;AAAA,QAEF,KAAK;AACH,eAAK,UAAU;AAAA,YACb;AAAA,YACA,CAAC,QAAQ;AACP,mBAAK,4BAA4B,GAAG;AACpC,sBAAQ,GAAG;AAAA,YACb;AAAA,YACA;AAAA,YACA;AAAA,UACF;AACA;AAAA,QAEF,KAAK;AACH,eAAK,UAAU;AAAA,YACb;AAAA,YACA,CAAC,QAAQ;AACP,mBAAK,4BAA4B,GAAG;AACpC,sBAAQ,GAAG;AAAA,YACb;AAAA,YACA;AAAA,YACA;AAAA,UACF;AACA;AAAA,QAEF,KAAK;AAAA,QACL,KAAK;AACH,eAAK,UAAU;AAAA,YACb;AAAA,YACA,CAAC,QAAQ;AACP,mBAAK,4BAA4B,GAAG;AACpC,sBAAQ,GAAG;AAAA,YACb;AAAA,YACA;AAAA,YACA;AAAA,UACF;AACA;AAAA,QAEF;AACE,iBAAO,IAAI,MAAM,2BAA2B,IAAI,EAAE,CAAC;AAAA,MACvD;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAwC;AACtC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,WAAkC;AAChC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAwC;AACtC,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa;AACX,SAAK,MAAM,MAAM;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,KAAa;AAC3B,SAAK,MAAM,OAAO,GAAG;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,eAAuB;AACrB,WAAO,KAAK,MAAM;AAAA,EACpB;AACF;","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -51,6 +51,7 @@ var THREE = __toESM(require("three"));
|
|
|
51
51
|
var import_GLTFLoader = require("three/examples/jsm/loaders/GLTFLoader.js");
|
|
52
52
|
var import_FBXLoader = require("three/examples/jsm/loaders/FBXLoader.js");
|
|
53
53
|
var import_OBJLoader = require("three/examples/jsm/loaders/OBJLoader.js");
|
|
54
|
+
var import_USDLoader = require("three/examples/jsm/loaders/USDLoader.js");
|
|
54
55
|
var AssetLoader = class extends THREE.EventDispatcher {
|
|
55
56
|
constructor() {
|
|
56
57
|
super();
|
|
@@ -63,6 +64,7 @@ var AssetLoader = class extends THREE.EventDispatcher {
|
|
|
63
64
|
this.gltfLoader = new import_GLTFLoader.GLTFLoader();
|
|
64
65
|
this.fbxLoader = new import_FBXLoader.FBXLoader();
|
|
65
66
|
this.objLoader = new import_OBJLoader.OBJLoader();
|
|
67
|
+
this.usdLoader = new import_USDLoader.USDLoader();
|
|
66
68
|
}
|
|
67
69
|
/**
|
|
68
70
|
* Create a placeholder cube with shader effect
|
|
@@ -80,7 +82,9 @@ var AssetLoader = class extends THREE.EventDispatcher {
|
|
|
80
82
|
opacity: { value: opacity },
|
|
81
83
|
fillProgress: { value: 0 },
|
|
82
84
|
time: { value: 0 },
|
|
83
|
-
isError: { value: 0 }
|
|
85
|
+
isError: { value: 0 },
|
|
86
|
+
yMin: { value: -height / 2 },
|
|
87
|
+
yMax: { value: height / 2 }
|
|
84
88
|
},
|
|
85
89
|
vertexShader: `
|
|
86
90
|
varying vec3 vPosition;
|
|
@@ -95,15 +99,17 @@ var AssetLoader = class extends THREE.EventDispatcher {
|
|
|
95
99
|
uniform float fillProgress;
|
|
96
100
|
uniform float time;
|
|
97
101
|
uniform float isError;
|
|
102
|
+
uniform float yMin;
|
|
103
|
+
uniform float yMax;
|
|
98
104
|
varying vec3 vPosition;
|
|
99
105
|
|
|
100
106
|
void main() {
|
|
101
|
-
float normalizedY = (vPosition.y
|
|
107
|
+
float normalizedY = (vPosition.y - yMin) / (yMax - yMin); // Normalize based on actual geometry bounds
|
|
102
108
|
float alpha = opacity;
|
|
103
109
|
|
|
104
110
|
// Create fill-up effect
|
|
105
111
|
if (normalizedY > fillProgress) {
|
|
106
|
-
alpha *= 0.
|
|
112
|
+
alpha *= 0.1; // Reduce opacity for unfilled parts
|
|
107
113
|
}
|
|
108
114
|
|
|
109
115
|
// Error state effects
|
|
@@ -229,7 +235,7 @@ var AssetLoader = class extends THREE.EventDispatcher {
|
|
|
229
235
|
lowResUrl,
|
|
230
236
|
enableCaching = true,
|
|
231
237
|
placeholderColor = 5227511,
|
|
232
|
-
placeholderOpacity = 0.
|
|
238
|
+
placeholderOpacity = 0.4,
|
|
233
239
|
errorColor = 16729156,
|
|
234
240
|
errorOpacity = 0.5
|
|
235
241
|
} = options;
|
|
@@ -290,14 +296,17 @@ var AssetLoader = class extends THREE.EventDispatcher {
|
|
|
290
296
|
loadModel(type, url, isLowRes) {
|
|
291
297
|
return new Promise((resolve, reject) => {
|
|
292
298
|
const onProgress = (event) => {
|
|
293
|
-
|
|
299
|
+
let percentage = -1;
|
|
300
|
+
if (event.lengthComputable && event.total > 0 && event.loaded <= event.total) {
|
|
301
|
+
percentage = event.loaded / event.total * 100;
|
|
302
|
+
}
|
|
294
303
|
this.dispatchEvent({
|
|
295
304
|
type: "progress",
|
|
296
305
|
loaded: event.loaded,
|
|
297
306
|
total: event.total,
|
|
298
307
|
percentage
|
|
299
308
|
});
|
|
300
|
-
if (!isLowRes) {
|
|
309
|
+
if (!isLowRes && percentage >= 0) {
|
|
301
310
|
this.updatePlaceholder(percentage / 100);
|
|
302
311
|
}
|
|
303
312
|
};
|
|
@@ -339,6 +348,18 @@ var AssetLoader = class extends THREE.EventDispatcher {
|
|
|
339
348
|
onError
|
|
340
349
|
);
|
|
341
350
|
break;
|
|
351
|
+
case "usd":
|
|
352
|
+
case "usdz":
|
|
353
|
+
this.usdLoader.load(
|
|
354
|
+
url,
|
|
355
|
+
(usd) => {
|
|
356
|
+
this.positionAssetAtBottomCenter(usd);
|
|
357
|
+
resolve(usd);
|
|
358
|
+
},
|
|
359
|
+
onProgress,
|
|
360
|
+
onError
|
|
361
|
+
);
|
|
362
|
+
break;
|
|
342
363
|
default:
|
|
343
364
|
reject(new Error(`Unsupported asset type: ${type}`));
|
|
344
365
|
}
|
|
@@ -448,6 +469,7 @@ var DualCameraControls = class extends import_camera_controls.default {
|
|
|
448
469
|
const initialCamera = initialMode === "orthographic" ? orthographicCamera : perspectiveCamera;
|
|
449
470
|
super(initialCamera, domElement);
|
|
450
471
|
this.updateClock = new THREE2.Clock();
|
|
472
|
+
this.externalCamera = null;
|
|
451
473
|
const initialTarget = toVector3(
|
|
452
474
|
options.initialTarget,
|
|
453
475
|
[0, 0, 0],
|
|
@@ -483,9 +505,10 @@ var DualCameraControls = class extends import_camera_controls.default {
|
|
|
483
505
|
* Switch to the perspective camera while keeping the current framing.
|
|
484
506
|
*/
|
|
485
507
|
switchToPerspective(enableTransition = false) {
|
|
486
|
-
if (this.activeMode === "perspective") {
|
|
508
|
+
if (this.activeMode === "perspective" && !this.externalCamera) {
|
|
487
509
|
return;
|
|
488
510
|
}
|
|
511
|
+
this.externalCamera = null;
|
|
489
512
|
const target = this.getTarget(tempVec3A);
|
|
490
513
|
const position = this.getPosition(tempVec3B);
|
|
491
514
|
const aspect = resolveAspect(this.renderer, this.domElementRef);
|
|
@@ -517,9 +540,10 @@ var DualCameraControls = class extends import_camera_controls.default {
|
|
|
517
540
|
* Switch to the orthographic camera while keeping the current framing.
|
|
518
541
|
*/
|
|
519
542
|
switchToOrthographic(enableTransition = false) {
|
|
520
|
-
if (this.activeMode === "orthographic") {
|
|
543
|
+
if (this.activeMode === "orthographic" && !this.externalCamera) {
|
|
521
544
|
return;
|
|
522
545
|
}
|
|
546
|
+
this.externalCamera = null;
|
|
523
547
|
const target = this.getTarget(tempVec3A);
|
|
524
548
|
const position = this.getPosition(tempVec3B);
|
|
525
549
|
const aspect = resolveAspect(this.renderer, this.domElementRef);
|
|
@@ -557,6 +581,92 @@ var DualCameraControls = class extends import_camera_controls.default {
|
|
|
557
581
|
this.switchToPerspective(enableTransition);
|
|
558
582
|
}
|
|
559
583
|
}
|
|
584
|
+
/**
|
|
585
|
+
* Returns true if currently using an external camera.
|
|
586
|
+
*/
|
|
587
|
+
get isUsingExternalCamera() {
|
|
588
|
+
return this.externalCamera !== null;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Sets an external camera to use with the controls.
|
|
592
|
+
* This allows using a camera created outside of DualCameraControls.
|
|
593
|
+
* Call `clearExternalCamera()` to return to the internal cameras.
|
|
594
|
+
*/
|
|
595
|
+
setCamera(camera, target, enableTransition = false) {
|
|
596
|
+
const previousCamera = this.camera;
|
|
597
|
+
this.externalCamera = camera;
|
|
598
|
+
const aspect = resolveAspect(this.renderer, this.domElementRef);
|
|
599
|
+
if (camera.type === "PerspectiveCamera") {
|
|
600
|
+
;
|
|
601
|
+
camera.aspect = aspect;
|
|
602
|
+
camera.updateProjectionMatrix();
|
|
603
|
+
} else if (camera.type === "OrthographicCamera") {
|
|
604
|
+
const ortho = camera;
|
|
605
|
+
const currentHeight = (ortho.top - ortho.bottom) * 0.5;
|
|
606
|
+
const newHalfWidth = currentHeight * aspect;
|
|
607
|
+
ortho.left = -newHalfWidth;
|
|
608
|
+
ortho.right = newHalfWidth;
|
|
609
|
+
ortho.updateProjectionMatrix();
|
|
610
|
+
}
|
|
611
|
+
this.camera = camera;
|
|
612
|
+
const mode = camera.type === "PerspectiveCamera" ? "perspective" : "orthographic";
|
|
613
|
+
this.activeMode = mode;
|
|
614
|
+
this.updateInputBindingsForMode(mode);
|
|
615
|
+
const targetVec = toVector3(target, [0, 0, 0], tempVec3A);
|
|
616
|
+
void this.setLookAt(
|
|
617
|
+
camera.position.x,
|
|
618
|
+
camera.position.y,
|
|
619
|
+
camera.position.z,
|
|
620
|
+
targetVec.x,
|
|
621
|
+
targetVec.y,
|
|
622
|
+
targetVec.z,
|
|
623
|
+
enableTransition
|
|
624
|
+
);
|
|
625
|
+
this.dispatchEvent({
|
|
626
|
+
type: "externalcamerachange",
|
|
627
|
+
camera,
|
|
628
|
+
previousCamera
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Clears the external camera and returns to using the internal cameras.
|
|
633
|
+
* Will switch to the camera matching the current mode.
|
|
634
|
+
*/
|
|
635
|
+
clearExternalCamera(enableTransition = false) {
|
|
636
|
+
if (!this.externalCamera) {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
const target = this.getTarget(tempVec3A);
|
|
640
|
+
const position = this.getPosition(tempVec3B);
|
|
641
|
+
this.externalCamera = null;
|
|
642
|
+
const internalCamera = this.activeMode === "orthographic" ? this.orthographicCamera : this.perspectiveCamera;
|
|
643
|
+
internalCamera.position.copy(position);
|
|
644
|
+
internalCamera.quaternion.copy(this.camera.quaternion);
|
|
645
|
+
internalCamera.up.copy(this.camera.up);
|
|
646
|
+
const aspect = resolveAspect(this.renderer, this.domElementRef);
|
|
647
|
+
if (this.activeMode === "orthographic") {
|
|
648
|
+
this.updateOrthographicFrustum(position, target, aspect);
|
|
649
|
+
} else {
|
|
650
|
+
this.perspectiveCamera.aspect = aspect;
|
|
651
|
+
this.perspectiveCamera.updateProjectionMatrix();
|
|
652
|
+
}
|
|
653
|
+
this.camera = internalCamera;
|
|
654
|
+
void this.setLookAt(
|
|
655
|
+
position.x,
|
|
656
|
+
position.y,
|
|
657
|
+
position.z,
|
|
658
|
+
target.x,
|
|
659
|
+
target.y,
|
|
660
|
+
target.z,
|
|
661
|
+
enableTransition
|
|
662
|
+
);
|
|
663
|
+
this.dispatchEvent({
|
|
664
|
+
type: "modechange",
|
|
665
|
+
mode: this.activeMode,
|
|
666
|
+
previousMode: this.activeMode,
|
|
667
|
+
camera: internalCamera
|
|
668
|
+
});
|
|
669
|
+
}
|
|
560
670
|
/**
|
|
561
671
|
* Update camera projection parameters when the viewport size changes.
|
|
562
672
|
*/
|