1
0
mirror of https://github.com/troisjs/trois.git synced 2024-11-24 04:12:02 +08:00

improve raycasting #67

This commit is contained in:
Kevin Levron 2021-06-08 19:27:05 +02:00
parent 995cdf7c49
commit bea23d4d4d
6 changed files with 77 additions and 67 deletions

View File

@ -26,6 +26,16 @@ export interface Object3DPublicInterface extends ComponentPublicInstance, Object
// return { scene, renderer } // return { scene, renderer }
// } // }
export const pointerProps = {
onPointerEnter: Function,
onPointerOver: Function,
onPointerMove: Function,
onPointerLeave: Function,
onPointerDown: Function,
onPointerUp: Function,
onClick: Function,
}
export interface Vector2PropInterface { export interface Vector2PropInterface {
x?: number x?: number
y?: number y?: number
@ -59,6 +69,7 @@ export default defineComponent({
props: { type: Object, default: () => ({}) }, props: { type: Object, default: () => ({}) },
disableAdd: { type: Boolean, default: false }, disableAdd: { type: Boolean, default: false },
disableRemove: { type: Boolean, default: false }, disableRemove: { type: Boolean, default: false },
...pointerProps,
}, },
setup(): Object3DSetupInterface { setup(): Object3DSetupInterface {
// return object3DSetup() // return object3DSetup()
@ -74,10 +85,24 @@ export default defineComponent({
}, },
unmounted() { unmounted() {
if (!this.disableRemove) this.removeFromParent() if (!this.disableRemove) this.removeFromParent()
if (this.o3d) {
if (this.renderer) this.renderer.three.removeIntersectObject(this.o3d)
}
}, },
methods: { methods: {
initObject3D(o3d: Object3D) { initObject3D(o3d: Object3D) {
this.o3d = o3d 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, 'position', o3d)
bindProp(this, 'rotation', o3d) bindProp(this, 'rotation', o3d)

View File

@ -1,6 +1,5 @@
import { Object3D } from 'three'
import { defineComponent, inject, PropType } from 'vue' import { defineComponent, inject, PropType } from 'vue'
import usePointer, { IntersectObject, PointerInterface, PointerIntersectCallbackType } from './usePointer' import usePointer, { PointerInterface, PointerIntersectCallbackType } from './usePointer'
import { RendererInjectionKey, RendererInterface } from './Renderer' import { RendererInjectionKey, RendererInterface } from './Renderer'
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
@ -39,7 +38,7 @@ export default defineComponent({
this.pointer = usePointer({ this.pointer = usePointer({
camera: renderer.camera, camera: renderer.camera,
domElement: renderer.canvas, domElement: renderer.canvas,
intersectObjects: this.getIntersectObjects(), intersectObjects: () => renderer.scene ? renderer.scene.children : [],
intersectRecursive: this.intersectRecursive, intersectRecursive: this.intersectRecursive,
onIntersectEnter: this.onPointerEnter, onIntersectEnter: this.onPointerEnter,
onIntersectOver: this.onPointerOver, onIntersectOver: this.onPointerOver,
@ -60,15 +59,6 @@ export default defineComponent({
this.renderer?.offBeforeRender(this.pointer.intersect) 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() { render() {
return [] return []
}, },

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-empty-function */ /* 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' import useRaycaster from './useRaycaster'
export interface PointerEventInterface { export interface PointerEventInterface {
@ -18,7 +18,6 @@ export interface PointerIntersectEventInterface {
export type PointerCallbackType = (e: PointerEventInterface) => void export type PointerCallbackType = (e: PointerEventInterface) => void
export type PointerIntersectCallbackType = (e: PointerIntersectEventInterface) => void export type PointerIntersectCallbackType = (e: PointerIntersectEventInterface) => void
export type IntersectObject = Mesh | InstancedMesh
export interface PointerPublicConfigInterface { export interface PointerPublicConfigInterface {
intersectMode?: 'frame' intersectMode?: 'frame'
@ -39,14 +38,14 @@ export interface PointerPublicConfigInterface {
export interface PointerConfigInterface extends PointerPublicConfigInterface { export interface PointerConfigInterface extends PointerPublicConfigInterface {
camera: Camera camera: Camera
domElement: HTMLCanvasElement domElement: HTMLCanvasElement
intersectObjects: IntersectObject[] intersectObjects: Object3D[] | (() => Object3D[])
} }
export interface PointerInterface { export interface PointerInterface {
position: Vector2 position: Vector2
positionN: Vector2 positionN: Vector2
positionV3: Vector3 positionV3: Vector3
intersectObjects: IntersectObject[] intersectObjects: Object3D[] | (() => Object3D[])
listeners: boolean listeners: boolean
addListeners(cb: void): void addListeners(cb: void): void
removeListeners(cb: void): void removeListeners(cb: void): void
@ -117,14 +116,15 @@ export default function usePointer(options: PointerConfigInterface): PointerInte
} }
function intersect() { function intersect() {
if (intersectObjects.length) { const _intersectObjects = getIntersectObjects()
const intersects = raycaster.intersect(positionN, intersectObjects, intersectRecursive) if (_intersectObjects.length) {
const offObjects: IntersectObject[] = [...intersectObjects] const intersects = raycaster.intersect(positionN, _intersectObjects, intersectRecursive)
const offObjects: Object3D[] = [..._intersectObjects]
const iMeshes: InstancedMesh[] = [] const iMeshes: InstancedMesh[] = []
intersects.forEach(intersect => { intersects.forEach(intersect => {
const { object } = intersect const { object } = intersect
const { component } = object.userData const component = getComponent(object)
// only once for InstancedMesh // only once for InstancedMesh
if (object instanceof InstancedMesh) { if (object instanceof InstancedMesh) {
@ -138,27 +138,27 @@ export default function usePointer(options: PointerConfigInterface): PointerInte
const enterEvent: PointerIntersectEventInterface = { ...overEvent, type: 'pointerenter' } const enterEvent: PointerIntersectEventInterface = { ...overEvent, type: 'pointerenter' }
onIntersectOver(overEvent) onIntersectOver(overEvent)
onIntersectEnter(enterEvent) onIntersectEnter(enterEvent)
component.onPointerOver?.(overEvent) component?.onPointerOver?.(overEvent)
component.onPointerEnter?.(enterEvent) component?.onPointerEnter?.(enterEvent)
} }
const moveEvent: PointerIntersectEventInterface = { type: 'pointermove', component, intersect } const moveEvent: PointerIntersectEventInterface = { type: 'pointermove', component, intersect }
onIntersectMove(moveEvent) onIntersectMove(moveEvent)
component.onPointerMove?.(moveEvent) component?.onPointerMove?.(moveEvent)
offObjects.splice(offObjects.indexOf((<IntersectObject>object)), 1) offObjects.splice(offObjects.indexOf((<Object3D>object)), 1)
}) })
offObjects.forEach(object => { offObjects.forEach(object => {
const { component } = object.userData const component = getComponent(object)
if (object.userData.over) { if (object.userData.over) {
object.userData.over = false object.userData.over = false
const overEvent: PointerIntersectEventInterface = { type: 'pointerover', over: false, component } const overEvent: PointerIntersectEventInterface = { type: 'pointerover', over: false, component }
const leaveEvent: PointerIntersectEventInterface = { ...overEvent, type: 'pointerleave' } const leaveEvent: PointerIntersectEventInterface = { ...overEvent, type: 'pointerleave' }
onIntersectOver(overEvent) onIntersectOver(overEvent)
onIntersectLeave(leaveEvent) onIntersectLeave(leaveEvent)
component.onPointerOver?.(overEvent) component?.onPointerOver?.(overEvent)
component.onPointerLeave?.(leaveEvent) component?.onPointerLeave?.(leaveEvent)
} }
}) })
} }
@ -177,12 +177,13 @@ export default function usePointer(options: PointerConfigInterface): PointerInte
function pointerClick(event: TouchEvent | MouseEvent) { function pointerClick(event: TouchEvent | MouseEvent) {
updatePosition(event) updatePosition(event)
if (intersectObjects.length) { const _intersectObjects = getIntersectObjects()
const intersects = raycaster.intersect(positionN, intersectObjects, intersectRecursive) if (_intersectObjects.length) {
const intersects = raycaster.intersect(positionN, _intersectObjects, intersectRecursive)
const iMeshes: InstancedMesh[] = [] const iMeshes: InstancedMesh[] = []
intersects.forEach(intersect => { intersects.forEach(intersect => {
const { object } = intersect const { object } = intersect
const { component } = object.userData const component = getComponent(object)
// only once for InstancedMesh // only once for InstancedMesh
if (object instanceof InstancedMesh) { if (object instanceof InstancedMesh) {
@ -192,7 +193,7 @@ export default function usePointer(options: PointerConfigInterface): PointerInte
const event: PointerIntersectEventInterface = { type: 'click', component, intersect } const event: PointerIntersectEventInterface = { type: 'click', component, intersect }
onIntersectClick(event) onIntersectClick(event)
component.onClick?.(event) component?.onClick?.(event)
}) })
} }
onClick({ type: 'click', position, positionN, positionV3 }) onClick({ type: 'click', position, positionN, positionV3 })
@ -203,6 +204,26 @@ export default function usePointer(options: PointerConfigInterface): PointerInte
onLeave({ type: 'pointerleave' }) 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() { function addListeners() {
domElement.addEventListener('mouseenter', pointerEnter) domElement.addEventListener('mouseenter', pointerEnter)
domElement.addEventListener('mousemove', pointerMove) domElement.addEventListener('mousemove', pointerMove)

View File

@ -1,10 +1,9 @@
import { Camera, Intersection, Plane, Raycaster, Vector2, Vector3 } from 'three' import { Camera, Intersection, Object3D, Plane, Raycaster, Vector2, Vector3 } from 'three'
import { IntersectObject } from './usePointer'
export interface RaycasterInterface { export interface RaycasterInterface {
position: Vector3 position: Vector3
updatePosition(coords: Vector2): void updatePosition(coords: Vector2): void
intersect(coords: Vector2, objects: IntersectObject[], recursive?: boolean): Intersection[], intersect(coords: Vector2, objects: Object3D[], recursive?: boolean): Intersection[],
} }
export interface RaycasterConfigInterface { export interface RaycasterConfigInterface {
@ -28,7 +27,7 @@ export default function useRaycaster(options: RaycasterConfigInterface): Raycast
raycaster.ray.intersectPlane(plane, position) 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) raycaster.setFromCamera(coords, camera)
return raycaster.intersectObjects(objects, recursive) return raycaster.intersectObjects(objects, recursive)
} }

View File

@ -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 { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.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 { export interface SizeInterface {
width: number width: number
@ -39,8 +39,8 @@ export interface ThreeInterface {
render(): void render(): void
renderC(): void renderC(): void
setSize(width: number, height: number): void setSize(width: number, height: number): void
addIntersectObject(o: IntersectObject): void addIntersectObject(o: Object3D): void
removeIntersectObject(o: IntersectObject): void removeIntersectObject(o: Object3D): void
} }
/** /**
@ -74,7 +74,7 @@ export default function useThree(params: ThreeConfigInterface): ThreeInterface {
const beforeRenderCallbacks: {(): void}[] = [] const beforeRenderCallbacks: {(): void}[] = []
const intersectObjects: IntersectObject[] = [] const intersectObjects: Object3D[] = []
const renderer = createRenderer() const renderer = createRenderer()
@ -191,7 +191,7 @@ export default function useThree(params: ThreeConfigInterface): ThreeInterface {
/** /**
* add intersect object * add intersect object
*/ */
function addIntersectObject(o: IntersectObject) { function addIntersectObject(o: Object3D) {
if (intersectObjects.indexOf(o) === -1) { if (intersectObjects.indexOf(o) === -1) {
intersectObjects.push(o) intersectObjects.push(o)
} }
@ -204,7 +204,7 @@ export default function useThree(params: ThreeConfigInterface): ThreeInterface {
/** /**
* remove intersect object * remove intersect object
*/ */
function removeIntersectObject(o: IntersectObject) { function removeIntersectObject(o: Object3D) {
const i = intersectObjects.indexOf(o) const i = intersectObjects.indexOf(o)
if (i !== -1) { if (i !== -1) {
intersectObjects.splice(i, 1) intersectObjects.splice(i, 1)

View File

@ -3,16 +3,6 @@ import { BufferGeometry, Material, Mesh as TMesh } from 'three'
import Object3D, { Object3DSetupInterface } from '../core/Object3D' import Object3D, { Object3DSetupInterface } from '../core/Object3D'
import { bindProp } from '../tools' 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 { export interface MeshSetupInterface extends Object3DSetupInterface {
mesh?: TMesh mesh?: TMesh
geometry?: BufferGeometry geometry?: BufferGeometry
@ -35,7 +25,6 @@ const Mesh = defineComponent({
props: { props: {
castShadow: Boolean, castShadow: Boolean,
receiveShadow: Boolean, receiveShadow: Boolean,
...pointerProps,
}, },
setup(): MeshSetupInterface { setup(): MeshSetupInterface {
return {} return {}
@ -52,21 +41,10 @@ const Mesh = defineComponent({
methods: { methods: {
initMesh() { initMesh() {
const mesh = new TMesh(this.geometry, this.material) const mesh = new TMesh(this.geometry, this.material)
mesh.userData.component = this
bindProp(this, 'castShadow', mesh) bindProp(this, 'castShadow', mesh)
bindProp(this, 'receiveShadow', 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.mesh = mesh
this.initObject3D(mesh) this.initObject3D(mesh)
}, },
@ -95,9 +73,6 @@ const Mesh = defineComponent({
}, },
}, },
unmounted() { unmounted() {
if (this.mesh) {
if (this.renderer) this.renderer.three.removeIntersectObject(this.mesh)
}
// for predefined mesh (geometry/material are not unmounted) // for predefined mesh (geometry/material are not unmounted)
if (this.geometry) this.geometry.dispose() if (this.geometry) this.geometry.dispose()
if (this.material) this.material.dispose() if (this.material) this.material.dispose()