OCCT尖点采样算法: 如何赋予 完美的“尖点” 一个半径

double xxx::CalculateRadiusFromThreePoints(const gp_Pnt& p1, const gp_Pnt& p2, const gp_Pnt& p3)
{
	// 检查三点是否重合或距离过近
	if (p1.IsEqual(p2, Precision::Confusion()) || p2.IsEqual(p3, Precision::Confusion()) || p1.IsEqual(p3, Precision::Confusion()))
	{
		return 0.0;
	}

	gp_Vec v1(p1, p2);
	gp_Vec v2(p1, p3);

	// 检查三点是否共线
	if (v1.Magnitude() < Precision::Confusion() || v2.Magnitude() < Precision::Confusion() || v1.IsParallel(v2, Precision::Angular()))
	{
		return 0.0;
	}

	double a = p1.Distance(p2);
	double b = p2.Distance(p3);
	double c = p3.Distance(p1);

	double s = (a + b + c) / 2.0;
	double area_squared = s * (s - a) * (s - b) * (s - c);

	if (area_squared <= Precision::SquareConfusion()) {
		return 0.0;
	}
	double area = sqrt(area_squared);
	return (a * b * c) / (4.0 * area);
}


bool xxx::AnalyzeSectionProfileBySpecification(
	const TopoDS_Shape& face,
	const double z_level,
	double& out_leadingEdgeRadius,
	double& out_trailingEdgeRadius,
	double& out_maxChordLength,
	double& out_twistAngle,
	double& out_maxThickness,
	TopoDS_Shape& out_sectionProfile)
{

	out_leadingEdgeRadius = 0.0;
	out_trailingEdgeRadius = 0.0;
	out_maxChordLength = 0.0;
	out_twistAngle = 0.0;
	out_maxThickness = 0.0;
	out_sectionProfile.Nullify();

	// 1. 输入验证
	if (face.IsNull() || face.ShapeType() != TopAbs_FACE) {
		std::cerr << "错误: 输入的不是一个有效的面。" << std::endl;
		return false;
	}

	try {
		// 2. 创建并执行截面操作
		gp_Pln sectionPlane(gp_Pnt(0, 0, z_level), gp_Dir(0, 0, 1));
		BRepAlgoAPI_Section sectionBuilder(face, sectionPlane);
		sectionBuilder.Build();
		if (!sectionBuilder.IsDone() || sectionBuilder.Shape().IsNull()) {
			std::cout << "信息: 在z=" << z_level << "高度处截面为空或创建失败。" << std::endl;
			return false;
		}

		// 3. 构建轮廓线
		BRepBuilderAPI_MakeWire wireMaker;
		TopExp_Explorer edgeExplorer(sectionBuilder.Shape(), TopAbs_EDGE);
		if (!edgeExplorer.More()) return false;
		for (; edgeExplorer.More(); edgeExplorer.Next()) {
			wireMaker.Add(TopoDS::Edge(edgeExplorer.Current()));
		}
		if (!wireMaker.IsDone()) return false;
		TopoDS_Wire sectionProfile = wireMaker.Wire();
		out_sectionProfile = sectionProfile;

		// 4. 离散化轮廓线获取采样点
		BRepAdaptor_CompCurve curveAdaptor(sectionProfile, Standard_True);
		const int SAMPLE_COUNT = 501;
		GCPnts_UniformAbscissa discretizer(curveAdaptor, SAMPLE_COUNT);
		if (!discretizer.IsDone()) return false;
		std::vector<gp_Pnt> profilePoints;
		profilePoints.reserve(SAMPLE_COUNT);
		for (int i = 1; i <= SAMPLE_COUNT; ++i) {
			profilePoints.push_back(curveAdaptor.Value(discretizer.Parameter(i)));
		}
		if (profilePoints.empty()) return false;

		// 5. 找到前缘点和后缘点的索引和坐标
		int leadingEdgeIndex = 0;
		int trailingEdgeIndex = 0;
		for (size_t i = 1; i < profilePoints.size(); ++i) {
			if (profilePoints[i].X() < profilePoints[leadingEdgeIndex].X()) leadingEdgeIndex = i;
			if (profilePoints[i].X() > profilePoints[trailingEdgeIndex].X()) trailingEdgeIndex = i;
		}
		const gp_Pnt& leadingEdgePoint = profilePoints[leadingEdgeIndex];
		const gp_Pnt& trailingEdgePoint = profilePoints[trailingEdgeIndex];

		// 6. 计算最大弦长
		out_maxChordLength = trailingEdgePoint.Distance(leadingEdgePoint); // 简化为前后缘点距离,可按需改回暴力法

		// 7. 计算弦线扭转角
		double dx = trailingEdgePoint.X() - leadingEdgePoint.X();
		double dy = trailingEdgePoint.Y() - leadingEdgePoint.Y();
		out_twistAngle = atan2(dy, dx) * 180.0 / M_PI;

		// 8. 计算半径 - 鲁棒混合算法
		try {
			auto calculateRadius = [&](const gp_Pnt& extremePoint, int extremePointIndex) -> double
				{
					double radius = 0.0;

					TopoDS_Edge bestEdge;
					double bestParam = 0.0;
					double minDistance = -1.0;
					TopExp_Explorer explorer(out_sectionProfile, TopAbs_EDGE);
					for (; explorer.More(); explorer.Next()) {
						TopoDS_Edge currentEdge = TopoDS::Edge(explorer.Current());
						TopLoc_Location loc; Standard_Real first, last;
						Handle(Geom_Curve) curve = BRep_Tool::Curve(currentEdge, loc, first, last);
						if (curve.IsNull()) continue;
						GeomAPI_ProjectPointOnCurve projector(extremePoint, curve, first, last);
						if (projector.NbPoints() > 0) {
							double currentDist = projector.LowerDistance();
							if (minDistance < 0 || currentDist < minDistance) {
								minDistance = currentDist;
								bestEdge = currentEdge;
								bestParam = projector.LowerDistanceParameter();
							}
						}
					}
					if (!bestEdge.IsNull() && minDistance < 1e-6) {
						TopLoc_Location loc; Standard_Real first, last;
						Handle(Geom_Curve) bestCurve = BRep_Tool::Curve(bestEdge, loc, first, last);
						if (!bestCurve.IsNull()) {
							gp_Pnt pt; gp_Vec d1, d2;
							bestCurve->D2(bestParam, pt, d1, d2);
							double d1_mag_sq = d1.Dot(d1);
							if (d1_mag_sq > Precision::SquareConfusion()) {
								double d1_mag = sqrt(d1_mag_sq);
								double curvature = d1.Crossed(d2).Magnitude() / (d1_mag_sq * d1_mag);
								if (curvature > Precision::Confusion()) {
									radius = 1.0 / curvature;
								}
							}
						}
					}


					if (radius < Precision::Confusion()) {
						std::cout << "信息: 解析法计算半径失败,回退至三点近似法。" << std::endl;
						if (profilePoints.size() >= 3) {
							// 寻找顺序排列的邻近点,并处理环绕情况
							const int step = 2; // 可调整的步长,避免点过于密集
							int prevIndex = (extremePointIndex - step + profilePoints.size()) % profilePoints.size();
							int nextIndex = (extremePointIndex + step) % profilePoints.size();

							radius = CalculateRadiusFromThreePoints(profilePoints[prevIndex], extremePoint, profilePoints[nextIndex]);
						}
					}
					return radius;
				};

			out_leadingEdgeRadius = calculateRadius(leadingEdgePoint, leadingEdgeIndex);
			out_trailingEdgeRadius = calculateRadius(trailingEdgePoint, trailingEdgeIndex);
		}
		catch (const Standard_Failure& e) {
			std::cerr << "警告: 半径计算时发生OCCT异常。错误: " << e.GetMessageString() << std::endl;
		}

		// 9. 计算最大厚度

		gp_Vec maxChordVec(leadingEdgePoint, trailingEdgePoint);
		gp_Dir maxChordDir = maxChordVec.Normalized();
		double maxDistUpper = 0.0;
		double maxDistLower = 0.0;
		gp_Dir perpDir = maxChordDir.Crossed(gp_Dir(0, 0, 1));
		for (const auto& pt : profilePoints) {
			gp_Vec vecFromChord(leadingEdgePoint, pt);
			double perpDistance = vecFromChord.Dot(perpDir);
			if (perpDistance > maxDistUpper) {
				maxDistUpper = perpDistance;
			}
			else if (perpDistance < maxDistLower) {
				maxDistLower = perpDistance;
			}
		}
		out_maxThickness = maxDistUpper - maxDistLower;

		return true;
	}
	catch (const Standard_Failure& e) {
		std::cerr << "计算截面特征参数时发生OpenCASCADE错误: " << e.GetMessageString() << std::endl;
		return false;
	}
	catch (...) {
		std::cerr << "计算截面特征参数时发生未知错误。" << std::endl;
		return false;
	}
}

从数学上讲,一个完美的“尖点”确实没有半径(或者说曲率是无穷大,半径为0)。我们的算法之所以能算出一个值,并不是因为它“算出”了那个尖点的半径,而是因为它非常聪明地执行了一个**“B计划”**。

您可以把我们的算法想象成一个有两套方案的工程师:

A计划:精密测量法 (解析法)

这是首选方案。它就像一个高精度的激光测量仪。

  1. 它会精确地找到截面线上X坐标最小的那个点(我们称之为前缘点)。
  2. 然后,它会调用OpenCASCADE的数学库,去问这条曲线:“在你(曲线)经过这个前缘点时,你的精确曲率是多少?”

在尖点处,这个A计划会失败!

为什么呢?因为在一个尖点,曲线是“突然转折”的,它在那个点上并不平滑(数学上叫“不可导”或“二阶导数不存在”)。所以,当你用精密仪器去测量时,仪器会报错,因为它没法在那一点上算出一个有效的曲率。

程序上表现出来就是,计算出的曲率是0,所以半径也是0。

B计划:聪明近似法 (三点画圆的回退方案)

当我们的工程师发现A计划失败了(算出的半径是0),他立刻启动了B计划。这个计划的思路是:

“既然我不能直接测量这个尖点,那我就看看它附近的形状有多‘圆’。”

它是这样做的:

  1. 找到三个点

    • 点2: 就是那个尖尖的前缘点本身。
    • 点1: 在离散化的点集中,找到前缘点前面紧挨着的那个点。
    • 点3: 在离散化的点集中,找到前缘点后面紧挨着的那个点。
  2. 用三个点画一个圆
    现在我们有了三个顺序排列的点。只要这三个点不完全在一条直线上,那么有且仅有一个圆能同时穿过这三个点。

  3. 测量这个圆的半径
    CalculateRadiusFromThreePoints 这个函数的工作,就是计算出这个穿过三个点的虚拟圆的半径。

结论

所以,这个算法最终给出的“前缘半径”:

  • 如果前缘是平滑的:它就是A计划算出的、那个点的真实、精确的曲率半径。
  • 如果前缘是个尖点:它就是B计划算出的、那个由尖点和它前后两个邻近点构成的虚拟圆的半径。这个半径非常好地近似了那个尖锐区域的“圆润程度”。

简单来说:它算的不是尖点“本身”的半径,而是对尖点附近曲线弯曲程度的一个非常合理、非常稳定的近似值。 这就是为什么这个算法即使面对尖点也能给出有意义的结果。

评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

心瞳几何原语

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值