之前做了一个题目是基于WebGL的家居设计,为此专门去学习了Three.js,过程中遇到了很多问题坎坎坷坷最后也都解决了,其中有一个捣鼓了很久的情况是这样的:
有一面空白的墙(就是一个立方体geometry),实现用鼠标点击这面墙的一面就能为其添加选中的墙纸。效果如下:
图1 图2刚刚开始的时候并没有搞懂的一个概念就是face。导致走了很多弯路。简单来说,在Threejs中,一个普通的方形面由两个三角形组成,其中一个三角形就是一个face。如下面的1和2
图3更复杂的面由更多的三角形(face)组成,比如上面抠去了一个窗的墙。
图4要实现鼠标点击改变墙纸,在Threejs中要通过鼠标点击实现的效果,那基本少不了raycaster.intersectObjects(XXX,false)。intersectObjects函数返回射线raycaster从起点向终点发出的射线碰到的所有XXX的子类组成的数组。第二个参数默认为false,改为true还可以遍历子类的子类,关于raycaster的使用网上有很多专门说明的文章,这里就不赘述了。
raycaster.setFromCamera( mouse, camera3D );
var Choosed = raycaster.intersectObjects( XX , false );
我们需要的只是数组中的第一个对象Choosed[0],射线碰到的第一个物体,也是从我们的视角看,鼠标点到的第一个物体。
打印一下Choosed[0]能看到
图5其中的face就是指鼠标点击到的物体(下面的object)的face。face下的属性abc是这个面的法向量,下面的normal是(a,b,c)的归一化(单位化)。标注出来的materialIndex就是对应object中的material的序号。要注意的是,对于一个原始的object,它的material并不是数组形式(图7)。
图6要在material中保存多个材质图,需要进行如下的操作。
假设我们想要object同时存储 material1 和 material2
var material01 = new THREE.MeshBasicMaterial({map:texture01});
var material02 = new THREE.MeshBasicMaterial({map:texture02});
var materials = [];
materials.push(material01);
materials.push(material02);
Choosed[0].object.material = materials;//这样material就能存储多个材质
之后改变图5中 face的materialIndex即可改变其对应的material
而要改变一面整墙的材质,需要遍历faces,找出其中和 face的normal相同的face 并改变其materialIndex
PS:因为精度的问题,一面墙上的两个face可能朝着同一面但两者的normal会有偏差。例如图3的1和2法向量分别为(1,1.000,1)和(1,1.001,1),这种情况写个函数精确到某一位再来比较就行了。
在改变后需要将geometry的groupsNeedUpdate属性设置为true来告知渲染器该对象需要更新
Choosed[0].object.geometry.groupsNeedUpdate = true;
当初在这里卡了大概有一周左右,毕竟是刚刚开始接触Threejs,网上百度相关的资料来来回回就是那几篇,很多概念没搞懂就冲上去瞎搞。(。_ 。)在最后从Threejs的doc里面一条一条的看,偶然看到了face的那一条才恍然大悟。虽然大悟后弯路也没少走就是了......
参考资料: https://threejs.org/docs