拾取事件
在三维应用中时常需要点击场景中的物体,引擎支持射线包围盒拾取和帧缓冲区拾取。
拾取支持的事件类型:
名称 | 解释 |
---|---|
PICK_OVER | 当触控点进入碰撞体范围时触发一次 |
PICK_OUT | 当触控点离开碰撞体范围时触发一次 |
PICK_CLICK | 当触控点在碰撞体范围内按下并松开,在松开时触发一次 |
PICK_MOVE | 当触控点在碰撞体范围内移动时触发 |
PICK_UP | 当触控点在碰撞体范围内松开时触发一次 |
PICK_DOWN | 当触控点在碰撞体范围内按下时触发一次 |
监听事件
拾取事件依赖 ColliderComponent 组件,我们可以直接对 Object3D
进行 PointerEvent3D
事件监听。引擎统一封装了两种拾取类型的用法,可以通过简单配置进行切换
ts
//引擎启动前需要配置开启拾取和拾取类型
Engine3D.setting.pick.enable = true;
// Bound: 包围盒拾取, pixel: 帧缓冲区拾取
Engine3D.setting.pick.mode = `bound`; // or 'pixel'
await Engine3D.init()
// 拾取检测依赖 Collider 碰撞组件
let obj = Object3D();
obj.addComponent(ColliderComponent);
// 在节点上添加 PickEvent 事件监听,在回调函数可以获取到对应的事件
obj.addEventListener(PointerEvent3D.PICK_CLICK, onPick, this);
// 或者通过 view.pickFire 监听所有物体点击事件
view.pickFire.addEventListener(PointerEvent3D.PICK_CLICK, onPick, this);
//回调函数中获取到事件信息
function onPick(e: PointerEvent3D) {
...
}
//引擎启动前需要配置开启拾取和拾取类型
Engine3D.setting.pick.enable = true;
// Bound: 包围盒拾取, pixel: 帧缓冲区拾取
Engine3D.setting.pick.mode = `bound`; // or 'pixel'
await Engine3D.init()
// 拾取检测依赖 Collider 碰撞组件
let obj = Object3D();
obj.addComponent(ColliderComponent);
// 在节点上添加 PickEvent 事件监听,在回调函数可以获取到对应的事件
obj.addEventListener(PointerEvent3D.PICK_CLICK, onPick, this);
// 或者通过 view.pickFire 监听所有物体点击事件
view.pickFire.addEventListener(PointerEvent3D.PICK_CLICK, onPick, this);
//回调函数中获取到事件信息
function onPick(e: PointerEvent3D) {
...
}
包围盒拾取
射线包围盒是一种常用的 CPU 计算拾取方法,需要计算 ColliderComponent 组件的 shape
和鼠标射线的交集,在物体数量不多的场景中性能较好,但是精度较差,因为包围盒往往不能够精准的表达物体的真实形状。
为了保持
cpu
性能,目前包围盒拾取只支持主动pick
点击拾取,暂不支持over/hover
状态拾取。
ts
import {Object3D, Collider, BoxColliderShape, Vector3} from '@orillusion/core';
let box = new Object3D();
let mr = box.addComponent(MeshRenderer);
// 设置 box geometry
mr.geometry = new BoxGeometry(1,1,1);
// 添加碰撞盒检测
let collider = box.addComponent(ColliderComponent);
// bound 模式需要手动设置碰撞盒样式和大小
// 拾取精度取决于 box.geometry 和 collider.shape 的匹配程度
collider.shape = new BoxColliderShape().setFromCenterAndSize(new Vector3(0, 0, 0), new Vector3(1, 1, 1));
import {Object3D, Collider, BoxColliderShape, Vector3} from '@orillusion/core';
let box = new Object3D();
let mr = box.addComponent(MeshRenderer);
// 设置 box geometry
mr.geometry = new BoxGeometry(1,1,1);
// 添加碰撞盒检测
let collider = box.addComponent(ColliderComponent);
// bound 模式需要手动设置碰撞盒样式和大小
// 拾取精度取决于 box.geometry 和 collider.shape 的匹配程度
collider.shape = new BoxColliderShape().setFromCenterAndSize(new Vector3(0, 0, 0), new Vector3(1, 1, 1));
- 左面的
box
使用同形状的BoxColliderShape
进行检测,精度较好 - 右边的
sphere
也使用BoxColliderShape
,但可点击区域就会比实际模型要大,精度较差
ts
import { Engine3D, Scene3D, Vector3, Object3D, AtmosphericComponent, Camera3D, View3D, LitMaterial, MeshRenderer, BoxColliderShape, ColliderComponent, BoxGeometry, ComponentBase, Color, PointerEvent3D, SphereGeometry, DirectLight } from '@orillusion/core'
export default class TouchDemo {
scene: Scene3D
cameraObj: Object3D
camera: Camera3D
constructor() {}
async run() {
console.log('start demo')
// enable pick and use bound mode
Engine3D.setting.pick.enable = true
Engine3D.setting.pick.mode = `bound`
await Engine3D.init()
this.scene = new Scene3D()
this.scene.addComponent(AtmosphericComponent)
this.cameraObj = new Object3D()
this.camera = this.cameraObj.addComponent(Camera3D)
this.scene.addChild(this.cameraObj)
this.camera.lookAt(new Vector3(0, 0, 10), new Vector3(0, 0, 0))
this.camera.perspective(60, Engine3D.aspect, 1, 10000.0)
// add a base light
let lightObj = new Object3D()
lightObj.addComponent(DirectLight)
this.scene.addChild(lightObj)
let box = this.createBox(-2, 0, 0)
let sphere = this.createSphere(2, 0, 0)
let view = new View3D()
view.scene = this.scene
view.camera = this.camera
// start render
Engine3D.startRenderView(view)
// listen all pick_click events
view.pickFire.addEventListener(PointerEvent3D.PICK_CLICK, this.onPick, this)
}
createBox(x: number, y: number, z: number) {
let boxObj = new Object3D()
boxObj.transform.localPosition = new Vector3(x, y, z)
let size: number = 2
let shape: BoxColliderShape = new BoxColliderShape().setFromCenterAndSize(new Vector3(0, 0, 0), new Vector3(size, size, size))
// add a box collider
let collider = boxObj.addComponent(ColliderComponent)
collider.shape = shape
let mr: MeshRenderer = boxObj.addComponent(MeshRenderer)
mr.geometry = new BoxGeometry(size, size, size)
mr.material = new LitMaterial()
this.scene.addChild(boxObj)
return boxObj
}
createSphere(x: number, y: number, z: number) {
let sphereObj = new Object3D()
sphereObj.transform.localPosition = new Vector3(x, y, z)
let size: number = 2
let shape: BoxColliderShape = new BoxColliderShape().setFromCenterAndSize(new Vector3(0, 0, 0), new Vector3(size, size, size))
// add a box collider
let collider = sphereObj.addComponent(ColliderComponent)
collider.shape = shape
let mr: MeshRenderer = sphereObj.addComponent(MeshRenderer)
mr.geometry = new SphereGeometry(size / 2, 20, 20)
mr.material = new LitMaterial()
this.scene.addChild(sphereObj)
return sphereObj
}
onPick(e: PointerEvent3D) {
console.log('onClick:', e)
let mr: MeshRenderer = e.target.getComponent(MeshRenderer)
mr.material.baseColor = Color.random()
}
}
new TouchDemo().run()
import { Engine3D, Scene3D, Vector3, Object3D, AtmosphericComponent, Camera3D, View3D, LitMaterial, MeshRenderer, BoxColliderShape, ColliderComponent, BoxGeometry, ComponentBase, Color, PointerEvent3D, SphereGeometry, DirectLight } from '@orillusion/core'
export default class TouchDemo {
scene: Scene3D
cameraObj: Object3D
camera: Camera3D
constructor() {}
async run() {
console.log('start demo')
// enable pick and use bound mode
Engine3D.setting.pick.enable = true
Engine3D.setting.pick.mode = `bound`
await Engine3D.init()
this.scene = new Scene3D()
this.scene.addComponent(AtmosphericComponent)
this.cameraObj = new Object3D()
this.camera = this.cameraObj.addComponent(Camera3D)
this.scene.addChild(this.cameraObj)
this.camera.lookAt(new Vector3(0, 0, 10), new Vector3(0, 0, 0))
this.camera.perspective(60, Engine3D.aspect, 1, 10000.0)
// add a base light
let lightObj = new Object3D()
lightObj.addComponent(DirectLight)
this.scene.addChild(lightObj)
let box = this.createBox(-2, 0, 0)
let sphere = this.createSphere(2, 0, 0)
let view = new View3D()
view.scene = this.scene
view.camera = this.camera
// start render
Engine3D.startRenderView(view)
// listen all pick_click events
view.pickFire.addEventListener(PointerEvent3D.PICK_CLICK, this.onPick, this)
}
createBox(x: number, y: number, z: number) {
let boxObj = new Object3D()
boxObj.transform.localPosition = new Vector3(x, y, z)
let size: number = 2
let shape: BoxColliderShape = new BoxColliderShape().setFromCenterAndSize(new Vector3(0, 0, 0), new Vector3(size, size, size))
// add a box collider
let collider = boxObj.addComponent(ColliderComponent)
collider.shape = shape
let mr: MeshRenderer = boxObj.addComponent(MeshRenderer)
mr.geometry = new BoxGeometry(size, size, size)
mr.material = new LitMaterial()
this.scene.addChild(boxObj)
return boxObj
}
createSphere(x: number, y: number, z: number) {
let sphereObj = new Object3D()
sphereObj.transform.localPosition = new Vector3(x, y, z)
let size: number = 2
let shape: BoxColliderShape = new BoxColliderShape().setFromCenterAndSize(new Vector3(0, 0, 0), new Vector3(size, size, size))
// add a box collider
let collider = sphereObj.addComponent(ColliderComponent)
collider.shape = shape
let mr: MeshRenderer = sphereObj.addComponent(MeshRenderer)
mr.geometry = new SphereGeometry(size / 2, 20, 20)
mr.material = new LitMaterial()
this.scene.addChild(sphereObj)
return sphereObj
}
onPick(e: PointerEvent3D) {
console.log('onClick:', e)
let mr: MeshRenderer = e.target.getComponent(MeshRenderer)
mr.material.baseColor = Color.random()
}
}
new TouchDemo().run()
帧缓冲拾取
pixel
模式和 bound
模式不同,帧缓冲拾取
利用的是 GPU
的像素检测,几乎不消耗 CPU
性能,可以无视场景中交互对象的数量和形状复杂度,支持所有触控事件。当场景模型形状复杂或物体数量众多的时候,我们推荐使用 pixel
模式进行拾取检测。
ts
import { AtmosphericComponent, BoxColliderShape, Camera3D, CameraUtil, ColliderComponent, Color, View3D, DirectLight, Engine3D, LitMaterial, HoverCameraController, KelvinUtil, MeshRenderer, Object3D, PointerEvent3D, Scene3D, SphereGeometry, Vector3 } from '@orillusion/core'
export class Sample_MousePick {
lightObj: Object3D
cameraObj: Camera3D
scene: Scene3D
hover: HoverCameraController
constructor() {}
async run() {
// enable pick and use pixel mode
Engine3D.setting.pick.enable = true
Engine3D.setting.pick.mode = `pixel`
await Engine3D.init()
this.scene = new Scene3D()
this.scene.addComponent(AtmosphericComponent)
let camera = CameraUtil.createCamera3DObject(this.scene)
camera.perspective(60, Engine3D.aspect, 1, 5000.0)
this.hover = camera.object3D.addComponent(HoverCameraController)
this.hover.setCamera(-30, -15, 120)
let wukong = await Engine3D.res.loadGltf('https://cdn.orillusion.com/gltfs/wukong/wukong.gltf')
wukong.transform.y = 30
wukong.transform.scaleX = 20
wukong.transform.scaleY = 20
wukong.transform.scaleZ = 20
wukong.forChild((node) => {
if (node.hasComponent(MeshRenderer)) {
node.addComponent(ColliderComponent)
}
})
this.scene.addChild(wukong)
this.initPickObject(this.scene)
let view = new View3D()
view.scene = this.scene
view.camera = camera
// start render
Engine3D.startRenderView(view)
// listen all mouse events
view.pickFire.addEventListener(PointerEvent3D.PICK_UP, this.onUp, this)
view.pickFire.addEventListener(PointerEvent3D.PICK_DOWN, this.onDow, this)
view.pickFire.addEventListener(PointerEvent3D.PICK_CLICK, this.onPick, this)
view.pickFire.addEventListener(PointerEvent3D.PICK_OVER, this.onOver, this)
view.pickFire.addEventListener(PointerEvent3D.PICK_OUT, this.onOut, this)
view.pickFire.addEventListener(PointerEvent3D.PICK_MOVE, this.onMove, this)
}
private initPickObject(scene: Scene3D): void {
/******** light *******/
{
this.lightObj = new Object3D()
this.lightObj.rotationX = 125
this.lightObj.rotationY = 0
this.lightObj.rotationZ = 40
let lc = this.lightObj.addComponent(DirectLight)
lc.lightColor = KelvinUtil.color_temperature_to_rgb(5355)
lc.castShadow = true
lc.intensity = 20
scene.addChild(this.lightObj)
}
let size: number = 9
let shape = new BoxColliderShape()
shape.setFromCenterAndSize(new Vector3(), new Vector3(size, size, size))
let geometry = new SphereGeometry(size / 2, 20, 20)
for (let i = 0; i < 10; i++) {
let obj = new Object3D()
obj.name = 'sphere ' + i
scene.addChild(obj)
obj.x = (i - 5) * 10
let mat = new LitMaterial()
mat.emissiveMap = Engine3D.res.grayTexture
mat.emissiveIntensity = 0.0
let renderer = obj.addComponent(MeshRenderer)
renderer.geometry = geometry
renderer.material = mat
obj.addComponent(ColliderComponent)
}
}
private onUp(e: PointerEvent3D) {
console.log('onUp', e.target.name, e.data.pickInfo)
let obj = e.target as Object3D
let mr = obj.getComponent(MeshRenderer)
mr.material.baseColor = Color.random()
}
private onDow(e: PointerEvent3D) {
console.log('onDown', e.target.name, e.data.pickInfo)
let obj = e.target as Object3D
let mr = obj.getComponent(MeshRenderer)
mr.material.baseColor = Color.random()
}
private onPick(e: PointerEvent3D) {
console.log('onPick', e.target.name, e.data.pickInfo)
let obj = e.target as Object3D
let mr = obj.getComponent(MeshRenderer)
mr.material.baseColor = Color.random()
}
private onOver(e: PointerEvent3D) {
console.log('onOver', e.target.name, e.data.pickInfo)
let obj = e.target as Object3D
let mr = obj.getComponent(MeshRenderer)
mr.material.baseColor = Color.random()
}
private onOut(e: PointerEvent3D) {
console.log('onOut', e.target.name, e.data.pickInfo)
let obj = e.target as Object3D
let mr = obj.getComponent(MeshRenderer)
mr.material.baseColor = Color.random()
}
private onMove(e: PointerEvent3D) {
console.log('onMove', e.target.name, e.data.pickInfo)
}
}
new Sample_MousePick().run()
import { AtmosphericComponent, BoxColliderShape, Camera3D, CameraUtil, ColliderComponent, Color, View3D, DirectLight, Engine3D, LitMaterial, HoverCameraController, KelvinUtil, MeshRenderer, Object3D, PointerEvent3D, Scene3D, SphereGeometry, Vector3 } from '@orillusion/core'
export class Sample_MousePick {
lightObj: Object3D
cameraObj: Camera3D
scene: Scene3D
hover: HoverCameraController
constructor() {}
async run() {
// enable pick and use pixel mode
Engine3D.setting.pick.enable = true
Engine3D.setting.pick.mode = `pixel`
await Engine3D.init()
this.scene = new Scene3D()
this.scene.addComponent(AtmosphericComponent)
let camera = CameraUtil.createCamera3DObject(this.scene)
camera.perspective(60, Engine3D.aspect, 1, 5000.0)
this.hover = camera.object3D.addComponent(HoverCameraController)
this.hover.setCamera(-30, -15, 120)
let wukong = await Engine3D.res.loadGltf('https://cdn.orillusion.com/gltfs/wukong/wukong.gltf')
wukong.transform.y = 30
wukong.transform.scaleX = 20
wukong.transform.scaleY = 20
wukong.transform.scaleZ = 20
wukong.forChild((node) => {
if (node.hasComponent(MeshRenderer)) {
node.addComponent(ColliderComponent)
}
})
this.scene.addChild(wukong)
this.initPickObject(this.scene)
let view = new View3D()
view.scene = this.scene
view.camera = camera
// start render
Engine3D.startRenderView(view)
// listen all mouse events
view.pickFire.addEventListener(PointerEvent3D.PICK_UP, this.onUp, this)
view.pickFire.addEventListener(PointerEvent3D.PICK_DOWN, this.onDow, this)
view.pickFire.addEventListener(PointerEvent3D.PICK_CLICK, this.onPick, this)
view.pickFire.addEventListener(PointerEvent3D.PICK_OVER, this.onOver, this)
view.pickFire.addEventListener(PointerEvent3D.PICK_OUT, this.onOut, this)
view.pickFire.addEventListener(PointerEvent3D.PICK_MOVE, this.onMove, this)
}
private initPickObject(scene: Scene3D): void {
/******** light *******/
{
this.lightObj = new Object3D()
this.lightObj.rotationX = 125
this.lightObj.rotationY = 0
this.lightObj.rotationZ = 40
let lc = this.lightObj.addComponent(DirectLight)
lc.lightColor = KelvinUtil.color_temperature_to_rgb(5355)
lc.castShadow = true
lc.intensity = 20
scene.addChild(this.lightObj)
}
let size: number = 9
let shape = new BoxColliderShape()
shape.setFromCenterAndSize(new Vector3(), new Vector3(size, size, size))
let geometry = new SphereGeometry(size / 2, 20, 20)
for (let i = 0; i < 10; i++) {
let obj = new Object3D()
obj.name = 'sphere ' + i
scene.addChild(obj)
obj.x = (i - 5) * 10
let mat = new LitMaterial()
mat.emissiveMap = Engine3D.res.grayTexture
mat.emissiveIntensity = 0.0
let renderer = obj.addComponent(MeshRenderer)
renderer.geometry = geometry
renderer.material = mat
obj.addComponent(ColliderComponent)
}
}
private onUp(e: PointerEvent3D) {
console.log('onUp', e.target.name, e.data.pickInfo)
let obj = e.target as Object3D
let mr = obj.getComponent(MeshRenderer)
mr.material.baseColor = Color.random()
}
private onDow(e: PointerEvent3D) {
console.log('onDown', e.target.name, e.data.pickInfo)
let obj = e.target as Object3D
let mr = obj.getComponent(MeshRenderer)
mr.material.baseColor = Color.random()
}
private onPick(e: PointerEvent3D) {
console.log('onPick', e.target.name, e.data.pickInfo)
let obj = e.target as Object3D
let mr = obj.getComponent(MeshRenderer)
mr.material.baseColor = Color.random()
}
private onOver(e: PointerEvent3D) {
console.log('onOver', e.target.name, e.data.pickInfo)
let obj = e.target as Object3D
let mr = obj.getComponent(MeshRenderer)
mr.material.baseColor = Color.random()
}
private onOut(e: PointerEvent3D) {
console.log('onOut', e.target.name, e.data.pickInfo)
let obj = e.target as Object3D
let mr = obj.getComponent(MeshRenderer)
mr.material.baseColor = Color.random()
}
private onMove(e: PointerEvent3D) {
console.log('onMove', e.target.name, e.data.pickInfo)
}
}
new Sample_MousePick().run()