多边形分割成若干凸多边形(NavMesh的初步形成)

多边形分割成若干凸多边形(NavMesh的初步形成)

本文基于 Arkin, Ronald C.的论文 “Path planning for a vision-based autonomous robot”.
论文链接 Path planning for a vision-based autonomous robot
部分计算几何的算法基于Computational Geometry in C
其源码可以参考:orourke-compc

现任佐治亚理工教授 Ronald C. Arkin,在1986年时发表了一篇叫做Path planning for a vison-based autonomous robot的报告,隶属robotics领域。文章提出了Meadow Map的方法以长时间存储地图。Meadow Map为现代Navmesh系统的雏形,提出了以下核心观点

  • 使用凸多边形构建可行走区域
  • 对生成的凸多边形集合,以其公共边中点为寻路节点,使用A*进行寻路
  • 使用路径改进算法,对A*结果进行改良

在本文中,我们主要探讨:通过Arkin教授的方法去实现给定一个任意多边形,得到其分割而成的若干凸多边形。

方法步骤:

  1. 对于现有的多边形 P P P,若其为凸多边形,则结束,否则进入步骤2
  2. P P P中找到一个的点,如图中点 A A A
  3. 由点 A A A去找一个同在 P P P中的另一不相邻点,如图中 D , E , H D, E, H D,E,H
  4. 选择其中一个完全在 P P P内部的线段,用于将 P P P分割成两个子多边形 P 1 , P 2 P_1, P_2 P1,P2
  5. P 1 , P 2 P_1, P_2 P1,P2做同样的操作

请添加图片描述

完全在 P P P内部(例如图中线段 A D AD AD)的判断方式:

  • 在角的内侧(对于 A D AD AD来说就是要在 ∠ J A B ∠JAB JAB ∠ C D E ∠CDE CDE的内侧)
  • 不与任何一条非相邻边相交(对于 A D AD AD来说就是不与 A J , A B , D C , D E AJ, AB, DC, DE AJ,AB,DC,DE以外的边相交)

在上图中,线段 A G , A H AG, AH AG,AH都是反例,要被排除掉,我们可以在 A C , A D , A E , A F AC, AD, AE, AF AC,AD,AE,AF中选择一条去分割多边形 P P P

关于在角的内侧,可以看这张图:
请添加图片描述

图中对角线 A C AC AC位于 ∠ H A B ∠HAB HAB ∠ B C D ∠BCD BCD外侧,而对角线 A D AD AD位于 ∠ H A B ∠HAB HAB外侧和 ∠ C D E ∠CDE CDE内侧。

对于这个算法来说,其难点就在于:1. 如何找到一个凹的点,2. 如何找到一个完全在内部的对角线。

1. 如何找到一个凹的点

我们按照逆时针顺序枚举多边形 P P P上的顶点,如下图。当我们枚举到点A时,记录其上一个节点为 A − A^- A,其下一个节点为 A + A^+ A+。通过判断 A + A^+ A+ A − A \boldsymbol{A^-A} AA 的关系,当 A + A^+ A+ A − A \boldsymbol{A^-A} AA的右侧时,则说明点 A A A是一个凹的点。

请添加图片描述
我们可以通过向量的叉积来进行左右方向的判断,设 A − A = ( x 1 , y 1 ) , A − A + = ( x 2 , y 2 ) \boldsymbol{A^-A} = (x_1, y_1), \boldsymbol{A^-A^+} = (x_2, y_2) AA=(x1,y1),AA+=(x2,y2) A − A × A − A + = x 1 y 2 − x 2 y 1 \boldsymbol{A^-A} \times \boldsymbol{A^-A^+} = x_1y_2 - x_2y_1 AA×AA+=x1y2x2y1 。这是一个在 z z z轴上的向量,即 ( 0 , 0 , x 1 y 2 − x 2 y 1 ) (0, 0, x_1y_2-x_2y_1) (0,0,x1y2x2y1)。当 A + A^+ A+位于 A − A \boldsymbol{A^-A} AA左侧时, x 1 y 2 − x 2 y 1 > 0 x_1y_2-x_2y_1>0 x1y2x2y1>0,否则 x 1 y 2 − x 2 y 1 < 0 x_1y_2-x_2y_1<0 x1y2x2y1<0,当 A + A^+ A+ A − A \boldsymbol{A^-A} AA共线时, x 1 y 2 − x 2 y 1 = 0 x_1y_2-x_2y_1=0 x1y2x2y1=0

/// <summary>
/// 判断点c与向量ab的位置关系
/// </summary>
/// <returns>c在ab左侧,返回1;c在ab右侧,返回-1;c与ab共线,返回0。</returns>
private static int AreaSign(Vector2 a, Vector2 b, Vector2 c)
{
	Vector2 ab = b - a;
	Vector2 ac = c - a;
	
	float area = ab.Cross(ac);

	if (area > EPS) return 1; // c 在 ab 左侧
	else if (area < -EPS) return -1; // c 在 ab 右侧
	else return 0; //a, b, c共线
}

/// <summary>
/// 叉积
/// </summary>
/// <returns>二维向量叉积的模</returns>
public static float Cross(this Vector2 a, Vector2 b)
{
	return a.x * b.y - a.y * b.x;
}

/// <summary>
/// 用于判断点<paramref name="c"/> 是否位于向量ab的左侧。
/// </summary>
/// <returns><paramref name="c"/>位于向量ab左侧为true,否则为false</returns>
public static bool Left(Vector2 a, Vector2 b, Vector2 c)
{
	return AreaSign(a, b, c) > 0;
}

/// <summary>
/// 用于判断点<paramref name="c"/> 是否位于向量ab的左侧或在ab上。
/// </summary>
/// <returns><paramref name="c"/>位于向量ab左侧或在ab上为true,否则为false</returns>
public static bool LeftOn(Vector2 a, Vector2 b, Vector2 c)
{
	return AreaSign(a, b, c) >= 0;
}

/// <summary>
/// 判断点<paramref name="a"/>, <paramref name="b"/>, <paramref name="c"/>是否共线
/// </summary>
/// <returns>三点共线则返回true,否则false</returns>
public static bool Collinear(Vector2 a, Vector2 b, Vector2 c)
{
	return AreaSign(a, b, c) == 0;
}

2. 如何找到一个完全在内部的对角线

对于一个凹的顶点 A A A,我们要逐个枚举多边形 P P P中不与 A A A相邻的其他顶点,然后判断其连线是否是1. 在角的内侧,2. 不与任何非相邻的边相交。

判断是否在角的内侧

如下图a、图b,当 ∠ A − A A + ∠A^-AA^+ AAA+是个劣角(即小于180°的角)时,要判断 A D AD AD是否在角内部,仅需保证 D D D同时在 A − A \boldsymbol{A^-A} AA A A + \boldsymbol{AA^+} AA+的左侧。(这里一定要注意我们 A − , A , A + A^-,A,A^+ A,A,A+是逆时针排列的)

对于图c, A D AD AD A − A \boldsymbol{A^-A} AA的左侧,但是在 A A + \boldsymbol{AA^+} AA+的右侧,故而其在角的外侧。

在图d中,我们可以看到两个浅蓝色框分别代表 A − A \boldsymbol{A^-A} AA A A + \boldsymbol{AA^+} AA+的左侧,则重叠的深色区域就是勇仕在两个向量的左侧的区域,即角的内侧。

请添加图片描述

那当 ∠ A − A A + ∠A^-AA^+ AAA+为优角(即大于180°小于360°的角)呢?其实可以反向思考以下,这个时候如果 A D AD AD ∠ A − A A + ∠A^-AA^+ AAA+对应的劣角中,岂不就说明 A D AD AD在优角 ∠ A − A A + ∠A^-AA^+ AAA+外侧了?

/// <summary>
/// 对角线ab是否在∠A内部
/// </summary>
/// <returns>ab在∠A内部返回true,否则false</returns>
public static bool InCone(Vertex a, Vertex b)
{
	Vertex a0 = a.Prev, a1 = a.Next;
	
	// 若∠A为劣角(<180°)
	if (LeftOn(a0.Position, a.Position, a1.Position))
	{
		return Left(a.Position, b.Position, a0.Position)
				&& Left(b.Position, a.Position, a1.Position);
	}

	return !(LeftOn(a.Position, b.Position, a1.Position)
			&& LeftOn(b.Position, a.Position, a0.Position));
}

在上述代码中,引入了一个新类型Vertex,其定义如下:

public class Vertex
{
	public Vector2 Position;
	public Vertex Prev;
	public Vertex Next;
}

目的是将多边形 P P P的所有顶点,逆时针方向连接起来。

判断是否与其他边相交

我们先来看一下两个线段的位置关系都有哪些,如下图:
请添加图片描述

对于严格相交这种情况,我们只需要确保 a , b a, b a,b在线段 c d cd cd的两侧,并且 c , d c, d c,d在线段 a b ab ab的两侧即可。如何判断两侧?这就又回到刚才我们判断点在向量左右那一步了,我们可以直接调用刚才写好的Left方法。

对比相交不相交这两种情况,其区别是三点共线时,共线的点是不是在线段上,即当 b c d bcd bcd三点共线时,要保证 b b b 位于 c d cd cd之中,就也属于相交的情况。而三点共线也可以直接用刚才写好的Collinear()方法,我们需要再写一个Between()方法去判断当Collinear()满足时,是否满足 b b b 位于 c d cd cd之中这种情况。

/// <summary>
/// 线段ab与线段cd严格相交
/// </summary>
/// <remarks>严格相交: 不包含三点共线的情况,例如 T 形</remarks>
/// <returns>ab与cd相交,返回true,否则false</returns>
public static bool IntersectStrictly(Vector2 a, Vector2 b, Vector2 c, Vector2 d)
{
	if (Collinear(a, b, c) || Collinear(a, b, d)
		|| Collinear(c, d, a) || Collinear(c, d, b))
		return false;

	return (Left(a, b, c) ^ Left(a, b, d))
			&& (Left(c, d, a) ^ Left(c, d, b));
}

/// <summary>
/// 线段ab与线段cd相交
/// </summary>
/// <returns>ab与cd相交则返回true,否则false</returns>
public static bool Intersect(Vector2 a, Vector2 b, Vector2 c, Vector2 d)
{
	if (IntersectStrictly(a, b, c, d))
		return true;

	if (Between(a, b, c) || Between(a, b, d)
		|| Between(c, d, a) || Between(c, d, b))
		return true;

	return false;
}

/// <summary>
/// 判断在abc共线时,<paramref name="c"/> 是否在 <paramref name="a"/>, <paramref name="b"/> 中间
/// </summary>
/// <returns><paramref name="c"/>在<paramref name="a"/>, <paramref name="b"/>中间则返回true,否则返回false,abc不共线直接返回false</returns>
public static bool Between(Vector2 a, Vector2 b, Vector2 c)
{
	if (!Collinear(a, b, c)) return false;

	if (a.x == b.x)
		return (a.y <= c.y && c.y <= b.y)
				|| (b.y <= c.y && c.y <= a.y);
	else
		return (a.x <= c.x && c.x <= b.x)
				|| (b.x <= c.x && c.x <= a.x);
}

解决了这两个问题后,我们可以用以下代码,解决问题:[[#2. 如何找到一个完全在内部的对角线]]

/// <summary>
/// 对角线ab是否与非点 <paramref name="a"/>, <paramref name="b"/> 相邻的边相交
/// </summary>
/// <returns>有相交返回true,否则false</returns>
public static bool DiagonalWithoutIntersect(Vertex a, Vertex b)
{
	Vertex current = a.Next;
	while (current.Next != a)
	{
		Vertex next = current.Next;

		if (current != a && current != b
			&& next != a && next != b
			&& Intersect(current.Position, next.Position, a.Position, b.Position))
			return false;

		current = next;
	}
	
	return true;
}

/// <summary>
/// 对角线ab是否为内部对角线
/// </summary>
/// <remarks>内部对角线: 指对角线完全在多边形的内部</remarks>
/// <returns>ab为内部对角线返回true,否则false</returns>
public static bool Diagonal(Vertex a, Vertex b)
{
	return InCone(a, b) && InCone(b, a) && DiagonalWithoutIntersect(a, b);
}

递归求解

由于我们的Vertex类型中记录了点的前驱Prev和后继Next,所以其实我们只需要拿到多边形中的任意一个点,就可以不断地通过Next获取到多边形中所有的顶点。所以我们可以定义如下多边形polygon类型:

public class Polygon
{
	public Vertex StartVertex => _startVertex;
	
	private Vertex _startVertex;

	#region Methods...
}

然后定义方法Split()去不断的完成本文最开头的5条方法步骤。不过在这里还有几个细节要处理。

请添加图片描述
可以看到,在以 A C AC AC为对角线,分割 P P P P 1 , P 2 P1,P2 P1,P2后,需要补充两个点 A ′ , C ′ A^\prime,C^\prime A,C。这一部分主要是各种链表的操作(因为我们用Prev,Next等将Vertex连接起来,其实就是一个双向循环链表)。在如下代码中,我标注了每个变量对应上图中的点,方便对照看去理解。

/// <summary>
/// 将Polygon分割成若干凸多边形,使用 divide-and-conquer 方法;
/// 该方法基于 Arkin, Ronald C.的论文
/// "Path planning for a vision-based autonomous robot".
/// </summary>
/// <returns>凸多边形数组,即此多边形的分割结果。</returns>
public List<Convex> Split()
{
	List<Convex> convexList = new List<Convex>();
	
	// 寻找一个凹的顶点 concave 对应上图的C点
	Vertex concave = FindConcaveVertex();

	if (concave == null)
	{
		// 没找到凹的顶点,说明this本身就是凸多边形
		convexList.Add(new Convex(this));
		return convexList;
	}
	else
	{
		// 找一个poly上的点,和concave组一个内部对角线,将poly分割成两部分
		Vertex splitVertex = null;
		Vertex current = _startVertex;
		do
		{
			if (BasicOperations.Diagonal(current, concave))
			{
				splitVertex = current;
				break;
			}
			current = current.Next;
		} while (current != _startVertex);

		// 这种情况理论上不会发生 //TODO: 报个Warning
		if (splitVertex == null) { return null; }
		// splitVertex对应上图中的A点
		
		// 将此多边形拆分成两个子多边形
		Vertex splitVertexNext = splitVertex.Next; // 对应上图B点
		Vertex concavePrev = concave.Prev; // 对应上图B点

		// 将AC连接起来
		splitVertex.Next = concave;
		concave.Prev = splitVertex;

		// A点的补充点 A'
		Vertex suppliedSplitVertex = new Vertex(splitVertex.Position);
		// C点的补充点 C'
		Vertex suppliedConcave = new Vertex(concave.Position);

		// 将A'C'连接起来
		suppliedSplitVertex.Prev = suppliedConcave;
		suppliedConcave.Next = suppliedSplitVertex;

		// 将A'和B连接起来,C'和B连接起来
		suppliedSplitVertex.Next = splitVertexNext;
		splitVertexNext.Prev = suppliedSplitVertex;
		suppliedConcave.Prev = concavePrev;
		concavePrev.Next = suppliedConcave;

		// 生成P1,P2
		Polygon childPolygon1 = new Polygon(concave);
		Polygon childPolygon2 = new Polygon(suppliedConcave);

		// 递归求解
		convexList.AddRange(childPolygon1.Split());
		convexList.AddRange(childPolygon2.Split());
	}

	return convexList;
}

这里我们引入了一个新类型Convex表示凸多边形。当然我们可以让Convex类继承自polygon,不过我们并不需要Convex包含太多方法(例如Split()等),故而我们单独开一个类表示它。

public class Convex
{
	public Vertex StartVertex => _startVertex;

	private Vertex _startVertex;
}

孔洞的处理

到此为止其实我们已经可以很好的将一个任意多边形分割成若干凸多边形了,但是我们忽略了一个很重要的事,就是中间有孔的多边形应该怎么处理?

对于孔洞的处理算法,来源于 Recast Navigation

首先孔也是一个多边形,我们记为 H o l e Hole Hole

  1. H o l e Hole Hole中选择一个顶点(如下图 F F F
  2. P P P上选择一个顶点(如下图 A A A),且 A F AF AF不与 P P P H o l e Hole Hole上的任意非 A , F A, F A,F相邻边相交

请添加图片描述
不合法的选择,例如 F C FC FC,会与 H o l e Hole Hole上的边 I H IH IH相交;例如 G D GD GD,会与 P P P上的边 B C BC BC相交。

  1. 沿着选择的连线(如上图 A F AF AF),将 P P P H o l e Hole Hole融合

这里要时刻记住我们的多边形,顶点都是按照逆时针顺序连接的, H o l e Hole Hole也不例外。然而当我们融合时,需要顺时针将 H o l e Hole Hole上的顶点添加到 P P P中,这样得到的 P P P的顶点才满足逆时针连接的条件。所以这里我们需要经过一个类似反转链表的过程。当然和之前相同的,这里我们也需要补充两个点 A ′ , F ′ A^\prime,F^\prime A,F

/// <summary>
/// 处理中间包含孔的多边形
/// </summary>
public void MergeHole(Polygon hole)
{
	if (!HasInnerPolygon(hole)) // hole必须在多边形内部才有必要去合并
		return; 

	bool hasFound = false;
	Vertex linkVertPoly = null, linkVertHole = null;
	Vertex holeVertex = hole.StartVertex;
	
	// 在P和hole上找到两个点,用其连线合并P和hole 
	do
	{
		Vertex vert = StartVertex;
		do
		{
			// vert - holeVertex 线段,不与P或者hole上的任何一个非相邻边相交
			if (BasicOperations.DiagonalWithoutIntersect(vert, holeVertex)
				&& BasicOperations.DiagonalWithoutIntersect(holeVertex, vert))
			{
				linkVertPoly = vert; // 对应上图中的点A
				linkVertHole = holeVertex; // 对应上图中的点F
				hasFound = true;
				break;
			}
			vert = vert.Next;
		} while (vert != StartVertex);

		if (hasFound) break;
		holeVertex = holeVertex.Next;
	} while (holeVertex != hole.StartVertex);

	// 将P与hole融合
	if (hasFound)
	{
		// 将A和F连接起来
		Vertex linkVertPolyNext = linkVertPoly.Next; // 对应上图点B
		linkVertPoly.Next = linkVertHole;
		Vertex linkVertHolePrev = linkVertHole.Prev; // 对应上图点I
		linkVertHole.Prev = linkVertPoly;

		// 反转链表
		// 在上图中可以看到F->I->H->G变成了F->G->H->I
		// 但是在实现的时候,并不会更换顶点,而是将Next指针和Prev指针做个交换
		Vertex prev = linkVertHole, current = linkVertHolePrev;
		while (current != linkVertHole)
		{
			Vertex currentPrev = current.Prev;
			prev.Next = current;
			current.Prev = prev;

			prev = current;
			current = currentPrev;
		}

		// 补充linkVertHole重合节点, 对应上图的点F'
		var suppliedLinkVertHole = new Vertex();
		suppliedLinkVertHole.Position = linkVertHole.Position;
		// 连接I和F'
		suppliedLinkVertHole.Prev = prev;
		prev.Next = suppliedLinkVertHole; 

		// 补充linkVertPoly重合节点, 对应上图的点A'
		var suppliedLinkVertPoly = new Vertex();
		suppliedLinkVertPoly.Position = linkVertPoly.Position;
		// 连接A'F'
		suppliedLinkVertPoly.Prev = suppliedLinkVertHole;
		suppliedLinkVertHole.Next = suppliedLinkVertPoly;
		// 连接A'B
		suppliedLinkVertPoly.Next = linkVertPolyNext;
		linkVertPolyNext.Prev = suppliedLinkVertPoly;
	}
}

在Unity中展示

当我们完成了全部的计算任务后,就可以在Unity中,将多边形绘制出来了。在这里我利用Unity自带的lineRenderer进行线段的绘制。

请添加图片描述

再上图中,可以在Inspector窗口编辑多边形 P P P以及孔洞 H o l e Hole Hole的个顶点坐标(注意要按照逆时针顺序),用黑色的线绘制出了 P P P,用红色的线绘制出了 H o l e Hole Hole,蓝色的线即分割线。注意有一条黑色的线连接了 P P P H o l e Hole Hole,这即是我们选择来融合 P P P H o l e Hole Hole的连接线

补充知识

判断一个多边形是否在另一个多边形内部

MergeHole()方法中有一段代码:

if (!HasInnerPolygon(hole)) // hole必须在多边形内部才有必要去合并
	return; 

进行了两个多边形相容性的判断,当 h o l e hole hole完全被 P P P包含时,返回true,并进行后续的融合操作。

而这里如何判断一个多边形是否被另一个多边形包含,是通过判断 h o l e hole hole中的每个顶点是否在 P P P中,若 h o l e hole hole的全部顶点都在 P P P中,则认为 h o l e hole hole P P P包含。由此将问题转化为了:如何判断一个顶点是否在一个多边形中。

解决这个问题有一个很经典的方法:

引射线法: 从目标点出发引一条射线(我们可以取水平向右的射线),看这条射线和多边形所有边的交点数目。如果有奇数个交点,则说明在内部,如果有偶数个交点,则说明在外部。

看上去很好实现,只需要 O ( n ) O(n) O(n)枚举多边形的每条边,然后判断是否相交即可,但是其实还是有不少细节的。

例如下图中这种过顶点的情况,如果不确定好统计规则,很容易顶点两条边都被记录一次,导致本在内部被判定为在外部(例如最下方的红点,经过多边形的顶点,会被记为经过了两条边)

请添加图片描述

此外,线段与射线的关系可以用下图展示:

请添加图片描述

我们可以很容易的判断线段在射线的上、下、左、重合/平行。为了处理前面说的过顶点的情况,我们认为经过线段下侧端点的射线与线段不相交。而剩下的就是计算一下线段上对应射线所在 y y y坐标点的 x x x坐标值,和射线起点的 x x x坐标值作比较,如果大于射线起点的 x x x坐标,则说明有交点。

/// <summary>
/// 从 <paramref name="raySource"/>发出的水平向右的射线,是否与线段ab相交
/// </summary>
/// <returns>相交则返回true,否则返回false</returns>
public static bool HorizontalRayIntersectSegment(Vector2 raySource, Vector2 a, Vector2 b)
{
	if (a.y == b.y) return false;                               // 线段与射线平行、重合
	if (a.y > raySource.y && b.y > raySource.y) return false;   // 线段在射线上方
	if (a.y < raySource.y && b.y < raySource.y) return false;   // 线段在射线下方
	if ((b.y < a.y && b.y == raySource.y)
		|| (a.y < b.y && a.y == raySource.y)) return false;     // 射线与下方端点相交
	if (a.x < raySource.x && b.x < raySource.x) return false;   // 线段在射线左边

	var x = b.x - (b.x - a.x) * (b.y - raySource.y) / (b.y - a.y); // 求交点x坐标
	return x >= raySource.x;
}

补充阅读

留白- Recast Navigation 源码剖析 01 - Meadow Map论文解析与实验

补充内容

  1. FindConcaveVertex 函数实现
        /// <summary>
        /// 在此多边形种寻找第一个凹的点位。
        /// </summary>
        /// <returns>返回凹的点 (Vertex) </returns>
        public Vertex FindConcaveVertex()
        {
            Vertex current = _startVertex;
            do
            {
                if (!BasicOperations.LeftOn(current.Prev.Position, current.Position, current.Next.Position))
                    return current;
                current = current.Next;
            } while (current != _startVertex);

            return null;
        }
  • 7
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
### 回答1: 将一个二维多边形分割圆弧的线段可以使用三角剖分的方法实现。三角剖分是将多边形划分为若干个三角形的过程。 首先,需要将多边形的边缘拆分线段,然后连接多边形的顶点,形成一个网格。接下来,按照某种规则对网格进行划分,使每个三角形包含一个顶点和两条边,从而将多边形划分若干个三角形。 这种方法能将多边形分割线段和圆弧,并且可以保证剖分后的三角形都是凸的,这样就能保证剖分的正确性。 具体的实现步骤可以参考一些现有的三角剖分算法,例如 Delaunay 三角剖分、Bowyer-Watson算法。 ### 回答2: 将一个二维多边形分割圆弧的线段,需要考虑多边形的形状和曲线的平滑度。 首先,我们需要确定多边形的边数和顶点坐标。根据多边形的形状,选择合适的点作为切点,以及切点所在边的曲线半径。然后,以切点为圆心,曲线半径为半径,描绘出圆弧。 为了保证曲线的平滑度,可以使用贝塞尔曲线或B样条曲线来描绘圆弧的线段。贝塞尔曲线通过控制点来确定曲线的形状,而B样条曲线通过节点和控制点来定义曲线。 在描绘圆弧线段时,可以选择合适的节点和控制点,以获得满足要求的曲线形状。节点可以位于多边形的顶点上,而控制点可以通过平均计算多边形的顶点来获得。其中,贝塞尔曲线的控制点数量取决于曲线的次数,而B样条曲线的控制点数量取决于节点的数量。 最后,使用算法将节点和控制点连接起来,形成平滑的圆弧线段。可以使用插值方法,如二次Bézier插值、三次Bézier插值或B样条插值等来实现曲线的平滑连接。 综上所述,将一个二维多边形分割圆弧的线段,需要考虑多边形的形状和曲线的平滑度,并选用合适的算法和描绘方法来实现。 ### 回答3: 将一个二维多边形分割圆弧的线段,可以通过以下步骤实现。 首先,选择多边形的一个顶点作为起始点,然后选择相邻的两个顶点作为一条边的起点和终点。 接下来,计算这条边的中点,这个中点将作为圆弧的起点和终点之间的连接点。 然后,计算这条边的长度,根据需求选择合适的圆弧半径。 利用这个半径,确定圆弧的起点坐标和终点坐标。可以通过通过旋转半径向量来获得圆弧上的不同点。 接着,根据选定的起点和终点,绘制圆弧。 重复以上步骤,直至将整个多边形分割由圆弧线段组的片段。 最后,根据需要,可以对这些圆弧片段进行调整和修饰,以达到更好的视觉效果。 需要注意的是,将一个多边形分割圆弧的线段时,要根据多边形的形状和大小合理选择圆弧半径,以保证拼接的顺畅和美观。此外,还需要考虑圆弧与多边形之间的连接方式,以及可能存在的交叠或相交情况,需要进行合理的处理和调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

F_CIL

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

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

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

打赏作者

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

抵扣说明:

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

余额充值