Three 之 three.js (webgl)鼠标/手指通过射线移动物体的简单整理封装

Three 之 three.js (webgl)鼠标/手指通过射线移动物体的简单整理封装

目录

Three 之 three.js (webgl)鼠标/手指通过射线移动物体的简单整理封装

一、简单介绍

二、实现原理

三、注意事项

四、效果预览

五、案例实现步骤

六、关键代码


一、简单介绍

Three js 开发的一些知识整理,方便后期遇到类似的问题,能够及时查阅使用。

本节介绍, three.js (webgl) 中,PC 端移动通过鼠标移动物体,移动端通过手指交互移动物体的整理,主要是通过对应的touchstart、touchmove、touchend ,以及 Threejs 中的 Raycaster 。其中,如果有不足之处,欢迎指出,或者你有更好的方法,欢迎留言。

二、实现原理

1、touchstart 点击屏幕,发射射线,选择物体

2、旋转对应移动物体,touchmove 中,发射射线,与地面交点的动态变化量,作为移动物体移动的移动量

3、touchend 取消物体选中,物体移动结束

三、注意事项

1、这里简单封装的是与地面交互移动,主要移动物体的 x 和 z 的值

2、其中,也添加了物体开始移动,和结束移动的事件,可以根据需要,在移动开始和结束的时候添加对应的事件处理

3、注意如果Threejs 渲染窗口不是全屏,需要注意 touch 触控点转换到对应的 container 中,作为Threejs 中的射线发射点

四、效果预览

 

五、案例实现步骤

1、为了方便学习,这里是基于 Github 代码,进行开发的,大家可以下载官网代码,很多值得学习的案例

GitHub - mrdoob/three.js: JavaScript 3D Library.

gitcode:mirrors / mrdoob / three.js · GitCode

2、在上面的基础上,添加一个 html ,用来实现案例效果,引入相关包     

3、初始化构建 3D 场景

4、其中, 场景中添加3个移动的 cube 、和一个地面 plane

 5、添加场景移动物体功能RayCasterMoveObjectsWrapper,然后把要移动和Cube组、交互移动的地面,开始结束移动事件传入,还有 Threejs 渲染的容器 container

6、并且在 animation 中 Update 更新移动

 7、一切准备好,运行场景、效果如下

六、关键代码

1、TestTouchRaycasterMoveObject.html


30TestTouchRaycasterMoveObjectthree.js - dashed lines example

2、RayCasterMoveObjectsWrapper.js

import {Raycaster,BufferGeometry,Line,LineBasicMaterial,Vector3} from 'three'/*
* 射线移动物体封装
* PC 端鼠标操作移动,移动端手指点击操作移动
* 使用说明
* 1、new 创建 RayCasterMoveObjectsWrapper 实例
* 2、disableMouseMoveObjs 使能移动功能,disableMouseMoveObjs 禁用移动功能
* 3、raycaseterUpdate 在 Updata 中实时监听移动
*/
export class RayCasterMoveObjectsWrapper {/*** 构造函数* moveObjsDataArray 要移动的物体数组* container threejs渲染的 容器* floorArray 物体移动交互的地面* onMoveStart 开始移动的事件* onMoveEnd 移动结束的事件*/constructor(moveObjsDataArray, container, floorArray = [], onMoveStart = null, onMoveEnd = null) {this.container = container;this.raycaster;// 开始把初始的鼠标位置设置到屏幕外,避免干扰this.mouse = {x: -10000,y: -10000}this.INTERSECTED;this.moveObjsDataArray = moveObjsDataArraythis.isCanMoveObject = false;this.curMouseX = null;this.curMouseY = null;this.onDocumentTouchStart = nullthis.onDocumentTouchEnd = nullthis.onDocumentTouchMove = nullthis.isEnableMoveObjs = false// 特殊处理的 Dining -tablethis.diningTable = nullthis.diningChairArray = []this.floor = null// 使用射线进行移动家具this.raycasterForMove = nullthis.floorArray = floorArray // 射线交互的地板// 移动事件this.mOnMoveStart = onMoveStartthis.mOnMoveEnd = onMoveEndthis.initRayccaster();}/*** 使能射线移动功能*/enableMouseMoveObjs() {this.isEnableMoveObjs = truethis.container.addEventListener('touchmove', this.onDocumentTouchMove, true);this.container.addEventListener('touchstart', this.onDocumentTouchStart, true);this.container.addEventListener('touchend', this.onDocumentTouchEnd, true);}/*** 禁用射线移动功能*/disableMouseMoveObjs() {this.container.removeEventListener('touchmove', this.onDocumentTouchMove, true);this.container.removeEventListener('touchstart', this.onDocumentTouchStart, true);this.container.removeEventListener('touchend', this.onDocumentTouchEnd, true);this.isEnableMoveObjs = false}/*** 射线移动的更新函数* @param {Object} curCamera 当前场景的相机*/raycaseterUpdate(curCamera) {if (this.isEnableMoveObjs === false) {return}if (this.isCanMoveObject === false) {return}this.raycasterSelectObject(curCamera)this.raycasterMoveObject(curCamera)}/*** 初始化射线*/initRayccaster() {//  创建射线this.raycaster = new Raycaster();this.raycasterForMove = new Raycaster();this.initTouchMoveFunction();}/*** 初始化移动交互事件*/initTouchMoveFunction() {this.onDocumentTouchMove = (event) => {var touch = event.touches[0];//  取消默认动作// event.preventDefault();//  数值归一化 介于 -1 与 1之间  这是一个固定公式if (this.container == null) {// 全屏幕的this.mouse.x = (parseInt(touch.pageX) / window.innerWidth) * 2 - 1;this.mouse.y = -(parseInt(touch.pageY) / window.innerHeight) * 2 + 1;} else {// 局部的this.mouse.x = ((parseInt(touch.pageX) - this.container.offsetLeft) / this.container.clientWidth) * 2 - 1this.mouse.y = -((parseInt(touch.pageY) - this.container.offsetTop) / this.container.clientHeight) * 2 + 1}if (this.isCanMoveObject && this.INTERSECTED) {// let x = parseInt(touch.pageX) - this.curMouseX// let z = parseInt(touch.pageY)- this.curMouseY// let smooth =2if (this.curMouseX === null || this.curMouseY === null) {if (this.floorRaycasterInfo) {this.curMouseX = this.floorRaycasterInfo.point.xthis.curMouseY = this.floorRaycasterInfo.point.z}// console.error("ddd xxx ")return}let x = 0let z = 0if (this.floorRaycasterInfo) {x = this.floorRaycasterInfo.point.x - this.curMouseXz = this.floorRaycasterInfo.point.z - this.curMouseY}let smooth = 1.0if (this.INTERSECTED !== null) {this.INTERSECTED.position.x += x * smooththis.INTERSECTED.position.z += z * smooth}if (this.floorRaycasterInfo) {this.curMouseX = this.floorRaycasterInfo.point.xthis.curMouseY = this.floorRaycasterInfo.point.z}}};this.onDocumentTouchStart = (event) => {//  取消默认动作event.preventDefault();var touch = event.touches[0];if (this.container == null) {// 全屏幕的this.mouse.x = (parseInt(touch.pageX) / window.innerWidth) * 2 - 1;this.mouse.y = -(parseInt(touch.pageY) / window.innerHeight) * 2 + 1;} else {// 局部的this.mouse.x = ((parseInt(touch.pageX) - this.container.offsetLeft) / this.container.clientWidth) * 2 - 1this.mouse.y = -((parseInt(touch.pageY) - this.container.offsetTop) / this.container.clientHeight) * 2 + 1}this.isCanMoveObject = true// console.log('dddd move down ')};this.onDocumentTouchEnd = (event) => {//  取消默认动作event.preventDefault();// console.log('dddd move up ')this.mouse = {x: -10000,y: -10000}this.curMouseX = null;this.curMouseY = null;this.isCanMoveObject = false//	恢复上一个对象颜色并置空变量if (this.INTERSECTED) this.INTERSECTED.material.color.setHex(this.INTERSECTED.currentHex);this.INTERSECTED = null;//	恢复上一个对象颜色并置空变量if (this.floor) this.floor.material.color.setHex(this.floor.currentHex);this.floor = null;if (this.mOnMoveEnd) {this.mOnMoveEnd()}};}/*** 射线旋转移动物体功能* @param {Object} curCamera*/raycasterSelectObject(curCamera) {this.raycaster.setFromCamera(this.mouse, curCamera);/***   intersectObjects 检测所有在射线与这些物体之间,包括或不包括后代的相交部分。返回结果时,相交部分将按距离进行排序,最近的位于第一个),相交部分和.intersectObject所返回的格式是相同的。*   objects —— 检测和射线相交的一组物体。*   recursive —— 若为true,则同时也会检测所有物体的后代。否则将只会检测对象本身的相交部分。默认值为false。*   optionalTarget —— (可选)(可选)设置结果的目标数组。如果不设置这个值,则一个新的Array会被实例化;如果设置了这个值,则在每次调用之前必须清空这个数组(例如:array.length = 0;)。*/// var intersects = raycaster.intersectObjects( scene.children );var intersects = this.raycaster.intersectObjects(this.moveObjsDataArray);if (intersects.length > 0) {if (this.INTERSECTED != intersects[0].object) {//emissive:该材质发射的属性if (this.INTERSECTED) this.INTERSECTED.material.color.setHex(this.INTERSECTED.currentHex);//	记录当前对象this.INTERSECTED = intersects[0].object;//	记录当前对象本身颜色this.INTERSECTED.currentHex = this.INTERSECTED.material.color.getHex();//	设置颜色为红色this.INTERSECTED.material.color.setHex(0xffff00);// console.log(" INTERSECTED ", this.INTERSECTED)// console.log(" intersects[ 0 ] ", intersects[ 0 ])if (this.mOnMoveStart) {this.mOnMoveStart()}}} else {//	恢复上一个对象颜色并置空变量// if ( this.INTERSECTED ) this.INTERSECTED.material.color.setHex( this.INTERSECTED.currentHex );// this.INTERSECTED = null;}}/*** 射线移动物体* @param {Object} curCamera*/raycasterMoveObject(curCamera) {this.raycaster.setFromCamera(this.mouse, curCamera);/***   intersectObjects 检测所有在射线与这些物体之间,包括或不包括后代的相交部分。返回结果时,相交部分将按距离进行排序,最近的位于第一个),相交部分和.intersectObject所返回的格式是相同的。*   objects —— 检测和射线相交的一组物体。*   recursive —— 若为true,则同时也会检测所有物体的后代。否则将只会检测对象本身的相交部分。默认值为false。*   optionalTarget —— (可选)(可选)设置结果的目标数组。如果不设置这个值,则一个新的Array会被实例化;如果设置了这个值,则在每次调用之前必须清空这个数组(例如:array.length = 0;)。*/// var intersects = raycaster.intersectObjects( scene.children );var intersects = this.raycaster.intersectObjects(this.floorArray);if (intersects.length > 0) {if (this.floor != intersects[0].object) {//emissive:该材质发射的属性if (this.floor) this.floor.material.color.setHex(this.floor.currentHex);//	记录当前对象this.floor = intersects[0].object;//	记录当前对象本身颜色this.floor.currentHex = this.floor.material.color.getHex();//	设置颜色为红色this.floor.material.color.setHex(0xff00ff);// console.log(" intersects.point ", intersects[0].point)}this.floorRaycasterInfo = intersects[0]} else {//	恢复上一个对象颜色并置空变量if (this.floor) this.floor.material.color.setHex(this.floor.currentHex);this.floor = null;this.floorRaycasterInfo = null}}/*** 射线辅助线* @constructor*/rayLinePaintHelper(scene) {const ori = this.raycaster.ray.originconst dir = this.raycaster.ray.directionconst dirT = new Vector3(ori.x + dir.x * 10000,ori.y + dir.y * 10000,ori.z + dir.z * 10000)// console.log(' mRaycaster.dirT  ' + dirT.x + ' ' + dirT.y)const material = new LineBasicMaterial({color: 0x0000ff,})const points = []points.push(ori)points.push(dirT)const geometry = new BufferGeometry().setFromPoints(points)const line = new Line(geometry, material)scene.add(line)}/*** 资源释放*/dispose() {this.container = nullthis.raycaster = nullthis.mouse = nullthis.INTERSECTED = nullthis.canMoveObjectArray = nullthis.dashLinesBoxArray = nullthis.diningTable = nullthis.diningChairArray = nullthis.isCanMoveObject = falsethis.curMouseX = 0this.curMouseY = 0}
}


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部