IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    提取三维表面标量场的等值线

    远行发表于 2015-02-06 08:16:47
    love 0

    上一篇文章说了提取拉普拉斯矩阵的第二个(或者其它)特征向量能得到一个有规律分布的标量场。现在讲的是如何在这样一个规律分布的标量场上提取出等值线。注意,这是在一个三角网格上面提取等值线,并不是真正的曲面。提取出等值线后,可以做一些相关的应用。

    那么,如何提取等值线了?

    首先,如果只是绘制出等值线,可以简单的设置纹理贴图值(比如间隔多少行,设置纹理值为白色),标量场对应一维纹理坐标。更详细的说明,可以参考我的一篇博文:在三维模型上可视化标量场及注意方面。

    现在考虑如何计算等值线上的间隔点。先看单个的三角面,无论如何可以得到一条等值线的。最简单的思想,就是把单个三角面上的等值线连接起来。

    给定一个三角面,如何计算从该三角面出发的一条等值线了?

    第一步,求出三个顶点的标量值顺序,那么标量范围在[fMin,fMax]之间。可以任选区间内的一个值,比如中间值作为该等值线的标量值。

    第二步,得到等值线标量值在[fMin,fMax]所在边pEdge的位置ptBeg,作为起始点,加入点集合isoLine。

    第三步,在pEdge所在的三角面上,找pEdge相邻的两条边的等值点(ptBeg的标量值相等的点)ptNext。如果存在这样的点,那么修改ptBeg = ptNext,加入点集合isoLine。同时,修改pEdge为对应的相邻边pEdge->next()或者是pEdge->next()->next()。如果不存在ptNext,进入第四步,同时设置不成环标志。否则,pEdge = pEdge->opposite(),(效果是进入相邻面,再循环第三步),如果发现ptNext等于isoLine的第一个点,那么说明已经成环,进入第四步,同时设置成环标志。

    第四步,返回等值线,以及等值线是否成环状。

    相关代码如下,里面使用了cgal的半边结构,同时该函数标记了当前面是否已经计算过等值线,避免等值线过密。

    	bool CSEMesh::ComputeIsoLine(std::vector& scalarField, Enriched_Model::Facet_iterator pBegFacet,
    		std::vector& bHasIsoLine, IsoLine& isoline)
    	{
    		//该面已经有通过的isoline
    		if (bHasIsoLine[pBegFacet->tag()] == true)
    		{
    			return false;
    		}
    
    		isoline.pts.clear();
    		std::vector visitFaces;//这次访问过的face
    
    		Enriched_Model::Halfedge_around_facet_circulator pHalfedge = pBegFacet->facet_begin();
    
    		int vertexs[3];
    		int i = 0;
    		do
    		{
    			vertexs[i++] = pHalfedge->vertex()->tag();
    		} while (++pHalfedge != pBegFacet->facet_begin());
    
    		struct ScalarInfor
    		{
    			int index;
    			double value;
    			bool operator < (const ScalarInfor& si) const
    			{
    				return value < si.value;
    			}
    			ScalarInfor(int i, double v) : index(i), value(v) {}
    		};
    		std::vector vecSi;
    		for (int i = 0; i < 3; ++i)
    		{
    			vecSi.push_back(ScalarInfor(vertexs[i], scalarField[vertexs[i]]));
    		}
    		std::sort(vecSi.begin(), vecSi.end());
    
    		int nMinIndex = vecSi[0].index;
    		int nMaxIndex = vecSi[2].index;
    		
    		Enriched_Model::Edge_iterator pEdge;
    		pHalfedge = pBegFacet->facet_begin();
    		do
    		{
    			if (nMinIndex == pHalfedge->vertex()->tag() && nMaxIndex == pHalfedge->opposite()->vertex()->tag()
    			||	nMaxIndex == pHalfedge->vertex()->tag() && nMinIndex == pHalfedge->opposite()->vertex()->tag())
    			{
    				pEdge = pHalfedge;
    				break;
    			}
    		} while (++pHalfedge != pBegFacet->facet_begin());
    
    		Enriched_Model::Point pt;
    		isoline.isoValue = (scalarField[nMinIndex] + scalarField[nMaxIndex]) / 2.0;//当前等值线的值
    
    		Enriched_Model::Point ptBeg = pEdge->opposite()->vertex()->point();
    		int iBeg = pEdge->opposite()->vertex()->tag();
    
    		Enriched_Model::Point ptEnd = pEdge->vertex()->point();
    		int iEnd = pEdge->vertex()->tag();
    		GetInterpolationPt(ptBeg, ptEnd, scalarField[iBeg], scalarField[iEnd], isoline.isoValue, pt);//得到对边的插值点作为终点
    		isoline.pts.push_back(pt);
    
    		//下一个三角面是pEdge所在的相邻三角面
    		bool bCircle = true;
    		while (1)
    		{
    			if (pEdge->is_border())
    			{
    				bCircle = false;
    				break;
    			}
    
    			int iBeg = pEdge->opposite()->vertex()->tag();
    			int iEnd = pEdge->vertex()->tag();
    			int iNext = pEdge->next()->vertex()->tag();
    			if (IsValueBetween(isoline.isoValue, scalarField[iNext], scalarField[iBeg]))
    			{
    				Enriched_Model::Point ptNext = pEdge->next()->vertex()->point();
    				Enriched_Model::Point ptBeg = pEdge->opposite()->vertex()->point();
    				GetInterpolationPt(ptNext, ptBeg, scalarField[iNext], scalarField[iBeg], isoline.isoValue, pt);//得到对边的插值点作为终点
    				isoline.pts.push_back(pt);
    				pEdge = pEdge->next()->next();
    
    				if (IsSamePoint(pt, isoline.pts.front()))
    				{
    					isoline.pts.back() = isoline.pts.front();
    					break;//已经成环状
    				}
    			}
    			else if (IsValueBetween(isoline.isoValue, scalarField[iEnd], scalarField[iNext]))
    			{
    				Enriched_Model::Point ptNext = pEdge->next()->vertex()->point();
    				Enriched_Model::Point ptEnd = pEdge->vertex()->point();
    				GetInterpolationPt(ptEnd, ptNext, scalarField[iEnd], scalarField[iNext], isoline.isoValue, pt);//得到对边的插值点作为终点
    				isoline.pts.push_back(pt);
    				pEdge = pEdge->next();
    
    				if (IsSamePoint(pt, isoline.pts.front()))
    				{
    					isoline.pts.back() = isoline.pts.front();
    					break;//已经成环状
    				}
    			}
    			else
    			{
    				bCircle = false;
    				break;
    			}
    
    			visitFaces.push_back(pEdge->facet()->tag());
    			pEdge = pEdge->opposite();
    		}
    
    		if (bCircle)
    		{
    			for (int i = 0; i < visitFaces.size(); ++i)
    			{
    				bHasIsoLine[visitFaces[i]] = true;
    			}
    		}
    
    		return bCircle;
    	}
    

    最后要讲的是如何遍历整个模型的三角面,使得计算出来的等值线有一定的规律?诚然,任意的顺序都能计算出等值线来。但是,会丢失一些信息。我用的广度优先搜索的顺序计算。我先找到最小的标量值,从这个顶点所在的一个面开始进行广度优先搜索遍历所有的面。同时,在队列里面保存进入队列的顺序,以进行下一步的等值线排序。最终,我得到的是一些根据标量值和进入队列顺序排序过的一堆等值线。相关代码如下:

    //计算所有等值线
    	void CSEMesh::ComputeIsoLines(std::vector& scalarField)
    	{
    		IsoLine isoline;
    		std::vector bHasIsoLine(FaceNum(), false);
    
    		m_isoLines.clear();
    
    		//找到值最小的顶点
    		Enriched_Model::Vertex_iterator pVertex = m_pModel->vertices_begin();
    		double fMinValue = 1e8;
    		Enriched_Model::Vertex_iterator pMinVertex;
    		while (pVertex != m_pModel->vertices_end())
    		{
    			if (scalarField[pVertex->tag()] < fMinValue)
    			{
    				fMinValue = scalarField[pVertex->tag()];
    				pMinVertex = pVertex;
    			}
    			pVertex++;
    		}
    
    		vector bVisited(FaceNum(), false);
    
    		Enriched_Model::Facet_iterator pSourceFace = pMinVertex->vertex_begin()->facet();
    		{
    			struct FaceInfor
    			{
    				Enriched_Model::Face_iterator pFace;
    				int nOrder;
    				FaceInfor(Enriched_Model::Face_iterator face, int order) : pFace(face), nOrder(order){}
    			};
    
    			std::queue queFaces;
    			bVisited[pSourceFace->tag()] = true;
    			queFaces.push(FaceInfor(pSourceFace, 0));
    
    			while (queFaces.empty() == false)
    			{
    				FaceInfor faceInfor = queFaces.front();
    				queFaces.pop();
    
    				if (ComputeIsoLine(scalarField, faceInfor.pFace, bHasIsoLine, isoline))
    				{
    					isoline.order = faceInfor.nOrder;
    					m_isoLines.push_back(isoline);
    				}
    
    				Enriched_Model::Halfedge_around_facet_circulator pHalfedge = faceInfor.pFace->facet_begin();
    				do
    				{
    					if (pHalfedge->opposite()->is_border())
    					{
    						continue;
    					}
    
    					Enriched_Model::Face_iterator pFace = pHalfedge->opposite()->facet();
    
    					if (bVisited[pFace->tag()] == false)
    					{
    						queFaces.push(FaceInfor(pFace, faceInfor.nOrder + 1));
    						bVisited[pFace->tag()] = true;
    					}
    				} while (++pHalfedge != faceInfor.pFace->facet_begin());
    			}
    
    		}
    
    		sort(m_isoLines.begin(), m_isoLines.end());
    	}
    

    等值线效果图:isoline

    您可能也喜欢:

    使用CGAL实现平面分割三维模型

    在三维模型上可视化标量场及注意方面

    使用CGAL计算SDF分割模型

    PaintMeshCutting的实现

    hdu 3584 Cube 三维树状数组
    无觅


沪ICP备19023445号-2号
友情链接