theme: cyanosis
开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情
本文主要内容可能和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 as StandardMaterial).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 as StandardMaterial).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 as StandardMaterial).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 as StandardMaterial).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 as StandardMaterial).diffuseColor = backColor ;
棱边
一个棱边由两个面组成,总共12个。 就不像上面那么啰嗦了,经历了上面的一系列旋转操作,想必你对于欧拉旋转已经了然于胸。
还是先来一个函数工厂, 默认是上层的前方。其余的都通过旋转得到
function createArrisBox(colorFront:Color3, colorUp:Color3){
let group = new Mesh('e'+id);
let mat = new StandardMaterial('sde'+id) ;
let frontMat = 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
} ;
let frontC =createCenterBox(frontColor) ;
frontC.position.z =-1 ;
let backC =createCenterBox(backColor) ;
backC.position.z =1 ;backC.rotation.y = PI ;
let leftC =createCenterBox(leftColor) ;
leftC.position.x = -1 ;leftC.rotation.y = PI/2 ;
let rightC =createCenterBox(rightColor) ;
rightC.position.x = 1 ;rightC.rotation.y = -PI/2 ;
let upC =createCenterBox(upColor) ;
upC.position.y = 1 ;upC.rotation.x = PI/2 ;
let downC =createCenterBox(downColor) ;
downC.position.y = -1 ;downC.rotation.x = -PI/2 ;
最后成品如下。
代码片段
结语
建模到这里就完成了。 作为一个魔方来说,更重要的当然是转动它。 我之前就一直觉得,如果能写一个魔方,这个过程,对拖曳交互的理解肯定会更加扎实。没想到这么麻烦 ,希望我能实现吧(不偷懒)。
暂无评论内容