|
@@ -1,85 +1,154 @@
|
|
-import type { OrthographicCamera } from 'three'
|
|
|
|
-import type { TresScene } from '../../types'
|
|
|
|
import type { TresContext } from '../useTresContextProvider'
|
|
import type { TresContext } from '../useTresContextProvider'
|
|
|
|
|
|
-import { Camera, PerspectiveCamera } from 'three'
|
|
|
|
|
|
+import type { ComputedRef, Ref } from 'vue'
|
|
import { computed, onUnmounted, ref, watchEffect } from 'vue'
|
|
import { computed, onUnmounted, ref, watchEffect } from 'vue'
|
|
-import { isCamera } from '../../utils/is'
|
|
|
|
|
|
+import { isOrthographicCamera, isPerspectiveCamera } from '../../utils/is'
|
|
|
|
+import type { TresCamera } from '../../types'
|
|
|
|
|
|
-export const useCamera = ({ sizes }: Pick<TresContext, 'sizes'> & { scene: TresScene }) => {
|
|
|
|
- // the computed does not trigger, when for example the camera position changes
|
|
|
|
- const cameras = ref<Camera[]>([])
|
|
|
|
- const camera = computed<Camera | undefined>(
|
|
|
|
- () => cameras.value[0],
|
|
|
|
|
|
+/**
|
|
|
|
+ * Interface for the return value of the useCamera composable
|
|
|
|
+ */
|
|
|
|
+export interface UseCameraReturn {
|
|
|
|
+ /**
|
|
|
|
+ * The active camera
|
|
|
|
+ */
|
|
|
|
+ activeCamera: ComputedRef<TresCamera | undefined>
|
|
|
|
+ /**
|
|
|
|
+ * The list of cameras
|
|
|
|
+ */
|
|
|
|
+ cameras: Ref<TresCamera[]>
|
|
|
|
+ /**
|
|
|
|
+ * Register a camera
|
|
|
|
+ * @param camera - The camera to register
|
|
|
|
+ * @param active - Whether to set the camera as active
|
|
|
|
+ */
|
|
|
|
+ registerCamera: (camera: TresCamera, active?: boolean) => void
|
|
|
|
+ /**
|
|
|
|
+ * Deregister a camera
|
|
|
|
+ * @param camera - The camera to deregister
|
|
|
|
+ */
|
|
|
|
+ deregisterCamera: (camera: TresCamera) => void
|
|
|
|
+ /**
|
|
|
|
+ * Set the active camera
|
|
|
|
+ * @param cameraOrUuid - The camera or its UUID to set as active
|
|
|
|
+ */
|
|
|
|
+ setActiveCamera: (cameraOrUuid: string | TresCamera) => void
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * Interface for the parameters of the useCamera composable
|
|
|
|
+ */
|
|
|
|
+interface UseCameraParams {
|
|
|
|
+ sizes: TresContext['sizes']
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * Composable for managing cameras in a Three.js scene
|
|
|
|
+ * @param params - The parameters for the composable
|
|
|
|
+ * @param params.sizes - The sizes object containing window dimensions
|
|
|
|
+ * @returns The camera management functions and state
|
|
|
|
+ */
|
|
|
|
+export const useCamera = ({ sizes }: UseCameraParams): UseCameraReturn => {
|
|
|
|
+ // Store all registered cameras
|
|
|
|
+ const cameras = ref<TresCamera[]>([])
|
|
|
|
+ // Store the UUID of the active camera
|
|
|
|
+ const activeCameraUuid = ref<string | null>(null)
|
|
|
|
+
|
|
|
|
+ // Computed property that returns the active camera
|
|
|
|
+ const activeCamera = computed<TresCamera | undefined>(
|
|
|
|
+ () => cameras.value.find(camera => camera.uuid === activeCameraUuid.value),
|
|
)
|
|
)
|
|
|
|
|
|
- const setCameraActive = (cameraOrUuid: string | Camera) => {
|
|
|
|
- const camera = cameraOrUuid instanceof Camera
|
|
|
|
- ? cameraOrUuid
|
|
|
|
- : cameras.value.find((camera: Camera) => camera.uuid === cameraOrUuid)
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Set the active camera
|
|
|
|
+ * @param cameraOrUuid - The camera or its UUID to set as active
|
|
|
|
+ */
|
|
|
|
+ const setActiveCamera = (cameraOrUuid: string | TresCamera): void => {
|
|
|
|
+ const camera = cameras.value.find((camera: TresCamera) => camera.uuid === cameraOrUuid)
|
|
|
|
|
|
if (!camera) { return }
|
|
if (!camera) { return }
|
|
|
|
|
|
|
|
+ // Move the active camera to the beginning of the array
|
|
const otherCameras = cameras.value.filter(({ uuid }) => uuid !== camera.uuid)
|
|
const otherCameras = cameras.value.filter(({ uuid }) => uuid !== camera.uuid)
|
|
cameras.value = [camera, ...otherCameras]
|
|
cameras.value = [camera, ...otherCameras]
|
|
|
|
+ activeCameraUuid.value = camera.uuid
|
|
}
|
|
}
|
|
|
|
|
|
- const registerCamera = (maybeCamera: unknown, active = false) => {
|
|
|
|
- if (isCamera(maybeCamera)) {
|
|
|
|
- const camera = maybeCamera
|
|
|
|
- if (cameras.value.some(({ uuid }) => uuid === camera.uuid)) { return }
|
|
|
|
-
|
|
|
|
- if (active) { setCameraActive(camera) }
|
|
|
|
- else { cameras.value.push(camera) }
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Register a camera
|
|
|
|
+ * @param camera - The camera to register
|
|
|
|
+ * @param active - Whether to set the camera as active
|
|
|
|
+ */
|
|
|
|
+ const registerCamera = (camera: TresCamera, active = false): void => {
|
|
|
|
+ // Skip if camera is already registered
|
|
|
|
+ // Skip if camera is already registered
|
|
|
|
+ if (cameras.value.some(({ uuid }) => uuid === camera.uuid)) { return }
|
|
|
|
+ cameras.value.push(camera)
|
|
|
|
+ if (active) {
|
|
|
|
+ setActiveCamera(camera.uuid)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- const deregisterCamera = (maybeCamera: unknown) => {
|
|
|
|
- if (isCamera(maybeCamera)) {
|
|
|
|
- const camera = maybeCamera
|
|
|
|
- cameras.value = cameras.value.filter(({ uuid }) => uuid !== camera.uuid)
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Deregister a camera
|
|
|
|
+ * @param camera - The camera to deregister
|
|
|
|
+ */
|
|
|
|
+ const deregisterCamera = (camera: TresCamera): void => {
|
|
|
|
+ cameras.value = cameras.value.filter(({ uuid }) => uuid !== camera.uuid)
|
|
|
|
+
|
|
|
|
+ // If the deregistered camera was active, clear the active camera
|
|
|
|
+ if (activeCameraUuid.value === camera.uuid) {
|
|
|
|
+ activeCameraUuid.value = null
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * Update camera aspect ratios when the window size changes
|
|
|
|
+ */
|
|
watchEffect(() => {
|
|
watchEffect(() => {
|
|
if (sizes.aspectRatio.value) {
|
|
if (sizes.aspectRatio.value) {
|
|
- cameras.value.forEach((camera: Camera & { manual?: boolean }) => {
|
|
|
|
- // NOTE: Don't mess with the camera if it belongs to the user.
|
|
|
|
- // https://github.com/pmndrs/react-three-fiber/blob/0ef66a1d23bf16ecd457dde92b0517ceec9861c5/packages/fiber/src/core/utils.ts#L457
|
|
|
|
- //
|
|
|
|
- // To set camera as "manual":
|
|
|
|
- // const myCamera = new PerspectiveCamera(); // or OrthographicCamera
|
|
|
|
- // (myCamera as any).manual = true
|
|
|
|
- if (!camera.manual && (camera instanceof PerspectiveCamera || isOrthographicCamera(camera))) {
|
|
|
|
- if (camera instanceof PerspectiveCamera) {
|
|
|
|
- camera.aspect = sizes.aspectRatio.value
|
|
|
|
- }
|
|
|
|
- else {
|
|
|
|
- camera.left = sizes.width.value * -0.5
|
|
|
|
- camera.right = sizes.width.value * 0.5
|
|
|
|
- camera.top = sizes.height.value * 0.5
|
|
|
|
- camera.bottom = sizes.height.value * -0.5
|
|
|
|
|
|
+ cameras.value.forEach((camera: TresCamera & { manual?: boolean }) => {
|
|
|
|
+ // Skip if camera is marked as manual by the user
|
|
|
|
+ if (camera.manual) { return }
|
|
|
|
+
|
|
|
|
+ // Update perspective camera
|
|
|
|
+ if (isPerspectiveCamera(camera)) {
|
|
|
|
+ camera.aspect = sizes.aspectRatio.value
|
|
|
|
+ camera.updateProjectionMatrix()
|
|
|
|
+ }
|
|
|
|
+ // Update orthographic camera
|
|
|
|
+ else if (isOrthographicCamera(camera)) {
|
|
|
|
+ // Use a fixed frustum size for better visualization
|
|
|
|
+ const frustumSize = 10
|
|
|
|
+ const aspect = sizes.aspectRatio.value
|
|
|
|
+
|
|
|
|
+ camera.left = frustumSize * aspect / -2
|
|
|
|
+ camera.right = frustumSize * aspect / 2
|
|
|
|
+ camera.top = frustumSize / 2
|
|
|
|
+ camera.bottom = frustumSize / -2
|
|
|
|
+
|
|
|
|
+ // Ensure the camera is at a good position to see the scene
|
|
|
|
+ if (!camera.position.z) {
|
|
|
|
+ camera.position.z = 10
|
|
}
|
|
}
|
|
|
|
+
|
|
camera.updateProjectionMatrix()
|
|
camera.updateProjectionMatrix()
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
|
|
|
|
+ // Clean up on unmount
|
|
onUnmounted(() => {
|
|
onUnmounted(() => {
|
|
cameras.value = []
|
|
cameras.value = []
|
|
|
|
+ activeCameraUuid.value = null
|
|
})
|
|
})
|
|
|
|
|
|
return {
|
|
return {
|
|
- camera,
|
|
|
|
|
|
+ activeCamera,
|
|
cameras,
|
|
cameras,
|
|
registerCamera,
|
|
registerCamera,
|
|
deregisterCamera,
|
|
deregisterCamera,
|
|
- setCameraActive,
|
|
|
|
|
|
+ setActiveCamera,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
-function isOrthographicCamera(o: any): o is OrthographicCamera {
|
|
|
|
- // eslint-disable-next-line no-prototype-builtins
|
|
|
|
- return o.hasOwnProperty('isOrthographicCamera') && o.isOrthographicCamera
|
|
|
|
-}
|
|
|