Browse Source

refactor: update camera handling to use Three.js Camera type

- Replaced instances of TresCamera with Three.js Camera type across the codebase for better compatibility and consistency.
- Updated camera management logic in useCamera composable and related components to reflect the new type.
- Simplified currentCamera logic to use computed properties instead of watch.
alvarosabu 2 weeks ago
parent
commit
cc4c4d41ef

+ 9 - 13
playground/vue/src/pages/cameras/index.vue

@@ -1,16 +1,19 @@
 <script setup lang="ts">
 import { Box } from '@tresjs/cientos'
-import type { TresCamera } from '@tresjs/core'
 import { TresCanvas } from '@tresjs/core'
 import { TresLeches, useControls } from '@tresjs/leches'
 import { OrthographicCamera, PerspectiveCamera } from 'three'
 import '@tresjs/leches/styles'
+import { useWindowSize } from '@vueuse/core'
 
 const perspectiveCamera = new PerspectiveCamera(75, 1, 0.1, 1000)
+const { width, height } = useWindowSize()
+const aspect = computed(() => width.value / height.value)
+
 const frustumSize = 10
 const orthographicCamera = new OrthographicCamera(
-  -frustumSize,
-  frustumSize,
+  -frustumSize * aspect.value,
+  frustumSize * aspect.value,
   frustumSize,
   -frustumSize,
   0.1,
@@ -22,8 +25,6 @@ perspectiveCamera.lookAt(0, 0, 0)
 orthographicCamera.position.set(8, 8, 8)
 orthographicCamera.lookAt(0, 0, 0)
 
-const currentCamera = ref<TresCamera>(perspectiveCamera)
-
 const { cameraType } = useControls({
   cameraType: {
     value: 'perspective',
@@ -37,14 +38,9 @@ const { cameraType } = useControls({
   },
 })
 
-watch(cameraType, (newCameraType) => {
-  if (newCameraType === 'perspective') {
-    currentCamera.value = perspectiveCamera
-  }
-  else {
-    currentCamera.value = orthographicCamera
-  }
-}, { immediate: true })
+const currentCamera = computed(() => {
+  return cameraType.value === 'perspective' ? perspectiveCamera : orthographicCamera
+})
 </script>
 
 <template>

+ 4 - 3
src/components/TresCanvas.vue

@@ -1,14 +1,15 @@
 <script setup lang="ts">
 import type {
+  Camera,
   ColorSpace,
   ShadowMapType,
   ToneMapping,
   WebGLRenderer,
   WebGLRendererParameters,
 } from 'three'
-import type { App, MaybeRef, Ref } from 'vue'
+import type { App, Ref } from 'vue'
 import type { RendererPresetsType } from '../composables/useRenderer/const'
-import type { TresCamera, TresObject, TresPointerEvent, TresScene } from '../types/'
+import type { TresObject, TresPointerEvent, TresScene } from '../types/'
 import { PerspectiveCamera, Scene } from 'three'
 import * as THREE from 'three'
 
@@ -53,7 +54,7 @@ export interface TresCanvasProps
   dpr?: number | [number, number]
 
   // required by useTresContextProvider
-  camera?: MaybeRef<TresCamera>
+  camera?: Camera
   preset?: RendererPresetsType
   windowSize?: boolean
 

+ 16 - 24
src/composables/useCamera/index.ts

@@ -1,9 +1,9 @@
 import type { TresContext } from '../useTresContextProvider'
 
 import type { ComputedRef, Ref } from 'vue'
-import { computed, onUnmounted, ref, watchEffect } from 'vue'
+import { computed, ref, watchEffect } from 'vue'
 import { isOrthographicCamera, isPerspectiveCamera } from '../../utils/is'
-import type { TresCamera } from '../../types'
+import type { Camera } from 'three'
 
 /**
  * Interface for the return value of the useCamera composable
@@ -12,27 +12,27 @@ export interface UseCameraReturn {
   /**
    * The active camera
    */
-  activeCamera: ComputedRef<TresCamera | undefined>
+  activeCamera: ComputedRef<Camera | undefined>
   /**
    * The list of cameras
    */
-  cameras: Ref<TresCamera[]>
+  cameras: Ref<Camera[]>
   /**
    * Register a camera
    * @param camera - The camera to register
    * @param active - Whether to set the camera as active
    */
-  registerCamera: (camera: TresCamera, active?: boolean) => void
+  registerCamera: (camera: Camera, active?: boolean) => void
   /**
    * Deregister a camera
    * @param camera - The camera to deregister
    */
-  deregisterCamera: (camera: TresCamera) => void
+  deregisterCamera: (camera: Camera) => void
   /**
    * Set the active camera
    * @param cameraOrUuid - The camera or its UUID to set as active
    */
-  setActiveCamera: (cameraOrUuid: string | TresCamera) => void
+  setActiveCamera: (cameraOrUuid: string | Camera) => void
 }
 
 /**
@@ -50,12 +50,12 @@ interface UseCameraParams {
  */
 export const useCameraManager = ({ sizes }: UseCameraParams): UseCameraReturn => {
   // Store all registered cameras
-  const cameras = ref<TresCamera[]>([])
+  const cameras = ref<Camera[]>([])
   // 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>(
+  const activeCamera = computed<Camera | undefined>(
     () => cameras.value.find(camera => camera.uuid === activeCameraUuid.value),
   )
 
@@ -65,9 +65,9 @@ export const useCameraManager = ({ sizes }: UseCameraParams): UseCameraReturn =>
    */
   const setActiveCamera = (cameraOrUuid: string | Camera) => {
     const uuid = typeof cameraOrUuid === 'string' ? cameraOrUuid : cameraOrUuid.uuid
-    const cameraExists = cameras.value.some((camera: TresCamera) => camera.uuid === uuid)
+    const cameraExists = cameras.value.some((camera: Camera) => camera.uuid === uuid)
 
-    if (!cameraExists) {
+    if (cameraExists) {
       activeCameraUuid.value = uuid
     }
   }
@@ -77,8 +77,7 @@ export const useCameraManager = ({ sizes }: UseCameraParams): UseCameraReturn =>
    * @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
+  const registerCamera = (camera: Camera, active = false): void => {
     if (cameras.value.some(({ uuid }) => uuid === camera.uuid)) { return }
     cameras.value.push(camera)
     if (active) {
@@ -90,7 +89,7 @@ export const useCameraManager = ({ sizes }: UseCameraParams): UseCameraReturn =>
    * Deregister a camera
    * @param camera - The camera to deregister
    */
-  const deregisterCamera = (camera: TresCamera): void => {
+  const deregisterCamera = (camera: Camera): void => {
     cameras.value = cameras.value.filter(({ uuid }) => uuid !== camera.uuid)
 
     // If the deregistered camera was active, clear the active camera
@@ -104,15 +103,14 @@ export const useCameraManager = ({ sizes }: UseCameraParams): UseCameraReturn =>
    */
   watchEffect(() => {
     if (sizes.aspectRatio.value) {
-      cameras.value.forEach((camera: TresCamera) => {
+      cameras.value.forEach((camera: Camera) => {
         // 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
+        /* else if (isOrthographicCamera(camera)) {
           const frustumSize = 10
           const aspect = sizes.aspectRatio.value
 
@@ -121,18 +119,12 @@ export const useCameraManager = ({ sizes }: UseCameraParams): UseCameraReturn =>
           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()
-        }
+        } */
       })
     }
   })
 
-
   return {
     activeCamera,
     cameras,

+ 6 - 6
src/composables/useRaycaster/index.ts

@@ -1,12 +1,11 @@
+import { createEventHook, useElementBounding, usePointer } from '@vueuse/core'
+import { Vector2, Vector3 } from 'three'
+import { computed, onUnmounted, shallowRef } from 'vue'
 import type { EventHook } from '@vueuse/core'
-import type { DomEvent, TresCamera, TresEvent, TresInstance } from 'src/types'
 import type { Intersection, Object3D, Object3DEventMap } from 'three'
 import type { ShallowRef } from 'vue'
+import type { DomEvent, TresEvent, TresInstance } from '../../types'
 import type { TresContext } from '../useTresContextProvider'
-import { createEventHook, useElementBounding, usePointer } from '@vueuse/core'
-
-import { Vector2, Vector3 } from 'three'
-import { computed, onUnmounted, shallowRef } from 'vue'
 
 export const useRaycaster = (
   objectsWithEvents: ShallowRef<TresInstance[]>,
@@ -84,7 +83,8 @@ export const useRaycaster = (
 
   const triggerEventHook = (eventHook: EventHook, event: PointerEvent | MouseEvent | WheelEvent) => {
     const eventProperties = copyMouseEventProperties(event)
-    const unprojectedPoint = new Vector3(event?.clientX, event?.clientY, 0).unproject(ctx.camera.activeCamera.value as TresCamera)
+    if (!ctx.camera.activeCamera.value) { return }
+    const unprojectedPoint = new Vector3(event?.clientX, event?.clientY, 0).unproject(ctx.camera.activeCamera.value)
     eventHook.trigger({
       ...eventProperties,
       intersections: intersects.value,

+ 2 - 2
src/composables/useRenderer/useRendererManager.ts

@@ -176,8 +176,8 @@ export function useRendererManager(
   const onRender = createEventHook<WebGLRenderer>()
 
   loop.register(() => {
-    if (camera.value && amountOfFramesToRender.value) {
-      instance.value.render(scene, camera.value)
+    if (camera.activeCamera.value && amountOfFramesToRender.value) {
+      instance.value.render(scene, camera.activeCamera.value)
 
       onRender.trigger(instance.value)
     }

+ 6 - 6
src/core/nodeOps.ts

@@ -1,10 +1,10 @@
 import type { TresContext } from '../composables'
-import type { DisposeType, LocalState, TresCamera, TresInstance, TresObject, TresObject3D, TresPrimitive, WithMathProps } from '../types'
+import type { DisposeType, LocalState, TresInstance, TresObject, TresObject3D, TresPrimitive, WithMathProps } from '../types'
 import { BufferAttribute, Object3D } from 'three'
 import { isRef, type RendererOptions } from 'vue'
 import { attach, deepArrayEqual, doRemoveDeregister, doRemoveDetach, invalidateInstance, isHTMLTag, kebabToCamel, noop, prepareTresInstance, setPrimitiveObject, unboxTresPrimitive } from '../utils'
 import { logError } from '../utils/logger'
-import { isArray, isCamera, isFunction, isObject, isObject3D, isScene, isUndefined } from '../utils/is'
+import { isArray, isCamera, isFunction, isObject, isObject3D, isScene, isTresInstance, isUndefined } from '../utils/is'
 import { createRetargetingProxy } from '../utils/primitive/createRetargetingProxy'
 import { catalogue } from './catalogue'
 
@@ -77,7 +77,7 @@ export const nodeOps: (context: TresContext) => RendererOptions<TresObject, Tres
     if (!obj) { return null }
 
     // Opinionated default to avoid user issue not seeing anything if camera is on origin
-    if (obj.isCamera) {
+    if (isCamera(obj)) {
       if (!props?.position) {
         obj.position.set(3, 3, 3)
       }
@@ -87,7 +87,7 @@ export const nodeOps: (context: TresContext) => RendererOptions<TresObject, Tres
     }
 
     obj = prepareTresInstance(obj, {
-      ...obj.__tres,
+      ...(isTresInstance(obj) ? obj.__tres : {}),
       type: name,
       memoizedProps: props,
       eventCount: 0,
@@ -116,7 +116,7 @@ export const nodeOps: (context: TresContext) => RendererOptions<TresObject, Tres
     }
 
     if (isCamera(child)) {
-      context.camera?.registerCamera(child as TresCamera)
+      context.camera?.registerCamera(child)
       context.camera?.setActiveCamera(child.uuid)
     }
     // NOTE: Track onPointerMissed objects separate from the scene
@@ -343,7 +343,7 @@ export const nodeOps: (context: TresContext) => RendererOptions<TresObject, Tres
     else if (!target.isColor && target.setScalar) { target.setScalar(value) }
     else { target.set(value) }
 
-    if (node.isCamera) {
+    if (isCamera(node)) {
       node.updateProjectionMatrix()
     }
 

+ 1 - 1
src/types/index.ts

@@ -114,7 +114,7 @@ export interface IntersectionEvent<TSourceEvent> extends Intersection {
   /** The ray that pierced it */
   ray: THREE.Ray
   /** The camera that was used by the raycaster */
-  camera: TresCamera
+  camera: THREE.Camera
   /** stopPropagation will stop underlying handlers from firing */
   stopPropagation: () => void
   /** The original host event */

+ 7 - 3
src/utils/index.ts

@@ -4,7 +4,7 @@ import type { Material, Mesh, Object3D, Texture } from 'three'
 import type { TresContext } from '../composables/useTresContextProvider'
 import { DoubleSide, MathUtils, MeshBasicMaterial, Scene, Vector3 } from 'three'
 import { HightlightMesh } from '../devtools/highlight'
-import { isFunction, isNumber, isString, isTresPrimitive, isUndefined } from './is'
+import { isCamera, isFunction, isNumber, isString, isTresPrimitive, isUndefined } from './is'
 
 export * from './logger'
 export function toSetMethodName(key: string) {
@@ -566,13 +566,17 @@ export function doRemoveDeregister(node: TresObject, context: TresContext) {
   // TODO: Refactor as `context.deregister`?
   // That would eliminate `context.deregisterCamera`.
   node.traverse?.((child: TresObject) => {
-    context.camera.deregisterCamera(child)
+    if (isCamera(child)) {
+      context.camera.deregisterCamera(child)
+    }
     // deregisterAtPointerEventHandlerIfRequired?.(child as TresObject)
     context.eventManager?.deregisterPointerMissedObject(child)
   })
 
   // NOTE: Deregister `node`
-  context.camera.deregisterCamera(node)
+  if (isCamera(node)) {
+    context.camera.deregisterCamera(node)
+  }
   /*  deregisterAtPointerEventHandlerIfRequired?.(node as TresObject) */
   invalidateInstance(node as TresObject)
 }

+ 20 - 3
src/utils/is.ts

@@ -1,5 +1,5 @@
-import type { TresObject, TresPrimitive } from 'src/types'
-import type { BufferGeometry, Camera, Fog, Light, Material, Object3D, OrthographicCamera, PerspectiveCamera, Scene } from 'three'
+import type { TresCamera, TresInstance, TresObject, TresPrimitive } from 'src/types'
+import type { BufferGeometry, Fog, Light, Material, Object3D, OrthographicCamera, PerspectiveCamera, Scene } from 'three'
 
 /**
  * Type guard to check if a value is undefined
@@ -159,7 +159,7 @@ export function isObject3D(value: unknown): value is Object3D {
  * }
  * ```
  */
-export function isCamera(value: unknown): value is Camera {
+export function isCamera(value: unknown): value is TresCamera {
   return isObject(value) && !!(value.isCamera)
 }
 
@@ -317,3 +317,20 @@ export function isTresObject(value: unknown): value is TresObject {
 export function isTresPrimitive(value: unknown): value is TresPrimitive {
   return isObject(value) && !!(value.isPrimitive)
 }
+
+/**
+ * Type guard to check if a value is a TresInstance (has __tres property)
+ * @param value - The value to check
+ * @returns True if the value is a TresInstance (has __tres property), false otherwise
+ * @example
+ * ```ts
+ * const value = new THREE.Mesh()
+ * if (isTresInstance(value)) {
+ *   // TypeScript knows value is TresInstance here
+ *   // You can safely access value.__tres
+ * }
+ * ```
+ */
+export function isTresInstance(value: unknown): value is TresInstance {
+  return isTresObject(value) && '__tres' in value
+}