From acbcd0fb50631731a5738c349829087fd099f982 Mon Sep 17 00:00:00 2001 From: Kevin Levron Date: Fri, 2 Apr 2021 22:38:37 +0200 Subject: [PATCH] improve pointer / raycaster (#34) --- src/core/Raycaster.js | 40 ++++++++++++++++++++++++ src/core/usePointer.js | 70 +++++++++++++++++++++++++++--------------- src/core/useThree.js | 38 +++++++++++++---------- 3 files changed, 106 insertions(+), 42 deletions(-) create mode 100644 src/core/Raycaster.js diff --git a/src/core/Raycaster.js b/src/core/Raycaster.js new file mode 100644 index 0000000..47f72d7 --- /dev/null +++ b/src/core/Raycaster.js @@ -0,0 +1,40 @@ +import usePointer from './usePointer'; + +export default { + name: 'Raycaster', + inject: ['three', 'rendererComponent'], + props: { + onPointerEnter: { type: Function, default: () => {} }, + onPointerOver: { type: Function, default: () => {} }, + onPointerMove: { type: Function, default: () => {} }, + onPointerLeave: { type: Function, default: () => {} }, + onPointerClick: { type: Function, default: () => {} }, + }, + mounted() { + this.rendererComponent.onMounted(() => { + this.pointer = usePointer({ + camera: this.three.camera, + domElement: this.three.renderer.domElement, + intersectObjects: this.getIntersectObjects(), + onIntersectEnter: this.onPointerEnter, + onIntersectOver: this.onPointerOver, + onIntersectMove: this.onPointerMove, + onIntersectLeave: this.onPointerLeave, + onIntersectClick: this.onPointerClick, + }); + this.pointer.addListeners(); + }); + }, + unmounted() { + if (this.pointer) this.pointer.removeListeners(); + }, + methods: { + getIntersectObjects() { + return this.three.scene.children.filter(e => e.type === 'Mesh'); + }, + }, + render() { + return []; + }, + __hmrId: 'Raycaster', +}; diff --git a/src/core/usePointer.js b/src/core/usePointer.js index 21b251a..fd084a5 100644 --- a/src/core/usePointer.js +++ b/src/core/usePointer.js @@ -8,18 +8,17 @@ export default function usePointer(options) { intersectObjects, touch = true, resetOnEnd = false, - resetPosition = new Vector2(), - resetPositionV3 = new Vector3(), - // onEnter = () => {}, - // onLeave = () => {}, - // onMove = () => {}, - // onDown = () => {}, - // onUp = () => {}, - // onClick = () => {}, + resetPosition = new Vector2(Infinity, Infinity), + resetPositionV3 = new Vector3(Infinity, Infinity, Infinity), + onIntersectEnter = () => {}, + onIntersectOver = () => {}, + onIntersectMove = () => {}, + onIntersectLeave = () => {}, + onIntersectClick = () => {}, } = options; const position = resetPosition.clone(); - const positionN = new Vector2(); + const positionN = new Vector2(Infinity, Infinity); const raycaster = useRaycaster({ camera }); const positionV3 = raycaster.position; @@ -28,9 +27,11 @@ export default function usePointer(options) { position, positionN, positionV3, + intersectObjects, listeners: false, addListeners, removeListeners, + intersect, }; return obj; @@ -58,12 +59,7 @@ export default function usePointer(options) { raycaster.updatePosition(positionN); }; - function pointerEnter(event) { - updatePosition(event); - // onEnter(); - }; - - function pointerChange() { + function intersect() { if (intersectObjects.length) { const intersects = raycaster.intersect(positionN, intersectObjects); const offObjects = [...intersectObjects]; @@ -81,10 +77,17 @@ export default function usePointer(options) { if (!object.over) { object.over = true; - if (component.onPointerOver) component.onPointerOver({ over: true, component, intersect }); - if (component.onPointerEnter) component.onPointerEnter({ component, intersect }); + const overEvent = { type: 'pointerover', over: true, component, intersect }; + const enterEvent = { ...overEvent, type: 'pointerenter' }; + onIntersectOver(overEvent); + onIntersectEnter(enterEvent); + if (component.onPointerOver) component.onPointerOver(overEvent); + if (component.onPointerEnter) component.onPointerEnter(enterEvent); } - if (component.onPointerMove) component.onPointerMove({ component, intersect }); + + const moveEvent = { type: 'pointermove', component, intersect }; + onIntersectMove(moveEvent); + if (component.onPointerMove) component.onPointerMove(moveEvent); offObjects.splice(offObjects.indexOf(object), 1); }); @@ -93,34 +96,50 @@ export default function usePointer(options) { const { component } = object; if (object.over) { object.over = false; - if (component.onPointerOver) component.onPointerOver({ over: false, component }); - if (component.onPointerLeave) component.onPointerLeave({ component }); + const overEvent = { type: 'pointerover', over: false, component }; + const leaveEvent = { ...overEvent, type: 'pointerleave' }; + onIntersectOver(overEvent); + onIntersectLeave(leaveEvent); + if (component.onPointerOver) component.onPointerOver(overEvent); + if (component.onPointerLeave) component.onPointerLeave(leaveEvent); } }); } }; + function pointerEnter(event) { + updatePosition(event); + }; + function pointerMove(event) { updatePosition(event); - pointerChange(); - // onMove(); + intersect(); }; function pointerClick(event) { updatePosition(event); if (intersectObjects.length) { const intersects = raycaster.intersect(positionN, intersectObjects); + const iMeshes = []; intersects.forEach(intersect => { const { object } = intersect; const { component } = object; - if (component.onClick) component.onClick({ component, intersect }); + + // only once for InstancedMesh + if (object instanceof InstancedMesh) { + if (iMeshes.indexOf(object) !== -1) return; + iMeshes.push(object); + } + + const event = { type: 'click', component, intersect }; + onIntersectClick(event); + if (component.onClick) component.onClick(event); }); } }; - function pointerLeave(event) { + function pointerLeave() { if (resetOnEnd) reset(); - // onLeave(); }; function addListeners() { @@ -140,6 +159,7 @@ export default function usePointer(options) { domElement.removeEventListener('mouseenter', pointerEnter); domElement.removeEventListener('mousemove', pointerMove); domElement.removeEventListener('mouseleave', pointerLeave); + domElement.removeEventListener('click', pointerClick); domElement.removeEventListener('touchstart', pointerEnter); domElement.removeEventListener('touchmove', pointerMove); diff --git a/src/core/useThree.js b/src/core/useThree.js index f87a5b9..4deb2fb 100644 --- a/src/core/useThree.js +++ b/src/core/useThree.js @@ -1,9 +1,5 @@ -import { - WebGLRenderer, -} from 'three'; - +import { WebGLRenderer } from 'three'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; - import usePointer from './usePointer'; /** @@ -17,7 +13,7 @@ export default function useThree() { alpha: false, autoClear: true, orbit_ctrl: false, - use_pointer: false, + pointer: false, resize: false, width: 300, height: 150, @@ -35,6 +31,8 @@ export default function useThree() { let afterResizeCallbacks = []; let beforeRenderCallbacks = []; + const intersectObjects = []; + // returned object const obj = { conf, @@ -43,7 +41,6 @@ export default function useThree() { cameraCtrl: null, scene: null, pointer: null, - intersectObjects: [], size, init, dispose, @@ -103,13 +100,18 @@ export default function useThree() { }; function initPointer() { - obj.pointer = usePointer({ + let pointerConf = { camera: obj.camera, domElement: obj.renderer.domElement, - intersectObjects: obj.intersectObjects, - }); + intersectObjects, + }; - if (conf.use_pointer || obj.intersectObjects.length) { + if (conf.pointer && conf.pointer instanceof Object) { + pointerConf = { ...pointerConf, ...conf.pointer }; + } + + obj.pointer = usePointer(pointerConf); + if (conf.pointer || intersectObjects.length) { obj.pointer.addListeners(); } } @@ -154,6 +156,7 @@ export default function useThree() { */ function render() { if (obj.orbitCtrl) obj.orbitCtrl.update(); + // if (obj.pointer) obj.pointer.intersect(); beforeRenderCallbacks.forEach(c => c()); obj.renderer.render(obj.scene, obj.camera); } @@ -163,6 +166,7 @@ export default function useThree() { */ function renderC() { if (obj.orbitCtrl) obj.orbitCtrl.update(); + // if (obj.pointer) obj.pointer.intersect(); beforeRenderCallbacks.forEach(c => c()); obj.composer.render(); } @@ -171,8 +175,8 @@ export default function useThree() { * add intersect object */ function addIntersectObject(o) { - if (obj.intersectObjects.indexOf(o) === -1) { - obj.intersectObjects.push(o); + if (intersectObjects.indexOf(o) === -1) { + intersectObjects.push(o); } // add listeners if needed if (obj.pointer && !obj.pointer.listeners) { @@ -184,18 +188,18 @@ export default function useThree() { * remove intersect object */ function removeIntersectObject(o) { - const i = obj.intersectObjects.indexOf(o); + const i = intersectObjects.indexOf(o); if (i !== -1) { - obj.intersectObjects.splice(i, 1); + intersectObjects.splice(i, 1); } // remove listeners if needed - if (obj.pointer && !conf.use_pointer && obj.intersectObjects.length === 0) { + if (obj.pointer && !conf.use_pointer && intersectObjects.length === 0) { obj.pointer.removeListeners(); } } /** - * remove listeners + * remove listeners and dispose */ function dispose() { beforeRenderCallbacks = [];