As-rigid-as possible shape manipulation: 第一步算法的C++代码实现

对于这篇论文,我们输入一个3D模型文件。如下图。

我们在这里输入的是obj文件,obj文件最重要的两个元素:点和面。对于一个简单的模型,如下图。

该文件内容如下。

v 0 0 0v 2 0 0v 1 2 0v 3 2 0f 1 2 3f 2 4 3

点的表示方式为 “v x y z ”, 用 “v” 表示点,后面是该点的三维坐标。

面的表示方式为 “f v1_id v2_id v3_id”,用 "f" 表示面(三角面),后面是该面的三个点的序号。点的序号按照代码从表示“点”的第一行算起,序号从1开始。

如 “f 1 2 3” 表示由序号为1、2、3的三个点表示。

一切从简,我们只对其中的点和三角面做操作。

关于代码,我们使用Eigen数学库,做一系列数学操作以及“点”和“面”的表示。使用trimesh2库读取obj文件。

如上图,我们声明两个简单的结构体:"Triangle"和"Vertex",以及表示三维模型的"Mesh"类。

"Triangle"由从obj文件中读取的面的三个顶点的序号和论文中需要的三个点的相对坐标,具体原因可看之前文章

"Vertex"由从obj文件中读取的序号和其三维坐标。

"Mesh"类很简单,类似于3D模型文件的结构。存储三维模型所有的点的动态数组"vertices",存储所有面的动态数组"triangles"。

下面我们看使用trimesh2读取obj文件内容的Read函数。

非常简单,Read函数中第一个for循环读取点存储到Mesh的vertices数组中,第二个for循环读取面存储到Mesh的triangles数组中。其中computeLocalXY函数是计算该三角形的三个顶点的相对坐标,计算方法见该文章。

现在我们可以看一下用OpenGL显示三维模型于窗口中的效果了。我们使用freeglut作为可视化工具。显示代码非常短,如下。

关于如何使用OpenGL写显示代码,这里就不赘述了。我们只简单的显示点和三角形,具体可看display()函数中的for循环。

显示效果如下。

下面我们写论文的算法代码。

我们在操作模型时,要在模型上选择控制点,然后拖动控制点对模型做变形。所以我们需要加入几个函数。

onMouseClick函数和onMouseMove函数表示当鼠标点击某个点,拖动某个点时的行为。

ScreenToOpenGL函数用来将窗口内坐标转为三维环境下三维坐标。该函数被用在onMouseMove函数中,当你拖动某个控制点时,你的移动轨迹其实是二维坐标形式,但是反映到三维模型的变形上,需要将2D的screen坐标转为3D的OpenGL坐标。

FindHitVertex函数用来处理用户点击窗口上某个位置时,程序计算用户点击的是三维模型上的哪个点,返回该点的序号。

这里我们先看FindHitVertex函数的代码。

OpenGLToScreen函数用来将三维环境下的三维坐标转为窗口内二维坐标。该函数用于FindHitVertex函数,计算用户点击的窗口位置(2D坐标)对应的OpenGL三维坐标是什么,以判断是三维模型上哪个点。

根据上一篇文章,考虑到在图形变形前,要把所有的点重新排为自由点"u"和控制点"q",然后计算G'和B两个矩阵。

所以我们在每次选取控制点时,都要更新mesh的vertices列表,重新排列为

<----u---->   <-q->

* * * * * * * * | * * * * 

mesh的vertices中每个点的内容都没有变,只不过在点在vertices中的位置发生了变化。

在这里,我的处理办法是给每个Vertex加一个bool变量"isU",表示其是控制点q还是自由点u。

因为mesh的vertices中每个点的内容没变,每个点还是存有序号和三维坐标,其中序号表示的是

该点在obj文件中读取出来的顺序。

mesh的triangles中存有三个顶点的序号和相对坐标。三个顶点的序号还是

该点在obj文件中读取出来的顺序。

在程序还未进行点选控制点之前,mesh的vertices中的顺序就是obj文件中读取出来的顺序,点选控制点后,mesh的vertices中的顺序就打乱重排为u和q的顺序。所以,我们要在Mesh类中添加一个find函数,该函数表示

根据某个点在obj文件中读取出来的序号

查看

该点在打乱重排的vertices中的顺序。

另外,我们还需在Mesh类中设置一个变量"q_size"用来记录控制点的数目,这个变量用来重置vertices为"uq"的形式。

下面看修改后的Mesh类。

Vertex结构体中加入了operator==函数,是为了配合STL的find函数,根据obj中的点顺序找到其在重排后的vertices中的顺序

下面,我们看关键的find函数和resetVerticesto_uq函数。

最后,最重要的PreComputeScaleFreeMatrix函数。根据上一篇文章最后推导的公式。我们遍历三角形列表,并遍历三角形三个顶点,根据每个顶点对应的在重排后的vertices中的顺序填充矩阵系数矩阵G。

这里需要说到的一点就是,我们注意到系数矩阵G每个元素都用的是"+="赋值。因为对于模型变形过程,每个三角形都对变形有贡献,所以我们遍历的是三角形列表,遍历过程中,必然会由大量的点是多次计算了。所以系数矩阵中对应的该点的数据需要累加

至此Mesh类算是完整了。

再看显示代码。

其中onMouseClickonMouseMove函数。

需要注意到,我们在每次点击选取控制点后都要重排mesh的vertices。

我们再看display函数和Deform函数。

注意到display函数中,每次显示都要做一次Deform函数。其中Deform函数用来生成所有的控制点"u"和自由点"q"。自由点"q"是读取得到,控制点"u"是通过scaleFreeMatrix与"q"的乘计算得到。

最后的实现效果如下。

该文所有代码都已开源,地址为:

https://coding.net/u/forestsen/p/ARAPShapeManipulation

本文分享自微信公众号 - 无雨森的技术分享(forestsen_tech)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-06-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券