From bea23d4d4d51aaee0ae49b3b109999f5299410e1 Mon Sep 17 00:00:00 2001 From: Kevin Levron Date: Tue, 8 Jun 2021 19:27:05 +0200 Subject: [PATCH] improve raycasting #67 --- src/core/Object3D.ts | 25 +++++++++++++++++ src/core/Raycaster.ts | 14 ++-------- src/core/usePointer.ts | 59 +++++++++++++++++++++++++++------------- src/core/useRaycaster.ts | 7 ++--- src/core/useThree.ts | 14 +++++----- src/meshes/Mesh.ts | 25 ----------------- 6 files changed, 77 insertions(+), 67 deletions(-) diff --git a/src/core/Object3D.ts b/src/core/Object3D.ts index c338755..290e2d9 100644 --- a/src/core/Object3D.ts +++ b/src/core/Object3D.ts @@ -26,6 +26,16 @@ export interface Object3DPublicInterface extends ComponentPublicInstance, Object // return { scene, renderer } // } +export const pointerProps = { + onPointerEnter: Function, + onPointerOver: Function, + onPointerMove: Function, + onPointerLeave: Function, + onPointerDown: Function, + onPointerUp: Function, + onClick: Function, +} + export interface Vector2PropInterface { x?: number y?: number @@ -59,6 +69,7 @@ export default defineComponent({ props: { type: Object, default: () => ({}) }, disableAdd: { type: Boolean, default: false }, disableRemove: { type: Boolean, default: false }, + ...pointerProps, }, setup(): Object3DSetupInterface { // return object3DSetup() @@ -74,10 +85,24 @@ export default defineComponent({ }, unmounted() { if (!this.disableRemove) this.removeFromParent() + if (this.o3d) { + if (this.renderer) this.renderer.three.removeIntersectObject(this.o3d) + } }, methods: { initObject3D(o3d: Object3D) { this.o3d = o3d + o3d.userData.component = this + + if (this.onPointerEnter || + this.onPointerOver || + this.onPointerMove || + this.onPointerLeave || + this.onPointerDown || + this.onPointerUp || + this.onClick) { + if (this.renderer) this.renderer.three.addIntersectObject(o3d) + } bindProp(this, 'position', o3d) bindProp(this, 'rotation', o3d) diff --git a/src/core/Raycaster.ts b/src/core/Raycaster.ts index 89a987d..5629153 100644 --- a/src/core/Raycaster.ts +++ b/src/core/Raycaster.ts @@ -1,6 +1,5 @@ -import { Object3D } from 'three' import { defineComponent, inject, PropType } from 'vue' -import usePointer, { IntersectObject, PointerInterface, PointerIntersectCallbackType } from './usePointer' +import usePointer, { PointerInterface, PointerIntersectCallbackType } from './usePointer' import { RendererInjectionKey, RendererInterface } from './Renderer' // eslint-disable-next-line @typescript-eslint/no-empty-function @@ -39,7 +38,7 @@ export default defineComponent({ this.pointer = usePointer({ camera: renderer.camera, domElement: renderer.canvas, - intersectObjects: this.getIntersectObjects(), + intersectObjects: () => renderer.scene ? renderer.scene.children : [], intersectRecursive: this.intersectRecursive, onIntersectEnter: this.onPointerEnter, onIntersectOver: this.onPointerOver, @@ -60,15 +59,6 @@ export default defineComponent({ this.renderer?.offBeforeRender(this.pointer.intersect) } }, - methods: { - getIntersectObjects() { - if (this.renderer && this.renderer.scene) { - const children = this.renderer.scene.children.filter((c: Object3D) => ['Mesh', 'InstancedMesh'].includes(c.type)) - return children as IntersectObject[] - } - return [] - }, - }, render() { return [] }, diff --git a/src/core/usePointer.ts b/src/core/usePointer.ts index 0dd5cd4..2cb6996 100644 --- a/src/core/usePointer.ts +++ b/src/core/usePointer.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-empty-function */ -import { Camera, InstancedMesh, Intersection, Mesh, Vector2, Vector3 } from 'three' +import { Camera, InstancedMesh, Intersection, Object3D, Vector2, Vector3 } from 'three' import useRaycaster from './useRaycaster' export interface PointerEventInterface { @@ -18,7 +18,6 @@ export interface PointerIntersectEventInterface { export type PointerCallbackType = (e: PointerEventInterface) => void export type PointerIntersectCallbackType = (e: PointerIntersectEventInterface) => void -export type IntersectObject = Mesh | InstancedMesh export interface PointerPublicConfigInterface { intersectMode?: 'frame' @@ -39,14 +38,14 @@ export interface PointerPublicConfigInterface { export interface PointerConfigInterface extends PointerPublicConfigInterface { camera: Camera domElement: HTMLCanvasElement - intersectObjects: IntersectObject[] + intersectObjects: Object3D[] | (() => Object3D[]) } export interface PointerInterface { position: Vector2 positionN: Vector2 positionV3: Vector3 - intersectObjects: IntersectObject[] + intersectObjects: Object3D[] | (() => Object3D[]) listeners: boolean addListeners(cb: void): void removeListeners(cb: void): void @@ -117,14 +116,15 @@ export default function usePointer(options: PointerConfigInterface): PointerInte } function intersect() { - if (intersectObjects.length) { - const intersects = raycaster.intersect(positionN, intersectObjects, intersectRecursive) - const offObjects: IntersectObject[] = [...intersectObjects] + const _intersectObjects = getIntersectObjects() + if (_intersectObjects.length) { + const intersects = raycaster.intersect(positionN, _intersectObjects, intersectRecursive) + const offObjects: Object3D[] = [..._intersectObjects] const iMeshes: InstancedMesh[] = [] intersects.forEach(intersect => { const { object } = intersect - const { component } = object.userData + const component = getComponent(object) // only once for InstancedMesh if (object instanceof InstancedMesh) { @@ -138,27 +138,27 @@ export default function usePointer(options: PointerConfigInterface): PointerInte const enterEvent: PointerIntersectEventInterface = { ...overEvent, type: 'pointerenter' } onIntersectOver(overEvent) onIntersectEnter(enterEvent) - component.onPointerOver?.(overEvent) - component.onPointerEnter?.(enterEvent) + component?.onPointerOver?.(overEvent) + component?.onPointerEnter?.(enterEvent) } const moveEvent: PointerIntersectEventInterface = { type: 'pointermove', component, intersect } onIntersectMove(moveEvent) - component.onPointerMove?.(moveEvent) + component?.onPointerMove?.(moveEvent) - offObjects.splice(offObjects.indexOf((object)), 1) + offObjects.splice(offObjects.indexOf((object)), 1) }) offObjects.forEach(object => { - const { component } = object.userData + const component = getComponent(object) if (object.userData.over) { object.userData.over = false const overEvent: PointerIntersectEventInterface = { type: 'pointerover', over: false, component } const leaveEvent: PointerIntersectEventInterface = { ...overEvent, type: 'pointerleave' } onIntersectOver(overEvent) onIntersectLeave(leaveEvent) - component.onPointerOver?.(overEvent) - component.onPointerLeave?.(leaveEvent) + component?.onPointerOver?.(overEvent) + component?.onPointerLeave?.(leaveEvent) } }) } @@ -177,12 +177,13 @@ export default function usePointer(options: PointerConfigInterface): PointerInte function pointerClick(event: TouchEvent | MouseEvent) { updatePosition(event) - if (intersectObjects.length) { - const intersects = raycaster.intersect(positionN, intersectObjects, intersectRecursive) + const _intersectObjects = getIntersectObjects() + if (_intersectObjects.length) { + const intersects = raycaster.intersect(positionN, _intersectObjects, intersectRecursive) const iMeshes: InstancedMesh[] = [] intersects.forEach(intersect => { const { object } = intersect - const { component } = object.userData + const component = getComponent(object) // only once for InstancedMesh if (object instanceof InstancedMesh) { @@ -192,7 +193,7 @@ export default function usePointer(options: PointerConfigInterface): PointerInte const event: PointerIntersectEventInterface = { type: 'click', component, intersect } onIntersectClick(event) - component.onClick?.(event) + component?.onClick?.(event) }) } onClick({ type: 'click', position, positionN, positionV3 }) @@ -203,6 +204,26 @@ export default function usePointer(options: PointerConfigInterface): PointerInte onLeave({ type: 'pointerleave' }) } + function getComponent(object: Object3D) { + if (object.userData.component) return object.userData.component + + let parent = object.parent + while (parent) { + if (parent.userData.component) { + return parent.userData.component + } + parent = parent.parent + } + + return undefined + } + + function getIntersectObjects() { + if (typeof intersectObjects === 'function') { + return intersectObjects() + } else return intersectObjects + } + function addListeners() { domElement.addEventListener('mouseenter', pointerEnter) domElement.addEventListener('mousemove', pointerMove) diff --git a/src/core/useRaycaster.ts b/src/core/useRaycaster.ts index 823bd6f..fc0f64d 100644 --- a/src/core/useRaycaster.ts +++ b/src/core/useRaycaster.ts @@ -1,10 +1,9 @@ -import { Camera, Intersection, Plane, Raycaster, Vector2, Vector3 } from 'three' -import { IntersectObject } from './usePointer' +import { Camera, Intersection, Object3D, Plane, Raycaster, Vector2, Vector3 } from 'three' export interface RaycasterInterface { position: Vector3 updatePosition(coords: Vector2): void - intersect(coords: Vector2, objects: IntersectObject[], recursive?: boolean): Intersection[], + intersect(coords: Vector2, objects: Object3D[], recursive?: boolean): Intersection[], } export interface RaycasterConfigInterface { @@ -28,7 +27,7 @@ export default function useRaycaster(options: RaycasterConfigInterface): Raycast raycaster.ray.intersectPlane(plane, position) } - const intersect = (coords: Vector2, objects: IntersectObject[], recursive = false) => { + const intersect = (coords: Vector2, objects: Object3D[], recursive = false) => { raycaster.setFromCamera(coords, camera) return raycaster.intersectObjects(objects, recursive) } diff --git a/src/core/useThree.ts b/src/core/useThree.ts index 4e21b61..b8fa5b4 100644 --- a/src/core/useThree.ts +++ b/src/core/useThree.ts @@ -1,7 +1,7 @@ -import { Camera, OrthographicCamera, PerspectiveCamera, Scene, WebGLRenderer } from 'three' +import { Camera, Object3D, OrthographicCamera, PerspectiveCamera, Scene, WebGLRenderer } from 'three' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js' -import usePointer, { IntersectObject, PointerConfigInterface, PointerPublicConfigInterface, PointerInterface } from './usePointer' +import usePointer, { PointerConfigInterface, PointerPublicConfigInterface, PointerInterface } from './usePointer' export interface SizeInterface { width: number @@ -39,8 +39,8 @@ export interface ThreeInterface { render(): void renderC(): void setSize(width: number, height: number): void - addIntersectObject(o: IntersectObject): void - removeIntersectObject(o: IntersectObject): void + addIntersectObject(o: Object3D): void + removeIntersectObject(o: Object3D): void } /** @@ -74,7 +74,7 @@ export default function useThree(params: ThreeConfigInterface): ThreeInterface { const beforeRenderCallbacks: {(): void}[] = [] - const intersectObjects: IntersectObject[] = [] + const intersectObjects: Object3D[] = [] const renderer = createRenderer() @@ -191,7 +191,7 @@ export default function useThree(params: ThreeConfigInterface): ThreeInterface { /** * add intersect object */ - function addIntersectObject(o: IntersectObject) { + function addIntersectObject(o: Object3D) { if (intersectObjects.indexOf(o) === -1) { intersectObjects.push(o) } @@ -204,7 +204,7 @@ export default function useThree(params: ThreeConfigInterface): ThreeInterface { /** * remove intersect object */ - function removeIntersectObject(o: IntersectObject) { + function removeIntersectObject(o: Object3D) { const i = intersectObjects.indexOf(o) if (i !== -1) { intersectObjects.splice(i, 1) diff --git a/src/meshes/Mesh.ts b/src/meshes/Mesh.ts index 426cd69..bdd0509 100644 --- a/src/meshes/Mesh.ts +++ b/src/meshes/Mesh.ts @@ -3,16 +3,6 @@ import { BufferGeometry, Material, Mesh as TMesh } from 'three' import Object3D, { Object3DSetupInterface } from '../core/Object3D' import { bindProp } from '../tools' -export const pointerProps = { - onPointerEnter: Function, - onPointerOver: Function, - onPointerMove: Function, - onPointerLeave: Function, - onPointerDown: Function, - onPointerUp: Function, - onClick: Function, -} - export interface MeshSetupInterface extends Object3DSetupInterface { mesh?: TMesh geometry?: BufferGeometry @@ -35,7 +25,6 @@ const Mesh = defineComponent({ props: { castShadow: Boolean, receiveShadow: Boolean, - ...pointerProps, }, setup(): MeshSetupInterface { return {} @@ -52,21 +41,10 @@ const Mesh = defineComponent({ methods: { initMesh() { const mesh = new TMesh(this.geometry, this.material) - mesh.userData.component = this bindProp(this, 'castShadow', mesh) bindProp(this, 'receiveShadow', mesh) - if (this.onPointerEnter || - this.onPointerOver || - this.onPointerMove || - this.onPointerLeave || - this.onPointerDown || - this.onPointerUp || - this.onClick) { - if (this.renderer) this.renderer.three.addIntersectObject(mesh) - } - this.mesh = mesh this.initObject3D(mesh) }, @@ -95,9 +73,6 @@ const Mesh = defineComponent({ }, }, unmounted() { - if (this.mesh) { - if (this.renderer) this.renderer.three.removeIntersectObject(this.mesh) - } // for predefined mesh (geometry/material are not unmounted) if (this.geometry) this.geometry.dispose() if (this.material) this.material.dispose()