diff --git a/src/components/examples/Example1.vue b/src/components/examples/Example1.vue index 1bc2b7a..250af89 100644 --- a/src/components/examples/Example1.vue +++ b/src/components/examples/Example1.vue @@ -1,17 +1,24 @@ diff --git a/src/core/Renderer.js b/src/core/Renderer.js index f07d814..ed0639d 100644 --- a/src/core/Renderer.js +++ b/src/core/Renderer.js @@ -8,6 +8,8 @@ export default { autoClear: { type: Boolean, default: true }, mouseMove: { type: [Boolean, String], default: false }, mouseRaycast: { type: Boolean, default: false }, + mouseOver: { type: Boolean, default: false }, + click: { type: Boolean, default: false }, orbitCtrl: { type: [Boolean, Object], default: false }, resize: { type: [Boolean, String], default: true }, shadow: Boolean, @@ -37,6 +39,8 @@ export default { orbit_ctrl: this.orbitCtrl, mouse_move: this.mouseMove, mouse_raycast: this.mouseRaycast, + mouse_over: this.mouseOver, + click: this.click, resize: this.resize, width: this.width, height: this.height, diff --git a/src/core/useThree.js b/src/core/useThree.js index 1da7255..eceb8cc 100644 --- a/src/core/useThree.js +++ b/src/core/useThree.js @@ -21,6 +21,8 @@ export default function useThree() { orbit_ctrl: false, mouse_move: false, mouse_raycast: false, + mouse_over: false, + click: false, resize: true, width: 0, height: 0, @@ -44,6 +46,9 @@ export default function useThree() { const mousePlane = new Plane(new Vector3(0, 0, 1), 0); const raycaster = new Raycaster(); + // raycast objects + const intersectObjects = []; + // returned object const obj = { conf, @@ -62,6 +67,7 @@ export default function useThree() { onAfterInit, onAfterResize, offAfterResize, onBeforeRender, offBeforeRender, + addIntersectObject, removeIntersectObject, }; /** @@ -103,6 +109,7 @@ export default function useThree() { window.addEventListener('resize', onResize); } + conf.mouse_move = conf.mouse_move || conf.mouse_over; if (conf.mouse_move) { if (conf.mouse_move === 'body') { obj.mouse_move_element = document.body; @@ -113,6 +120,10 @@ export default function useThree() { obj.mouse_move_element.addEventListener('mouseleave', onMouseleave); } + if (conf.click) { + obj.renderer.domElement.addEventListener('click', onClick); + } + afterInitCallbacks.forEach(c => c()); return true; @@ -171,6 +182,25 @@ export default function useThree() { obj.composer.render(); } + /** + * add intersect object + */ + function addIntersectObject(o) { + if (intersectObjects.indexOf(o) === -1) { + intersectObjects.push(o); + } + } + + /** + * remove intersect object + */ + function removeIntersectObject(o) { + const i = intersectObjects.indexOf(o); + if (i !== -1) { + intersectObjects.splice(i, 1); + } + } + /** * remove listeners */ @@ -181,37 +211,76 @@ export default function useThree() { obj.mouse_move_element.removeEventListener('mousemove', onMousemove); obj.mouse_move_element.removeEventListener('mouseleave', onMouseleave); } + obj.renderer.domElement.removeEventListener('click', onClick); if (obj.orbitCtrl) obj.orbitCtrl.dispose(); this.renderer.dispose(); } + /** + * click listener + */ + function onClick(e) { + mouse.x = (e.clientX / size.width) * 2 - 1; + mouse.y = -(e.clientY / size.height) * 2 + 1; + raycaster.setFromCamera(mouse, obj.camera); + const objects = raycaster.intersectObjects(intersectObjects); + for (let i = 0; i < objects.length; i++) { + const o = objects[i].object; + if (o.onClick) o.onClick(e); + } + } + /** * mousemove listener */ function onMousemove(e) { mouse.x = (e.clientX / size.width) * 2 - 1; mouse.y = -(e.clientY / size.height) * 2 + 1; - updateMouseV3(); + onMousechange(e); } /** * mouseleave listener */ function onMouseleave(e) { - mouse.x = 0; - mouse.y = 0; - updateMouseV3(); + // mouse.x = 0; + // mouse.y = 0; + onMousechange(e); } /** - * get 3d mouse position + * mouse change */ - function updateMouseV3() { - if (conf.mouse_raycast) { - obj.camera.getWorldDirection(mousePlane.normal); - mousePlane.normal.normalize(); + function onMousechange(e) { + if (conf.mouse_over || conf.mouse_raycast) { raycaster.setFromCamera(mouse, obj.camera); - raycaster.ray.intersectPlane(mousePlane, mouseV3); + + if (conf.mouse_raycast) { + // get mouse 3d position + obj.camera.getWorldDirection(mousePlane.normal); + mousePlane.normal.normalize(); + raycaster.ray.intersectPlane(mousePlane, mouseV3); + } + + if (conf.mouse_over) { + const onObjects = raycaster.intersectObjects(intersectObjects); + const offObjects = [...intersectObjects]; + for (let i = 0; i < onObjects.length; i++) { + const o = onObjects[i].object; + if (!o.hover && o.onHover) { + o.hover = true; + o.onHover(true); + } + offObjects.splice(offObjects.indexOf(o), 1); + } + for (let i = 0; i < offObjects.length; i++) { + const o = offObjects[i]; + if (o.hover && o.onHover) { + o.hover = false; + o.onHover(false); + } + } + } } } diff --git a/src/meshes/Mesh.js b/src/meshes/Mesh.js index a737591..18b325b 100644 --- a/src/meshes/Mesh.js +++ b/src/meshes/Mesh.js @@ -12,6 +12,8 @@ export default { scale: Object, castShadow: Boolean, receiveShadow: Boolean, + onHover: Function, + onClick: Function, }, // can't use setup because it will not be used in sub components // setup() {}, @@ -29,7 +31,10 @@ export default { }, unmounted() { // console.log('Mesh unmounted'); - if (this.mesh) this.parent.remove(this.mesh); + if (this.mesh) { + this.three.removeIntersectObject(this.mesh); + this.parent.remove(this.mesh); + } if (this.geometry) this.geometry.dispose(); if (this.material && !this.materialId) this.material.dispose(); }, @@ -39,6 +44,17 @@ export default { this.material = this.three.materials[this.materialId]; } this.mesh = new Mesh(this.geometry, this.material); + + if (this.onHover) { + this.mesh.onHover = (over) => { this.onHover({ component: this, over }); }; + this.three.addIntersectObject(this.mesh); + } + + if (this.onClick) { + this.mesh.onClick = (e) => { this.onClick({ component: this, event: e }); }; + this.three.addIntersectObject(this.mesh); + } + this.bindProps(); this.parent.add(this.mesh); this.$emit('ready');