From 20a5b9eee94755ca58ff4936aef20a070f920a7e Mon Sep 17 00:00:00 2001 From: Tino Koch <17991193+Tinoooo@users.noreply.github.com> Date: Mon, 19 Jun 2023 08:42:38 +0200 Subject: [PATCH 1/5] fix: raycaster does not work properly when scene is not in full screen (#304) * chore: tinkering on possible solutions concerning pointer event handling * chore: made click listeners work with changed architectures concerning raycaster * chore: changed callback structure * chore: made pointer move work * chore: made other pointer events work * chore: code cleanup * chore: added deregistration of pointer event handlers for when an Oject3D is removed * chore: handled the case when the pointer leaves an Object3D but also the canvas * chore: replaced useRaycaster * fix: raycaster works properly when scene does not take up the whole viewport fix: onPointerMove does not fire too often anymore * chore: made types in nodeOps a little more specific * chore: improved click event handling * docs: adjusted events page * chore: fixed typo * chore: cleanup * chore: adjusted code so tests pass * chore: merge latest main --------- Co-authored-by: Tino Koch Co-authored-by: alvarosabu --- docs/api/events.md | 30 ++-- playground/components.d.ts | 1 + src/components/TresScene.ts | 45 ++---- src/composables/index.ts | 1 + .../usePointerEventHandler/index.ts | 83 +++++++++++ src/composables/useRaycaster/index.ts | 141 ++++++++++++------ .../useRaycaster/useRaycaster.test.ts | 23 --- src/composables/useRenderer/index.ts | 2 +- src/composables/useSeek/useSeek.test.ts | 2 +- src/composables/useTres/index.ts | 18 ++- src/core/nodeOps.ts | 33 ++-- src/keys.ts | 6 + src/types/index.ts | 1 - src/utils/index.ts | 15 ++ 14 files changed, 254 insertions(+), 147 deletions(-) create mode 100644 src/composables/usePointerEventHandler/index.ts delete mode 100644 src/composables/useRaycaster/useRaycaster.test.ts diff --git a/docs/api/events.md b/docs/api/events.md index 2dee1fc0c..6814697ae 100644 --- a/docs/api/events.md +++ b/docs/api/events.md @@ -1,6 +1,6 @@ # Events -**TresJS** Mesh objects emit pointer events when they are interacted with using `raycaster` and `pointer` objects under the hood. +**TresJS** components emit pointer events when they are interacted with. This is the case for the components that represent Three.js classes that derive from [THREE.Object3D](https://threejs.org/docs/index.html?q=object#api/en/core/Object3D) (like meshes, groups,...). @@ -8,24 +8,18 @@ ```html ``` -## Event Data +| Event | fires when ... | Event Handler Parameter Type(s) | +| ------------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| click   | ... the events pointerdown and pointerup fired on the same object one after the other | [Intersection](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/three/src/core/Raycaster.d.ts#L16), [PointerEvent](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent) | +| pointer-move | ... the pointer is moving above the object | [Intersection](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/three/src/core/Raycaster.d.ts#L16), [PointerEvent](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent) | +| pointer-enter | ... the pointer is entering the object | [Intersection](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/three/src/core/Raycaster.d.ts#L16), [PointerEvent](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent) | +| pointer-leave | ... the pointer is leaves the object | [PointerEvent](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent) | -The event data is a `TresEvent` object that contains the following properties: - -```ts -;({ - object: Object3D, // The mesh object that emitted the event - distance: number, // The distance between the camera and the mesh - point: Vector3, // The intersection point between the ray and the mesh - uv: Vector2, // The uv coordinates of the intersection point - face: Face3, // The face of the mesh that was intersected - faceIndex: number, // The index of the face that was intersected -}) -``` +The returned [Intersection](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/three/src/core/Raycaster.d.ts#L16) includes the [Object3D](https://threejs.org/docs/index.html?q=object#api/en/core/Object3D) that triggered the event. You can access it via `intersection.object`. diff --git a/playground/components.d.ts b/playground/components.d.ts index 1faa12e07..a25994168 100644 --- a/playground/components.d.ts +++ b/playground/components.d.ts @@ -10,6 +10,7 @@ declare module 'vue' { AnimatedModel: typeof import('./src/components/AnimatedModel.vue')['default'] Cameras: typeof import('./src/components/Cameras.vue')['default'] DanielTest: typeof import('./src/components/DanielTest.vue')['default'] + DeleteMe: typeof import('./src/components/DeleteMe.vue')['default'] FBXModels: typeof import('./src/components/FBXModels.vue')['default'] Gltf: typeof import('./src/components/gltf/index.vue')['default'] MeshWobbleMaterial: typeof import('./src/components/meshWobbleMaterial/index.vue')['default'] diff --git a/src/components/TresScene.ts b/src/components/TresScene.ts index a7ec34400..46a08f0ab 100644 --- a/src/components/TresScene.ts +++ b/src/components/TresScene.ts @@ -1,7 +1,6 @@ -import { App, defineComponent, h, onMounted, onUnmounted, ref, watch, watchEffect, VNode } from 'vue' +import { App, defineComponent, h, onMounted, onUnmounted, ref, watch, VNode } from 'vue' import * as THREE from 'three' import { ColorSpace, ShadowMapType, ToneMapping } from 'three' -import { useEventListener } from '@vueuse/core' import { isString } from '@alvarosabu/utils' import { createTres } from '../core/renderer' import { TresCamera } from '../types/' @@ -12,11 +11,12 @@ import { useCamera, useRenderer, useRenderLoop, - useRaycaster, useTres, + usePointerEventHandler, } from '../composables' import { extend } from '../core/catalogue' import { type RendererPresetsType } from '../composables/useRenderer/const' +import { OBJECT_3D_USER_DATA_KEYS } from '../keys' export interface TresSceneProps { shadows?: boolean @@ -67,12 +67,18 @@ export const TresScene = defineComponent({ const container = ref() const canvas = ref() + const scene = new THREE.Scene() + + const pointerEventHandler = usePointerEventHandler() const { setState } = useTres() + scene.userData[OBJECT_3D_USER_DATA_KEYS.REGISTER_AT_POINTER_EVENT_HANDLER] = pointerEventHandler.registerObject + setState('scene', scene) setState('canvas', canvas) setState('container', container) + setState('pointerEventHandler', pointerEventHandler) const isCameraAvailable = ref() @@ -105,41 +111,8 @@ export const TresScene = defineComponent({ pushCamera(props.camera as any) } - const { raycaster, pointer } = useRaycaster() - - // TODO: Type raycasting events correctly - let prevInstance: any = null - let currentInstance: any = null - - watchEffect(() => { - if (activeCamera.value) raycaster.value.setFromCamera(pointer.value, activeCamera.value) - }) - onLoop(() => { if (activeCamera.value && props.disableRender !== true) renderer.value?.render(scene, activeCamera.value) - - if (raycaster.value) { - const intersects = raycaster.value.intersectObjects(scene.children) - - if (intersects.length > 0) { - currentInstance = intersects[0] - if (prevInstance === null) { - currentInstance.object?.events?.onPointerEnter?.(currentInstance) - } - currentInstance.object?.events?.onPointerMove?.(currentInstance) - } else { - if (prevInstance !== null) { - currentInstance?.object?.events?.onPointerLeave?.(prevInstance) - currentInstance = null - } - } - prevInstance = currentInstance - } - }) - - useEventListener(canvas.value, 'click', () => { - if (currentInstance === null) return - currentInstance.object?.events?.onClick?.(currentInstance) }) } diff --git a/src/composables/index.ts b/src/composables/index.ts index 1c238117a..bd52fc10a 100644 --- a/src/composables/index.ts +++ b/src/composables/index.ts @@ -7,3 +7,4 @@ export * from './useTres' export * from './useRaycaster' export * from './useLogger' export * from './useSeek' +export * from './usePointerEventHandler' diff --git a/src/composables/usePointerEventHandler/index.ts b/src/composables/usePointerEventHandler/index.ts new file mode 100644 index 000000000..552ce17b3 --- /dev/null +++ b/src/composables/usePointerEventHandler/index.ts @@ -0,0 +1,83 @@ +import { uniqueBy } from '../../utils' +import { useRaycaster } from '../useRaycaster' +import { computed, reactive } from 'vue' +import type { Intersection, Event, Object3D } from 'three' + +type CallbackFn = (intersection: Intersection>, event: PointerEvent) => void //TODO document +type CallbackFnPointerLeave = (object: Object3D, event: PointerEvent) => void + +type EventProps = { + onClick?: CallbackFn + onPointerEnter?: CallbackFn + onPointerMove?: CallbackFn + onPointerLeave?: CallbackFnPointerLeave +} + +export const usePointerEventHandler = () => { + const objectsWithEventListeners = reactive({ + click: new Map(), + pointerMove: new Map(), + pointerEnter: new Map(), + pointerLeave: new Map(), + }) + + const deregisterObject = (object: Object3D) => { + Object.values(objectsWithEventListeners).forEach(map => map.delete(object)) + } + + const registerObject = (object: Object3D & 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) + + object.addEventListener('removed', () => { + object.traverse((child: Object3D) => { + deregisterObject(child) + }) + + deregisterObject(object) + }) + } + + const objectsToWatch = computed(() => + uniqueBy( + Object.values(objectsWithEventListeners) + .map(map => Array.from(map.keys())) + .flat(), + ({ uuid }) => uuid, + ), + ) + + const { onClick, onPointerMove } = useRaycaster(objectsToWatch) + + onClick(({ intersects, event }) => { + if (intersects.length) objectsWithEventListeners.click.get(intersects[0].object)?.(intersects[0], event) + }) + + let previouslyIntersectedObject: Object3D | null + + onPointerMove(({ intersects, event }) => { + const firstObject = intersects?.[0]?.object + + const { pointerLeave, pointerEnter, pointerMove } = objectsWithEventListeners + + if (previouslyIntersectedObject && previouslyIntersectedObject !== firstObject) + pointerLeave.get(previouslyIntersectedObject)?.(previouslyIntersectedObject, event) + + if (firstObject) { + if (previouslyIntersectedObject !== firstObject) pointerEnter.get(firstObject)?.(intersects[0], event) + + pointerMove.get(firstObject)?.(intersects[0], event) + } + + previouslyIntersectedObject = firstObject || null + }) + + return { + registerObject, + deregisterObject, + } +} diff --git a/src/composables/useRaycaster/index.ts b/src/composables/useRaycaster/index.ts index b6410851e..1861f57dd 100644 --- a/src/composables/useRaycaster/index.ts +++ b/src/composables/useRaycaster/index.ts @@ -1,59 +1,108 @@ import { useTres } from '../useTres' -import { Raycaster, Vector2 } from 'three' -import { onUnmounted, Ref, ref, ShallowRef, shallowRef } from 'vue' - -/** - * Raycaster composable return type - * - * @export - * @interface UseRaycasterReturn - */ -export interface UseRaycasterReturn { - /** - * Raycaster instance - * - * @type {ShallowRef} - * @memberof UseRaycasterReturn - */ - raycaster: ShallowRef - /** - * Pointer position - * - * @type {Ref} - * @memberof UseRaycasterReturn - */ - pointer: Ref +import { Object3D, Raycaster, Vector2 } from 'three' +import { Ref, computed, onUnmounted, watchEffect } from 'vue' +import { EventHook, createEventHook, useElementBounding, usePointer } from '@vueuse/core' + +export type Intersects = THREE.Intersection>[] +interface PointerMoveEventPayload { + intersects?: Intersects + event: PointerEvent +} + +interface PointerClickEventPayload { + intersects: Intersects + event: PointerEvent } -/** - * Composable to provide raycaster support and pointer information - * - * @see https://threejs.org/docs/index.html?q=raycas#api/en/core/Raycaster - * @export - * @return {*} {UseRaycasterReturn} - */ -export function useRaycaster(): UseRaycasterReturn { - const raycaster = shallowRef(new Raycaster()) - const pointer = ref(new Vector2()) - const currentInstance = ref(null) - const { setState, state } = useTres() +export const useRaycaster = (objects: Ref) => { + const { state } = useTres() + + const canvas = computed(() => state.canvas?.value) // having a seperate computed makes useElementBounding work + + const { x, y } = usePointer({ target: canvas }) + + const { width, height, top, left } = useElementBounding(canvas) + + const raycaster = new Raycaster() + + const getRelativePointerPosition = ({ x, y }: { x: number; y: number }) => { + if (!canvas.value) return + + return { + x: ((x - left.value) / width.value) * 2 - 1, + y: -((y - top.value) / height.value) * 2 + 1, + } + } + + const getIntersectsByRelativePointerPosition = ({ x, y }: { x: number; y: number }) => { + if (!state.camera) return + + raycaster.setFromCamera(new Vector2(x, y), state.camera) + + return raycaster.intersectObjects(objects.value, false) + } + + const getIntersects = (event?: PointerEvent | MouseEvent) => { + const pointerPosition = getRelativePointerPosition({ + x: event?.clientX ?? x.value, + y: event?.clientY ?? y.value, + }) + if (!pointerPosition) return [] + + return getIntersectsByRelativePointerPosition(pointerPosition) || [] + } + + const intersects = computed(() => getIntersects()) + + const eventHookClick = createEventHook() + const eventHookPointerMove = createEventHook() + + const triggerEventHook = (eventHook: EventHook, event: PointerEvent | MouseEvent) => { + eventHook.trigger({ event, intersects: getIntersects(event) }) + } + + const onPointerMove = (event: PointerEvent) => { + triggerEventHook(eventHookPointerMove, event) + } + + // a click event is fired whenever a pointerdown happened after pointerup on the same object - setState('raycaster', raycaster.value) - setState('pointer', pointer) - setState('currentInstance', currentInstance) + let mouseDownObject: Object3D | undefined = undefined - function onPointerMove(event: MouseEvent) { - pointer.value.x = (event.clientX / window.innerWidth) * 2 - 1 - pointer.value.y = -(event.clientY / window.innerHeight) * 2 + 1 + const onPointerDown = (event: PointerEvent) => { + mouseDownObject = getIntersects(event)[0]?.object } - state?.renderer?.domElement.addEventListener('pointermove', onPointerMove) + const onPointerUp = (event: MouseEvent) => { + if (!(event instanceof PointerEvent)) return // prevents triggering twice on mobile devices + + if (mouseDownObject === getIntersects(event)[0]?.object) triggerEventHook(eventHookClick, event) + } + + const onPointerLeave = (event: PointerEvent) => eventHookPointerMove.trigger({ event, intersects: [] }) + + const unwatch = watchEffect(() => { + if (!canvas?.value) return + + canvas.value.addEventListener('pointerup', onPointerUp) + canvas.value.addEventListener('pointerdown', onPointerDown) + canvas.value.addEventListener('pointermove', onPointerMove) + canvas.value.addEventListener('pointerleave', onPointerLeave) + + unwatch() + }) onUnmounted(() => { - state?.renderer?.domElement.removeEventListener('pointermove', onPointerMove) + if (!canvas?.value) return + canvas.value.removeEventListener('pointerup', onPointerUp) + canvas.value.removeEventListener('pointerdown', onPointerDown) + canvas.value.removeEventListener('pointermove', onPointerMove) + canvas.value.removeEventListener('pointerleave', onPointerLeave) }) + return { - raycaster, - pointer, + intersects, + onClick: (fn: (value: PointerClickEventPayload) => void) => eventHookClick.on(fn).off, + onPointerMove: (fn: (value: PointerMoveEventPayload) => void) => eventHookPointerMove.on(fn).off, } } diff --git a/src/composables/useRaycaster/useRaycaster.test.ts b/src/composables/useRaycaster/useRaycaster.test.ts deleted file mode 100644 index 2645ff8ab..000000000 --- a/src/composables/useRaycaster/useRaycaster.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Raycaster, Vector2 } from 'three' -/* import { useRaycaster } from '.' */ -import { withSetup } from '../../utils/test-utils' - -/* const [composable, app] = withSetup(() => useRaycaster()) */ - -describe.skip('useRaycaster', () => { - /* afterEach(() => { - app.unmount() - }) */ - /* test.skip('provides raycaster', () => { - const { raycaster } = composable - expect(raycaster).toBeDefined() - expect(raycaster.value).toBeInstanceOf(Raycaster) - }) - test.skip('provides pointer', () => { - const { pointer } = composable - expect(pointer).toBeDefined() - expect(pointer.value).toBeInstanceOf(Vector2) - }) */ -}) - -// TODO: find a way to test this with useTresProvider approach diff --git a/src/composables/useRenderer/index.ts b/src/composables/useRenderer/index.ts index 6831a1eb2..3b68e9946 100644 --- a/src/composables/useRenderer/index.ts +++ b/src/composables/useRenderer/index.ts @@ -159,7 +159,7 @@ export function useRenderer(options: UseRendererOptions) { const aspectRatio = computed(() => width.value / height.value) setTimeout(() => { - if (!toValue(windowSize) && !state.canvas.value.offsetHeight) { + if (!toValue(windowSize) && !state.canvas?.value.offsetHeight) { logWarning(`Oops... Seems like your canvas height is currently 0px, it's posible that you couldn't watch your scene. You could set windowSize=true to force the canvas to be the size of the window.`) } diff --git a/src/composables/useSeek/useSeek.test.ts b/src/composables/useSeek/useSeek.test.ts index 8d31b9847..91c6cb527 100644 --- a/src/composables/useSeek/useSeek.test.ts +++ b/src/composables/useSeek/useSeek.test.ts @@ -4,7 +4,7 @@ import { withSetup } from '../../utils/test-utils' const [composable, app] = withSetup(() => useSeek()) -describe('useRaycaster', () => { +describe('useSeek', () => { afterEach(() => { app.unmount() }) diff --git a/src/composables/useTres/index.ts b/src/composables/useTres/index.ts index dc93c0f44..3b4da3f96 100644 --- a/src/composables/useTres/index.ts +++ b/src/composables/useTres/index.ts @@ -1,7 +1,8 @@ import { Clock, EventDispatcher, Raycaster, Scene, Vector2, WebGLRenderer } from 'three' import { generateUUID } from 'three/src/math/MathUtils' -import { computed, ComputedRef, inject, provide, shallowReactive, toRefs } from 'vue' +import { ComputedRef, inject, provide, Ref, shallowReactive, toRefs } from 'vue' import { Camera } from '../useCamera' +import type { usePointerEventHandler } from '../usePointerEventHandler' export interface TresState { /** @@ -88,6 +89,15 @@ export interface TresState { * @memberof TresState */ controls?: (EventDispatcher & { enabled: boolean }) | null + + canvas?: Ref + + /** + * The entity that handles pointer events + * @type {ReturnType} + * @memberof TresState + */ + pointerEventHandler?: ReturnType [key: string]: any } @@ -113,9 +123,11 @@ export function useTresProvider() { uuid: generateUUID(), camera: undefined, cameras: [], + canvas: undefined, scene: undefined, renderer: undefined, - aspectRatio: computed(() => window.innerWidth / window.innerHeight), + aspectRatio: undefined, + pointerEventHandler: undefined, }) /** * Get a state value. @@ -155,8 +167,10 @@ export const useTres = () => { state: shallowReactive({ camera: undefined, cameras: [], + canvas: undefined, scene: undefined, renderer: undefined, + pointerEventHandler: undefined, }), }) diff --git a/src/core/nodeOps.ts b/src/core/nodeOps.ts index 1602333c2..ddf77bfde 100644 --- a/src/core/nodeOps.ts +++ b/src/core/nodeOps.ts @@ -1,10 +1,12 @@ import { RendererOptions } from 'vue' -import { BufferAttribute, BufferGeometry, Material, Scene } from 'three' +import { BufferAttribute, Scene } from 'three' import { useCamera, useLogger } from '../composables' import { isFunction } from '@alvarosabu/utils' import { catalogue } from './catalogue' -import { EventHandlers, TresObject } from '../types' +import { TresObject } from '../types' import { isHTMLTag, kebabToCamel } from '../utils' +import { OBJECT_3D_USER_DATA_KEYS } from '../keys' +import type { Material, BufferGeometry, Object3D } from 'three' const onRE = /^on[^a-z]/ export const isOn = (key: string) => onRE.test(key) @@ -15,10 +17,6 @@ function noop(fn: string): any { let fallback: TresObject | null = null let scene: Scene | null = null -const OBJECT_3D_USER_DATA_KEYS = { - GEOMETRY_VIA_PROP: 'tres__geometryViaProp', - MATERIAL_VIA_PROP: 'tres__materialViaProp', -} const { logError } = useLogger() @@ -70,12 +68,10 @@ export const nodeOps: RendererOptions = { const { GEOMETRY_VIA_PROP, MATERIAL_VIA_PROP } = OBJECT_3D_USER_DATA_KEYS if (instance.isObject3D) { - if (props?.material?.isMaterial) (instance as TresObject).userData[MATERIAL_VIA_PROP] = true - if (props?.geometry?.isBufferGeometry) (instance as TresObject).userData[GEOMETRY_VIA_PROP] = true + if (props?.material?.isMaterial) (instance as Object3D).userData[MATERIAL_VIA_PROP] = true + if (props?.geometry?.isBufferGeometry) (instance as Object3D).userData[GEOMETRY_VIA_PROP] = true } - instance.events = {} - return instance }, insert(child, parent) { @@ -96,6 +92,7 @@ export const nodeOps: RendererOptions = { if (child?.isObject3D && parent?.isObject3D) { parent.add(child) child.dispatchEvent({ type: 'added' }) + scene?.userData?.[OBJECT_3D_USER_DATA_KEYS.REGISTER_AT_POINTER_EVENT_HANDLER]?.(child) } else if (child?.isFog) { parent.fog = child } else if (typeof child?.attach === 'string') { @@ -110,17 +107,19 @@ export const nodeOps: RendererOptions = { // remove is only called on the node being removed and not on child nodes. if (node.isObject3D) { - const object3D = node as unknown as TresObject + const object3D = node as unknown as Object3D - const disposeMaterialsAndGeometries = (object3D: TresObject) => { + const disposeMaterialsAndGeometries = (object3D: Object3D) => { const { GEOMETRY_VIA_PROP, MATERIAL_VIA_PROP } = OBJECT_3D_USER_DATA_KEYS - if (!object3D.userData[MATERIAL_VIA_PROP]) (object3D as TresObject & { material: Material }).material?.dispose() + if (!object3D.userData[MATERIAL_VIA_PROP]) (object3D as Object3D & { material: Material }).material?.dispose() if (!object3D.userData[GEOMETRY_VIA_PROP]) - (object3D as TresObject & { geometry: BufferGeometry }).geometry?.dispose() + (object3D as Object3D & { geometry: BufferGeometry }).geometry?.dispose() } - object3D.traverse((child: TresObject) => disposeMaterialsAndGeometries(child)) + object3D.traverse((child: Object3D) => { + disposeMaterialsAndGeometries(child) + }) disposeMaterialsAndGeometries(object3D) } @@ -153,10 +152,6 @@ export const nodeOps: RendererOptions = { finalKey = key.toLowerCase() if (!target?.set) root = chain.reduce((acc, key) => acc[kebabToCamel(key)], root) } - if (isOn(key)) { - const eventHandlerKey: keyof EventHandlers = key as keyof EventHandlers // This is fine - node.events[eventHandlerKey] = nextValue - } let value = nextValue if (value === '') value = true // Set prop, prefer atomic methods if applicable diff --git a/src/keys.ts b/src/keys.ts index 1ed7589bb..9b7361faa 100644 --- a/src/keys.ts +++ b/src/keys.ts @@ -1 +1,7 @@ export const UseTresStateSymbol = Symbol('UseTresState') + +export const OBJECT_3D_USER_DATA_KEYS = { + GEOMETRY_VIA_PROP: 'tres__geometryViaProp', + MATERIAL_VIA_PROP: 'tres__materialViaProp', + REGISTER_AT_POINTER_EVENT_HANDLER: 'tres__registerAtPointerEventHandler', +} diff --git a/src/types/index.ts b/src/types/index.ts index 8d40dceee..14835cfe3 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -30,7 +30,6 @@ export interface InstanceProps { interface TresBaseObject { attach?: string - events: EventHandlers removeFromParent?: () => void dispose?: () => void [prop: string]: any // for arbitrary properties diff --git a/src/utils/index.ts b/src/utils/index.ts index 440d7542c..6872cb4c9 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -38,3 +38,18 @@ export function makeMap(str: string, expectsLowerCase?: boolean): (key: string) } return expectsLowerCase ? val => !!map[val.toLowerCase()] : val => !!map[val] } + +export const uniqueBy = (array: T[], iteratee: (value: T) => K): T[] => { + const seen = new Set() + const result: T[] = [] + + for (const item of array) { + const identifier = iteratee(item) + if (!seen.has(identifier)) { + seen.add(identifier) + result.push(item) + } + } + + return result +} From 2ba6b2f31386408faa75ee2e1c786278591f08c1 Mon Sep 17 00:00:00 2001 From: Alvaro Saburido Date: Mon, 19 Jun 2023 12:59:55 +0200 Subject: [PATCH 2/5] chore: docs types typos (#309) * docs: fix typos * docs: fix getting-started types typo --- docs/guide/getting-started.md | 6 +++--- docs/guide/index.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index 58620349a..77bcd2b64 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -27,15 +27,15 @@ TresJS is written in Typescript and it's fully typed. If you are using Typescrip ::: code-group ```bash [npm] -npm install @three/types -D +npm install @types/three -D ``` ```bash [yarn] -yarn add @three/types -D +yarn add @types/three -D ``` ```bash [pnpm] -pnpm add @three/types -D +pnpm add @types/three -D ``` ::: diff --git a/docs/guide/index.md b/docs/guide/index.md index f5844f59d..dfe9cd2a2 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -29,15 +29,15 @@ TresJS is written in Typescript and it's fully typed. If you are using Typescrip ::: code-group ```bash [npm] -npm install @three/types -D +npm install @types/three -D ``` ```bash [yarn] -yarn add @three/types -D +yarn add @types/three -D ``` ```bash [pnpm] -pnpm add @three/types -D +pnpm add @types/three -D ``` ::: From e9509bab177a7a23f802ad2716f42d1f36f7654b Mon Sep 17 00:00:00 2001 From: Alvaro Saburido Date: Mon, 19 Jun 2023 17:26:27 +0200 Subject: [PATCH 3/5] feat: removed `useCamera` logic from nodeOps (#308) * feat: removed `useCamera` logic from nodeOps * docs: update docs/guide/your-first-scene.md Co-authored-by: Tino Koch <17991193+Tinoooo@users.noreply.github.com> * docs: update src/components/TresScene.ts Co-authored-by: Tino Koch <17991193+Tinoooo@users.noreply.github.com> * feat: use `getObjectByProperty` to get the camera * docs: remove camera troubleshooting * chore: removed unused scene imports --------- Co-authored-by: Tino Koch <17991193+Tinoooo@users.noreply.github.com> --- docs/guide/migration-guide.md | 2 -- docs/guide/troubleshooting.md | 21 --------------- docs/guide/your-first-scene.md | 2 +- playground/src/components/TheEvents.vue | 9 +++++-- src/components/TresScene.ts | 35 ++++++++++++++----------- src/core/nodeOps.ts | 8 ++---- 6 files changed, 29 insertions(+), 48 deletions(-) diff --git a/docs/guide/migration-guide.md b/docs/guide/migration-guide.md index abb3180ee..7271c3e50 100644 --- a/docs/guide/migration-guide.md +++ b/docs/guide/migration-guide.md @@ -163,8 +163,6 @@ watch(modelRef, model => { The `TresOrbitControls` component needs to be after the camera in the tree. This is because the controls need to know the camera to work. -Read more about it here: [Troubleshooting](/guide/troubleshooting.md) - Change this: ```vue {3,5} diff --git a/docs/guide/troubleshooting.md b/docs/guide/troubleshooting.md index fafe07e8a..9d189714e 100644 --- a/docs/guide/troubleshooting.md +++ b/docs/guide/troubleshooting.md @@ -12,27 +12,6 @@ You followed the [Getting started guide](/guide/getting-started.md) but you stil These are the most common reasons why you might not be able to see your scene: -### Make sure you have a camera 🎥 - -The first thing you need to do is to make sure you have a camera in your scene. If you don't have a camera, you won't be able to see anything. - -![No camera found](/no-camera-found.png) - -```vue - - - - -``` - -```vue - - - - - -``` - ### Check the height of your canvas 📏 Another common issue is that the `TresCanvas` component is creating by default a `canvas` element takes the `width` and `height` of the parent element. If the parent element has no height, the canvas will have no height either. diff --git a/docs/guide/your-first-scene.md b/docs/guide/your-first-scene.md index b3cbcc306..6e555c606 100644 --- a/docs/guide/your-first-scene.md +++ b/docs/guide/your-first-scene.md @@ -103,7 +103,7 @@ Then you can add a [**PerspectiveCamera**](https://threejs.org/docs/index.html?q ``` ::: warning -A common issue is that the camera default position is the origin of the scene (0,0,0), if you still can see your scene try adding a position to the camera `` +A common issue is that the camera default position is the origin of the scene (0,0,0), TresJS will automatically set the position of your camera to `[3,3,3]` if the prop `position`. If no camera is defined in you scene, a perspective camera is added automatically.` ::: ## Adding a 🍩 diff --git a/playground/src/components/TheEvents.vue b/playground/src/components/TheEvents.vue index 44dc90708..49114812f 100644 --- a/playground/src/components/TheEvents.vue +++ b/playground/src/components/TheEvents.vue @@ -36,11 +36,15 @@ function onPointerMove(ev) { console.log(ev) } } + +const visible = ref(true)