期末临近,各种DDL接踵而来,小编的期末项目之一呢就是用WebGL做一个东西。小编就决定做一个魔方,当然,是可以操作的魔方。现在用WebGL端做3D,基本上都是使用three.js框架。在用three.js写魔方的过程中,在网上看了许多demo与代码,遇到许多坑。小编在这里想先介绍关于写魔方旋转的最佳体验。遇到的坑以后有空再写吧···
需求分析一
- 整个魔方的旋转(包括绕X,Y,Z轴)
众所周知,一个大魔方是由27个小魔方构成的,每个小魔方就是一个BoxGeometry。
- 问题:如果对整个魔方进行旋转,我需要去改变每个小魔方的rotation和position参数吗?
对于我们程序员本身来说,肯定是不希望每次旋转都具体去改每个小魔方的参数。我们希望做到的是每次旋转就改一个整体的rotation的值。
以整个魔方的绕Y轴逆时针旋转为例子,我们希望写出来的代码是
parent.rotation[y] += angle;
那么我们试想一下,定义一个父类parent,将所有小魔方作为这个父类的子类。这样转一次是没有问题的。那么绕Y轴转了一次之后,再绕Z轴转有没有问题呢?
事实上是会出问题的。 Object在WebGL中绕Y轴逆方向转ANGLE角后,Object.rotation的值是[0,ANGLE,0]。绕Z轴逆方向转ANGLE角后,我们觉得应该是[0,ANGLE,ANGLE]。但事实上不是这样的,Object.rotation.x的值也会改变。因为在webgl中旋转是由一个4元矩阵确定的。不是由rotation的值直接确定的。所以我们单独改变Object的rotation值是会出很多问题的。
所以,我们得出结论,若将所有小魔方放入一个父类,那么对这个父类做绕不同轴的旋转后将会出现旋转混乱的问题。
这个问题怎么解决呢?很简单。我们需要做五件事
- 每次旋转结束之后更新每个小魔方的位置,旋转信息;
- 解除与父类的绑定,从场景中移除父类;
- 下一次旋转开始前,将父类的rotation值设为[0,0,0]。
- 再将小魔方与父类重新绑定
- 将父类重新加入到场景中
做这五件事情是为了保证每次父类旋转只改变一个rotation的值,与上次旋转没有任何关系。
需求分析二
- 单层魔方的旋转
其实将上个需求解决之后,对于单层魔方的旋转问题已经解决了九成;整体魔方是将27个小魔方放入父类,那么单层魔方的旋转就是将需要旋转的9个小魔方放入父类。那么,还有一成是什么?
- 问题:如何找到需要旋转的9个小魔方?
第一次旋转我可以通过索引来找到该层的九个小魔方,那多次旋转之后呢?举个例子,第一层原来索引是1,2,3,4,5,6,7,8,9,经过多次旋转之后肯定不是这些。那我是需要每次旋转都更新一次索引吗?
当然不能这么做。以世界坐标轴原点为最中心的小魔方轴心为例。最上面那层红色面朝上的9个小魔方有一个共同点,就是position[y]的坐标是一样的。我们是根据cube的position值来选择需要转动的魔方。
魔方
实现
在这里我只贴出来旋转函数的代码,所有代码可以在Github上
var centerRotate = function(element,direct,axis){
//第三步
parent.rotation.set(0,0,0);
parent.updateMatrixWorld();
//第四步
element.forEach(function (e) {
THREE.SceneUtils.attach(e,scene,parent);
})
//第五步
scene.add(parent);
var time = 0; //旋转次数
//单次旋转
function singleTurn(){
//父类在对应轴上进行旋转
parent.rotation[axis] += direct*SINGLE_ANGLE;
parent.updateMatrixWorld(); //更新状态
renderer.render(scene,camera); //渲染
time ++;
if(time === TOTAL_TIMES){
parent.updateMatrixWorld();
element.forEach(function (cube) {
//对应第一步:更新每个魔方信息
cube.updateMatrixWorld();
//第二步:解除绑定
THREE.SceneUtils.detach(cube, parent, scene);
})
scene.remove(parent);
clearInterval(timer);
}
}
var timer = setInterval(singleTurn,FRAME_SPEED);
}
网友评论