本文主要内容可能和babylon并无太紧密的关联, 主要是对旋转( 空间想象力 )的练习。 本来想写个魔方练练,就想着顺便练练baboly. 结果反正是最重要的交互逻辑没有实现。
标题已经说明了本文的主题是建模,也就是说,是可以用建模软件来替代这部分的代码实现。不过,从性能上说,肯定是直接代码顶点实现要好些。
关于拧转魔方
之前想到一个思路,就是每次触发转动时其实,就要知道转动的那一层,把八个块都添加到中间的chidren里,这样就可以一起转动了。缺点就是要能判断开始和结束,每次转动开始就整体化,结束就把八个块再移回去,似乎,还不如直接用改变旋转中心的办法。
开始建模
魔方的组成
我是要做一个魔方,因此,常规的立方体并不适用,因为需要每个面都有独立的颜色,也许用立方体纹理可以解决,但是合成这个天空盒就很麻烦。 直接用多个平面拼凑吧, 这样最少可以减少一半的面。
实际上 ,魔方的每一小块最多就只会显示三个面,其余的面看不到,就可以直接认为没有,那我们就可以偷工减料。 现实的魔方也是这样的,不过,这里可以比它更省
一个魔方小块,实际上就只有三种,拆过魔方的都知道。 没拆过的,现在你也会知道了。
代码接之前的初次使用babylon.js
八个顶角
先来做顶角的类。我这里直接用普通函数,因为不知道babylonjs的分组是怎么做的,我直接用Mesh这个类。
默认是 顶层的 左上角。 那么我们就需要三个面, 上面, 左面和后面。这个函数应该让外部传入每个面的颜色。 至于最后一个参数,是否是底层。是因为遇到麻烦了。
左手系的问题是在这个过程中发现的。 这里,假装我已经知道是左手系,那么想要换成右手系,就直接z取反即可。 用plane通过旋转变换以及平移变换得到一个角,其他的七个角都可以通过一定的旋转变换得到。 我之前确实是这么想的。但是,实际做起来有点问题。
/**
* 八个角作为角来说只需要三个面 这里以上面的左上角为默认
*/
let id=0 ;
function createCornerBox(colorUp:Color3 , colorLeft:Color3, colorBack:Color3,down = 0 ){ let group= new Mesh('p'+id);let mat= new StandardMaterial('sd'+id) ;let upMat = mat, leftMat = mat.clone('left'+id), backMat = mat.clone('back'+id) ;upMat.diffuseColor= colorUp ;leftMat.diffuseColor = colorLeft ;backMat.diffuseColor = colorBack;const upFace = MeshBuilder.CreatePlane('upF'+id) ;upFace.material = upMat ;upFace.rotation.x = PI*(.5 + down);// babylon的旋转方向是反着来的?upFace.position.y=.5+down ;const leftFace =MeshBuilder.CreatePlane('leftF'+id, {size:1}) ;leftFace.rotation.y = PI/2 ;leftFace.material = leftMat ;leftFace.position.x = -.5 ;const backFace =MeshBuilder.CreatePlane('backF'+ id, {size:1}) ;backFace.rotation.x = PI ;backFace.position.z = .5 ;backFace.material = backMat ;group.addChild(upFace) ;group.addChild(leftFace) ;group.addChild(backFace) ;id++return group ;
}
第一个角是左上角,只要把这个角,绕Y轴 顺时针旋转90度就可以得到右上角,后面的右下角,左下角以此类推,同时注意颜色。 我这个截图之所以这个角看上去是因为只使用了单面,只能从这个角度观察。
const corner1 = createCornerBox(upColor,leftColor,backColor) ;// 左上
corner1.position.set(-1,1,1);// z取反了
顶层, 其余三个角
const corner2 = createCornerBox(upColor,backColor,rightColor) ; // 右上
const corner3 = createCornerBox(upColor,rightColor,frontColor) ;// 右下
const corner4 = createCornerBox(upColor,frontColor,leftColor) ;// 左下
// translate是方向和距离
corner2.rotation.y= PI/2 ;
corner2.position.set( 1,1,1) ;
corner3.position.set(1,1,-1) ;
corner4.position.set(-1,1,-1) ;
corner3.rotation.y= PI ;
corner4.rotation.y= -PI/2 ;
底层的角,按之前的想法就有点麻烦了,比如说,底层的左下角要经过什么样的旋转才能得到,要先绕z旋转180,再绕y顺时针旋转90 , 但是颜色还得换,我觉得麻烦。
于是又加了区分上层和底层, 直接在函数里把底层的底面做好和顶层的上面区分。 这样,底层的代码就很容易写了, 只要全体y值取负,把上面的颜色换成下面的颜色即可。
upFace.material = upMat ;
upFace.rotation.x = PI*(.5 + down);// babylon的旋转方向是反着来的?
upFace.position.y=.5+down ;
底层八个角。
// 底面
const corner5 = createCornerBox(downColor,leftColor,backColor, -1) ;// 左上
const corner6 = createCornerBox(downColor,backColor,rightColor, -1) ; // 右上
const corner7 = createCornerBox(downColor,rightColor,frontColor, -1) ;// 右下
const corner8 = createCornerBox(downColor,frontColor,leftColor, -1) ;// 左下
corner5.position.set(-1,-1,1); // translate是方向和距离
corner6.rotation.y= PI/2 ;
corner6.position.set( 1,-1,1) ;
corner7.position.set(1,-1,-1) ;
corner8.position.set(-1,-1,-1) ;
corner7.rotation.y= PI ;
corner8.rotation.y= -PI/2 ;
这样八个顶角全部完工。
小问题
中途还遇到了一个小问题,但是查了半天。 结果发现是没有初始化engine 就开始建模引起的错误。这里就要再提一提,babylon设计的神奇之处, 实例化的mesh不需要手动添加到scene 中就可以正常渲染,但是scene 这些东西大概也因此必须提前初始化,他们之间有强关联。
轴承
另外,它这个坐标轴辅助的渲染顺序是靠后的,我又不知道如何调整。 所以不用它了,顺便加个轴承。
/**
* 轴承 不要那个坐标指示器了
*/
const mat= new StandardMaterial('axisUp') ;
mat.diffuseColor = upColor ;
const upAxis = MeshBuilder.CreateCylinder('up', {height:.5,diameterBottom:.3,diameterTop:.3}) ;
upAxis.position.set(0, .5, 0) ;upAxis.material = mat ;
const downAxis = upAxis.clone('downAxis') ;
downAxis.position.set(0,-.5, 0) ; downAxis.material = mat.clone('down');
(downAxis.material asStandardMaterial).diffuseColor = downColor ;
const leftAxis = upAxis.clone('leftAxis') ;
leftAxis.position.set(-.5,0, 0) ; leftAxis.material = mat.clone('left');
leftAxis.rotation.z = PI/2 ;
(leftAxis.material asStandardMaterial).diffuseColor = leftColor ;
const rightAxis = upAxis.clone('rightAxis') ;
rightAxis.position.set( .5,0, 0) ; rightAxis.material = mat.clone('right');
rightAxis.rotation.z = -PI/2 ;
(rightAxis.material asStandardMaterial).diffuseColor = rightColor ;
const frontAxis = upAxis.clone('frontAxis') ;
frontAxis.position.set( 0, 0,-.5) ; frontAxis.material = mat.clone('front');
frontAxis.rotation.x = PI/2 ;
(frontAxis.material asStandardMaterial).diffuseColor = frontColor ;
const backAxis = upAxis.clone('frontAxis') ;
backAxis.position.set( 0, 0,.5) ; backAxis.material = mat.clone('back');
backAxis.rotation.x = -PI/2 ;
(backAxis.material asStandardMaterial).diffuseColor = backColor ;
棱边
一个棱边由两个面组成,总共12个。 就不像上面那么啰嗦了,经历了上面的一系列旋转操作,想必你对于欧拉旋转已经了然于胸。
还是先来一个函数工厂, 默认是上层的前方。其余的都通过旋转得到
function createArrisBox(colorFront:Color3, colorUp:Color3){ let group= new Mesh('e'+id);let mat= new StandardMaterial('sde'+id) ;letfrontMat = mat.clone('fronte'+id) ;mat.diffuseColor= colorUp ;frontMat.diffuseColor = colorFront;const upFace = MeshBuilder.CreatePlane('upFe'+id) ;const frontFace = upFace.clone('frontFe'+id) ; upFace.rotation.x = PI/2 ;upFace.position.y = .5 ;upFace.material = mat ;frontFace.position.z = -.5 ;frontFace.material = frontMat ;group.addChild(frontFace);group.addChild(upFace);id++group.scaling.set(.9,.9,.9) // 留间隙return group ;
}
然后,还是分上中下三层。
// 顶层
let arris1 = createArrisBox(frontColor, upColor) ; // 前
arris1.position.set(0,1,-1);
let arris2 = createArrisBox(backColor, upColor) ; // 后
arris2.position.set(0,1,1);
arris2.rotation.y =PI ;
let arris3 = createArrisBox(leftColor, upColor) ; // 左
arris3.position.set(-1,1, 0);
arris3.rotation.y =PI/2 ;
let arris4 = createArrisBox(rightColor, upColor) ; // 右
arris4.position.set(1,1,0);
arris4.rotation.y = -PI/2 ;
// 中层 到了发挥想象力的时候,这次我决定全部用 旋转 不额外建模了
let arris5 =createArrisBox( frontColor,leftColor) ; //正面的左棱
arris5.rotation.z = PI/2 ; // 这里是因为这个 前面的法向实际上是-z。所以这里旋转是顺时针90
arris5.position.set(-1,0,-1) ;
let arris6 =createArrisBox( frontColor,rightColor) ; //正面的右棱
arris6.rotation.z = -PI/2 ; // 左右对称
arris6.position.set(1,0,-1) ;
let arris7 =createArrisBox( rightColor,backColor) ; //背面的右棱 理论上只要在正面的基础上 x轴旋转90 逆时针但是欧拉顺序不是这样的 ,当然可以改变其Order
// arris7.rotation.reorderInPlace('YZX') ; //这个方法不是设置Order的
arris7.rotation.set( 0,-PI/2, -PI/2) ;
arris7.position.set(1,0,1) ;
let arris8 =createArrisBox( leftColor,backColor) ; //背面的左棱
// 这次不改其Order 那么就是先顺时针 y再逆时针 -z左手系 我又把坐标改成右手系的,导致z轴方向一直反着,反正看结果就知道了
arris8.rotation.set(0,PI/2, PI/2 ) ;
arris8.position.set(-1,0,1) ;
// 底层理论上 只要把初始位置绕z轴旋转 180然后绕y轴 旋转一定度数,但是y轴在前,注意
let arris9 =createArrisBox( frontColor, downColor) ; //前
arris9.rotation.set(0,0, PI ) ;
arris9.position.set(0,-1,-1) ;
let arris10 =createArrisBox( backColor, downColor) ; //后
arris10.rotation.set(0,PI, PI ) ;
arris10.position.set(0,-1,1) ;
let arris11 =createArrisBox( downColor,leftColor, ) ; //左
arris11.rotation.set(-PI/2,PI/2, 0 ) ;
arris11.position.set(-1,-1,0) ;
let arris12 =createArrisBox( downColor,rightColor, ) ; //右
arris12.rotation.set( -PI/2,-PI/2, 0 ) ;
arris12.position.set(1,-1,0) ;
中心面
这个就没啥好说的了,和轴承一样简单。 单面,然后分别绕单轴旋转一定角度就能得到其余五个面。
/**
* 六个中心默认 前面暂且就弄一个面,要仿真的话可以再加四个面
*/
function createCenterBox(color){ let group= new Mesh('c'+id);let mat= new StandardMaterial('sec'+id) ;mat.diffuseColor= color;const frontFace = MeshBuilder.CreatePlane('frontFc'+id) ; frontFace.position.z = -.5 ;frontFace.material = mat ;group.addChild(frontFace);id++group.scaling.set(.9,.9,.9) // 留间隙 return group
} ;
letfrontC=createCenterBox(frontColor) ;
frontC.position.z =-1 ;
letbackC=createCenterBox(backColor) ;
backC.position.z =1 ;backC.rotation.y = PI ;
letleftC=createCenterBox(leftColor) ;
leftC.position.x = -1 ;leftC.rotation.y = PI/2 ;
letrightC=createCenterBox(rightColor) ;
rightC.position.x = 1 ;rightC.rotation.y = -PI/2 ;
letupC=createCenterBox(upColor) ;
upC.position.y = 1 ;upC.rotation.x = PI/2 ;
letdownC=createCenterBox(downColor) ;
downC.position.y = -1 ;downC.rotation.x = -PI/2 ;
最后成品如下。
最后
最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。
有需要的小伙伴,可以点击下方卡片领取,无偿分享