查看: 2539|回复: 25

制作魔方的全过程:提问遇到的问题和进行的总结

[复制链接]

该用户从未签到

发表于 2013-6-21 11:37:43 | 显示全部楼层 |阅读模式
本帖最后由 hunter_wwq 于 2013-7-9 11:33 编辑

想画个魔方,osg::Box支持支着一种颜色,就想自己绘制一个立方体,然后给每个面都桌上不同的颜色,但是这个地方不会,请各位前辈指教啊!

该用户从未签到

 楼主| 发表于 2013-6-21 14:12:25 | 显示全部楼层
要怎么弄啊,本人很菜,求关注。。。。

该用户从未签到

发表于 2013-6-21 15:31:02 | 显示全部楼层
osg::Box 没这个功能   自己实现一个Drawable吧

该用户从未签到

 楼主| 发表于 2013-6-21 15:42:59 | 显示全部楼层
我是准备自己来绘制,但是我不知道怎么给画好的立方体来指定每个面的颜色

该用户从未签到

发表于 2013-6-21 17:46:05 | 显示全部楼层
用着色器,根据表面法线的方向指定颜色就行了呗。

该用户从未签到

发表于 2013-6-22 21:53:42 | 显示全部楼层
这个不是很难吧 使用geometry,设置几何顶点颜色数组和索引就可以了啦

该用户从未签到

 楼主| 发表于 2013-6-24 11:20:49 | 显示全部楼层
garyliyong 发表于 2013-6-22 21:53
这个不是很难吧 使用geometry,设置几何顶点颜色数组和索引就可以了啦

刚刚试了一下,这个方法确实就可以了!

该用户从未签到

 楼主| 发表于 2013-6-24 11:22:44 | 显示全部楼层
the_mercury 发表于 2013-6-21 17:46
用着色器,根据表面法线的方向指定颜色就行了呗。

着色器暂时还没学到,刚刚用的是颜色数组的方式来实现的

该用户从未签到

 楼主| 发表于 2013-6-24 11:27:36 | 显示全部楼层
本帖最后由 hunter_wwq 于 2013-6-24 11:31 编辑

刚刚试了下设置颜色数组的方式来为立方体每个面定义一种颜色,这其中颜色对顶点的修饰规则我大致总结了下,以后像我这样的菜鸟们可以参考一下,前辈们看到有总结的不对的地方还希望能指点出来啊

例举颜色修饰顶点的规则:(以绘制一个六个面均为不同颜色的立方体为例)
首先分析下立方体的构造:立方体有8个顶点、六个面,每个面有4个顶点,若以osg:: PrimitiveSet:: QUADS进行绘制,则会以4个顶点为一组进行绘制,所以 绘制几何体有两种方案:第一种,顶点数组添加4*6个顶点,依次以4个顶点为一组定义一个平面;第二种,顶点数组添加8个顶点,然后设置顶点索引,顶点索引是从顶点数组中选出顶点进行组合,也是以4个顶点为一组来定义一个平面。第一种方案的代码:

  1.         // 设置顶点
  2.         ref_ptr<Vec3Array> vertextArr = new Vec3Array;
  3.         // 前
  4.         vertextArr->push_back(Vec3(0.0f, 0.0f, 0.0f));
  5.         vertextArr->push_back(Vec3(1.0f, 0.0f, 0.0f));
  6.         vertextArr->push_back(Vec3(1.0f, 0.0f, 1.0f));
  7.         vertextArr->push_back(Vec3(0.0f, 0.0f, 1.0f));
  8.         // 左
  9.         vertextArr->push_back(Vec3(0.0f, 0.0f, 0.0f));
  10.         vertextArr->push_back(Vec3(0.0f, 1.0f, 0.0f));
  11.         vertextArr->push_back(Vec3(0.0f, 1.0f, 1.0f));
  12.         vertextArr->push_back(Vec3(0.0f, 0.0f, 1.0f));
  13.         // 底
  14.         vertextArr->push_back(Vec3(0.0f, 0.0f, 0.0f));
  15.         vertextArr->push_back(Vec3(1.0f, 0.0f, 0.0f));
  16.         vertextArr->push_back(Vec3(1.0f, 1.0f, 0.0f));
  17.         vertextArr->push_back(Vec3(0.0f, 1.0f, 0.0f));
  18.         // 顶
  19.         vertextArr->push_back(Vec3(1.0f, 1.0f, 1.0f));
  20.         vertextArr->push_back(Vec3(0.0f, 1.0f, 1.0f));
  21.         vertextArr->push_back(Vec3(0.0f, 0.0f, 1.0f));
  22.         vertextArr->push_back(Vec3(1.0f, 0.0f, 1.0f));
  23.         // 右
  24.         vertextArr->push_back(Vec3(1.0f, 1.0f, 1.0f));
  25.         vertextArr->push_back(Vec3(1.0f, 0.0f, 1.0f));
  26.         vertextArr->push_back(Vec3(1.0f, 0.0f, 0.0f));
  27.         vertextArr->push_back(Vec3(1.0f, 1.0f, 0.0f));
  28.         // 后
  29.         vertextArr->push_back(Vec3(1.0f, 1.0f, 1.0f));
  30.         vertextArr->push_back(Vec3(0.0f, 1.0f, 1.0f));
  31.         vertextArr->push_back(Vec3(0.0f, 1.0f, 0.0f));
  32.         vertextArr->push_back(Vec3(1.0f, 1.0f, 0.0f));
  33.         geometry->setVertexArray(vertextArr.get());
复制代码
第二种方案的代码:

  1.         // 设置顶点
  2.         ref_ptr<Vec3Array> vertextArr = new Vec3Array;
  3.         vertextArr->push_back(Vec3(0.0f, 0.0f, 0.0f));
  4.         vertextArr->push_back(Vec3(1.0f, 0.0f, 0.0f));
  5.         vertextArr->push_back(Vec3(1.0f, 0.0f, 1.0f));
  6.         vertextArr->push_back(Vec3(0.0f, 0.0f, 1.0f));
  7.         vertextArr->push_back(Vec3(1.0f, 1.0f, 1.0f));
  8.         vertextArr->push_back(Vec3(0.0f, 1.0f, 1.0f));
  9.         vertextArr->push_back(Vec3(0.0f, 1.0f, 0.0f));
  10.         vertextArr->push_back(Vec3(1.0f, 1.0f, 0.0f));
  11.         geometry->setVertexArray(vertextArr.get());

  12.         ref_ptr<DrawElementsUInt> vertexIndex = new DrawElementsUInt(PrimitiveSet::QUADS, 0);
  13.         vertexIndex->push_back(0);
  14.         vertexIndex->push_back(1);
  15.         vertexIndex->push_back(2);
  16.         vertexIndex->push_back(3);
  17.         vertexIndex->push_back(0);
  18.         vertexIndex->push_back(1);
  19.         vertexIndex->push_back(7);
  20.         vertexIndex->push_back(6);
  21.         vertexIndex->push_back(0);
  22.         vertexIndex->push_back(6);
  23.         vertexIndex->push_back(5);
  24.         vertexIndex->push_back(3);
  25.         vertexIndex->push_back(4);
  26.         vertexIndex->push_back(5);
  27.         vertexIndex->push_back(3);
  28.         vertexIndex->push_back(2);
  29.         vertexIndex->push_back(4);
  30.         vertexIndex->push_back(5);
  31.         vertexIndex->push_back(6);
  32.         vertexIndex->push_back(7);
  33.         vertexIndex->push_back(4);
  34.         vertexIndex->push_back(2);
  35.         vertexIndex->push_back(1);
  36.         vertexIndex->push_back(7);
  37.         geometry->addPrimitiveSet(vertexIndex.get());
复制代码
接下来为每个面定义颜色,因为一个面均为一种填充颜色,所以一个面上的四个顶点均为同一种颜色,每个顶点都得定义一种颜色,一个立方体有6个面,则相当于要定义6*4个颜色值,而颜色值只是针对顶点数组中的顶点值去一一对应,并不会与设置的顶点索引去挂钩所以在绘制立方体时若采用第二种方案,是没有办法为每个面第一种不同的颜色的(一个面只有一种填充色)。所以若要给立方体每个面定义一种不同的颜色时,在构造立方体就只能采用第一种方案。现在设置颜色也有两种方案,第一种是设置仅颜色数组,设置24个,对应上每个顶点;第二种是先设置颜色数组,设置6个,然后设置颜色索引,定义24个,其中每个平面上的四个点的颜色值应该一致。这里只列举第二种方案的代码:

  1.         // 设置颜色
  2.         ref_ptr<Vec4Array> colorArr = new Vec4Array;
  3.         colorArr->push_back(m_mccWhite);
  4.         colorArr->push_back(m_mccYellow);
  5.         colorArr->push_back(m_mccBlue);
  6.         colorArr->push_back(m_mccGreen);
  7.         colorArr->push_back(m_mccRed);
  8.         colorArr->push_back(m_mccOrange);
  9.         geometry->setColorArray(colorArr.get());

  10.         ref_ptr<TemplateIndexArray<unsigned int, Array::Vec3ArrayType, 4, 4>> colorIndex = new TemplateIndexArray<unsigned int, Array::Vec3ArrayType, 4, 4>;
  11.         for (int i = 0; i < 6; i++)
  12.         {
  13.                 colorIndex->push_back(i);
  14.                 colorIndex->push_back(i);
  15.                 colorIndex->push_back(i);
  16.                 colorIndex->push_back(i);
  17.         }
  18.         geometry->setColorIndices(colorIndex.get());
  19.         geometry->setColorBinding(Geometry::BIND_PER_VERTEX);
复制代码

该用户从未签到

 楼主| 发表于 2013-7-9 15:01:04 | 显示全部楼层
本帖最后由 hunter_wwq 于 2013-7-9 15:14 编辑

设计的魔方的操作类如下,CMagicCube->构造并存储魔方的属性数据,包括存储魔方的单元面的颜色值、单元方格节点,和旋转中心索引;CHandleMagicCube->对魔方进行旋转,添加旋转的效果;CSolveMagicCube->解魔方用。此三类的关联关系如图所示:

魔方类设计图

魔方类设计图

其中CMagicCube中对魔方属性数据的存储都是用三维数组来存储,用来标记是处在那个位置上的。
这三个类均采用的模板类进行设计,举例,CMagicCube的类为:

  1. template<int orderNum>
  2. class CMagicCube
  3. {};
复制代码
其中非类型模板参数orderNum代表魔方的阶数,理论上可以有达到任意阶数!

该用户从未签到

 楼主| 发表于 2013-7-9 15:27:29 | 显示全部楼层
本帖最后由 hunter_wwq 于 2013-7-9 15:37 编辑

在对魔方进行旋转的时候,开始始终不能理解@鸩婚 和 @小白 等前辈们说的什么原点,中心点,后来才发现对魔方单元方块进行旋转的时候它是绕着原点进行旋转地,而不是绕着单元方块上的某个顶点进行旋转,所以也就明白了把魔方的中心点设置为原点的说法!现在要做的就是找到如何将魔方的中心年设置为原点的方法。
图例[0][0][1][单元方块绕原点进行旋转:

单元方块绕原点旋转示例

单元方块绕原点旋转示例

单元方块绕原点旋转示例

单元方块绕原点旋转示例

该用户从未签到

发表于 2013-7-9 19:04:09 | 显示全部楼层
旋转无非就是translate(-c) * rotate() * translate(c),明白这一点就可以最快地实现了

该用户从未签到

 楼主| 发表于 2013-7-9 19:41:03 | 显示全部楼层
本帖最后由 hunter_wwq 于 2013-7-9 19:43 编辑

我今天也在群里讨论了,他们也有说到用这个,但是我的想法是:我不想知道旋转至目的坐标是什么,我只知道单元方块的初始位置,和原点位置,然后我再通过设置这个旋转角度,就能到将单元方块直接旋转到目的坐标。其中最关键的一点是我想让魔方在旋转的时候是绕着原点进行旋转地,而不是先平移再旋转在平移,就是在视觉上也要达到那种看上去是旋转到目的位置的效果。

该用户从未签到

 楼主| 发表于 2013-7-14 10:36:19 | 显示全部楼层
根据@鸩婚 的建议,将魔方的中心点设置为坐标原点。
将魔方的中心点置为坐标原点,即在绘制魔方的时候明确好各单元方块中心点的坐标以及框架中心点坐标,分别如下:

  1.                 float xp = (DEFAULT_CELL_LENGTH + DEFAULT_SPACE)*x;
  2.                 float yp = (DEFAULT_CELL_LENGTH + DEFAULT_SPACE)*y;
  3.                 float zp = (DEFAULT_CELL_LENGTH + DEFAULT_SPACE)*z;
  4.                 xp -= (DEFAULT_CELL_LENGTH+DEFAULT_SPACE)*(orderNum-1)/2;
  5.                 yp -= (DEFAULT_CELL_LENGTH+DEFAULT_SPACE)*(orderNum-1)/2;
  6.                 zp -= (DEFAULT_CELL_LENGTH+DEFAULT_SPACE)*(orderNum-1)/2;
复制代码

  1.         float points[orderNum];///<存储绘制间隙所需要的点,其中最后一个点存储的是一个中心点
  2.         for (int i = 0; i <= orderNum-2; i++)
  3.         {
  4.                 points[i] = DEFAULT_CELL_LENGTH + DEFAULT_SPACE/2 +
  5.                                                         (DEFAULT_CELL_LENGTH + DEFAULT_SPACE)*i;
  6.                 points[i] -= (DEFAULT_CELL_LENGTH*orderNum + DEFAULT_SPACE*(orderNum-1))/2;
  7.         }
  8.         points[orderNum-1] = 0;
复制代码
魔方旋转功能搞定!!

该用户从未签到

 楼主| 发表于 2013-7-14 10:36:22 | 显示全部楼层
根据@鸩婚 的建议,将魔方的中心点设置为坐标原点。
将魔方的中心点置为坐标原点,即在绘制魔方的时候明确好各单元方块中心点的坐标以及框架中心点坐标,分别如下:

  1.                 float xp = (DEFAULT_CELL_LENGTH + DEFAULT_SPACE)*x;
  2.                 float yp = (DEFAULT_CELL_LENGTH + DEFAULT_SPACE)*y;
  3.                 float zp = (DEFAULT_CELL_LENGTH + DEFAULT_SPACE)*z;
  4.                 xp -= (DEFAULT_CELL_LENGTH+DEFAULT_SPACE)*(orderNum-1)/2;
  5.                 yp -= (DEFAULT_CELL_LENGTH+DEFAULT_SPACE)*(orderNum-1)/2;
  6.                 zp -= (DEFAULT_CELL_LENGTH+DEFAULT_SPACE)*(orderNum-1)/2;
复制代码

  1.         float points[orderNum];///<存储绘制间隙所需要的点,其中最后一个点存储的是一个中心点
  2.         for (int i = 0; i <= orderNum-2; i++)
  3.         {
  4.                 points[i] = DEFAULT_CELL_LENGTH + DEFAULT_SPACE/2 +
  5.                                                         (DEFAULT_CELL_LENGTH + DEFAULT_SPACE)*i;
  6.                 points[i] -= (DEFAULT_CELL_LENGTH*orderNum + DEFAULT_SPACE*(orderNum-1))/2;
  7.         }
  8.         points[orderNum-1] = 0;
复制代码
魔方旋转功能搞定!!

该用户从未签到

 楼主| 发表于 2013-7-14 10:40:51 | 显示全部楼层
先对魔方组件类进行重新设计,如图所示:

魔方类设计图二

魔方类设计图二

为魔方类CMagicCube增加三个方法:

  1. void swapCellplaneColorArrayPos(const SRotateParam& rsRP);
  2. void swapCellcubeArrayPos(const SRotateParam& rsRP);
  3. std::vector<osg::ref_ptr<osg::MatrixTransform>> getCellcubes(const SRotateParam& rsRP);
复制代码
swapCellplaneColorArrayPos: 根据旋转参数来对调单元面颜色数组中的位置
swapCellcubeArrayPos: 根据旋转参数来对调单元方块节点的位置
getCellcubes: 根据旋转参数来获取要旋转的单元方块数组

该用户从未签到

 楼主| 发表于 2013-7-14 10:43:31 | 显示全部楼层
本帖最后由 hunter_wwq 于 2013-7-14 11:19 编辑

总结:关于旋转矩阵节点osg::MatrixTransform设置矩阵对其进行变换操作(平移、旋转、缩放)
旋转矩阵节点有一个最初状态,当设置某个变换矩阵对其进行变换操作后,它的状态(位置/大小)会改变,此时状态为状态2,当再次设置某个变换矩阵对其进行变换操作后,它的状态的改变仍然是参考最初状态而不是参考状态2。比如说,对某个单元方块最初状态是在上面,逆转90度后,到了后面,在顺转90度,它到了前面,而不是回到上面,因为它的变换始终是参考其最初状态的
所以在对魔方进行旋转是,声明一个静态变量来保存它的角度值:

  1.         static double xAngle = 0.0f, yAngle = 0.0f, zAngle = 0.0f;
复制代码
当对其进行旋转时,若逆转,则减PI/2,若顺转,则加PI/2,然后再带入矩阵:

  1.                 Matrix mtr;
  2.                 if (rsRP._rotateDir == SRotateParam::RD_ANTICLOCKWISE)
  3.                 {
  4.                         xAngle -= PI/2;
  5.                 }
  6.                 else
  7.                 {
  8.                         xAngle += PI/2;
  9.                 }
  10.                 mtr.makeRotate(xAngle, 1.0f, 0.0f, 0.0f);
复制代码
同理,若对魔方进行平移操作,也可以声明一个静态变量来保存它的平移值。

该用户从未签到

 楼主| 发表于 2013-7-14 11:14:12 | 显示全部楼层
本帖最后由 hunter_wwq 于 2013-7-14 11:16 编辑

提问:但是旋转矩阵节点osg::MatrixTransform这样的旋转特点会很大不利于对魔方的旋转,若要达到如下图所示的旋转结果:

旋转结果示例

旋转结果示例

第一步是将[0][0][0]位置的方块经过绕X轴逆时针旋转90°至[0][0][3]位置,然后再从[0][0][3]位置绕Z轴逆时针旋转至[3][0][3]位置。但是因为osg::MatrixTransform旋转始终是参考其节点的初始状态进行旋转的,所以执行step1操作时设置的矩阵是Matrix::makeRotate(PI/2, 1.0f, 0.0f, 0.0f),执行step2操作时设置的矩阵是Matrix::makeRotate(PI/2, 0.0f, 0.0f, 1.0f),但是这样一转只会转到[3][0][0]位置,而不会转到[3][0][3]位置。
有没有什么方法可以让旋转矩阵节点在做变换时都是参考其前一个状态进行旋转而不是始终参考其初始状态进行旋转的?

该用户从未签到

发表于 2013-7-16 10:12:40 | 显示全部楼层
相对值的旋转可以直接对矩阵postMultRotate(如果之前没有translate的话),或者自己保存一个Quat然后叠加新的Quat值给它

该用户从未签到

 楼主| 发表于 2013-7-17 11:10:54 | 显示全部楼层
array 发表于 2013-7-16 10:12
相对值的旋转可以直接对矩阵postMultRotate(如果之前没有translate的话),或者自己保存一个Quat然后叠加新 ...

谢谢array的解答,我上次也在群里问了,@黎曼球 和 @HelloWorld 说用osg:: MatrixTransform的两个方法preMult和postMult试一下,经测试,得出:使用preMult方法,就是让节点先经过现设置的变换矩阵进行变换,然后再进行之前设置的变换矩阵进行变换;使用postMult方法,就是让节点先经过之前设置的变换矩阵进行变换,然后再经过现设置的变换矩阵进行变换,使用这个方法,正是我说的参考旋转节点的前一个状态来进行变换,所以使用postMult方法可行!

该用户从未签到

 楼主| 发表于 2013-7-17 11:27:15 | 显示全部楼层
魔方节点的旋转效果已经搞定!接下来是在旋转时对应魔方单元方块数组中位置的对换以及对应单元面颜色数组中位置的对换。前面也已经提到了这两个数组采用的三维数组arrays[x][y][z],其中x,y,z代表三维空间中的一个具体的位置。经过分析发现,这两块值的旋转均可采用迭代的形式来进行旋转,即可将其迭代为n个魔方,比如魔方为orderNum阶,则迭代下去有orderNum-2阶,orderNum-4阶,直到2阶或3阶,到此结束迭代。分析如下图所示:

偶数阶非边界单元方块的旋转

偶数阶非边界单元方块的旋转

通过在方法中设置魔方阶数和偏移量,即可进行数组位置迭代对换,定义的两个方法如下:

  1.         /**
  2.          * 根据旋转参数来对调 侧面/非侧面 单元面颜色数组中的位置
  3.          * @param [in] bSideFlag 标识是否为侧面
  4.          * @exception trow std::exception
  5.          * @warning 当bSideFlag=false时,addOffset必须等于0,否则抛异常并退出函数
  6.          */
  7.         void swapCellplaneColorArrayPos(const SRotateParam& rsRP,
  8.                                                                         const int orderNum, const int addOffset = 0,
  9.                                                                         bool bSideFlag = false);

  10.         /**
  11.          * 根据旋转参数来对调单元方块节点的位置
  12.          * @param [in] orderNum 阶数
  13.          * @param [in] addOffset 表示在x\y\z轴上需要增加的偏移量
  14.          * @warning 除旋转参数外的参数均是用来模拟orderNum魔方的
  15.          */
  16.         void swapCellcubeArrayPos(const SRotateParam& rsRP,
  17.                                                                 const int orderNum,
  18.                                                                 const int addOffset = 0);
复制代码
经测试可得,迭代方法可行!
至此魔方的旋转问题已经解决,包括旋转矩阵节点的旋转、单元方块在数组位置中的对换、单元面颜色在数组位置中的对换!
下一步想做的事是:在魔方区域内操作,根据鼠标滑动的速度、方向、以及停留等来转动魔方;在非魔方区域内操作,则用以调整魔方视图。

该用户从未签到

 楼主| 发表于 2013-7-19 13:36:36 | 显示全部楼层
在定义操作器场景漫游操作的时候终于还是要了解以下几个概念了
1. 有了矩阵,为什么还需要逆矩阵?逆矩阵起到什么作用?
2. 相机的作用?相机控制器起到的作用?
哎,还是要摸索下这几个概念了!!

该用户从未签到

 楼主| 发表于 2013-7-24 14:06:53 | 显示全部楼层
hunter_wwq 发表于 2013-7-19 13:36
在定义操作器场景漫游操作的时候终于还是要了解以下几个概念了:
1. 有了矩阵,为什么还需要逆矩阵?逆矩 ...

此块知识的学习我重新开了个帖子
探索3D数学-矩阵知识,以及矩阵在OSG中相机和相机操纵器的应用

该用户从未签到

 楼主| 发表于 2013-8-25 21:36:10 | 显示全部楼层
在这里先说一个C++中类设计的一点经验和教训,因为对n阶魔方的构造和操作都有共性,所以我在设计魔方的时候直接将魔方设计为模板类,而这样直接导致在设计的其他类中如果要声明魔方类的属性变量时不得不将该类也设计成模板类,这样就大大限制了其他类的自由,所以我在后来为魔方类定义了一个抽象基类,这个基类仅仅引出我需要调用的接口。在我的类的实现中,将抽象基类定义为了:

  1. #pragma once
  2. #include <osg/Array>
  3. #include <osg/Geode>

  4. #include <vector>
  5. #include <sstream>
  6. #include <string>

  7. /**
  8. * 魔方旋转参数结构
  9. */
  10. struct SRotateParam
  11. {
  12.         enum AxisType
  13.         {
  14.                 AT_X,
  15.                 AT_Y,
  16.                 AT_Z
  17.         };
  18.         enum RotateDir
  19.         {
  20.                 RD_CLOCKWISE,
  21.                 RD_ANTICLOCKWISE
  22.         };

  23.         void reset() { _num = -1; }
  24.         bool isValid() { return _num >= 0; }
  25.         bool operator== (const SRotateParam& sRP) const
  26.         {
  27.                 return _axisType == sRP._axisType && _rotateDir == sRP._rotateDir && _num == sRP._num;
  28.         }

  29.         const SRotateParam& operator= (const SRotateParam& sRP)
  30.         {
  31.                 _axisType = sRP._axisType;
  32.                 _rotateDir = sRP._rotateDir;
  33.                 _num = sRP._num;
  34.                 return *this;
  35.         }

  36.         AxisType        _axisType;        ///<旋转轴
  37.         RotateDir        _rotateDir;        ///<旋转方向
  38.         int                        _num;                ///<旋转轴上第几个面,从0开始计数!
  39. };

  40. /**
  41. * 定义魔方接口
  42. */
  43. class CMagicCube
  44. {
  45. protected:
  46.         CMagicCube(void) {};
  47.         virtual ~CMagicCube(void) {};

  48. public:
  49.         typedef std::vector<osg::ref_ptr<osg::MatrixTransform>> cellcube_vec_type;

  50. // methods
  51. public:
  52.         /**
  53.          * 获得魔方
  54.          */
  55.         virtual osg::ref_ptr<osg::Node> getMagicCube() = 0;

  56.         /**
  57.          * 获取魔方的中心点坐标
  58.          */
  59.         virtual osg::Vec3 getMagicCubeCenter() = 0;

  60.         /**
  61.          * 根据单元方块的中心点来获取单元方块在方块数组中的位置Vec3(x, y, z)
  62.          * @return 返回false表示中心点并不是模仿中某个单元方块的中心点,true则表示是
  63.          */
  64.         virtual bool getCellCubeArrayPos(const osg::ref_ptr<osg::Node> node, osg::Vec3s& cellcubeArrayPos) = 0;

  65.         /**
  66.          * 处理旋转
  67.          */
  68.         virtual void handleRotate(const SRotateParam& rsRP) = 0;
  69.        
  70.         /**
  71.          * 根据旋转参数来获取要旋转的单元方块数组
  72.          * @exception trow std::exception
  73.          * @warning 此方法与swapCellcubeArrayPos方法都是对单元方块节点数组进行操作,
  74.          *       前者为读操作,后者为修改操作,在使用时应该注意两方法的调用顺序
  75.          */
  76.         virtual cellcube_vec_type getNeedRotateCellcubes(const SRotateParam& rsRP) = 0;
  77. };
复制代码
将魔方类的实现类定义为:

  1. #pragma once
  2. #include <osg/MatrixTransform>
  3. #include <osg/Array>
  4. #include <osg/Shape>
  5. #include <osg/Geode>
  6. #include <osg/ShapeDrawable>

  7. #include <vector>
  8. #include <sstream>
  9. #include <string>

  10. #include "MagicCube.h"
  11. #include "ColorsBox.h"

  12. /**
  13. * @warn orderNum 必须大于0
  14. */
  15. template<int orderNum>
  16. class CMagicCubeImpl : public CMagicCube
  17. {
  18. public:
  19.         CMagicCubeImpl(void);
  20.         ~CMagicCubeImpl(void);

  21. // methods
  22. public:
  23.         /**
  24.          * 获得魔方
  25.          */
  26.         virtual osg::ref_ptr<osg::Node> getMagicCube();

  27.         /**
  28.          * 获取魔方的中心点坐标
  29.          */
  30.         virtual osg::Vec3 getMagicCubeCenter() { return osg::Vec3(0.0f, 0.0f, 0.0f); }

  31.         /**
  32.          * 根据单元方块的中心点来获取单元方块在方块数组中的位置Vec3(x, y, z)
  33.          * @return 返回false表示中心点并不是模仿中某个单元方块的中心点,true则表示是
  34.          */
  35.         virtual bool getCellCubeArrayPos(const osg::ref_ptr<osg::Node> node, osg::Vec3s& cellcubeArrayPos);

  36.         /**
  37.          * 处理旋转
  38.          * @exception throw string
  39.          */
  40.         virtual void handleRotate(const SRotateParam& rsRP);
  41.        
  42.         /**
  43.          * 根据旋转参数来获取要旋转的单元方块数组
  44.          * @exception trow std::exception
  45.          * @warning 此方法与swapCellcubeArrayPos方法都是对单元方块节点数组进行操作,
  46.          *       前者为读操作,后者为修改操作,在使用时应该注意两方法的调用顺序
  47.          */
  48.         virtual cellcube_vec_type getNeedRotateCellcubes(const SRotateParam& rsRP);

  49. protected:
  50.         /**
  51.          * 创建魔方框架
  52.          */
  53.         osg::ref_ptr<osg::Node> createFrame();

  54.         /**
  55.          * 创建魔方,并构造颜色数组
  56.          */
  57.         osg::ref_ptr<osg::Node> createMagicCube();

  58.         /**
  59.          * 构造单元面颜色数组
  60.          */
  61.         void constructCellplaneColors();

  62.         /**
  63.          * 根据旋转参数来对调单元面颜色数组中的位置
  64.          * @exception trow std::exception
  65.          */
  66.         void swapCellplaneColorArrayPos(const SRotateParam& rsRP);

  67.         /**
  68.          * 根据旋转参数来对调单元方块节点的位置
  69.          * @exception trow std::exception
  70.          * @warning 此方法与getCellcubes方法都是对单元方块节点数组进行操作,
  71.          *       前者为修改操作,后者为读操作,在使用时应该注意两方法的调用顺序
  72.          */
  73.         void swapCellcubeArrayPos(const SRotateParam& rsRP);

  74.         /**
  75.          * 判断某节点是否是模仿的单元方块节点
  76.          */
  77.         bool isCellCubeNode(osg::ref_ptr<osg::Node> node) const { return node->getName() == "magiccube_cellcube"; }
  78. private:

  79.         /**
  80.          * 根据旋转参数来对调 侧面/非侧面 单元面颜色数组中的位置
  81.          * @param [in] bSideFlag 标识是否为侧面
  82.          * @exception trow std::exception
  83.          * @warning 当bSideFlag=false时,addOffset必须等于0,否则抛异常并退出函数
  84.          */
  85.         void swapCellplaneColorArrayPos(const SRotateParam& rsRP,
  86.                                                                         const int orderNum, const int addOffset = 0,
  87.                                                                         bool bSideFlag = false);

  88.         /**
  89.          * 根据旋转参数来对调单元方块节点的位置
  90.          * @param [in] orderNum 阶数
  91.          * @param [in] addOffset 表示在x\y\z轴上需要增加的偏移量
  92.          * @warning 除旋转参数外的参数均是用来模拟orderNum魔方的
  93.          */
  94.         void swapCellcubeArrayPos(const SRotateParam& rsRP,
  95.                                                                 const int orderNum,
  96.                                                                 const int addOffset = 0);

  97. // properties
  98. protected:
  99.         // 魔方颜色
  100.         static const osg::Vec4 m_mccFront;
  101.         static const osg::Vec4 m_mccBack;
  102.         static const osg::Vec4 m_mccLeft;
  103.         static const osg::Vec4 m_mccRight;
  104.         static const osg::Vec4 m_mccTop;
  105.         static const osg::Vec4 m_mccBottom;
  106.         // 魔方颜色索引
  107.         enum ColorIndex
  108.         {
  109.                 CI_FRONT,
  110.                 CI_BACK,
  111.                 CI_LEFT,
  112.                 CI_RIGHT,
  113.                 CI_TOP,
  114.                 CI_BOTTOM
  115.         };

  116.         static const osg::Vec4        SPACE_COLOR;                        ///< 间距填充颜色
  117.         static const double                DEFAULT_CELL_LENGTH;        ///< 单元格默认长度
  118.         static const double                DEFAULT_SPACE;                        ///< 单元格之间默认的间距长度

  119.         osg::ref_ptr<osg::MatrixTransform>        ***m_cellcubes;        ///<单元方块
  120.         osg::ref_ptr<osg::Group> m_cellcubesNode;                                                        ///<魔方单元格节点
  121.         osg::ref_ptr<osg::Geode> m_frame;                                                                ///<魔方框架
  122.         osg::ref_ptr<osg::Group> m_magicCube;                                                        ///<魔方

  123.         unsigned int                                                ***m_cellplaneColors;        ///<平面颜色,数组[0][0][0]位置不可用
  124. };
复制代码

该用户从未签到

 楼主| 发表于 2013-8-25 21:55:08 | 显示全部楼层
本帖最后由 hunter_wwq 于 2013-8-25 21:58 编辑

现在讲一下我通过鼠标操作来实现对魔方的旋转,原来的想法是通过重实现事件处理器osgGA::GAEventHandler的方式来达到鼠标操作旋转魔方的效果,当时觉得我只要能够捕捉到鼠标所点击的位置是魔方的哪个单元方块即可,后来想到在旋转魔方的时候我需要判断它是拖向那个面去旋转的,这时候我想到得用到轨迹球漫游器中的trackball方法:通过模拟出一个轨迹球,然后计算从一个点拖动到另一个点,这个旋转过程中所用到的旋转轴axis,和旋转角度angle,这样我就能够在通过简易的计算来得出它是要朝着那个面去旋转。这个简单的计算思路是:知道旋转轴axis,我通过计算单位向量axis分别到x, y, z轴的距离,距离最小的则认为是在那个轴上做的旋转,然后判断angle为正值还是为负值,为正值则为向逆时针方向旋转,为负值则为向顺时针方向旋转。代码实现如下:

  1. SRotateParam CMagicCubeManipulator::calcRotateParam(const osg::Vec3d& axis, const float angle)
  2. {
  3.         SRotateParam sRP;

  4.         double disX, disY, disZ;
  5.         disX = sqrt(axis.length2() - (axis.x()*axis.x()));
  6.         disY = sqrt(axis.length2() - (axis.y()*axis.y()));
  7.         disZ = sqrt(axis.length2() - (axis.z()*axis.z()));
  8.         char axisFlag;
  9.         double minDis = std::min<double>(disX, disY);
  10.         axisFlag = disX < disY ? 'X':'Y';
  11.         axisFlag = minDis < disZ ? axisFlag:'Z';

  12.         if (axisFlag == 'X')
  13.         {
  14.                 sRP._axisType = SRotateParam::AT_X;
  15.                 sRP._num = m_cellcubeArrayPos[0];
  16.                 if (axis.x()*angle > 0.0)
  17.                 {
  18.                         sRP._rotateDir = SRotateParam::RD_CLOCKWISE;
  19.                 }
  20.                 else
  21.                 {
  22.                         sRP._rotateDir = SRotateParam::RD_ANTICLOCKWISE;
  23.                 }
  24.         }
  25.         else if (axisFlag == 'Y')
  26.         {
  27.                 sRP._axisType = SRotateParam::AT_Y;
  28.                 sRP._num = m_cellcubeArrayPos[1];
  29.                 if (axis.x()*angle > 0.0)
  30.                 {
  31.                         sRP._rotateDir = SRotateParam::RD_CLOCKWISE;
  32.                 }
  33.                 else
  34.                 {
  35.                         sRP._rotateDir = SRotateParam::RD_ANTICLOCKWISE;
  36.                 }
  37.         }
  38.         else
  39.         {
  40.                 sRP._axisType = SRotateParam::AT_Z;
  41.                 sRP._num = m_cellcubeArrayPos[2];
  42.                 if (axis.x()*angle > 0.0)
  43.                 {
  44.                         sRP._rotateDir = SRotateParam::RD_CLOCKWISE;
  45.                 }
  46.                 else
  47.                 {
  48.                         sRP._rotateDir = SRotateParam::RD_ANTICLOCKWISE;
  49.                 }
  50.         }

  51.         return sRP;
  52. }
复制代码
所以最终还是确定通过继承轨迹球漫游器osgGA::TrackballManipulator的方式来实现鼠标操作旋转魔方,定义的类如下:

  1. class CMagicCubeManipulator : public osgGA::TrackballManipulator
  2. {
  3.         friend void rotate_thread/*<orderNum>*/(void* obj);
  4. public:
  5.         CMagicCubeManipulator(CMagicCube* pMagicCube, osg::ref_ptr<osgText::Text>updateText);
  6.         virtual ~CMagicCubeManipulator(void) { };

  7. // operations
  8. public:
  9.        
  10. protected:
  11.         /**
  12.          * 拾取魔方
  13.          * @retval true标识拾取到,false标识没拾取到
  14.          */
  15.         bool pickMagicCube(osg::ref_ptr<osgViewer::Viewer> viewer, float x, float y);

  16.         /**
  17.          * 计算旋转参数
  18.          */
  19.         SRotateParam calcRotateParam(const osg::Vec3d& axis, const float angle);

  20.         /**
  21.          * 旋转魔方
  22.          * @param [in] rRotateParam 旋转参数
  23.          * @param [in] rotateNum 旋转次数
  24.          * @param [in] valPerRotate 旋转一次转的度数, 此参数为正数
  25.          * @param [in] speed
  26.          * @warning rotateNum>=0
  27.          * #Q: 是否有限定valPerRotate为正数的必要性
  28.          */
  29.         void doRotate(const SRotateParam& rRotateParam, const int rotateNum, const double valPerRotate, const int speed = 100);

  30.         /**
  31.          * 检测是否可以进行旋转操作
  32.          */
  33.         bool checkAccessRotate(const osg::Vec3d& axis, const float angle);

  34.         /**
  35.          * 进行旋转
  36.          */
  37.         void doRotate();

  38.         // 重载以下三个方法
  39.         virtual bool handleMouseDrag( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us );
  40.         virtual bool handleMousePush( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us );
  41.         virtual bool handleMouseRelease( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& us );

  42. // properties
  43. protected:
  44.         static const double m_valPerRotate;

  45.         CMagicCube*                m_pMagicCube;
  46.         osg::ref_ptr<osgText::Text> m_updateText;
  47.         bool                        m_bPickMagicCube;///< 标识已经拾取魔方
  48.         osg::Vec3s                m_cellcubeArrayPos;///< 拾取的魔方方块
  49.         SRotateParam        m_rotateParam;///< 旋转参数
  50.         bool                        m_bReleased;///< 在drag之后有没有release
  51. };
复制代码

该用户从未签到

 楼主| 发表于 2013-8-25 22:12:35 | 显示全部楼层
之前想的是在重载方法handleMouseDrag中通过设置矩阵来实现魔方的连续渐渐旋转的效果,结果显示的效果却是直接旋转90度而不是渐渐旋转的效果,经调试发现在视景器执行frame时是先处理完所有的事件后再进行渲染的,所以在handle方法还没有返回前不会执行渲染的动作,而我再handleMouseDrag方法中进行设计旋转矩阵效果并没有体现出来,它是在做完所有的旋转矩阵后用到最后一次旋转矩阵结果来对指定面上的单元魔方方格进行旋转的。所以我将设计旋转矩阵来实现魔方渐渐旋转的动作放在了另一个工作线程中,然后通过信号量来限定魔方旋转和旋转事件处理的同步问题。响应一次旋转事件是旋转90度,在旋转的过程中不再响应旋转事件的处理动作。
最终这个方法可行。但是上一楼所说的那个判断旋转参数的方法calcRotateParam实现并没有很精确,老是顺逆不分,而且对鼠标旋转动作的识别也不是很准确,所以在这部分仍然需要继续优化一下。现在优化也正在进行,等做好了后再把代码分享给跟我一样菜的菜鸟们吧。。。。。。。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

OSG中国官方论坛-有您OSG在中国才更好

网站简介:osgChina是国内首个三维相关技术开源社区,旨在为国内更多的技术开发人员提供最前沿的技术资讯,为更多的三维从业者提供一个学习、交流的技术平台。

联系我们

  • 工作时间:09:00--18:00
  • 反馈邮箱:1315785073@qq.com
快速回复 返回顶部 返回列表