babylon.js魔方建模


theme: cyanosis

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

本文主要内容可能和babylon并无太紧密的关联, 主要是对旋转( 空间想象力 )的练习。 本来想写个魔方练练,就想着顺便练练baboly. 结果反正是最重要的交互逻辑没有实现。

标题已经说明了本文的主题是建模,也就是说,是可以用建模软件来替代这部分的代码实现。不过,从性能上说,肯定是直接代码顶点实现要好些。

关于拧转魔方

之前想到一个思路,就是每次触发转动时其实,就要知道转动的那一层,把八个块都添加到中间的chidren里,这样就可以一起转动了。缺点就是要能判断开始和结束,每次转动开始就整体化,结束就把八个块再移回去,似乎,还不如直接用改变旋转中心的办法。

开始建模

魔方的组成

我是要做一个魔方,因此,常规的立方体并不适用,因为需要每个面都有独立的颜色,也许用立方体纹理可以解决,但是合成这个天空盒就很麻烦。 直接用多个平面拼凑吧, 这样最少可以减少一半的面。

实际上 ,魔方的每一小块最多就只会显示三个面,其余的面看不到,就可以直接认为没有,那我们就可以偷工减料。 现实的魔方也是这样的,不过,这里可以比它更省

一个魔方小块,实际上就只有三种,拆过魔方的都知道。 没拆过的,现在你也会知道了。

image.png
其中八个顶角,都是有三个面, 每条棱中间的那个块两个面, 其余中心块一个面, 高阶魔方也是一样。

代码接之前的初次使用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 这些东西大概也因此必须提前初始化,他们之间有强关联。

轴承

另外,它这个坐标轴辅助的渲染顺序是靠后的,我又不知道如何调整。 所以不用它了,顺便加个轴承。

image.png


/**
 * 轴承 不要那个坐标指示器了
 */
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) ;

image.png

中心面

这个就没啥好说的了,和轴承一样简单。 单面,然后分别绕单轴旋转一定角度就能得到其余五个面。

/**
 * 六个中心  默认 前面  暂且就弄一个面,要仿真的话可以再加四个面
 */

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 ;


image.png

最后成品如下。
图片[2]-babylon.js魔方建模-烟雨网
代码片段

结语

建模到这里就完成了。 作为一个魔方来说,更重要的当然是转动它。 我之前就一直觉得,如果能写一个魔方,这个过程,对拖曳交互的理解肯定会更加扎实。没想到这么麻烦 ,希望我能实现吧(不偷懒)。

© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容