魔方这东西很早就见过,但一直没有系统地研究过,直到2009年的某一天。当时无聊,正好机房邻座同学有个魔方,我就拿来把玩,然后上网搜魔方的还原方法,找到了一个专门介绍魔方的网站。那个网站上用一个flash来演示魔方的还原动作,照着上面一步步来,还真把魔方给还原了。正好当时我在关注新兴起的HTML5标准,对里面新加入的canvas画布元素比较感兴趣,就想着能不能用canvas把魔方展示出来,最好还能动起来,做成一个可以玩的魔方。利用课余时间,我陆续把这个网页版魔方实现了。这里把开发过程记录下来,与大家分享。
魔方的显示
魔方是一个立体的东西,而网页是个平面。要把立体的东西显示到平面上,有两种选择,用WebGL直接做三维,另一种是用二维自己做三维坐到二维平面坐标的转换。当时WebGL技术刚出来,只有Chrome浏览器的测试版本才支持,而除了IE外的浏览器对二维画布都已经支持,加上WebGL涉及的渲染什么的用的不仅仅是javascript,比较复杂。而二维画布除了IE9以下,其他浏览器都支持了,API相对比较简单,就是按坐标描线、加填充。所以最后选择了用二维画布。
从三维坐标转为二维平面坐标本质上就是投影。这里为了使魔方看起来有立体感,要选用透视投影。通过研究素描里的两点透视画法,把坐标转换的代码写出来了。
然后就是构建魔方的三维模型(其实就是几个方块的定点坐标),把他们转换为平面坐标。魔方的轮廓基本上能画出来了,立体感还是很不错的。
接下来的问题是如果记录魔方的状态,魔方有6个面,每个面又有9个色块。我用三个字母的组合来标记每个色块,然后记录每个色块的颜色。表示色块的位置的字母来自“F B L R U D M”,分别表示前、后、左、右、上、下、中。三个字母中的第一位表示色块所处的面,后两位表示色块在该面的中的位置。如“FLU”表示前面中左上角的色块,“UBM”表示上面中靠近后面一排的中间色块。
最后要对魔方的面进行填充颜色。这里涉及到哪些面是正对着我们,哪些是背对着,需要能够判断出来。魔方还不算太复杂,可以看作是个实心的正方体,所有面都只有正对和背对两种情况,不用考虑面之间的遮挡。通过将每个面的顶点的三维坐标按照正对我们时的顺时针顺序存储,如果转换为二维坐标时,这四个点仍为顺时针顺序,则为可视,需要填色,否则就不可视,忽略。这样一个立体的魔方就绘制在了画布上。

魔方的转动
不能转动的魔方是不能算是魔方,顶多算是个涂了颜色的木头块。魔方的转动主要包括整体的转动和各个层的转动。还需要考虑转动的中间状态和结束状态。只考虑结束状态的转动只需要调整相应色块的颜色即可。而中间状态需要考虑方块顶点坐标的旋转变换,具体来说就是表示块定点的坐标绕着指定的向量旋转一定的角度得到新的坐标。为了计算方便,我吧魔方的中心作为为坐标原点,x、y、z三个坐标轴作为旋转轴。
通过设置时间和旋转角度的关系,用javascript中的定时器每隔若干毫秒按旋转角度计算魔方坐标的位置,并重新天色绘制,就可以看到魔方在转了。
有一个需要注意的问题是单独一层旋转的过程中,魔方中间的一个面会露出来,所以改进了一下色块的绘制策略,根据魔方的姿态和旋转的层来调整各个面的绘制顺序,让露出的那个面在适当的位置绘制。
魔方的交互
最后一个任务就是让用户能够控制魔方的转动。我设计的交互方案是使用鼠标拖拽来控制魔方:当拖拽魔方的某个面时,该面及其所在的层向拖拽的方向转动90度,当拖拽空白区域时,魔方整体转动。对与鼠标拖拽的捕捉主要是通过onMouseDown、onMouseMove、onMouseUp三个时间的监听来完成的。鼠标点下时要判断是否点在魔方上,是的话点在了哪个色块上。鼠标放开是判断是不是拖拽了一定的距离,拖拽的方向是什么,沿这个方向旋转是转动魔方的哪一层,然后调用魔方旋转的方法显示旋转的动画。
项目演示:http://tigerlihao.cn/cubic/
项目源代码:GitHub: Rubiks Cube in HTML5 Canvas
Update 2014-05-19:
为了纪念魔方发明40周年,今天Google首页doodle放了一个可以玩的魔方,点击查看。这个魔方是用CSS3的transform 3D变换和transition动画来实现的,更为精妙。
发表回复