首页 > 其他分享 >拉普拉斯网格变形实现

拉普拉斯网格变形实现

时间:2024-06-30 23:34:07浏览次数:16  
标签:变形 拉普拉斯 网格 pVertEnd int num Vext now

因为课题需要,除了RBF还做了一个Laplace网格变形,其他大佬已经把原理写的很详细了,我就简单介绍一下公式,主要还是写写实现过程。过程同样参考了大佬的部分代码,而且实现的时候刚开始敲代码不久,所以有点乱QAQ。

首先,计算离散拉普拉斯坐标,网格上的点vi的拉普拉斯坐标δi为:

\[\delta_{i}=v_{i}-\sum_{j \in N(i)} \omega_{i j} v_{j} \]

式中,ωij为点vj相对vi的权值,如图所示,点vj就是点vi的邻接点。本文实现的权值有均匀权值和余切权值。

均匀权值的计算公式为:

\[\mathit{\omega {\tiny ij} }=\frac{1}{d{\tiny i} } \]

di就是邻接顶点的数量,代码也相对于余切权值简单不少,但是变形效果较差。

余切权值的计算公式为:

\[\mathit{\omega {\tiny ij} }=\frac{\cot \alpha {\tiny ij} +\cot \beta {\tiny ij} }{2 } \]

αijβij其实就是边ij的两个对角,如图所示:

然后,根据输入的待变形顶点点集,移动点点集和固定点点集,构建拉普拉斯矩阵,然后通过求解一个线性方程组就可以获取变形后的顶点位置:

\[Lv_{}^{\prime } = \Delta \]

其中,L就是拉普拉斯矩阵,v为变形后的顶点,Δ为拉普拉斯坐标矩阵,剩下的流程在代码里说明。

首先是构建权值矩阵:

void Laplace::calculateCotValue(Vector3d A, Vector3d B, Vector3d C,double &cotValue)
{
	Eigen::Vector3d Vab = B - A;
	Eigen::Vector3d Vac = C - A;
	double cosValue = Vab.dot(Vac) / (Vab.norm() * Vac.norm());
	double sinValue = (Vab.cross(Vac)).norm() / (Vab.norm() * Vac.norm());
	cotValue = cosValue / sinValue;
}
void Laplace::baseCotWeight(STLModel* p_STL)
{
	double smoothingFactor = 0.0001;  // 光顺因子
	/*-------------------------*/
	//建立邻接表
	int Vert_num = this->m_DeforPoint.size();//参与变形的数量
	int Move_num = this->m_MoveIndex.size();//移动锚点的数量
	int Fix_num = this->m_FixPoint.size();//固定锚点的数量
	unordered_set<BaseStruct::PVERT> Jud;//用于判断变形点是否为所选择的点
	for (auto it : this->m_DeforPoint)
	{
		Jud.insert(it);
	}
 	Weight.resize(Vert_num, Vert_num);
	L.resize(Vert_num, Vert_num);
	Ls.resize(Vert_num, Vert_num);
	/*------------------邻接点赋值为wij,i点处赋值为1,其他非邻接赋值为0------------------*/
	typedef Eigen::Triplet<double> T_Cot;//创造三元组记录余切值
	std::vector<T_Cot> tripletList;
	for (int i = 0;i < Vert_num;i++)
	{
		BaseStruct::PVERT temp_vert = this->m_DeforPoint[i];
		BaseStruct::PHEDGE start = temp_vert->pHEdgeOut->pHEdgeNext;
		BaseStruct::PHEDGE now = start;
		double sum_weight = 0.0; //权值总和
		map<unsigned int, double>vWeight;
		do
		{
			//确定点ABC
			PVERT Target = now->pHEdgePair->pVertEnd;
			double cotValue;double cotValue1;double cotValue2;
			Eigen::Vector3d A(now->pVertEnd->x, now->pVertEnd->y, now->pVertEnd->z);
			Eigen::Vector3d B(now->pHEdgePair->pVertEnd->x, now->pHEdgePair->pVertEnd->y, now->pHEdgePair->pVertEnd->z);
			Eigen::Vector3d C(now->pHEdgeNext->pVertEnd->x, now->pHEdgeNext->pVertEnd->y, now->pHEdgeNext->pVertEnd->z);
			Eigen::Vector3d D(now->pHEdgePair->pHEdgeNext->pVertEnd->x, now->pHEdgePair->pHEdgeNext->pVertEnd->y, now->pHEdgePair->pHEdgeNext->pVertEnd->z);
			calculateCotValue(B, A, C, cotValue1);
			calculateCotValue(B, A, D, cotValue2);
			cotValue = (0.5) * (cotValue1 + cotValue2 + epsilon);
			if (Jud.find(Target) != Jud.end())
			{
				vWeight.insert(pair<unsigned int, double>(now->pHEdgePair->pVertEnd->index, cotValue));
				sum_weight += cotValue;
			}
			now = now->pHEdgePair->pHEdgePre;
		} while (start != now);
		for (map< unsigned int, double>::iterator it = vWeight.begin();it!=vWeight.end();it++)
		{
			double cotvalue =  it->second / sum_weight;
			tripletList.push_back(T_Cot(temp_vert->index, it->first, -cotvalue));
		}
		tripletList.push_back(T_Cot(temp_vert->index, temp_vert->index, 1));
	}	
		Weight.setFromTriplets(tripletList.begin(), tripletList.end());
		Ls = L = Weight;
		Ls.conservativeResize(Vert_num + Fix_num + Move_num, Vert_num);//添加锚点
	/*-------------------------------------------------------*/
	//计算Ls和LsT、LsTLs
	/*-------------------------------------------------------*/
	for (int a = 0;a < this->m_FixPoint.size();a++)
	{
		Ls.coeffRef(a + Vert_num, this->m_FixPoint[a]->index) = 1;
	}
	
	int a = 0;
	for (auto it : m_MoveIndex)
	{
		Ls.coeffRef(a +Vert_num+Fix_num, it) = 1;
		a++;
	}
	LsT = Ls.transpose();
	LsTLs = LsT * Ls;

这段代码中,第二个for循环就是计算拉普拉斯矩阵的主要部分,这部分我用到的方法基于半边结构实现的,而且写的很乱,但是逻辑就是前面介绍的余切权值计算方法。计算完网格的拉普拉斯矩阵L后,再构建加入固定点和变形点的拉普拉斯矩阵Ls,最后求转置再相乘的原因是为了让其可逆,以便等等能够求解变形后的顶点坐标。

void Laplace::BuildLsTb(STLModel* p_STL)
{
	/*----------顶点、移动点、固定点数量-------------*/
	const int move_anchors_num = Move_anchor_coor.size(); 
	const int Vext_num = this->m_DeforPoint.size();
	const int Fix_num = this->m_FixPoint.size();//固定锚点的数量
	/*-----------计算拉普拉斯坐标B-------------------*/
	vx.resize(Vext_num);vy.resize(Vext_num);vz.resize(Vext_num);//每一个顶点x,y,z坐标
	for (int i = 0;i < Vext_num;i++)
	{
		vx.coeffRef(i) = this->m_DeforPoint[i]->x;
		vy.coeffRef(i) = this->m_DeforPoint[i]->y;
		vz.coeffRef(i) = this->m_DeforPoint[i]->z;
	}
	bx = L * vx; 
	by = L * vy; 
	bz = L * vz;//变形前的拉普拉斯坐标值
	bx.conservativeResize(Vext_num + Fix_num + move_anchors_num);
	by.conservativeResize(Vext_num + Fix_num + move_anchors_num);
	bz.conservativeResize(Vext_num + Fix_num + move_anchors_num);
	// 用形变后坐标对移动锚点坐标进行赋值
	int i = 0;
	for (auto it : this->m_FixPoint)
	{
		bx.coeffRef(Vext_num + i) = it->x;
		by.coeffRef(Vext_num + i) = it->y;
		bz.coeffRef(Vext_num + i) = it->z;
		i++;
	}
	for (int i = 0;i < move_anchors_num;i++)
	{
		bx.coeffRef(Vext_num + Fix_num + i) = Move_anchor_coor[i].x;
		by.coeffRef(Vext_num + Fix_num + i) = Move_anchor_coor[i].y;
		bz.coeffRef(Vext_num + Fix_num + i) = Move_anchor_coor[i].z;
	}
	LsTbx = LsT * bx;
	LsTby = LsT * by;
	LsTbz = LsT * bz;
}

这一段代码目的很明确,就是计算网格顶点变形前的拉普拉斯坐标,并构建坐标矩阵。

void Laplace :: LaplaceDeformation(STLModel* p_STL)
{
	LsTLs.makeCompressed();
	SparseLU<SparseMatrix<double>> LU;
	LU.analyzePattern(LsTLs);
	LU.factorize(LsTLs);
	LU.info();
	vx_New = LU.solve(LsTbx);
	vy_New = LU.solve(LsTby);
	vz_New = LU.solve(LsTbz);
}

然后计算变形后的坐标值,这里我把x,y,z三个值分别拿出来进行计算,根据计算公式,最后求解Ax=b这个线性方程组,求解结果就是每个顶点对应的新坐标值,最后将其赋值给变形点就完成了整个变形。

变形效果如图,变形前:

变形后:

效果还是不错的。

标签:变形,拉普拉斯,网格,pVertEnd,int,num,Vext,now
From: https://www.cnblogs.com/nanjofish/p/18277158

相关文章

  • 鹊桥-零信任安全网格网络
    产品简介鹊桥(英文:MagpieBridge)是有安科技率先推出的国内首款分布式安全Mesh网格网络产品。通过有安科技特有的极高成功率p2p隧道打通技术,可以实现分布式网格网络,省去昂贵的专线带宽费用,创造出超越现有产品体验的超级组网产品。广泛适用于家庭游戏娱乐、远程安全办公、跨地域物联......
  • GridPane网格布局
    JavaFX的GridPane是一种基于网格的布局方式,它允许你将子节点放置在网格的行和列中。GridPane提供了高度的灵活性来创建复杂的用户界面布局。以下是GridPane的一些基本用法:添加节点到网格:使用add方法将子节点添加到特定的行和列。行和列的索引:行和列的索引都是从0开......
  • 量化交易:日内网格交易策略
    哈喽,大家好,我是木头左!本文将详细介绍日内网格交易策略的原理,并结合Python代码示例,展示如何在掘金平台上实现这一策略。策略原理日内网格交易策略的核心思想是在一天的交易时间内,通过设置多个买卖点(即网格),在价格达到这些点时自动执行交易。这种策略的优势在于能够充分利用市场......
  • 【二维差分】2132. 用邮票贴满网格图
    本文涉及知识点二维差分LeetCode2132.用邮票贴满网格图给你一个mxn的二进制矩阵grid,每个格子要么为0(空)要么为1(被占据)。给你邮票的尺寸为stampHeightxstampWidth。我们想将邮票贴进二进制矩阵中,且满足以下限制和要求:覆盖所有空格子。不覆盖任何......
  • ANSYS有限元网格划分的基本原则
    一、引言ANSYS有限元网格划分是进行数值模拟分析至关重要的一步,它直接影响着后续数值计算分析结果的精确性。网格划分涉及单元的形状及其拓扑类型、单元类型、网格生成器的选择、网格的密度、单元的编号以及几何体素。从几何表达上讲,梁和杆是相同的,从物理和数值求解上讲则是......
  • Java与服务网格(Service Mesh):构建高效微服务架构
    在微服务架构成为企业开发标准的今天,如何有效地管理众多微服务之间复杂的通信成为了一个挑战。服务网格作为一种解决方案,它通过提供一个专门的基础设施层来处理服务间通信,从而使得应用开发更加专注于业务逻辑而非通信细节。本文将介绍服务网格的基本概念,探讨其在Java环境中的应......
  • 界面控件DevExpress WinForms垂直&属性网格组件 - 拥有更灵活的UI选择(一)
    DevExpressWinForms垂直&属性网格组件旨在提供UI灵活性,它允许用户显示数据集中的单个行或在其90度倒置网格容器中显示多行数据集。另外,用户可以把它用作一个属性网格,就像在VisualStudioIDE中那样。P.S:DevExpressWinForms拥有180+组件和UI库,能为WindowsForms平台创建具有......
  • 界面控件DevExpress WinForms垂直&属性网格组件 - 拥有更灵活的UI选择(一)
    DevExpressWinForms垂直&属性网格组件旨在提供UI灵活性,它允许用户显示数据集中的单个行或在其90度倒置网格容器中显示多行数据集。另外,用户可以把它用作一个属性网格,就像在VisualStudioIDE中那样。P.S:DevExpressWinForms拥有180+组件和UI库,能为WindowsForms平台创建具有影响......
  • Star-ccm+网格划分技巧之网格类型及适用场合
    大家在进行网格划分时有没有遇到这样的情况:1、画网格时间很长;2、画网格到中途发生错误,这时候就要用到并行网格划分(ParallelMeshing)。并行网格划分(ParallelMeshing)就是使用多个内核数来加速网格生成,同时比单个内核创建更大的网格。在对大型零件进行网格划分时,此功能特别有......
  • HTML5+CSS3+JS小实例:网格图库
    实例:网格图库技术栈:HTML+CSS+JS效果:源码:【HTML】<!DOCTYPEhtml><htmllang="zh-CN"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width,initial-scale=1.0">......