Browse Source

chore: type issues (#663)

* fix: fix some internal types

* chore: fix linters

* fix: typescript issues on event manager
Alvaro Saburido 1 year ago
parent
commit
d4e1fe287f

+ 0 - 4
playground/components.d.ts

@@ -9,9 +9,6 @@ declare module 'vue' {
   export interface GlobalComponents {
     AkuAku: typeof import('./src/components/AkuAku.vue')['default']
     BlenderCube: typeof import('./src/components/BlenderCube.vue')['default']
-<<<<<<< HEAD
-    DynamicModel: typeof import('./src/components/DynamicModel.vue')['default']
-=======
     Box: typeof import('./src/components/Box.vue')['default']
     CameraOperator: typeof import('./src/components/CameraOperator.vue')['default']
     Cameras: typeof import('./src/components/Cameras.vue')['default']
@@ -23,7 +20,6 @@ declare module 'vue' {
     EventsPropogation: typeof import('./src/components/EventsPropogation.vue')['default']
     FBXModels: typeof import('./src/components/FBXModels.vue')['default']
     Gltf: typeof import('./src/components/gltf/index.vue')['default']
->>>>>>> v4
     GraphPane: typeof import('./src/components/GraphPane.vue')['default']
     LocalOrbitControls: typeof import('./src/components/LocalOrbitControls.vue')['default']
     RenderingLogger: typeof import('./src/components/RenderingLogger.vue')['default']

+ 5 - 22
src/components/TresCanvas.vue

@@ -38,7 +38,7 @@ import { registerTresDevtools } from '../devtools'
 import { disposeObject3D } from '../utils/'
 
 import type { RendererPresetsType } from '../composables/useRenderer/const'
-import type { TresCamera, TresObject } from '../types/'
+import type { TresCamera, TresObject, TresScene } from '../types/'
 
 export interface TresCanvasProps
   extends Omit<WebGLRendererParameters, 'canvas'> {
@@ -105,7 +105,7 @@ const canvas = ref<HTMLCanvasElement>()
  renderer uses it to mount the app nodes. This happens before `useTresContextProvider` is called.
  The custom renderer requires `scene` to be editable (not readonly).
 */
-const scene = shallowRef(new Scene())
+const scene = shallowRef<TresScene | Scene>(new Scene())
 
 const { resume } = useRenderLoop()
 
@@ -136,34 +136,17 @@ const mountCustomRenderer = (context: TresContext) => {
 }
 
 const dispose = (context: TresContext, force = false) => {
-  disposeObject3D(context.scene.value)
+  disposeObject3D(context.scene.value as unknown as TresObject)
   if (force) {
     context.renderer.value.dispose()
     context.renderer.value.renderLists.dispose()
     context.renderer.value.forceContextLoss()
   }
-  scene.value.__tres = {
+  (scene.value as TresScene).__tres = {
     root: context,
   }
   mountCustomRenderer(context)
   resume()
-  /* disposeObject3D(scene.value) */
-  /*  scene.value.children.forEach((child) => {
-    child.removeFromParent()
-    disposeObject3D(child)
-  })
-  context.scene.value.children.forEach((child) => {
-    child.removeFromParent()
-    disposeObject3D(child)
-  }) */
-  /* console.log('disposing', scene.value.children)
-  if (force) {
-    context.renderer.value.dispose()
-    context.renderer.value.renderLists.dispose()
-    context.renderer.value.forceContextLoss()
-  }
-  mountCustomRenderer(context)
-  resume() */
 }
 
 const disableRender = computed(() => props.disableRender)
@@ -176,7 +159,7 @@ onMounted(() => {
   const existingCanvas = canvas as Ref<HTMLCanvasElement>
 
   context.value = useTresContextProvider({
-    scene: scene.value,
+    scene: scene.value as TresScene,
     canvas: existingCanvas,
     windowSize: props.windowSize ?? false,
     disableRender: disableRender.value ?? false,

+ 16 - 15
src/composables/usePointerEventHandler/index.ts

@@ -1,5 +1,6 @@
 import type { Intersection, Object3D, Object3DEventMap } from 'three'
 import { computed, reactive, ref } from 'vue'
+import type { TresObject } from 'src/types'
 import { uniqueBy } from '../../utils'
 import { useRaycaster } from '../useRaycaster'
 
@@ -27,26 +28,26 @@ export const usePointerEventHandler = (
 
   const blockingObjects = ref(new Set<Object3D>())
 
-  const registerBlockingObject = (object: Object3D) => {
-    blockingObjects.value.add(object)
+  const registerBlockingObject = (object: TresObject) => {
+    blockingObjects.value.add(object as Object3D)
   }
 
-  const deregisterBlockingObject = (object: Object3D) => {
-    blockingObjects.value.delete(object)
+  const deregisterBlockingObject = (object: TresObject) => {
+    blockingObjects.value.delete(object as Object3D)
   }
 
-  const deregisterObject = (object: Object3D) => {
-    Object.values(objectsWithEventListeners).forEach(map => map.delete(object))
+  const deregisterObject = (object: TresObject) => {
+    Object.values(objectsWithEventListeners).forEach(map => map.delete(object as Object3D))
     deregisterBlockingObject(object)
   }
 
-  const registerObject = (object: Object3D & EventProps) => {
+  const registerObject = (object: TresObject & EventProps) => {
     const { onClick, onPointerMove, onPointerEnter, onPointerLeave } = object
 
-    if (onClick) { objectsWithEventListeners.click.set(object, onClick) }
-    if (onPointerMove) { objectsWithEventListeners.pointerMove.set(object, onPointerMove) }
-    if (onPointerEnter) { objectsWithEventListeners.pointerEnter.set(object, onPointerEnter) }
-    if (onPointerLeave) { objectsWithEventListeners.pointerLeave.set(object, onPointerLeave) }
+    if (onClick) { objectsWithEventListeners.click.set(object as Object3D, onClick) }
+    if (onPointerMove) { objectsWithEventListeners.pointerMove.set(object as Object3D, onPointerMove) }
+    if (onPointerEnter) { objectsWithEventListeners.pointerEnter.set(object as Object3D, onPointerEnter) }
+    if (onPointerLeave) { objectsWithEventListeners.pointerLeave.set(object as Object3D, onPointerLeave) }
   }
 
   const objectsToWatch = computed(() =>
@@ -70,7 +71,7 @@ export const usePointerEventHandler = (
   const { onClick, onPointerMove } = useRaycaster(objectsToWatch, ctx)
 
   onClick(({ intersects, event }) => {
-    if (intersects.length) { objectsWithEventListeners.click.get(intersects[0].object)?.(intersects[0], event) }
+    if (intersects.length) { objectsWithEventListeners.click.get(intersects[0].object)?.(intersects[0], event as PointerEvent) }
   })
 
   let previouslyIntersectedObject: Object3D | null
@@ -80,12 +81,12 @@ export const usePointerEventHandler = (
 
     const { pointerLeave, pointerEnter, pointerMove } = objectsWithEventListeners
 
-    if (previouslyIntersectedObject && previouslyIntersectedObject !== firstObject) { pointerLeave.get(previouslyIntersectedObject)?.(previouslyIntersectedObject, event) }
+    if (previouslyIntersectedObject && previouslyIntersectedObject !== firstObject) { pointerLeave.get(previouslyIntersectedObject)?.(previouslyIntersectedObject, event as PointerEvent) }
 
     if (firstObject) {
-      if (previouslyIntersectedObject !== firstObject) { pointerEnter.get(firstObject)?.(intersects[0], event) }
+      if (previouslyIntersectedObject !== firstObject) { pointerEnter.get(firstObject)?.(intersects[0], event as PointerEvent) }
 
-      pointerMove.get(firstObject)?.(intersects[0], event)
+      pointerMove.get(firstObject)?.(intersects[0], event as PointerEvent)
     }
 
     previouslyIntersectedObject = firstObject || null

+ 26 - 41
src/composables/useRaycaster/index.ts

@@ -1,35 +1,20 @@
 import { Vector2, Vector3 } from 'three'
-import type { Intersection, Object3D, Object3DEventMap } from 'three'
+import type { Intersection, Object3D } from 'three'
 import type { Ref, ShallowRef } from 'vue'
 import { computed, onUnmounted, shallowRef } from 'vue'
 import type { EventHook } from '@vueuse/core'
 import { createEventHook, useElementBounding, usePointer } from '@vueuse/core'
 
+import type { DomEvent, TresCamera, TresEvent } from 'src/types'
 import type { TresContext } from '../useTresContextProvider'
 
-export type Intersects = Intersection<Object3D<Object3DEventMap>>[]
-interface PointerMoveEventPayload {
-  intersects?: Intersects
-  event: PointerEvent
-}
-
-interface PointerClickEventPayload {
-  intersects: Intersects
-  event: PointerEvent
-}
-
-interface WheelEventPayload {
-  intersects: Intersects
-  event: WheelEvent
-}
-
 export const useRaycaster = (
   objects: Ref<Object3D[]>,
   ctx: TresContext,
 ) => {
   // having a separate computed makes useElementBounding work
   const canvas = computed(() => ctx.renderer.value.domElement as HTMLCanvasElement)
-  const intersects: ShallowRef<Intersects[]> = shallowRef([])
+  const intersects: ShallowRef<Intersection[]> = shallowRef([])
   const { x, y } = usePointer({ target: canvas })
   let delta = 0
 
@@ -53,7 +38,7 @@ export const useRaycaster = (
     return intersects.value
   }
 
-  const getIntersects = (event?: PointerEvent | MouseEvent | WheelEvent) => {
+  const getIntersects = (event?: DomEvent) => {
     const pointerPosition = getRelativePointerPosition({
       x: event?.clientX ?? x.value,
       y: event?.clientY ?? y.value,
@@ -63,14 +48,14 @@ export const useRaycaster = (
     return getIntersectsByRelativePointerPosition(pointerPosition) || []
   }
 
-  const eventHookClick = createEventHook<PointerClickEventPayload>()
-  const eventHookDblClick = createEventHook<PointerClickEventPayload>()
-  const eventHookPointerMove = createEventHook<PointerMoveEventPayload>()
-  const eventHookPointerUp = createEventHook<PointerMoveEventPayload>()
-  const eventHookPointerDown = createEventHook<PointerMoveEventPayload>()
-  const eventHookPointerMissed = createEventHook<PointerClickEventPayload>()
-  const eventHookContextMenu = createEventHook<PointerClickEventPayload>()
-  const eventHookWheel = createEventHook<WheelEventPayload>()
+  const eventHookClick = createEventHook<TresEvent>()
+  const eventHookDblClick = createEventHook<TresEvent>()
+  const eventHookPointerMove = createEventHook<TresEvent>()
+  const eventHookPointerUp = createEventHook<TresEvent>()
+  const eventHookPointerDown = createEventHook<TresEvent>()
+  const eventHookPointerMissed = createEventHook<TresEvent>()
+  const eventHookContextMenu = createEventHook<TresEvent>()
+  const eventHookWheel = createEventHook<TresEvent>()
 
   /* ({
     ...DomEvent                   // All the original event data
@@ -92,19 +77,19 @@ export const useRaycaster = (
 
     for (const property in event) {
       // Copy all non-function properties
-      if (typeof property !== 'function') { mouseEventProperties[property] = event[property] }
+      if (typeof property !== 'function') { mouseEventProperties[property] = (event as Record<string, any>)[property] }
     }
     return mouseEventProperties
   }
 
   const triggerEventHook = (eventHook: EventHook, event: PointerEvent | MouseEvent | WheelEvent) => {
     const eventProperties = copyMouseEventProperties(event)
-
+    const unprojectedPoint = new Vector3(event?.clientX, event?.clientY, 0).unproject(ctx.camera?.value as TresCamera)
     eventHook.trigger({
       ...eventProperties,
       intersections: intersects.value,
       // The unprojectedPoint is wrong, math needs to be fixed
-      unprojectedPoint: new Vector3(event?.clientX, event?.clientY, 0).unproject(ctx.camera?.value),
+      unprojectedPoint,
       ray: ctx.raycaster?.value.ray,
       camera: ctx.camera?.value,
       sourceEvent: event,
@@ -127,8 +112,8 @@ export const useRaycaster = (
 
   // a click event is fired whenever a pointerdown happened after pointerup on the same object
   let mouseDownObject: Object3D | undefined
-  let mouseDownPosition
-  let mouseUpPosition
+  let mouseDownPosition: Vector2
+  let mouseUpPosition: Vector2
 
   const onPointerDown = (event: PointerEvent) => {
     mouseDownObject = intersects.value[0]?.object
@@ -160,7 +145,7 @@ export const useRaycaster = (
       )
 
       // Compute the distance between the mouse down and mouse up events
-      delta = mouseDownPosition.distanceTo(mouseUpPosition)
+      delta = mouseDownPosition?.distanceTo(mouseUpPosition)
 
       if (event.button === 0) {
         // Left click
@@ -214,14 +199,14 @@ export const useRaycaster = (
 
   return {
     intersects,
-    onClick: (fn: (value: PointerClickEventPayload) => void) => eventHookClick.on(fn).off,
-    onDblClick: (fn: (value: PointerClickEventPayload) => void) => eventHookDblClick.on(fn).off,
-    onContextMenu: (fn: (value: PointerClickEventPayload) => void) => eventHookContextMenu.on(fn).off,
-    onPointerMove: (fn: (value: PointerMoveEventPayload) => void) => eventHookPointerMove.on(fn).off,
-    onPointerUp: (fn: (value: PointerMoveEventPayload) => void) => eventHookPointerUp.on(fn).off,
-    onPointerDown: (fn: (value: PointerMoveEventPayload) => void) => eventHookPointerDown.on(fn).off,
-    onPointerMissed: (fn: (value: PointerClickEventPayload) => void) => eventHookPointerMissed.on(fn).off,
-    onWheel: (fn: (value: WheelEventPayload) => void) => eventHookWheel.on(fn).off,
+    onClick: (fn: (value: TresEvent) => void) => eventHookClick.on(fn).off,
+    onDblClick: (fn: (value: TresEvent) => void) => eventHookDblClick.on(fn).off,
+    onContextMenu: (fn: (value: TresEvent) => void) => eventHookContextMenu.on(fn).off,
+    onPointerMove: (fn: (value: TresEvent) => void) => eventHookPointerMove.on(fn).off,
+    onPointerUp: (fn: (value: TresEvent) => void) => eventHookPointerUp.on(fn).off,
+    onPointerDown: (fn: (value: TresEvent) => void) => eventHookPointerDown.on(fn).off,
+    onPointerMissed: (fn: (value: TresEvent) => void) => eventHookPointerMissed.on(fn).off,
+    onWheel: (fn: (value: TresEvent) => void) => eventHookWheel.on(fn).off,
     forceUpdate,
   }
 }

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

@@ -10,7 +10,7 @@ import {
 
 import type { ColorSpace, Scene, ShadowMapType, ToneMapping, WebGLRendererParameters } from 'three'
 import { useLogger } from '../useLogger'
-import type { TresColor } from '../../types'
+import type { EmitEventFn, TresColor } from '../../types'
 import { useRenderLoop } from '../useRenderLoop'
 import { normalizeColor } from '../../utils/normalize'
 
@@ -108,7 +108,7 @@ export function useRenderer(
     canvas: MaybeRef<HTMLCanvasElement>
     scene: Scene
     options: UseRendererOptions
-    emit: (event: string, ...args: any[]) => void
+    emit: EmitEventFn
     contextParts: Pick<TresContext, 'sizes' | 'camera' | 'render'> & { invalidate: () => void, advance: () => void }
     disableRender: MaybeRefOrGetter<boolean>
   },

+ 9 - 8
src/composables/useTresContextProvider/index.ts

@@ -1,6 +1,6 @@
 import { useFps, useMemory, useRafFn } from '@vueuse/core'
 import { computed, inject, onUnmounted, provide, readonly, ref, shallowRef } from 'vue'
-import type { Camera, EventDispatcher, Object3D, WebGLRenderer } from 'three'
+import type { Camera, EventDispatcher, WebGLRenderer } from 'three'
 import { Raycaster } from 'three'
 import type { ComputedRef, DeepReadonly, MaybeRef, MaybeRefOrGetter, Ref, ShallowRef } from 'vue'
 import { calculateMemoryUsage } from '../../utils/perf'
@@ -9,7 +9,7 @@ import type { UseRendererOptions } from '../useRenderer'
 import { useRenderer } from '../useRenderer'
 import { extend } from '../../core/catalogue'
 import { useLogger } from '../useLogger'
-import type { TresScene } from '../../types'
+import type { EmitEventFn, TresObject, TresScene } from '../../types'
 import type { EventProps } from '../usePointerEventHandler'
 import type { TresEventManager } from '../useTresEventManager'
 import useSizes, { type SizesType } from '../useSizes'
@@ -69,14 +69,14 @@ export interface TresContext {
   registerCamera: (camera: Camera) => void
   setCameraActive: (cameraOrUuid: Camera | string) => void
   deregisterCamera: (camera: Camera) => void
-  eventManager: TresEventManager
+  eventManager?: TresEventManager
   // Events
   // Temporaly add the methods to the context, this should be handled later by the EventManager state on the context https://github.com/Tresjs/tres/issues/515
   // When thats done maybe we can short the names of the methods since the parent will give the context.
-  registerObjectAtPointerEventHandler: (object: Object3D & EventProps) => void
-  deregisterObjectAtPointerEventHandler: (object: Object3D) => void
-  registerBlockingObjectAtPointerEventHandler: (object: Object3D) => void
-  deregisterBlockingObjectAtPointerEventHandler: (object: Object3D) => void
+  registerObjectAtPointerEventHandler?: (object: TresObject & EventProps) => void
+  deregisterObjectAtPointerEventHandler?: (object: TresObject) => void
+  registerBlockingObjectAtPointerEventHandler?: (object: TresObject) => void
+  deregisterBlockingObjectAtPointerEventHandler?: (object: TresObject) => void
 }
 
 export function useTresContextProvider({
@@ -92,7 +92,8 @@ export function useTresContextProvider({
   windowSize: MaybeRefOrGetter<boolean>
   disableRender: MaybeRefOrGetter<boolean>
   rendererOptions: UseRendererOptions
-  emit: (event: string, ...args: any[]) => void
+  emit: EmitEventFn
+
 }): TresContext {
   const { logWarning } = useLogger()
 

+ 22 - 22
src/composables/useTresEventManager/index.ts

@@ -1,8 +1,8 @@
 import { computed, shallowRef } from 'vue'
-import type { Object3D, Scene } from 'three'
+import type { Object3D, Object3DEventMap, Scene } from 'three'
+import type { EmitEventFn, EmitEventName, Intersection, TresEvent, TresObject } from 'src/types'
 import type { TresContext } from '../useTresContextProvider'
 import { useRaycaster } from '../useRaycaster'
-import type { Intersects } from '../useRaycaster'
 import { hyphenate } from '../../utils'
 
 export interface TresEventManager {
@@ -15,14 +15,14 @@ export interface TresEventManager {
    * So we need to track them separately
    * Note: These are used in nodeOps
    */
-  registerPointerMissedObject: (object: Object3D) => void
-  deregisterPointerMissedObject: (object: Object3D) => void
+  registerPointerMissedObject: (object: TresObject) => void
+  deregisterPointerMissedObject: (object: TresObject) => void
 }
 
 export function useTresEventManager(
   scene: Scene,
   context: TresContext,
-  emit: (event: string, ...args: any[]) => void,
+  emit: EmitEventFn,
 ) {
   const _scene = shallowRef<Scene>()
   const _context = shallowRef<TresContext>()
@@ -37,7 +37,7 @@ export function useTresEventManager(
 
   function executeEventListeners(
     listeners: Function | Function[],
-    event,
+    event: TresEvent,
   ) {
     // Components with multiple event listeners will have an array of functions
     if (Array.isArray(listeners)) {
@@ -59,7 +59,7 @@ export function useTresEventManager(
    * @param eventName - The name of the event to propogate
    * @param event - The event object to propogate
    */
-  function propogateEvent(eventName: string, event) {
+  function propogateEvent(eventName: string, event: TresEvent) {
     // Array of objects we've already propogated to
     const duplicates = []
 
@@ -75,8 +75,8 @@ export function useTresEventManager(
       event = { ...event, ...intersection }
 
       const { object } = intersection
-      event.eventObject = object
-      executeEventListeners(object[eventName], event)
+      event.eventObject = object as TresObject
+      executeEventListeners((object as Record<string, any>)[eventName], event)
       duplicates.push(object)
 
       // Propogate the event up the parent chain before moving on to the next intersected object
@@ -88,14 +88,14 @@ export function useTresEventManager(
         }
 
         // Sets eventObject to object that registered the event listener
-        event.eventObject = parentObj
-        executeEventListeners(parentObj[eventName], event)
+        event.eventObject = parentObj as TresObject
+        executeEventListeners((parentObj as Record<string, any>)[eventName], event)
         duplicates.push(parentObj)
         parentObj = parentObj.parent
       }
 
       // Convert eventName to kebab case and emit event from TresCanvas
-      const kebabEventName = hyphenate(eventName.slice(2))
+      const kebabEventName = hyphenate(eventName.slice(2)) as EmitEventName
       emit(kebabEventName, { intersection, event })
     }
   }
@@ -119,16 +119,16 @@ export function useTresEventManager(
   onContextMenu(event => propogateEvent('onContextMenu', event))
   onWheel(event => propogateEvent('onWheel', event))
 
-  let prevIntersections: Intersects = []
+  let prevIntersections: Intersection[] = []
 
   onPointerMove((event) => {
     // Current intersections mapped as meshes
     const hits = event.intersections.map(({ object }) => object)
 
     // Previously intersected mesh is no longer intersected, fire onPointerLeave
-    prevIntersections.forEach((hit) => {
+    prevIntersections.forEach((hit: Intersection) => {
       if (
-        !hits.includes(hit)
+        !hits.includes(hit as unknown as Object3D<Object3DEventMap>)
       ) {
         propogateEvent('onPointerLeave', event)
         propogateEvent('onPointerOut', event)
@@ -137,7 +137,7 @@ export function useTresEventManager(
 
     // Newly intersected mesh is not in the previous intersections, fire onPointerEnter
     event.intersections.forEach(({ object: hit }) => {
-      if (!prevIntersections.includes(hit)) {
+      if (!prevIntersections.includes(hit as unknown as Intersection)) {
         propogateEvent('onPointerEnter', event)
         propogateEvent('onPointerOver', event)
       }
@@ -147,20 +147,20 @@ export function useTresEventManager(
     propogateEvent('onPointerMove', event)
 
     // Update previous intersections
-    prevIntersections = hits
+    prevIntersections = hits as unknown as Intersection[]
   })
 
   /**
    * We need to track pointer missed objects separately
    * since they will not be a part of the raycaster intersection
    */
-  const pointerMissedObjects: Object3D[] = []
-  onPointerMissed((event) => {
+  const pointerMissedObjects: TresObject[] = []
+  onPointerMissed((event: TresEvent) => {
     // Flag that is set to true when the stopProgatingFn is called
     const stopPropagatingFn = () => (event.stopPropagating = true)
     event.stopPropagation = stopPropagatingFn
 
-    pointerMissedObjects.forEach((object: Object3D) => {
+    pointerMissedObjects.forEach((object: TresObject) => {
       if (event.stopPropagating) { return }
 
       // Set eventObject to object that registered the event
@@ -172,11 +172,11 @@ export function useTresEventManager(
     emit('pointer-missed', { event })
   })
 
-  function registerPointerMissedObject(object: Object3D) {
+  function registerPointerMissedObject(object: TresObject) {
     pointerMissedObjects.push(object)
   }
 
-  function deregisterPointerMissedObject(object: Object3D) {
+  function deregisterPointerMissedObject(object: TresObject) {
     const index = pointerMissedObjects.indexOf(object)
     if (index > -1) {
       pointerMissedObjects.splice(index, 1)

+ 29 - 38
src/core/nodeOps.ts

@@ -1,11 +1,11 @@
 import type { RendererOptions } from 'vue'
 import { BufferAttribute } from 'three'
 import { isFunction } from '@alvarosabu/utils'
-import type { Camera, Object3D } from 'three'
+import type { Camera } from 'three'
 import type { TresContext } from '../composables'
 import { useLogger } from '../composables'
 import { deepArrayEqual, disposeObject3D, isHTMLTag, kebabToCamel } from '../utils'
-import type { TresObject, TresObject3D, TresScene } from '../types'
+import type { InstanceProps, TresObject, TresObject3D, TresScene } from '../types'
 import { catalogue } from './catalogue'
 
 function noop(fn: string): any {
@@ -33,7 +33,7 @@ const supportedPointerEvents = [
 ]
 
 export function invalidateInstance(instance: TresObject) {
-  const ctx = instance.__tres.root
+  const ctx = instance?.__tres?.root
 
   if (!ctx) { return }
 
@@ -44,7 +44,7 @@ export function invalidateInstance(instance: TresObject) {
 
 export const nodeOps: () => RendererOptions<TresObject, TresObject | null> = () => {
   let scene: TresScene | null = null
-  function createElement(tag, _isSVG, _anchor, props): TresObject | null {
+  function createElement(tag: string, _isSVG: undefined, _anchor: any, props: InstanceProps): TresObject | null {
     if (!props) { props = {} }
 
     if (!props.args) {
@@ -69,7 +69,7 @@ export const nodeOps: () => RendererOptions<TresObject, TresObject | null> = ()
         )
       }
       // eslint-disable-next-line new-cap
-      instance = new target(...props.args)
+      instance = new target(...props.args) as TresObject
     }
 
     if (!instance) { return null }
@@ -100,34 +100,34 @@ export const nodeOps: () => RendererOptions<TresObject, TresObject | null> = ()
     // determine whether the material was passed via prop to
     // prevent it's disposal when node is removed later in it's lifecycle
 
-    if (instance.isObject3D && (props?.material || props?.geometry)) {
+    if (instance.isObject3D && instance.__tres && (props?.material || props?.geometry)) {
       instance.__tres.disposable = false
     }
 
     return instance as TresObject
   }
-  function insert(child, parent) {
+  function insert(child: TresObject, parent: TresObject) {
     if (!child) { return }
 
     if (parent && parent.isScene) {
       scene = parent as unknown as TresScene
     }
 
-    if (scene) {
+    if (scene && child.__tres) {
       child.__tres.root = scene.__tres.root as TresContext
     }
 
     const parentObject = parent || scene
 
     if (child?.isObject3D) {
-      const { registerCamera } = child.__tres.root
+      const { registerCamera } = child?.__tres?.root as TresContext
       if (child?.isCamera) {
         registerCamera(child as unknown as Camera)
       }
 
       // Track onPointerMissed objects separate from the scene
-      if (child.onPointerMissed) {
-        child.__tres.root.eventManager.registerPointerMissedObject(child as Object3D)
+      if (child.onPointerMissed && child?.__tres?.root) {
+        child?.__tres?.root?.eventManager?.registerPointerMissedObject(child)
       }
     }
 
@@ -146,17 +146,17 @@ export const nodeOps: () => RendererOptions<TresObject, TresObject | null> = ()
     }
   }
 
-  function remove(node) {
+  function remove(node: TresObject | null) {
     if (!node) { return }
     const ctx = node.__tres
     // remove is only called on the node being removed and not on child nodes.
     node.parent = node.parent || scene
 
     if (node.isObject3D) {
-      const deregisterCameraIfRequired = (object: Object3D) => {
-        const deregisterCamera = node.__tres.root.deregisterCamera
+      const deregisterCameraIfRequired = (object: TresObject) => {
+        const deregisterCamera = node?.__tres?.root?.deregisterCamera
 
-        if ((object as Camera).isCamera) { deregisterCamera?.(object as Camera) }
+        if ((object as unknown as Camera).isCamera) { deregisterCamera?.(object as unknown as Camera) }
       }
 
       node.removeFromParent?.()
@@ -164,33 +164,33 @@ export const nodeOps: () => RendererOptions<TresObject, TresObject | null> = ()
       // Remove nested child objects. Primitives should not have objects and children that are
       // attached to them declaratively ...
 
-      node.traverse((child: Object3D) => {
+      node.traverse((child: TresObject) => {
         deregisterCameraIfRequired(child)
         // deregisterAtPointerEventHandlerIfRequired?.(child as TresObject)
         if (child.onPointerMissed) {
-          ctx.root.eventManager.deregisterPointerMissedObject(child)
+          ctx?.root?.eventManager?.deregisterPointerMissedObject(child)
         }
       })
 
-      deregisterCameraIfRequired(node as Object3D)
+      deregisterCameraIfRequired(node)
       /*  deregisterAtPointerEventHandlerIfRequired?.(node as TresObject) */
       invalidateInstance(node as TresObject)
 
       // Dispose the object if it's disposable, primitives needs to be manually disposed by
       // calling dispose from `@tresjs/core` package like this `dispose(model)`
-      const isPrimitive = node.__tres.primitive
+      const isPrimitive = node.__tres?.primitive
 
-      if (!isPrimitive && node.__tres.disposable) {
-        disposeObject3D(node as TresObject3D)
+      if (!isPrimitive && node.__tres?.disposable) {
+        disposeObject3D(node)
       }
       node.dispose?.()
     }
   }
-  function patchProp(node, prop, prevValue, nextValue) {
+  function patchProp(node: TresObject, prop: string, prevValue: any, nextValue: any) {
     if (node) {
       let root = node
       let key = prop
-      if (node.__tres.primitive && key === 'object' && prevValue !== null) {
+      if (node?.__tres?.primitive && key === 'object' && prevValue !== null) {
         // If the prop 'object' is changed, we need to re-instance the object and swap the old one with the new one
         const newInstance = createElement('primitive', undefined, undefined, {
           object: nextValue,
@@ -205,9 +205,11 @@ export const nodeOps: () => RendererOptions<TresObject, TresObject | null> = ()
           else if (!target.isColor && target.setScalar) { target.setScalar(value) }
           else { target.set(value) }
         }
-        newInstance.__tres.root = scene?.__tres.root
+        if (newInstance?.__tres) {
+          newInstance.__tres.root = scene?.__tres.root
+        }
         // This code is needed to handle the case where the prop 'object' type change from a group to a mesh or vice versa, otherwise the object will not be rendered correctly (models will be invisible)
-        if (newInstance.isGroup) {
+        if (newInstance?.isGroup) {
           node.geometry = undefined
           node.material = undefined
         }
@@ -216,18 +218,7 @@ export const nodeOps: () => RendererOptions<TresObject, TresObject | null> = ()
         }
       }
 
-      if (node.__tres.root) {
-        // const {
-        //   registerBlockingObjectAtPointerEventHandler,
-        //   deregisterBlockingObjectAtPointerEventHandler,
-        // } = node.__tres.root
-      }
       if (node?.isObject3D && key === 'blocks-pointer-events') {
-      //   if (nextValue || nextValue === '')
-      //     registerBlockingObjectAtPointerEventHandler(node as Object3D)
-      //   else
-      //     deregisterBlockingObjectAtPointerEventHandler(node as Object3D)
-
         if (nextValue || nextValue === '') { node[key] = nextValue }
         else { delete node[key] }
         return
@@ -240,7 +231,7 @@ export const nodeOps: () => RendererOptions<TresObject, TresObject | null> = ()
         const prevNode = node as TresObject3D
         const prevArgs = prevValue ?? []
         const args = nextValue ?? []
-        const instanceName = node.__tres.type || node.type
+        const instanceName = node?.__tres?.type || node.type
 
         if (
           instanceName
@@ -293,7 +284,7 @@ export const nodeOps: () => RendererOptions<TresObject, TresObject | null> = ()
     }
   }
 
-  function parentNode(node) {
+  function parentNode(node: TresObject) {
     return node?.parent || null
   }
 

+ 1 - 1
src/devtools/highlight.ts

@@ -3,7 +3,7 @@ import * as THREE from 'three'
 export class HightlightMesh extends THREE.Mesh {
   type = 'HightlightMesh'
   createTime: number
-  constructor(...args: THREE.Mesh['args']) {
+  constructor(...args: any[]) {
     super(...args)
     this.createTime = Date.now()
   }

+ 7 - 8
src/devtools/plugin.ts

@@ -5,7 +5,7 @@ import {
   setupDevtoolsPlugin,
 } from '@vue/devtools-api'
 import { reactive } from 'vue'
-import type { Mesh, Object3D } from 'three'
+import type { Mesh } from 'three'
 import { createHighlightMesh, editSceneObject } from '../utils'
 import { bytesToKB, calculateMemoryUsage } from '../utils/perf'
 import type { TresContext } from '../composables'
@@ -122,7 +122,6 @@ export function registerTresDevtools(app: DevtoolsApp, tres: TresContext) {
     (api) => {
       if (typeof api.now !== 'function') {
         toastMessage(
-
           'You seem to be using an outdated version of Vue Devtools. Are you still using the Beta release instead of the stable one? You can find the links at https://devtools.vuejs.org/guide/installation.html.',
         )
       }
@@ -145,19 +144,19 @@ export function registerTresDevtools(app: DevtoolsApp, tres: TresContext) {
       api.on.getInspectorTree((payload) => {
         if (payload.inspectorId === INSPECTOR_ID) {
           // Your logic here
-          const root = createNode(tres.scene.value)
-          buildGraph(tres.scene.value, root, payload.filter)
+          const root = createNode(tres.scene.value as unknown as TresObject)
+          buildGraph(tres.scene.value as unknown as TresObject, root, payload.filter)
           state.sceneGraph = root
           payload.rootNodes = [root]
         }
       })
       let highlightMesh: Mesh | null = null
-      let prevInstance: Object3D | null = null
+      let prevInstance: TresObject | null = null
 
       api.on.getInspectorState((payload) => {
         if (payload.inspectorId === INSPECTOR_ID) {
           // Your logic here
-          const [instance] = tres.scene.value.getObjectsByProperty('uuid', payload.nodeId)
+          const [instance] = tres.scene.value.getObjectsByProperty('uuid', payload.nodeId) as TresObject[]
           if (!instance) { return }
           if (prevInstance && highlightMesh && highlightMesh.parent) {
             prevInstance.remove(highlightMesh)
@@ -269,14 +268,14 @@ export function registerTresDevtools(app: DevtoolsApp, tres: TresContext) {
               lines: tres.renderer.value.info.render.lines,
             }
             payload.state.programs = tres.renderer.value.info.programs?.map(program => ({
-              key: program.name || program.type,
+              key: program.name,
               value: {
                 ...program,
                 vertexShader: program.vertexShader,
                 attributes: program.getAttributes(),
                 uniforms: program.getUniforms(),
               },
-            }))
+            })) || []
           }
         }
       })

+ 4 - 0
src/devtools/utils.ts

@@ -25,3 +25,7 @@ export function toastMessage(
     console.log(tresMessage)
   }
 }
+
+function __VUE_DEVTOOLS_TOAST__(tresMessage: string, type: string | undefined) {
+  throw new Error(tresMessage + type)
+}

+ 35 - 15
src/types/index.ts

@@ -19,6 +19,9 @@ export type Args<T> = T extends ConstructorRepresentation ? ConstructorParameter
 export interface TresCatalogue {
   [name: string]: ConstructorRepresentation
 }
+
+export type EmitEventName = 'render' | 'click' | 'double-click' | 'context-menu' | 'pointer-move' | 'pointer-up' | 'pointer-down' | 'pointer-enter' | 'pointer-leave' | 'pointer-over' | 'pointer-out' | 'pointer-missed' | 'wheel'
+export type EmitEventFn = (event: EmitEventName, ...args: any[]) => void
 export type TresCamera = THREE.OrthographicCamera | THREE.PerspectiveCamera
 
 export interface InstanceProps<T = any, P = any> {
@@ -27,6 +30,7 @@ export interface InstanceProps<T = any, P = any> {
   visible?: boolean
   dispose?: null
   attach?: AttachType<T>
+  [prop: string]: any
 }
 
 interface TresBaseObject {
@@ -39,14 +43,14 @@ interface TresBaseObject {
 export interface LocalState {
   type: string
   // objects and parent are used when children are added with `attach` instead of being added to the Object3D scene graph
-  objects: TresObject3D[]
-  parent: TresObject3D | null
+  objects?: TresObject3D[]
+  parent?: TresObject3D | null
   primitive?: boolean
-  eventCount: number
-  handlers: Partial<EventHandlers>
-  memoizedProps: { [key: string]: any }
-  disposable: boolean
-  root: TresContext
+  eventCount?: number
+  handlers?: Partial<EventHandlers>
+  memoizedProps?: { [key: string]: any }
+  disposable?: boolean
+  root?: TresContext
 }
 
 // Custom type for geometry and material properties in Object3D
@@ -56,7 +60,7 @@ export interface TresObject3D extends THREE.Object3D<THREE.Object3DEventMap> {
 }
 
 export type TresObject =
-  TresBaseObject & (TresObject3D | THREE.BufferGeometry | THREE.Material | THREE.Fog) & { __tres: LocalState }
+  TresBaseObject & (TresObject3D | THREE.BufferGeometry | THREE.Material | THREE.Fog) & { __tres?: LocalState }
 
 export interface TresScene extends THREE.Scene {
   __tres: {
@@ -97,6 +101,15 @@ export interface IntersectionEvent<TSourceEvent> extends Intersection {
 export type ThreeEvent<TEvent> = IntersectionEvent<TEvent> & Properties<TEvent>
 export type DomEvent = PointerEvent | MouseEvent | WheelEvent
 
+export interface TresEvent {
+  eventObject: TresObject
+  event: DomEvent
+  stopPropagation: () => void
+  stopPropagating: boolean
+  intersections: Intersection[]
+  intersects: Intersection[]
+}
+
 export interface Events {
   onClick: EventListener
   onContextMenu: EventListener
@@ -144,13 +157,20 @@ export type MathType<T extends MathRepresentation | THREE.Euler> = T extends THR
 
   : T extends VectorRepresentation | THREE.Layers | THREE.Euler ? T | Parameters<T['set']> | number | VectorCoordinates : T | Parameters<T['set']>
 
-export type TresVector2 = MathType<THREE.Vector2>
-export type TresVector3 = MathType<THREE.Vector3>
-export type TresVector4 = MathType<THREE.Vector4>
-export type TresColor = MathType<THREE.Color>
-export type TresLayers = MathType<THREE.Layers>
-export type TresQuaternion = MathType<THREE.Quaternion>
-export type TresEuler = MathType<THREE.Euler>
+type VectorLike<VectorClass extends THREE.Vector2 | THREE.Vector3 | THREE.Vector4> =
+  | VectorClass
+  | Parameters<VectorClass['set']>
+  | Readonly<Parameters<VectorClass['set']>>
+  | Parameters<VectorClass['setScalar']>[0]
+
+export type TresVector2 = VectorLike<THREE.Vector2>
+export type TresVector3 = VectorLike<THREE.Vector3>
+export type TresVector4 = VectorLike<THREE.Vector4>
+export type TresColor = ConstructorParameters<typeof THREE.Color> | THREE.Color | number | string // Parameters<T> will not work here because of multiple function signatures in three.js types
+export type TresColorArray = typeof THREE.Color | [color: THREE.ColorRepresentation]
+export type TresLayers = THREE.Layers | Parameters<THREE.Layers['set']>[0]
+export type TresQuaternion = THREE.Quaternion | Parameters<THREE.Quaternion['set']>
+export type TresEuler = THREE.Euler
 
 type WithMathProps<P> = { [K in keyof P]: P[K] extends MathRepresentation | THREE.Euler ? MathType<P[K]> : P[K] }
 

+ 4 - 3
src/utils/index.ts

@@ -1,5 +1,6 @@
 import type { Material, Mesh, Object3D, Texture } from 'three'
 import { DoubleSide, MeshBasicMaterial, Scene, Vector3 } from 'three'
+import type { TresObject } from 'src/types'
 import { HightlightMesh } from '../devtools/highlight'
 
 export function toSetMethodName(key: string) {
@@ -244,7 +245,7 @@ export function stopHighlightAnimation(): void {
   }
 }
 
-export function createHighlightMesh(object: Object3D): Mesh {
+export function createHighlightMesh(object: TresObject): Mesh {
   const highlightMaterial = new MeshBasicMaterial({
     color: 0xA7E6D7, // Highlight color, e.g., yellow
     transparent: true,
@@ -280,7 +281,7 @@ export function disposeMaterial(material: Material): void {
   material.dispose()
 }
 
-export function disposeObject3D(object: Object3D): void {
+export function disposeObject3D(object: TresObject): void {
   if (object.parent) {
     object.removeFromParent?.()
   }
@@ -293,7 +294,7 @@ export function disposeObject3D(object: Object3D): void {
     // Optionally handle Scene-specific cleanup
   }
   else {
-    const mesh = object as Mesh
+    const mesh = object as unknown as Partial<Mesh>
     if (mesh.geometry) {
       mesh.geometry.dispose()
       delete mesh.geometry

+ 2 - 1
src/utils/test-utils.ts

@@ -1,6 +1,7 @@
+import type { Fn } from '@vueuse/core'
 import { createApp } from 'vue'
 
-export function withSetup(composable) {
+export function withSetup(composable: Fn) {
   let result
   const app = createApp({
     setup() {