From e66c347e6997b9ff87defb52c3b7d362ee10edea Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Mon, 22 Mar 2021 09:30:25 -0400 Subject: [PATCH 01/24] update cameras to extend base component and render slot --- src/core/Camera.js | 11 +++++++++++ src/core/OrthographicCamera.js | 3 ++- src/core/PerspectiveCamera.js | 3 ++- 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 src/core/Camera.js diff --git a/src/core/Camera.js b/src/core/Camera.js new file mode 100644 index 0000000..659af02 --- /dev/null +++ b/src/core/Camera.js @@ -0,0 +1,11 @@ +// import Object3D from '../core/Object3D.js'; + +export default { + // TODO: eventually extend Object3D, for now: error 'injection "scene" not found' + // because camera is a sibling of scene in Trois + // extends: Object3D, + inject: ['three'], + render() { + return this.$slots.default ? this.$slots.default() : []; + }, +} \ No newline at end of file diff --git a/src/core/OrthographicCamera.js b/src/core/OrthographicCamera.js index 14dc5e2..f221491 100644 --- a/src/core/OrthographicCamera.js +++ b/src/core/OrthographicCamera.js @@ -1,8 +1,10 @@ import { OrthographicCamera } from 'three'; import { watch } from 'vue'; import { bindProp } from '../tools.js'; +import Camera from './Camera.js'; export default { + extends: Camera, name: 'OrthographicCamera', inject: ['three'], props: { @@ -28,6 +30,5 @@ export default { this.three.camera = this.camera; }, - render() { return []; }, __hmrId: 'OrthographicCamera', }; diff --git a/src/core/PerspectiveCamera.js b/src/core/PerspectiveCamera.js index 7ba8e6c..0c818fa 100644 --- a/src/core/PerspectiveCamera.js +++ b/src/core/PerspectiveCamera.js @@ -1,8 +1,10 @@ import { PerspectiveCamera } from 'three'; import { watch } from 'vue'; import { bindProp } from '../tools.js'; +import Camera from './Camera.js'; export default { + extends: Camera, name: 'PerspectiveCamera', inject: ['three'], props: { @@ -29,6 +31,5 @@ export default { this.three.camera = this.camera; }, - render() { return []; }, __hmrId: 'PerspectiveCamera', }; From 6bf383d6a632604457a3cb67f7d7a23cbc4babc2 Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Mon, 22 Mar 2021 09:30:56 -0400 Subject: [PATCH 02/24] Setup for raycaster --- src/core/Raycaster.js | 21 +++++++++++++++++++++ src/core/index.js | 1 + src/plugin.js | 1 + 3 files changed, 23 insertions(+) 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..47e6952 --- /dev/null +++ b/src/core/Raycaster.js @@ -0,0 +1,21 @@ +import { watch } from 'vue'; +import { bindProp } from '../tools.js'; + +export default { + name: 'Raycaster', + inject: ['three'], + emits: ['created', 'ready'], + props: { + }, + // can't use setup because it will not be used in sub components + // setup() {}, + mounted() { + console.log('TODO: raycaster') + }, + methods: { + }, + render() { + return this.$slots.default ? this.$slots.default() : []; + }, + __hmrId: 'Raycaster', +}; diff --git a/src/core/index.js b/src/core/index.js index 4428c1e..93be142 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -5,3 +5,4 @@ export { default as Camera } from './PerspectiveCamera.js'; export { default as Group } from './Group.js'; export { default as Scene } from './Scene.js'; export { default as Object3D } from './Object3D.js'; +export { default as Raycaster } from './Raycaster.js'; \ No newline at end of file diff --git a/src/plugin.js b/src/plugin.js index 04e1bfa..f4f4e08 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -7,6 +7,7 @@ export const TroisJSVuePlugin = { 'Camera', 'OrthographicCamera', 'PerspectiveCamera', + 'Raycaster', 'Renderer', 'Scene', 'Group', From 68d7bd19ea9819dbb2f5d37710c98922cb0b1fef Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Mon, 22 Mar 2021 14:56:24 -0400 Subject: [PATCH 03/24] wip on raycaster --- src/core/Raycaster.js | 117 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 113 insertions(+), 4 deletions(-) diff --git a/src/core/Raycaster.js b/src/core/Raycaster.js index 47e6952..382f662 100644 --- a/src/core/Raycaster.js +++ b/src/core/Raycaster.js @@ -1,18 +1,127 @@ import { watch } from 'vue'; import { bindProp } from '../tools.js'; +import { Raycaster, Vector2 } from 'three' export default { name: 'Raycaster', inject: ['three'], - emits: ['created', 'ready'], + // emits: ['created', 'ready'], props: { + onBeforeRender: { + type: Function, + default: null + }, + onPointerEnter: { + type: Function, + default: null + }, + onPointerLeave: { + type: Function, + default: null + }, + onPointerOver: { + type: Function, + default: null + }, + scene: { + type: Object, + default: null + }, + // user-provided camera. defaults to first parent camera + camera: { + type: Object, + default: null + }, + intersects: { + type: Array, + default: null + } + }, + setup() { + const raycaster = new Raycaster(); + const mouse = new Vector2(); + + return { mouse, raycaster } + }, + data() { + return { + raycasterCamera: null + } }, - // can't use setup because it will not be used in sub components - // setup() {}, mounted() { - console.log('TODO: raycaster') + // prep non-reactive list of intersections + this._intersects = [] + + // save camera if we don't already have one + if (!this.camera) { + let parent = this.$parent + while (parent && !this.raycasterCamera) { + this.raycasterCamera = parent.camera + parent = parent.$parent + } + } + + // add update method + this.three.onBeforeRender(this.update) }, methods: { + update() { + // custom callback + if (this.onBeforeRender) { + this.onBeforeRender(this.raycaster); + return; + } + + // save scene + const scene = this.scene || this.three.scene; + if (!scene) { + console.log('No scene detected'); + return; + } + + // run standard camera-based raycaster... + this.raycaster.setFromCamera(this.mouse, this.raycasterCamera); + const intersects = this.raycaster.intersectObjects( + // ...against either our predefined objects or all objects in scene + this.intersects || scene.children + ); + + // capture new intersects + if (this.onPointerEnter) { + const old = this._intersects.map(intersect => { + return { + object: intersect.object, + instanceId: intersect.instanceId + } + }); + const newIntersects = intersects.filter(intersect => !old.find(val => val.object === intersect.object && val.instanceId === intersect.instanceId)); + if (newIntersects.length) { + this.onPointerEnter(newIntersects) + } + } + + // capture current intersects + if (this.onPointerOver) { + this.onPointerOver(intersects) + } + + // capture expired intersects + if (this.onPointerLeave) { + const newObjects = intersects.map(intersect => { + return { + object: intersect.object, + instanceId: intersect.instanceId + } + }); + const expiredIntersects = this._intersects.filter(intersect => !newObjects.find(val => val.object === intersect.object && val.instanceId === intersect.instanceId)); + if (expiredIntersects.length) { + this.onPointerLeave(expiredIntersects) + } + } + + // save internal intersect list + this._intersects = intersects; + } }, render() { return this.$slots.default ? this.$slots.default() : []; From 26e0ca60633f6ca562b9cef7438488b2c0a660e5 Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Mon, 22 Mar 2021 14:57:22 -0400 Subject: [PATCH 04/24] optimize notes --- src/core/Raycaster.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/Raycaster.js b/src/core/Raycaster.js index 382f662..49d62e3 100644 --- a/src/core/Raycaster.js +++ b/src/core/Raycaster.js @@ -94,6 +94,7 @@ export default { instanceId: intersect.instanceId } }); + // TODO: optimize const newIntersects = intersects.filter(intersect => !old.find(val => val.object === intersect.object && val.instanceId === intersect.instanceId)); if (newIntersects.length) { this.onPointerEnter(newIntersects) @@ -113,6 +114,7 @@ export default { instanceId: intersect.instanceId } }); + // TODO: optimize const expiredIntersects = this._intersects.filter(intersect => !newObjects.find(val => val.object === intersect.object && val.instanceId === intersect.instanceId)); if (expiredIntersects.length) { this.onPointerLeave(expiredIntersects) From 10ea7d445d78c8789a60cf65bdc77bf9c245e6ea Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Mon, 22 Mar 2021 14:58:22 -0400 Subject: [PATCH 05/24] more notes --- src/core/Raycaster.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/Raycaster.js b/src/core/Raycaster.js index 49d62e3..9f18c99 100644 --- a/src/core/Raycaster.js +++ b/src/core/Raycaster.js @@ -39,6 +39,7 @@ export default { }, setup() { const raycaster = new Raycaster(); + // TODO: change to 'pointer', add event listeners for mousemove and touch const mouse = new Vector2(); return { mouse, raycaster } From 6a6080dfd0080f724a0d662f530f7d4e0542fdce Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Mon, 22 Mar 2021 15:38:02 -0400 Subject: [PATCH 06/24] adding mouse and touch --- src/core/Raycaster.js | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/core/Raycaster.js b/src/core/Raycaster.js index 9f18c99..ae4b9a6 100644 --- a/src/core/Raycaster.js +++ b/src/core/Raycaster.js @@ -39,10 +39,9 @@ export default { }, setup() { const raycaster = new Raycaster(); - // TODO: change to 'pointer', add event listeners for mousemove and touch - const mouse = new Vector2(); + const pointer = new Vector2(); - return { mouse, raycaster } + return { pointer, raycaster } }, data() { return { @@ -62,6 +61,11 @@ export default { } } + // add event listeners + window.addEventListener('mousemove', this.onMouseMove) + window.addEventListener('touchstart', this.onTouchMove) + window.addEventListener('touchmove', this.onTouchMove) + // add update method this.three.onBeforeRender(this.update) }, @@ -81,7 +85,7 @@ export default { } // run standard camera-based raycaster... - this.raycaster.setFromCamera(this.mouse, this.raycasterCamera); + this.raycaster.setFromCamera(this.pointer, this.raycasterCamera); const intersects = this.raycaster.intersectObjects( // ...against either our predefined objects or all objects in scene this.intersects || scene.children @@ -124,10 +128,25 @@ export default { // save internal intersect list this._intersects = intersects; + }, + onMouseMove(evt) { + this.pointer.x = (evt.offsetX / this.three.size.width) * 2 - 1; + this.pointer.y = - (evt.offsetY / this.three.size.height) * 2 + 1; + }, + onTouchMove(evt) { + const touch = evt.touches[0] + const { top: canvasTop, left: canvasLeft } = touch.target.getBoundingClientRect() + this.pointer.x = ((touch.clientX - canvasLeft) / this.three.size.width) * 2 - 1 + this.pointer.y = -((touch.clientY) / this.three.size.height) * 2 + 1 } }, render() { return this.$slots.default ? this.$slots.default() : []; }, + unmounted() { + window.removeEventListener('mousemove', this.onMouseMove) + window.removeEventListener('touchstart', this.onTouchMove) + window.removeEventListener('touchmove', this.onTouchMove) + }, __hmrId: 'Raycaster', }; From cfbca7934bf5b60867dd83008c97950ab73710a4 Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Mon, 22 Mar 2021 15:40:33 -0400 Subject: [PATCH 07/24] fix custom onBeforeRender --- src/core/Raycaster.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/core/Raycaster.js b/src/core/Raycaster.js index ae4b9a6..a4c06d1 100644 --- a/src/core/Raycaster.js +++ b/src/core/Raycaster.js @@ -49,6 +49,14 @@ export default { } }, mounted() { + // add update method + this.three.onBeforeRender(this.update) + + // if we have a custom onBeforeRender method, assume + // the user is handling everything and exit setup + if (this.onBeforeRender) return; + + // prep non-reactive list of intersections this._intersects = [] @@ -65,9 +73,6 @@ export default { window.addEventListener('mousemove', this.onMouseMove) window.addEventListener('touchstart', this.onTouchMove) window.addEventListener('touchmove', this.onTouchMove) - - // add update method - this.three.onBeforeRender(this.update) }, methods: { update() { From 975e0661b337ee2e4ee709a2ccafa597ff0e5e45 Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Tue, 23 Mar 2021 09:41:32 -0400 Subject: [PATCH 08/24] update event order --- src/core/Raycaster.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/core/Raycaster.js b/src/core/Raycaster.js index a4c06d1..ea1ba59 100644 --- a/src/core/Raycaster.js +++ b/src/core/Raycaster.js @@ -96,6 +96,21 @@ export default { this.intersects || scene.children ); + // capture expired intersects + if (this.onPointerLeave) { + const newObjects = intersects.map(intersect => { + return { + object: intersect.object, + instanceId: intersect.instanceId + } + }); + // TODO: optimize + const expiredIntersects = this._intersects.filter(intersect => !newObjects.find(val => val.object === intersect.object && val.instanceId === intersect.instanceId)); + if (expiredIntersects.length) { + this.onPointerLeave(expiredIntersects) + } + } + // capture new intersects if (this.onPointerEnter) { const old = this._intersects.map(intersect => { @@ -116,21 +131,6 @@ export default { this.onPointerOver(intersects) } - // capture expired intersects - if (this.onPointerLeave) { - const newObjects = intersects.map(intersect => { - return { - object: intersect.object, - instanceId: intersect.instanceId - } - }); - // TODO: optimize - const expiredIntersects = this._intersects.filter(intersect => !newObjects.find(val => val.object === intersect.object && val.instanceId === intersect.instanceId)); - if (expiredIntersects.length) { - this.onPointerLeave(expiredIntersects) - } - } - // save internal intersect list this._intersects = intersects; }, From cc16eae50354d2fecf5cd73471128360a5c1691b Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Tue, 23 Mar 2021 13:00:41 -0400 Subject: [PATCH 09/24] remove extra imports --- src/core/Raycaster.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/Raycaster.js b/src/core/Raycaster.js index ea1ba59..6df5d86 100644 --- a/src/core/Raycaster.js +++ b/src/core/Raycaster.js @@ -1,5 +1,3 @@ -import { watch } from 'vue'; -import { bindProp } from '../tools.js'; import { Raycaster, Vector2 } from 'three' export default { From e622bedc6dd77c26706262b62d21e16f548035bc Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Tue, 23 Mar 2021 13:32:30 -0400 Subject: [PATCH 10/24] wip on per-object raycasting - starting raycaster at vec2(infinity, infinity) to prevent false positive on init --- src/core/Object3D.js | 15 ++++++++ src/core/Renderer.js | 14 ++++--- src/core/useThree.js | 88 ++++++++++++++++++++++++++------------------ 3 files changed, 76 insertions(+), 41 deletions(-) diff --git a/src/core/Object3D.js b/src/core/Object3D.js index a5506c8..ea83fd1 100644 --- a/src/core/Object3D.js +++ b/src/core/Object3D.js @@ -10,6 +10,7 @@ export default { rotation: { type: Object, default: { x: 0, y: 0, z: 0 } }, scale: { type: Object, default: { x: 1, y: 1, z: 1 } }, lookAt: { type: Object, default: null }, + onPointerEnter: { type: Function, default: null } }, // can't use setup because it will not be used in sub components // setup() {}, @@ -29,6 +30,11 @@ export default { if (this.lookAt) this.o3d.lookAt(this.lookAt.x, this.lookAt.y, this.lookAt.z); watch(() => this.lookAt, (v) => { this.o3d.lookAt(v.x, v.y, v.z); }, { deep: true }); + if (this.onPointerEnter) { + this.three.onBeforeRender(this.raycastEnter) + } + + // find first viable parent let parent = this.$parent; while (parent) { if (parent.add) { @@ -43,6 +49,15 @@ export default { }, add(o) { this.o3d.add(o); }, remove(o) { this.o3d.remove(o); }, + raycastEnter() { + this.three.raycaster.setFromCamera(this.three.mouse, this.three.camera) + const intersects = this.three.raycaster.intersectObjects([this.o3d]) + if (intersects.length) { + console.log(intersects[0].distance) + + this.onPointerEnter(intersects[0]) + } + } }, render() { return this.$slots.default ? this.$slots.default() : []; diff --git a/src/core/Renderer.js b/src/core/Renderer.js index 0a39747..df757cb 100644 --- a/src/core/Renderer.js +++ b/src/core/Renderer.js @@ -7,9 +7,10 @@ export default { antialias: Boolean, alpha: Boolean, autoClear: { type: Boolean, default: true }, - mouseMove: { type: [Boolean, String], default: false }, - mouseRaycast: { type: Boolean, default: false }, - mouseOver: { type: Boolean, default: false }, + // mouseMove: { type: [Boolean, String], default: false }, + // mouseRaycast: { type: Boolean, default: false }, + // mouseOver: { type: Boolean, default: false }, + usePointer: { type: Boolean, default: true }, click: { type: Boolean, default: false }, orbitCtrl: { type: [Boolean, Object], default: false }, resize: { type: [Boolean, String], default: false }, @@ -38,9 +39,10 @@ export default { alpha: this.alpha, autoClear: this.autoClear, orbit_ctrl: this.orbitCtrl, - mouse_move: this.mouseMove, - mouse_raycast: this.mouseRaycast, - mouse_over: this.mouseOver, + // mouse_move: this.mouseMove, + // mouse_raycast: this.mouseRaycast, + // mouse_over: this.mouseOver, + use_pointer: this.usePointer, click: this.click, resize: this.resize, width: this.width, diff --git a/src/core/useThree.js b/src/core/useThree.js index dc982e9..ced8d12 100644 --- a/src/core/useThree.js +++ b/src/core/useThree.js @@ -19,9 +19,10 @@ export default function useThree() { alpha: false, autoClear: true, orbit_ctrl: false, - mouse_move: false, - mouse_raycast: false, - mouse_over: false, + // mouse_move: false, + // mouse_raycast: false, + // mouse_over: false, + use_pointer: true, click: false, resize: true, width: 0, @@ -41,7 +42,7 @@ export default function useThree() { let beforeRenderCallbacks = []; // mouse tracking - const mouse = new Vector2(); + const mouse = new Vector2(Infinity, Infinity); const mouseV3 = new Vector3(); const mousePlane = new Plane(new Vector3(0, 0, 1), 0); const raycaster = new Raycaster(); @@ -59,6 +60,7 @@ export default function useThree() { scene: null, size, mouse, mouseV3, + raycaster, init, dispose, render, @@ -109,15 +111,17 @@ export default function useThree() { setSize(conf.width | 300, conf.height | 150); } - conf.mouse_move = conf.mouse_move || conf.mouse_over; - if (conf.mouse_move) { - if (conf.mouse_move === 'body') { + // conf.mouse_move = conf.mouse_move || conf.mouse_over; + if (conf.use_pointer) { + if (conf.use_pointer === true) { obj.mouse_move_element = document.body; } else { obj.mouse_move_element = obj.renderer.domElement; } obj.mouse_move_element.addEventListener('mousemove', onMousemove); obj.mouse_move_element.addEventListener('mouseleave', onMouseleave); + obj.mouse_move_element.addEventListener('touchstart', onTouchstart); + obj.mouse_move_element.addEventListener('touchmove', onTouchmove) } if (conf.click) { @@ -258,36 +262,50 @@ export default function useThree() { * mouse change */ function onMousechange(e) { - if (conf.mouse_over || conf.mouse_raycast) { - raycaster.setFromCamera(mouse, obj.camera); + // if (conf.mouse_over || conf.mouse_raycast) { + // raycaster.setFromCamera(mouse, obj.camera); - if (conf.mouse_raycast) { - // get mouse 3d position - obj.camera.getWorldDirection(mousePlane.normal); - mousePlane.normal.normalize(); - 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); - } - } - } - } + // 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); + // } + // } + // } + // } + } + + /** + * touch start + */ + function onTouchstart(evt) { + console.log('TODO: touchstart', evt) + } + + /** + * touch move + */ + function onTouchmove(evt) { + console.log('TODO: touchmove', evt) } /** From abbe3d433c8d8142e6e510bdfcffa7be22e1531a Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Thu, 25 Mar 2021 09:24:30 -0400 Subject: [PATCH 11/24] updated tools location --- src/components/liquid/LiquidPlane.js | 2 +- src/components/sliders/Slider1.vue | 2 +- src/components/sliders/Slider2.vue | 2 +- src/core/Object3D.js | 12 +++++++----- src/core/OrthographicCamera.js | 2 +- src/core/PerspectiveCamera.js | 2 +- src/effects/TiltShiftPass.js | 2 +- src/effects/ZoomBlurPass.js | 2 +- src/index.js | 2 +- src/lights/Light.js | 2 +- src/materials/BasicMaterial.js | 2 +- src/materials/LambertMaterial.js | 2 +- src/materials/MatcapMaterial.js | 2 +- src/materials/PhongMaterial.js | 2 +- src/materials/PhysicalMaterial.js | 2 +- src/materials/ShaderMaterial.js | 2 +- src/materials/StandardMaterial.js | 2 +- src/materials/Texture.js | 2 +- src/materials/ToonMaterial.js | 2 +- src/meshes/Gem.js | 2 +- src/meshes/InstancedMesh.js | 2 +- src/meshes/RefractionMesh.js | 2 +- src/{tools.js => tools/index.js} | 0 23 files changed, 28 insertions(+), 26 deletions(-) rename src/{tools.js => tools/index.js} (100%) diff --git a/src/components/liquid/LiquidPlane.js b/src/components/liquid/LiquidPlane.js index 37d29bf..dcb5505 100644 --- a/src/components/liquid/LiquidPlane.js +++ b/src/components/liquid/LiquidPlane.js @@ -1,7 +1,7 @@ import { DoubleSide, Mesh, MeshStandardMaterial, PlaneGeometry } from 'three'; import { watch } from 'vue'; import Object3D from '../../core/Object3D.js'; -import { bindProps } from '../../tools.js'; +import { bindProps } from '../../tools'; import LiquidEffect from './LiquidEffect.js'; export default { diff --git a/src/components/sliders/Slider1.vue b/src/components/sliders/Slider1.vue index 86836e2..fcb947e 100644 --- a/src/components/sliders/Slider1.vue +++ b/src/components/sliders/Slider1.vue @@ -14,7 +14,7 @@ import Camera from '../../core/PerspectiveCamera.js'; import Renderer from '../../core/Renderer.js'; import Scene from '../../core/Scene.js'; -import { lerp } from '../../tools.js'; +import { lerp } from '../../tools'; import AnimatedPlane from './AnimatedPlane.js'; import useTextures from '../../use/useTextures'; diff --git a/src/components/sliders/Slider2.vue b/src/components/sliders/Slider2.vue index be3d5ef..b277ba1 100644 --- a/src/components/sliders/Slider2.vue +++ b/src/components/sliders/Slider2.vue @@ -13,7 +13,7 @@ import OrthographicCamera from '../../core/OrthographicCamera.js'; import Renderer from '../../core/Renderer.js'; import Scene from '../../core/Scene.js'; -import { lerp, lerpv2 } from '../../tools.js'; +import { lerp, lerpv2 } from '../../tools'; import ZoomBlurImage from './ZoomBlurImage.js'; import useTextures from '../../use/useTextures.js'; diff --git a/src/core/Object3D.js b/src/core/Object3D.js index ea83fd1..75570ea 100644 --- a/src/core/Object3D.js +++ b/src/core/Object3D.js @@ -1,5 +1,5 @@ import { watch } from 'vue'; -import { bindProp } from '../tools.js'; +import { bindProp } from '../tools/index.js'; export default { name: 'Object3D', @@ -10,7 +10,9 @@ export default { rotation: { type: Object, default: { x: 0, y: 0, z: 0 } }, scale: { type: Object, default: { x: 1, y: 1, z: 1 } }, lookAt: { type: Object, default: null }, - onPointerEnter: { type: Function, default: null } + onPointerEnter: { type: Function, default: null }, + onPointerOver: { type: Function, default: null }, + onPointerLeave: { type: Function, default: null } }, // can't use setup because it will not be used in sub components // setup() {}, @@ -30,8 +32,8 @@ export default { if (this.lookAt) this.o3d.lookAt(this.lookAt.x, this.lookAt.y, this.lookAt.z); watch(() => this.lookAt, (v) => { this.o3d.lookAt(v.x, v.y, v.z); }, { deep: true }); - if (this.onPointerEnter) { - this.three.onBeforeRender(this.raycastEnter) + if (this.onPointerEnter || this.onPointerOver || this.onPointerLeave) { + this.three.onBeforeRender(this.pointerHandler) } // find first viable parent @@ -49,7 +51,7 @@ export default { }, add(o) { this.o3d.add(o); }, remove(o) { this.o3d.remove(o); }, - raycastEnter() { + pointerHandler() { this.three.raycaster.setFromCamera(this.three.mouse, this.three.camera) const intersects = this.three.raycaster.intersectObjects([this.o3d]) if (intersects.length) { diff --git a/src/core/OrthographicCamera.js b/src/core/OrthographicCamera.js index f221491..c5bb443 100644 --- a/src/core/OrthographicCamera.js +++ b/src/core/OrthographicCamera.js @@ -1,6 +1,6 @@ import { OrthographicCamera } from 'three'; import { watch } from 'vue'; -import { bindProp } from '../tools.js'; +import { bindProp } from '../tools'; import Camera from './Camera.js'; export default { diff --git a/src/core/PerspectiveCamera.js b/src/core/PerspectiveCamera.js index 0c818fa..0ece470 100644 --- a/src/core/PerspectiveCamera.js +++ b/src/core/PerspectiveCamera.js @@ -1,6 +1,6 @@ import { PerspectiveCamera } from 'three'; import { watch } from 'vue'; -import { bindProp } from '../tools.js'; +import { bindProp } from '../tools'; import Camera from './Camera.js'; export default { diff --git a/src/effects/TiltShiftPass.js b/src/effects/TiltShiftPass.js index 7046969..bd6e404 100644 --- a/src/effects/TiltShiftPass.js +++ b/src/effects/TiltShiftPass.js @@ -3,7 +3,7 @@ import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'; import { watch } from 'vue'; import EffectPass from './EffectPass.js'; import TiltShift from '../shaders/TiltShift.js'; -import { bindProp } from '../tools.js'; +import { bindProp } from '../tools'; export default { extends: EffectPass, diff --git a/src/effects/ZoomBlurPass.js b/src/effects/ZoomBlurPass.js index b492af5..24eb76a 100644 --- a/src/effects/ZoomBlurPass.js +++ b/src/effects/ZoomBlurPass.js @@ -1,7 +1,7 @@ import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'; import EffectPass from './EffectPass.js'; import ZoomBlur from '../shaders/ZoomBlur.js'; -import { bindProp } from '../tools.js'; +import { bindProp } from '../tools'; export default { extends: EffectPass, diff --git a/src/index.js b/src/index.js index c648fae..87dd77d 100644 --- a/src/index.js +++ b/src/index.js @@ -8,4 +8,4 @@ export * from './effects/index.js'; // export * from './components/index.js'; -export * from './tools.js'; +export * from './tools'; diff --git a/src/lights/Light.js b/src/lights/Light.js index 73dcf3c..d42dacd 100644 --- a/src/lights/Light.js +++ b/src/lights/Light.js @@ -1,6 +1,6 @@ import { watch } from 'vue'; import Object3D from '../core/Object3D.js'; -import { bindProp, setFromProp } from '../tools.js'; +import { bindProp, setFromProp } from '../tools'; export default { extends: Object3D, diff --git a/src/materials/BasicMaterial.js b/src/materials/BasicMaterial.js index 6a82eca..d0183a2 100644 --- a/src/materials/BasicMaterial.js +++ b/src/materials/BasicMaterial.js @@ -1,5 +1,5 @@ import { MeshBasicMaterial } from 'three'; -import { bindProps, propsValues } from '../tools.js'; +import { bindProps, propsValues } from '../tools'; import Material, { wireframeProps } from './Material'; export default { diff --git a/src/materials/LambertMaterial.js b/src/materials/LambertMaterial.js index 7e33e35..b29c605 100644 --- a/src/materials/LambertMaterial.js +++ b/src/materials/LambertMaterial.js @@ -1,5 +1,5 @@ import { MeshLambertMaterial } from 'three'; -import { bindProps, propsValues } from '../tools.js'; +import { bindProps, propsValues } from '../tools'; import Material, { wireframeProps } from './Material'; export default { diff --git a/src/materials/MatcapMaterial.js b/src/materials/MatcapMaterial.js index 6595aed..0f4349a 100644 --- a/src/materials/MatcapMaterial.js +++ b/src/materials/MatcapMaterial.js @@ -1,6 +1,6 @@ import { MeshMatcapMaterial, TextureLoader } from 'three'; // import { watch } from 'vue'; -import { propsValues, getMatcapUrl } from '../tools.js'; +import { propsValues, getMatcapUrl } from '../tools'; import Material from './Material'; export default { diff --git a/src/materials/PhongMaterial.js b/src/materials/PhongMaterial.js index 2a0258a..9585a96 100644 --- a/src/materials/PhongMaterial.js +++ b/src/materials/PhongMaterial.js @@ -1,6 +1,6 @@ import { MeshPhongMaterial } from 'three'; import { watch } from 'vue'; -import { bindProps, propsValues } from '../tools.js'; +import { bindProps, propsValues } from '../tools'; import Material, { wireframeProps } from './Material'; export default { diff --git a/src/materials/PhysicalMaterial.js b/src/materials/PhysicalMaterial.js index d8dca76..84edc6a 100644 --- a/src/materials/PhysicalMaterial.js +++ b/src/materials/PhysicalMaterial.js @@ -1,5 +1,5 @@ import { MeshPhysicalMaterial } from 'three'; -import { propsValues } from '../tools.js'; +import { propsValues } from '../tools'; import StandardMaterial from './StandardMaterial'; export default { diff --git a/src/materials/ShaderMaterial.js b/src/materials/ShaderMaterial.js index 1677b10..d9d14af 100644 --- a/src/materials/ShaderMaterial.js +++ b/src/materials/ShaderMaterial.js @@ -1,6 +1,6 @@ import { ShaderMaterial } from 'three'; import { watch } from 'vue'; -import { propsValues, defaultFragmentShader, defaultVertexShader } from '../tools.js'; +import { propsValues, defaultFragmentShader, defaultVertexShader } from '../tools'; export default { inject: ['three', 'mesh'], diff --git a/src/materials/StandardMaterial.js b/src/materials/StandardMaterial.js index 1c39a0d..3e18406 100644 --- a/src/materials/StandardMaterial.js +++ b/src/materials/StandardMaterial.js @@ -1,6 +1,6 @@ import { MeshStandardMaterial } from 'three'; import { watch } from 'vue'; -import { bindProp, bindProps, propsValues } from '../tools.js'; +import { bindProp, bindProps, propsValues } from '../tools'; import Material, { wireframeProps } from './Material'; const props = { diff --git a/src/materials/Texture.js b/src/materials/Texture.js index 7797089..c7c3033 100644 --- a/src/materials/Texture.js +++ b/src/materials/Texture.js @@ -1,6 +1,6 @@ import { ClampToEdgeWrapping, LinearFilter, LinearMipmapLinearFilter, TextureLoader, UVMapping } from 'three'; import { watch } from 'vue'; -import { bindProp } from '../tools.js'; +import { bindProp } from '../tools'; export default { inject: ['material'], diff --git a/src/materials/ToonMaterial.js b/src/materials/ToonMaterial.js index f790edd..b87d4a1 100644 --- a/src/materials/ToonMaterial.js +++ b/src/materials/ToonMaterial.js @@ -1,5 +1,5 @@ import { MeshToonMaterial } from 'three'; -import { bindProps, propsValues } from '../tools.js'; +import { bindProps, propsValues } from '../tools'; import Material, { wireframeProps } from './Material'; export default { diff --git a/src/meshes/Gem.js b/src/meshes/Gem.js index 71f90f1..9d33be1 100644 --- a/src/meshes/Gem.js +++ b/src/meshes/Gem.js @@ -8,7 +8,7 @@ import { WebGLCubeRenderTarget, } from 'three'; import Mesh from './Mesh.js'; -import { bindProp } from '../tools.js'; +import { bindProp } from '../tools'; export default { extends: Mesh, diff --git a/src/meshes/InstancedMesh.js b/src/meshes/InstancedMesh.js index d038dd2..a5a0bca 100644 --- a/src/meshes/InstancedMesh.js +++ b/src/meshes/InstancedMesh.js @@ -1,6 +1,6 @@ import { InstancedMesh } from 'three'; import Object3D from '../core/Object3D.js'; -import { bindProp } from '../tools.js'; +import { bindProp } from '../tools'; export default { extends: Object3D, diff --git a/src/meshes/RefractionMesh.js b/src/meshes/RefractionMesh.js index 431a5f4..ce0a443 100644 --- a/src/meshes/RefractionMesh.js +++ b/src/meshes/RefractionMesh.js @@ -6,7 +6,7 @@ import { WebGLCubeRenderTarget, } from 'three'; import Mesh from './Mesh.js'; -import { bindProp } from '../tools.js'; +import { bindProp } from '../tools'; export default { extends: Mesh, diff --git a/src/tools.js b/src/tools/index.js similarity index 100% rename from src/tools.js rename to src/tools/index.js From 136ec03c07169eae4018d1b11b13baca62d6037c Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Thu, 25 Mar 2021 09:36:02 -0400 Subject: [PATCH 12/24] snapshot --- src/core/Object3D.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/core/Object3D.js b/src/core/Object3D.js index 75570ea..3a921a3 100644 --- a/src/core/Object3D.js +++ b/src/core/Object3D.js @@ -14,6 +14,11 @@ export default { onPointerOver: { type: Function, default: null }, onPointerLeave: { type: Function, default: null } }, + data(){ + return { + pointerIsOver: null + } + }, // can't use setup because it will not be used in sub components // setup() {}, unmounted() { @@ -55,8 +60,6 @@ export default { this.three.raycaster.setFromCamera(this.three.mouse, this.three.camera) const intersects = this.three.raycaster.intersectObjects([this.o3d]) if (intersects.length) { - console.log(intersects[0].distance) - this.onPointerEnter(intersects[0]) } } From 09d5248f24e212ec84dfb0be29aadd1c0d6f623b Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Thu, 25 Mar 2021 10:06:22 -0400 Subject: [PATCH 13/24] mousemove element fixes --- src/core/Object3D.js | 48 +++++++++++++++++++++++++++++++++++++++----- src/core/useThree.js | 12 ++++++----- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/core/Object3D.js b/src/core/Object3D.js index 3a921a3..9be47ba 100644 --- a/src/core/Object3D.js +++ b/src/core/Object3D.js @@ -4,7 +4,7 @@ import { bindProp } from '../tools/index.js'; export default { name: 'Object3D', inject: ['three', 'scene', 'rendererComponent'], - emits: ['created', 'ready'], + emits: ['created', 'ready', 'pointerEnter', 'pointerOver', 'pointerLeave'], props: { position: { type: Object, default: { x: 0, y: 0, z: 0 } }, rotation: { type: Object, default: { x: 0, y: 0, z: 0 } }, @@ -12,11 +12,12 @@ export default { lookAt: { type: Object, default: null }, onPointerEnter: { type: Function, default: null }, onPointerOver: { type: Function, default: null }, - onPointerLeave: { type: Function, default: null } + onPointerLeave: { type: Function, default: null }, + usePointerEvents: { type: Boolean, default: false } }, - data(){ + data() { return { - pointerIsOver: null + pointerOver: null } }, // can't use setup because it will not be used in sub components @@ -60,7 +61,44 @@ export default { this.three.raycaster.setFromCamera(this.three.mouse, this.three.camera) const intersects = this.three.raycaster.intersectObjects([this.o3d]) if (intersects.length) { - this.onPointerEnter(intersects[0]) + // pass single intersection if we only have one, for convenience + const toPass = intersects.length === 1 ? intersects[0] : intersects; + + // pointer is newly over o3d + if (!this.pointerOver) { + this.pointerOver = true; + + if (this.onPointerEnter) { + + this.onPointerEnter({ object: this.o3d, intersects: toPass }); + + if (this.usePointerEvents) { + this.$emit('pointerEnter', toPass); + } + } + } + // pointer is still over o3d + else if (this.onPointerOver) { + this.onPointerOver({ object: this.o3d, intersects: toPass }); + + if (this.usePointerEvents) { + this.$emit('pointerOver', toPass); + } + } + } else { + // pointer is not over o3d + + // pointer has just left o3d + if (this.pointerOver) { + this.pointerOver = false; + if (this.onPointerLeave) { + this.onPointerLeave({ object: this.o3d }); + } + + if (this.usePointerEvents) { + this.$emit('pointerLeave'); + } + } } } }, diff --git a/src/core/useThree.js b/src/core/useThree.js index ced8d12..05c27a6 100644 --- a/src/core/useThree.js +++ b/src/core/useThree.js @@ -114,9 +114,11 @@ export default function useThree() { // conf.mouse_move = conf.mouse_move || conf.mouse_over; if (conf.use_pointer) { if (conf.use_pointer === true) { - obj.mouse_move_element = document.body; - } else { + // use renderer as mousemove by default obj.mouse_move_element = obj.renderer.domElement; + } else { + // use custom element as mousemove element + obj.mouse_move_element = conf.use_pointer; } obj.mouse_move_element.addEventListener('mousemove', onMousemove); obj.mouse_move_element.addEventListener('mouseleave', onMouseleave); @@ -223,9 +225,9 @@ export default function useThree() { /** */ function updateMouse(e) { - const rect = e.target.getBoundingClientRect(); - mouse.x = ((e.clientX - rect.left) / size.width) * 2 - 1; - mouse.y = -((e.clientY - rect.top) / size.height) * 2 + 1; + const rect = obj.mouse_move_element.getBoundingClientRect(); + mouse.x = ((e.x - rect.left) / size.width) * 2 - 1; + mouse.y = -((e.y - rect.top) / size.height) * 2 + 1; } /** From 0eb03737ded77814684e662712fe1d19f3db4edc Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Thu, 25 Mar 2021 10:11:36 -0400 Subject: [PATCH 14/24] fix raycaster scoping --- src/core/Raycaster.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/core/Raycaster.js b/src/core/Raycaster.js index 6df5d86..bf6b545 100644 --- a/src/core/Raycaster.js +++ b/src/core/Raycaster.js @@ -133,14 +133,15 @@ export default { this._intersects = intersects; }, onMouseMove(evt) { - this.pointer.x = (evt.offsetX / this.three.size.width) * 2 - 1; - this.pointer.y = - (evt.offsetY / this.three.size.height) * 2 + 1; + const { top: canvasTop, left: canvasLeft } = this.three.mouse_move_element.getBoundingClientRect() + this.pointer.x = ((evt.x - canvasLeft) / this.three.size.width) * 2 - 1; + this.pointer.y = - ((evt.y - canvasTop) / this.three.size.height) * 2 + 1; }, onTouchMove(evt) { const touch = evt.touches[0] - const { top: canvasTop, left: canvasLeft } = touch.target.getBoundingClientRect() - this.pointer.x = ((touch.clientX - canvasLeft) / this.three.size.width) * 2 - 1 - this.pointer.y = -((touch.clientY) / this.three.size.height) * 2 + 1 + const { top: canvasTop, left: canvasLeft } = this.three.mouse_move_element.getBoundingClientRect() + this.pointer.x = ((touch.x - canvasLeft) / this.three.size.width) * 2 - 1 + this.pointer.y = -((touch.y - canvasTop) / this.three.size.height) * 2 + 1 } }, render() { From c325d4761715dbb2dca69660310556657605266b Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Thu, 25 Mar 2021 10:12:11 -0400 Subject: [PATCH 15/24] note on touch --- src/core/Raycaster.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/core/Raycaster.js b/src/core/Raycaster.js index bf6b545..ec7437c 100644 --- a/src/core/Raycaster.js +++ b/src/core/Raycaster.js @@ -138,10 +138,11 @@ export default { this.pointer.y = - ((evt.y - canvasTop) / this.three.size.height) * 2 + 1; }, onTouchMove(evt) { - const touch = evt.touches[0] - const { top: canvasTop, left: canvasLeft } = this.three.mouse_move_element.getBoundingClientRect() - this.pointer.x = ((touch.x - canvasLeft) / this.three.size.width) * 2 - 1 - this.pointer.y = -((touch.y - canvasTop) / this.three.size.height) * 2 + 1 + console.log('TODO: handle touch') + // const touch = evt.touches[0] + // const { top: canvasTop, left: canvasLeft } = this.three.mouse_move_element.getBoundingClientRect() + // this.pointer.x = ((touch.x - canvasLeft) / this.three.size.width) * 2 - 1 + // this.pointer.y = -((touch.y - canvasTop) / this.three.size.height) * 2 + 1 } }, render() { From 2b917a52e615f516b3c045da3177d875c6403151 Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Thu, 25 Mar 2021 14:03:28 -0400 Subject: [PATCH 16/24] teardown listeners, object pool, renderer mouse leave event --- src/core/Object3D.js | 61 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/src/core/Object3D.js b/src/core/Object3D.js index 9be47ba..bcc1afb 100644 --- a/src/core/Object3D.js +++ b/src/core/Object3D.js @@ -1,3 +1,4 @@ +import { Vector2 } from 'three'; import { watch } from 'vue'; import { bindProp } from '../tools/index.js'; @@ -13,7 +14,8 @@ export default { onPointerEnter: { type: Function, default: null }, onPointerOver: { type: Function, default: null }, onPointerLeave: { type: Function, default: null }, - usePointerEvents: { type: Boolean, default: false } + usePointerEvents: { type: Boolean, default: false }, + pointerObjects: { type: [Boolean, Array], default: null } }, data() { return { @@ -24,6 +26,12 @@ export default { // setup() {}, unmounted() { if (this._parent) this._parent.remove(this.o3d); + + // teardown listeners + this.three.offBeforeRender(this.pointerHandler); + if (this.three.mouse_move_element) { + this.three.mouse_move_element.removeEventListener('mouseleave', this.renderElementLeaveHandler) + } }, methods: { initObject3D(o3d) { @@ -39,7 +47,12 @@ export default { watch(() => this.lookAt, (v) => { this.o3d.lookAt(v.x, v.y, v.z); }, { deep: true }); if (this.onPointerEnter || this.onPointerOver || this.onPointerLeave) { - this.three.onBeforeRender(this.pointerHandler) + this.three.onBeforeRender(this.pointerHandler); + } + if (this.onPointerLeave) { + // we need to wait a tick so the mouse_move_element is created + // TODO: more robust fix + this.$nextTick(() => this.three.mouse_move_element.addEventListener('mouseleave', this.renderElementLeaveHandler)); } // find first viable parent @@ -58,19 +71,40 @@ export default { add(o) { this.o3d.add(o); }, remove(o) { this.o3d.remove(o); }, pointerHandler() { - this.three.raycaster.setFromCamera(this.three.mouse, this.three.camera) - const intersects = this.three.raycaster.intersectObjects([this.o3d]) - if (intersects.length) { - // pass single intersection if we only have one, for convenience - const toPass = intersects.length === 1 ? intersects[0] : intersects; + this.three.raycaster.setFromCamera(this.three.mouse, this.three.camera); + // determine what we're raycasting against + let objectsToCastAgainst = this.pointerObjects; + if (objectsToCastAgainst) { + // cast against all objects in scene if prop is `true` + if (objectsToCastAgainst === true) { + objectsToCastAgainst = this.three.scene.children; + } + } else { + // default: just cast against this object + objectsToCastAgainst = [this.o3d]; + } + + // find all intersects + const intersects = this.three.raycaster.intersectObjects(objectsToCastAgainst); + // determine if the first intersect is this object + const match = intersects.length && + intersects[0].object.uuid === this.o3d.uuid + ? intersects[0] + : null; + + // if so, let's start the callback process + if (match) { // pointer is newly over o3d if (!this.pointerOver) { this.pointerOver = true; if (this.onPointerEnter) { - this.onPointerEnter({ object: this.o3d, intersects: toPass }); + this.onPointerEnter({ + object: this.o3d, + intersect: match + }); if (this.usePointerEvents) { this.$emit('pointerEnter', toPass); @@ -79,7 +113,10 @@ export default { } // pointer is still over o3d else if (this.onPointerOver) { - this.onPointerOver({ object: this.o3d, intersects: toPass }); + this.onPointerOver({ + object: this.o3d, + intersect: match + }); if (this.usePointerEvents) { this.$emit('pointerOver', toPass); @@ -100,6 +137,12 @@ export default { } } } + }, + renderElementLeaveHandler() { + // since the mouse is off the renderer, we'll set its values to an unreachable number + this.three.mouse.x = this.three.mouse.y = Infinity; + // then run the normal pointer handler with these updated mouse values + this.pointerHandler(); } }, render() { From 18b425ff507e3165bff691de722d2e4368959a66 Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Fri, 26 Mar 2021 09:15:14 -0400 Subject: [PATCH 17/24] remove extra code, formatting --- src/core/Raycaster.js | 37 +++++++++++---------------- src/core/useThree.js | 59 +++---------------------------------------- 2 files changed, 18 insertions(+), 78 deletions(-) diff --git a/src/core/Raycaster.js b/src/core/Raycaster.js index ec7437c..b6435ed 100644 --- a/src/core/Raycaster.js +++ b/src/core/Raycaster.js @@ -3,7 +3,6 @@ import { Raycaster, Vector2 } from 'three' export default { name: 'Raycaster', inject: ['three'], - // emits: ['created', 'ready'], props: { onBeforeRender: { type: Function, @@ -48,7 +47,7 @@ export default { }, mounted() { // add update method - this.three.onBeforeRender(this.update) + this.three.onBeforeRender(this.update); // if we have a custom onBeforeRender method, assume // the user is handling everything and exit setup @@ -56,21 +55,21 @@ export default { // prep non-reactive list of intersections - this._intersects = [] + this._intersects = []; // save camera if we don't already have one if (!this.camera) { - let parent = this.$parent + let parent = this.$parent; while (parent && !this.raycasterCamera) { - this.raycasterCamera = parent.camera - parent = parent.$parent + this.raycasterCamera = parent.camera; + parent = parent.$parent; } } // add event listeners - window.addEventListener('mousemove', this.onMouseMove) - window.addEventListener('touchstart', this.onTouchMove) - window.addEventListener('touchmove', this.onTouchMove) + window.addEventListener('mousemove', this.onMouseMove); + window.addEventListener('touchmove', this.onTouchMove); + // TODO: touch }, methods: { update() { @@ -120,38 +119,32 @@ export default { // TODO: optimize const newIntersects = intersects.filter(intersect => !old.find(val => val.object === intersect.object && val.instanceId === intersect.instanceId)); if (newIntersects.length) { - this.onPointerEnter(newIntersects) + this.onPointerEnter(newIntersects); } } // capture current intersects if (this.onPointerOver) { - this.onPointerOver(intersects) + this.onPointerOver(intersects); } // save internal intersect list this._intersects = intersects; }, onMouseMove(evt) { - const { top: canvasTop, left: canvasLeft } = this.three.mouse_move_element.getBoundingClientRect() + const { top: canvasTop, left: canvasLeft } = this.three.mouse_move_element.getBoundingClientRect(); this.pointer.x = ((evt.x - canvasLeft) / this.three.size.width) * 2 - 1; this.pointer.y = - ((evt.y - canvasTop) / this.three.size.height) * 2 + 1; }, - onTouchMove(evt) { - console.log('TODO: handle touch') - // const touch = evt.touches[0] - // const { top: canvasTop, left: canvasLeft } = this.three.mouse_move_element.getBoundingClientRect() - // this.pointer.x = ((touch.x - canvasLeft) / this.three.size.width) * 2 - 1 - // this.pointer.y = -((touch.y - canvasTop) / this.three.size.height) * 2 + 1 - } }, render() { return this.$slots.default ? this.$slots.default() : []; }, unmounted() { - window.removeEventListener('mousemove', this.onMouseMove) - window.removeEventListener('touchstart', this.onTouchMove) - window.removeEventListener('touchmove', this.onTouchMove) + window.removeEventListener('mousemove', this.onMouseMove); + window.removeEventListener('touchstart', this.onTouchMove); + + // TODO: touch }, __hmrId: 'Raycaster', }; diff --git a/src/core/useThree.js b/src/core/useThree.js index 05c27a6..b2afd2a 100644 --- a/src/core/useThree.js +++ b/src/core/useThree.js @@ -122,8 +122,7 @@ export default function useThree() { } obj.mouse_move_element.addEventListener('mousemove', onMousemove); obj.mouse_move_element.addEventListener('mouseleave', onMouseleave); - obj.mouse_move_element.addEventListener('touchstart', onTouchstart); - obj.mouse_move_element.addEventListener('touchmove', onTouchmove) + // TODO: touch } if (conf.click) { @@ -248,66 +247,14 @@ export default function useThree() { */ function onMousemove(e) { updateMouse(e); - onMousechange(e); } /** * mouseleave listener */ function onMouseleave(e) { - // mouse.x = 0; - // mouse.y = 0; - onMousechange(e); - } - - /** - * mouse change - */ - function onMousechange(e) { - // if (conf.mouse_over || conf.mouse_raycast) { - // raycaster.setFromCamera(mouse, obj.camera); - - // 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); - // } - // } - // } - // } - } - - /** - * touch start - */ - function onTouchstart(evt) { - console.log('TODO: touchstart', evt) - } - - /** - * touch move - */ - function onTouchmove(evt) { - console.log('TODO: touchmove', evt) + mouse.x = Infinity; + mouse.y = Infinity; } /** From aa0bb919493e33d6e3824d3051ff4ca804e280f3 Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Fri, 26 Mar 2021 09:39:58 -0400 Subject: [PATCH 18/24] Snapshot --- src/core/README.md | 13 +++++++++++++ src/core/useThree.js | 31 ++++++++++++++++--------------- 2 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 src/core/README.md diff --git a/src/core/README.md b/src/core/README.md new file mode 100644 index 0000000..0f92849 --- /dev/null +++ b/src/core/README.md @@ -0,0 +1,13 @@ +Changelog: + +### Raycaster component + + +### Deprecations + +* `mouseMove`, `mouseRaycast`, and `mouseOver` are all no longer used on the renderer. +* `usePointer` on the `Renderer` is `true` by default. Switch to `false` to prevent mouse event handling on the renderer element. Example: + ```html + + + ``` \ No newline at end of file diff --git a/src/core/useThree.js b/src/core/useThree.js index b2afd2a..90b9973 100644 --- a/src/core/useThree.js +++ b/src/core/useThree.js @@ -23,7 +23,7 @@ export default function useThree() { // mouse_raycast: false, // mouse_over: false, use_pointer: true, - click: false, + // click: false, resize: true, width: 0, height: 0, @@ -114,7 +114,7 @@ export default function useThree() { // conf.mouse_move = conf.mouse_move || conf.mouse_over; if (conf.use_pointer) { if (conf.use_pointer === true) { - // use renderer as mousemove by default + // use renderer element as mousemove by default obj.mouse_move_element = obj.renderer.domElement; } else { // use custom element as mousemove element @@ -125,9 +125,9 @@ export default function useThree() { // TODO: touch } - if (conf.click) { - obj.renderer.domElement.addEventListener('click', onClick); - } + // if (conf.click) { + // obj.renderer.domElement.addEventListener('click', onClick); + // } afterInitCallbacks.forEach(c => c()); @@ -216,7 +216,8 @@ export default function useThree() { obj.mouse_move_element.removeEventListener('mousemove', onMousemove); obj.mouse_move_element.removeEventListener('mouseleave', onMouseleave); } - obj.renderer.domElement.removeEventListener('click', onClick); + // obj.renderer.domElement.removeEventListener('click', onClick); + // TODO: touch if (obj.orbitCtrl) obj.orbitCtrl.dispose(); this.renderer.dispose(); } @@ -232,15 +233,15 @@ export default function useThree() { /** * click listener */ - function onClick(e) { - updateMouse(e); - 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); - } - } + // function onClick(e) { + // updateMouse(e); + // 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 From 1fbbed6380adce5a870d51bf8fa490958c473a4f Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Fri, 26 Mar 2021 09:56:18 -0400 Subject: [PATCH 19/24] wip on raycaster changelog --- src/core/README.md | 26 +++++++++++++++++++++++++- src/core/Raycaster.js | 2 ++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/core/README.md b/src/core/README.md index 0f92849..e0731f3 100644 --- a/src/core/README.md +++ b/src/core/README.md @@ -1,7 +1,31 @@ -Changelog: +## TODO +- [X] Document raycaster component +- [ ] Document new events/props on Object3D +- [ ] Click events on Raycaster +- [ ] Click events on Object3D + +## Changelog ### Raycaster component +See [here](https://troisjs-instancedcolors.netlify.app/) for an example ([source](https://github.com/SaFrMo/trois-examples/blob/instanced-colors-standalone-demo/src/components/InstancedColors/InstancedColors.vue)). + +```html + + + + +``` + ### Deprecations diff --git a/src/core/Raycaster.js b/src/core/Raycaster.js index b6435ed..1c06d45 100644 --- a/src/core/Raycaster.js +++ b/src/core/Raycaster.js @@ -64,6 +64,8 @@ export default { this.raycasterCamera = parent.camera; parent = parent.$parent; } + } else { + this.raycasterCamera = this.camera; } // add event listeners From f1b9c13c943d651a883459ebb33385fb85cc4b27 Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Fri, 26 Mar 2021 12:21:26 -0400 Subject: [PATCH 20/24] docs wip --- src/core/Object3D.js | 14 +++++++++++--- src/core/README.md | 15 ++++++++++++--- src/core/Renderer.js | 4 ++-- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/core/Object3D.js b/src/core/Object3D.js index bcc1afb..705a297 100644 --- a/src/core/Object3D.js +++ b/src/core/Object3D.js @@ -107,7 +107,10 @@ export default { }); if (this.usePointerEvents) { - this.$emit('pointerEnter', toPass); + this.$emit('pointerEnter', { + object: this.o3d, + intersect: match + }); } } } @@ -119,7 +122,10 @@ export default { }); if (this.usePointerEvents) { - this.$emit('pointerOver', toPass); + this.$emit('pointerOver', { + object: this.o3d, + intersect: match + }); } } } else { @@ -133,7 +139,9 @@ export default { } if (this.usePointerEvents) { - this.$emit('pointerLeave'); + this.$emit('pointerLeave', { + object: this.o3d + }); } } } diff --git a/src/core/README.md b/src/core/README.md index e0731f3..259d6b6 100644 --- a/src/core/README.md +++ b/src/core/README.md @@ -1,6 +1,6 @@ ## TODO - [X] Document raycaster component -- [ ] Document new events/props on Object3D +- [X] Document new events/props on Object3D - [ ] Click events on Raycaster - [ ] Click events on Object3D @@ -26,10 +26,19 @@ See [here](https://troisjs-instancedcolors.netlify.app/) for an example ([source ``` +### Object3D additions -### Deprecations +New Object3D props (all optional): -* `mouseMove`, `mouseRaycast`, and `mouseOver` are all no longer used on the renderer. +* `onPointerEnter`: Function, accepts an object with properties `{ object, intersect }`, containing the object being hovered and the actual intersection. +* `onPointerOver`: Function, accepts an object with properties `{ object, intersect }`, containing the object being hovered and the actual intersection. +* `onPointerLeave`: : Function, accepts an object with properties `{ object }`, containing the object no longer being hvoered. +* `usePointerEvents`: Boolean, default false. If set to `true`, this object will emit `pointerEnter`, `pointerOver`, and `pointerLeave` events, all with the same arguments as the props. Function props are preferred over events to avoid the extra overhead of event emitting, but this option is provided if the user prefers events. +* `pointerObjects`: Array of objects to cast against. Defaults to just the given object. Set to `true` (ie, ``) to cast against all scene children. + +### Deprecations and Other Notes + +* `mouseMove`, `mouseRaycast`, `mouseOver`, and `click` are all no longer used on the renderer. * `usePointer` on the `Renderer` is `true` by default. Switch to `false` to prevent mouse event handling on the renderer element. Example: ```html diff --git a/src/core/Renderer.js b/src/core/Renderer.js index df757cb..c9ec58d 100644 --- a/src/core/Renderer.js +++ b/src/core/Renderer.js @@ -11,7 +11,7 @@ export default { // mouseRaycast: { type: Boolean, default: false }, // mouseOver: { type: Boolean, default: false }, usePointer: { type: Boolean, default: true }, - click: { type: Boolean, default: false }, + // click: { type: Boolean, default: false }, orbitCtrl: { type: [Boolean, Object], default: false }, resize: { type: [Boolean, String], default: false }, shadow: Boolean, @@ -43,7 +43,7 @@ export default { // mouse_raycast: this.mouseRaycast, // mouse_over: this.mouseOver, use_pointer: this.usePointer, - click: this.click, + // click: this.click, resize: this.resize, width: this.width, height: this.height, From 50d158c7c641d3c8f1733753ee7ce06b20b5f2f6 Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Fri, 26 Mar 2021 12:25:29 -0400 Subject: [PATCH 21/24] more docs --- src/core/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/README.md b/src/core/README.md index 259d6b6..7f0f454 100644 --- a/src/core/README.md +++ b/src/core/README.md @@ -28,7 +28,7 @@ See [here](https://troisjs-instancedcolors.netlify.app/) for an example ([source ### Object3D additions -New Object3D props (all optional): +See [here](https://troisjs-pointer-tester-demo.netlify.app/) for an example ([source](https://github.com/SaFrMo/trois-examples/blob/pointer-tester-demo/src/components/PointerTester.vue)). New Object3D props (all optional): * `onPointerEnter`: Function, accepts an object with properties `{ object, intersect }`, containing the object being hovered and the actual intersection. * `onPointerOver`: Function, accepts an object with properties `{ object, intersect }`, containing the object being hovered and the actual intersection. From 808d32b83a9b87f6ac080937eaa8dff92680bc5b Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Fri, 26 Mar 2021 12:38:11 -0400 Subject: [PATCH 22/24] click on object3d --- src/core/Object3D.js | 28 ++++++++++++++++++++++++++-- src/core/README.md | 7 ++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/core/Object3D.js b/src/core/Object3D.js index 705a297..773108f 100644 --- a/src/core/Object3D.js +++ b/src/core/Object3D.js @@ -5,7 +5,7 @@ import { bindProp } from '../tools/index.js'; export default { name: 'Object3D', inject: ['three', 'scene', 'rendererComponent'], - emits: ['created', 'ready', 'pointerEnter', 'pointerOver', 'pointerLeave'], + emits: ['created', 'ready', 'pointerEnter', 'pointerOver', 'pointerLeave', 'click'], props: { position: { type: Object, default: { x: 0, y: 0, z: 0 } }, rotation: { type: Object, default: { x: 0, y: 0, z: 0 } }, @@ -14,6 +14,7 @@ export default { onPointerEnter: { type: Function, default: null }, onPointerOver: { type: Function, default: null }, onPointerLeave: { type: Function, default: null }, + onClick: { type: Function, default: null }, usePointerEvents: { type: Boolean, default: false }, pointerObjects: { type: [Boolean, Array], default: null } }, @@ -46,7 +47,11 @@ export default { if (this.lookAt) this.o3d.lookAt(this.lookAt.x, this.lookAt.y, this.lookAt.z); watch(() => this.lookAt, (v) => { this.o3d.lookAt(v.x, v.y, v.z); }, { deep: true }); - if (this.onPointerEnter || this.onPointerOver || this.onPointerLeave) { + if (this.usePointerEvents + || this.onPointerEnter + || this.onPointerOver + || this.onPointerLeave + || this.onClick) { this.three.onBeforeRender(this.pointerHandler); } if (this.onPointerLeave) { @@ -54,6 +59,10 @@ export default { // TODO: more robust fix this.$nextTick(() => this.three.mouse_move_element.addEventListener('mouseleave', this.renderElementLeaveHandler)); } + if (this.onClick) { + window.addEventListener('click', this.clickHandler); + // TODO: touch + } // find first viable parent let parent = this.$parent; @@ -146,6 +155,21 @@ export default { } } }, + clickHandler(evt) { + if (this.pointerOver) { + // cast a ray so we can provide hit info + this.three.raycaster.setFromCamera(this.three.mouse, this.three.camera); + const [intersect] = this.three.raycaster.intersectObjects([this.o3d]); + + // callbacks and events + if (this.onClick) { + this.onClick({ object: this.o3d, intersect }); + } + if (this.usePointerEvents) { + this.$emit('click', { object: this.o3d, intersect }); + } + } + }, renderElementLeaveHandler() { // since the mouse is off the renderer, we'll set its values to an unreachable number this.three.mouse.x = this.three.mouse.y = Infinity; diff --git a/src/core/README.md b/src/core/README.md index 7f0f454..b815e41 100644 --- a/src/core/README.md +++ b/src/core/README.md @@ -2,7 +2,7 @@ - [X] Document raycaster component - [X] Document new events/props on Object3D - [ ] Click events on Raycaster -- [ ] Click events on Object3D +- [X] Click events on Object3D ## Changelog @@ -32,8 +32,9 @@ See [here](https://troisjs-pointer-tester-demo.netlify.app/) for an example ([so * `onPointerEnter`: Function, accepts an object with properties `{ object, intersect }`, containing the object being hovered and the actual intersection. * `onPointerOver`: Function, accepts an object with properties `{ object, intersect }`, containing the object being hovered and the actual intersection. -* `onPointerLeave`: : Function, accepts an object with properties `{ object }`, containing the object no longer being hvoered. -* `usePointerEvents`: Boolean, default false. If set to `true`, this object will emit `pointerEnter`, `pointerOver`, and `pointerLeave` events, all with the same arguments as the props. Function props are preferred over events to avoid the extra overhead of event emitting, but this option is provided if the user prefers events. +* `onPointerLeave`: : Function, accepts an object with properties `{ object }`, containing the object no longer being hovered. +* `onClick`: Function, accepts an object with properties `{ object, intersect }`, containing the object being clicked and the actual intersection. +* `usePointerEvents`: Boolean, default false. If set to `true`, this object will emit `pointerEnter`, `pointerOver`, `pointerLeave`, and `click` events, all with the same arguments as the props. Function props are preferred over events to avoid the extra overhead of event emitting, but this option is provided if the user prefers events. * `pointerObjects`: Array of objects to cast against. Defaults to just the given object. Set to `true` (ie, ``) to cast against all scene children. ### Deprecations and Other Notes From c4679c6d1ad8eb5c38d2262278968d93db340057 Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Fri, 26 Mar 2021 13:37:15 -0400 Subject: [PATCH 23/24] click on raycaster --- src/core/README.md | 3 ++- src/core/Raycaster.js | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/core/README.md b/src/core/README.md index b815e41..ba82129 100644 --- a/src/core/README.md +++ b/src/core/README.md @@ -1,7 +1,7 @@ ## TODO - [X] Document raycaster component - [X] Document new events/props on Object3D -- [ ] Click events on Raycaster +- [X] Click events on Raycaster - [X] Click events on Object3D ## Changelog @@ -17,6 +17,7 @@ See [here](https://troisjs-instancedcolors.netlify.app/) for an example ([source :onPointerOver="callback that accepts an array of all new intersections, like onMouseEnter" :onPointerLeave="callback that accepts an array of all newly-ended intersections, like onMouseLeave" :onPointerOver="callback that accepts array of all current intersections" + :onClick="callback that accepts array of currently intersected objects" :onBeforeRender="callback that fires every frame - optional, accepts the created raycaster. setting this property assumes the user is implementing all raycaster functionality and nullifies all other props and built-in functionality." :scene="THREE scene - optional, defaults to current scene" diff --git a/src/core/Raycaster.js b/src/core/Raycaster.js index 1c06d45..3f5f9a1 100644 --- a/src/core/Raycaster.js +++ b/src/core/Raycaster.js @@ -20,6 +20,10 @@ export default { type: Function, default: null }, + onClick: { + type: Function, + default: null + }, scene: { type: Object, default: null @@ -71,6 +75,9 @@ export default { // add event listeners window.addEventListener('mousemove', this.onMouseMove); window.addEventListener('touchmove', this.onTouchMove); + if (this.onClick) { + window.addEventListener('click', this.clickHandler); + } // TODO: touch }, methods: { @@ -106,7 +113,7 @@ export default { // TODO: optimize const expiredIntersects = this._intersects.filter(intersect => !newObjects.find(val => val.object === intersect.object && val.instanceId === intersect.instanceId)); if (expiredIntersects.length) { - this.onPointerLeave(expiredIntersects) + this.onPointerLeave(expiredIntersects); } } @@ -138,6 +145,12 @@ export default { this.pointer.x = ((evt.x - canvasLeft) / this.three.size.width) * 2 - 1; this.pointer.y = - ((evt.y - canvasTop) / this.three.size.height) * 2 + 1; }, + clickHandler(evt) { + // if we have any intersects, fire onclick method + if (this._intersects && this._intersects.length) { + this.onClick(this._intersects); + } + } }, render() { return this.$slots.default ? this.$slots.default() : []; @@ -145,6 +158,7 @@ export default { unmounted() { window.removeEventListener('mousemove', this.onMouseMove); window.removeEventListener('touchstart', this.onTouchMove); + window.removeEventListener('click', this.clickHandler); // TODO: touch }, From 6d4c2fe849e41c8f071e4f4ec8738045ff3df254 Mon Sep 17 00:00:00 2001 From: Sander Moolin Date: Fri, 26 Mar 2021 13:37:53 -0400 Subject: [PATCH 24/24] remove extra docs --- src/core/README.md | 48 ---------------------------------------------- 1 file changed, 48 deletions(-) delete mode 100644 src/core/README.md diff --git a/src/core/README.md b/src/core/README.md deleted file mode 100644 index ba82129..0000000 --- a/src/core/README.md +++ /dev/null @@ -1,48 +0,0 @@ -## TODO -- [X] Document raycaster component -- [X] Document new events/props on Object3D -- [X] Click events on Raycaster -- [X] Click events on Object3D - -## Changelog - -### Raycaster component - -See [here](https://troisjs-instancedcolors.netlify.app/) for an example ([source](https://github.com/SaFrMo/trois-examples/blob/instanced-colors-standalone-demo/src/components/InstancedColors/InstancedColors.vue)). - -```html - - - - -``` - -### Object3D additions - -See [here](https://troisjs-pointer-tester-demo.netlify.app/) for an example ([source](https://github.com/SaFrMo/trois-examples/blob/pointer-tester-demo/src/components/PointerTester.vue)). New Object3D props (all optional): - -* `onPointerEnter`: Function, accepts an object with properties `{ object, intersect }`, containing the object being hovered and the actual intersection. -* `onPointerOver`: Function, accepts an object with properties `{ object, intersect }`, containing the object being hovered and the actual intersection. -* `onPointerLeave`: : Function, accepts an object with properties `{ object }`, containing the object no longer being hovered. -* `onClick`: Function, accepts an object with properties `{ object, intersect }`, containing the object being clicked and the actual intersection. -* `usePointerEvents`: Boolean, default false. If set to `true`, this object will emit `pointerEnter`, `pointerOver`, `pointerLeave`, and `click` events, all with the same arguments as the props. Function props are preferred over events to avoid the extra overhead of event emitting, but this option is provided if the user prefers events. -* `pointerObjects`: Array of objects to cast against. Defaults to just the given object. Set to `true` (ie, ``) to cast against all scene children. - -### Deprecations and Other Notes - -* `mouseMove`, `mouseRaycast`, `mouseOver`, and `click` are all no longer used on the renderer. -* `usePointer` on the `Renderer` is `true` by default. Switch to `false` to prevent mouse event handling on the renderer element. Example: - ```html - - - ``` \ No newline at end of file