程序员@IT·互联网

玩“转”Three.js -- Rubik's Cube

2017-06-17  本文已影响827人  smart_Alex

期末临近,各种DDL接踵而来,小编的期末项目之一呢就是用WebGL做一个东西。小编就决定做一个魔方,当然,是可以操作的魔方。现在用WebGL端做3D,基本上都是使用three.js框架。在用three.js写魔方的过程中,在网上看了许多demo与代码,遇到许多坑。小编在这里想先介绍关于写魔方旋转的最佳体验。遇到的坑以后有空再写吧···

需求分析一

众所周知,一个大魔方是由27个小魔方构成的,每个小魔方就是一个BoxGeometry。

对于我们程序员本身来说,肯定是不希望每次旋转都具体去改每个小魔方的参数。我们希望做到的是每次旋转就改一个整体的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值是会出很多问题的。

所以,我们得出结论,若将所有小魔方放入一个父类,那么对这个父类做绕不同轴的旋转后将会出现旋转混乱的问题。
这个问题怎么解决呢?很简单。我们需要做五件事

  1. 每次旋转结束之后更新每个小魔方的位置,旋转信息;
  2. 解除与父类的绑定,从场景中移除父类;
  3. 下一次旋转开始前,将父类的rotation值设为[0,0,0]。
  4. 再将小魔方与父类重新绑定
  5. 将父类重新加入到场景中

做这五件事情是为了保证每次父类旋转只改变一个rotation的值,与上次旋转没有任何关系。


需求分析二

其实将上个需求解决之后,对于单层魔方的旋转问题已经解决了九成;整体魔方是将27个小魔方放入父类,那么单层魔方的旋转就是将需要旋转的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);
    }
上一篇 下一篇

猜你喜欢

热点阅读