【Godot4.2】2D辅助类Geometry2D入门

概述

Godot4.2提供了一个名叫Geometry2D的类。它提供了一些用于2D几何图形如多边形(Polygon)、折线(PolyLine)相关的函数,可以方便实现诸如多边形与多边形多边形与折线布尔运算求交点等。

这是一个非常强大的2D几何辅助类,可以方便你基于几何图形的一些复杂操作。

本篇是笔者2023年7月受到B站一个搬运视频启发,然后基于Godot内置文档研究Geometry2D并逐个尝试方法,从而总结的一份笔记。原始笔记分为两篇,这里合二为一,并可能会做一定的改写和扩充。期望对你的学习或项目有用。

参考视频

多边形与多边形的布尔运算

我们创建如下场景:

  • 添加3个Polygon2D节点,其中:Polygon1Polygon2是用于布尔运算的多边形。PolygonResult用于显示布尔运算的结果。
  • 我们在Polygon1Polygon2中分别绘制一个多边形,并修改其中一个的颜色。

多边形与多边形的布尔运算测试场景

求交集

我们为根节点添加如下代码:

extends Node2D

@onready var polygon1 = $Polygon1   # 多边形1
@onready var polygon2 = $Polygon2   # 多边形2
@onready var polygon_result = $PolygonResult   # 显示布尔运算结果的Polygon2D节点


func _ready():
	var p1 = polygon1.polygon
	var p2 = polygon2.polygon
	# 显示布尔运算后的图形
	polygon_result.polygon = Geometry2D.intersect_polygons(p1,p2)[0]

运行场景后可以看到如下的结果:
进行交集运算的多边形(左),交集运算结果(右)
修改图形:
进行交集运算的多边形(左),交集运算结果(右)
再次修改:
进行交集运算的多边形(左),交集运算结果(右)


注意Polygon1Polygon2的原点需要对齐,否则可能会出现不准确的结果。这也很容易理解,因为原点不对齐,意味着坐标系不重合,那么整个多边形顶点的坐标就相当于发生了偏移。

Polygon1和Polygon2的原点需要对齐


亦或运算

extends Node2D

@onready var polygon1 = $Polygon1
@onready var polygon2 = $Polygon2
@onready var polygon_result = $PolygonResult
@onready var polygon_result2 = $PolygonResult2


func _ready():
	var p1 = polygon1.polygon
	var p2 = polygon2.polygon
	
	var result := Geometry2D.exclude_polygons(p1,p2)
	print(result.size())
	if result.size() == 1:
		polygon_result.polygon = result[0]
	elif result.size() == 2:
		polygon_result.polygon = result[0]
		polygon_result2.polygon = result[1]
	pass

image.png
image.png

求差集

extends Node2D

@onready var polygon1 = $Polygon1
@onready var polygon2 = $Polygon2
@onready var polygon_result = $PolygonResult
@onready var polygon_result2 = $PolygonResult2


func _ready():
	var p1 = polygon1.polygon
	var p2 = polygon2.polygon
	
	var result := Geometry2D.clip_polygons(p1,p2)
	print(result.size())
	if result.size() == 1:
		polygon_result.polygon = result[0]
	elif result.size() == 2:
		polygon_result.polygon = result[0]
		polygon_result2.polygon = result[1]
	pass

image.png
image.png
image.png

求并集

extends Node2D

@onready var polygon1 = $Polygon1
@onready var polygon2 = $Polygon2
@onready var polygon_result = $PolygonResult
@onready var polygon_result2 = $PolygonResult2


func _ready():
	var p1 = polygon1.polygon
	var p2 = polygon2.polygon
	
	var result := Geometry2D.merge_polygons(p1,p2)
	print(result.size())
	if result.size() == 1:
		polygon_result.polygon = result[0]
	elif result.size() == 2:
		polygon_result.polygon = result[0]
		polygon_result2.polygon = result[1]
	pass

image.png
image.png

总结

多边形与多边形可以进行四种布尔运算,返回的结果可能是0个,1个或多个多边形。

布尔运算方法布尔操作
交集intersect_polygonsOPERATION_INTERSECTION
并集merge_polygonsOPERATION_UNION
差集clip_polygonsOPERATION_DIFFERENCE
亦或exclude_polygonsOPERATION_XOR

PolyLine和Polygon的布尔运算

我们更改场景节点如下:
简单绘制一段折线与一个多边形。

image.png

求交集

extends Node2D

@onready var path = $Path
@onready var polygon = $Polygon
@onready var polyline_result = $PolylineResult
@onready var polyline_result2 = $PolylineResult2



func _ready():
	var l = path.points
	var p = polygon.polygon
	
	var result := Geometry2D.intersect_polyline_with_polygon(l,p)
	print(result.size())
	if result.size() == 1:
		polyline_result.points = result[0]
	pass

用多边形求与折线的交集

求差集

extends Node2D

@onready var path = $Path
@onready var polygon = $Polygon
@onready var polyline_result = $PolylineResult
@onready var polyline_result2 = $PolylineResult2



func _ready():
	var l = path.points
	var p = polygon.polygon
	
	var result := Geometry2D.clip_polyline_with_polygon(l,p)
	print(result)
	if result.size() == 1:
		polyline_result.points = result[0]
	elif result.size() == 2:
		polyline_result.points = result[0]
		polyline_result2.points = result[1]
	pass

用多边形求与折线的差集
image.png

总结

多边形与折线只能求交集或差集,返回的可能是一条或多条折线。

布尔运算方法布尔操作
交集intersect_polyline_with_polygonOPERATION_INTERSECTION
差集clip_polyline_with_polygonOPERATION_DIFFERENCE

求交点

判断两条直线是否相交以及获得交点

@tool
extends Control

var line1 = [Vector2(0,0),Vector2(400,400)]
var line2 = [Vector2(350,150),Vector2(10,300)]

func _draw():
	draw_line(line1[0],line1[1],Color.GOLDENROD,2)
	draw_line(line2[0],line2[1],Color.GREEN_YELLOW,2)
	if Geometry2D.line_intersects_line(line1[0],line1[0].direction_to(line1[1]),line2[0],line2[0].direction_to(line2[1])):
		var j_point:Vector2 = Geometry2D.line_intersects_line(line1[0],line1[0].direction_to(line1[1]),line2[0],line2[0].direction_to(line2[1]))
		draw_circle(j_point,4,Color.BLUE_VIOLET)

判断并求取两条直线的交点

判断线段与圆是否相交以及获得焦点

extends Node2D

# 定义圆
var center = Vector2(400,200)
var r = 50

var pos:Vector2

func _process(delta):
	pos = get_global_mouse_position()
	queue_redraw()

func _draw():
	# 绘制圆
	draw_circle(center,r,Color.AQUA)
	# 绘制线段
	var line = [center,pos]
	draw_line(line[0],line[1],Color.YELLOW)
	var x = Geometry2D.segment_intersects_circle(line[0],line[1],center,r) # 判断与圆是否有交点
	if x:
		var x_pos = lerp(line[0],line[1],x)
		draw_circle(x_pos,5,Color.ORANGE_RED)
  • segment_intersects_circle判断一个线段与圆的边界是否有交点
  • 如果没有交点,则返回-1,如果有则返回一个0.0-1.0之间的数字
  • 这个数字返回的是焦点在线段上的偏移值(从起始点到终点)
  • 通过lerp(线段起始点,线段终点,偏移值)形式,我们就可以获得实际交点的位置

请添加图片描述
请添加图片描述
请添加图片描述
通过观察线段起点不是圆心的情况,可以发现求得的交点是类似于RayCast2D求得的碰撞点,也就是首先接触的那个交点。

其实通过求反向线段与圆的交点,就可以同时求出两个交点。

# 求反向线段 (颠倒起始点、终点)
var line2 = line.duplicate()
line2.reverse()

效果:
请添加图片描述

完整代码:

extends Node2D

# 定义圆
var center = Vector2(400,200)
var r = 50

var pos:Vector2

func _process(delta):
	pos = get_global_mouse_position()
	queue_redraw()

func _draw():
	# 绘制圆
	draw_circle(center,r,Color.AQUA)
	# 绘制线段
	var line = [center+Vector2(200,200),pos]
	var line2 = line.duplicate()
	line2.reverse()
	draw_line(line[0],line[1],Color.YELLOW)
	var x = Geometry2D.segment_intersects_circle(line[0],line[1],center,r) # 判断与圆是否有交点
	if x != -1:
		var x_pos = lerp(line[0],line[1],x)
		draw_circle(x_pos,5,Color.ORANGE_RED)
	var x2 = Geometry2D.segment_intersects_circle(line2[0],line2[1],center,r) # 判断与圆是否有交点
	if x2 != -1:
		var x2_pos = lerp(line2[0],line2[1],x2)
		draw_circle(x2_pos,5,Color.ORANGE_RED)

判断点是否在一个几何图形内

如果你熟悉Rect2的话,你就会知道它有一个名叫has_point的方法,可以判断一个点是否在矩形内。最常见的用途就是判断鼠标是否进入矩形区域内或移出。

Geometry2D则提供了对圆形多边形三角形判断一个点是否在其内部的方法。

判断点是否在圆内

@tool
extends Control

var pos:Vector2

func _process(delta):
	pos = get_global_mouse_position()
	queue_redraw()

func _draw():
	var center = Vector2(400,200)
	var r = 50
	if Geometry2D.is_point_in_circle(pos,center,r): # 鼠标进入圆
		draw_circle(center,r,Color.AQUA)
	else: # 鼠标在圆外
		draw_circle(center,r,Color.AQUAMARINE)
	
	draw_circle(pos,4,Color.ORANGE) # 绘制鼠标位置

判断鼠标是否进入圆的区域

判断点是否在多边形内

@tool
extends Control

var polygon:PackedVector2Array = [
	Vector2(100,100),Vector2(300,100),
	Vector2(450,150),Vector2(300,300),
	Vector2(200,200),Vector2(100,100)
]
var pos:Vector2

func _process(delta):
	pos = get_global_mouse_position()
	queue_redraw()

func _draw():
	if Geometry2D.is_point_in_polygon(pos,polygon): # 鼠标进入圆
		draw_polygon(polygon,[Color.AQUA])
	else: # 鼠标在圆外
		draw_polygon(polygon,[Color.AQUAMARINE])
	
	draw_circle(pos,4,Color.ORANGE) # 绘制鼠标位置

判断鼠标是否进入多边形区域

判断点是否在三角形内

@tool
extends Control

var polygon:PackedVector2Array = [
	Vector2(100,100),Vector2(300,100),
	Vector2(250,350)
]
var pos:Vector2

func _process(delta):
	pos = get_global_mouse_position()
	queue_redraw()

func _draw():
	if Geometry2D.point_is_inside_triangle(pos,polygon[0],polygon[1],polygon[2]): # 鼠标进入三角形
		draw_polygon(polygon,[Color.AQUA])
	else: # 鼠标在三角形外
		draw_polygon(polygon,[Color.AQUAMARINE])
	
	draw_circle(pos,4,Color.ORANGE) # 绘制鼠标位置

判断鼠标点是否在三角形内

获取最近点

求取线段上离鼠标位置最近的点

@tool
extends Control

var line1 = [Vector2(0,0),Vector2(400,400)]
var pos:Vector2

func _process(delta):
	pos = get_global_mouse_position()
	queue_redraw()

func _draw():
	draw_line(line1[0],line1[1],Color.GOLDENROD,2)
	var c_point = Geometry2D.get_closest_point_to_segment(pos,line1[0],line1[1])
	draw_circle(c_point,4,Color.GREEN_YELLOW)
	draw_circle(pos,4,Color.BLUE)

求取线段上离鼠标位置最近的点
可以看到:

  • 如果鼠标点在与线段平行的所有线段组成的无限矩形范围内,则最近点就是垂线与线段的交点。
  • 如果超出线段端点范围,则最近点停留在端点上。

求取直线上离鼠标最近的点

@tool
extends Control

var line1 = [Vector2(0,0),Vector2(400,400)]
var pos:Vector2

func _process(delta):
	pos = get_global_mouse_position()
	queue_redraw()

func _draw():
	draw_line(line1[0],line1[1],Color.GOLDENROD,2)
	var c_point = Geometry2D.get_closest_point_to_segment_uncapped(pos,line1[0],line1[1])
	draw_circle(c_point,4,Color.GREEN_YELLOW)
	draw_circle(pos,4,Color.BLUE)

求取直线上离鼠标最近的点
与线段的概念不同,直线是一个无限长的几何图形,所以求某个点离直线最近的点,就是经过该点做直线的垂线时,获得的交点。

求取两条线段之间最近的两个点

@tool
extends Control

var line1 = [Vector2(100,100),Vector2(400,100)]
var line2 = [Vector2(200,200),Vector2(500,500)]
var pos:Vector2

func _process(delta):
	line2[1] = get_global_mouse_position()
	queue_redraw()

func _draw():
	draw_line(line1[0],line1[1],Color.GOLDENROD,2)
	draw_line(line2[0],line2[1],Color.ORANGE_RED,2)
	var c_points:PackedVector2Array = Geometry2D.get_closest_points_between_segments(line1[0],line1[1],line2[0],line2[1])
	draw_circle(c_points[0],4,Color.GREEN_YELLOW)
	draw_circle(c_points[1],4,Color.GREEN_YELLOW)

求取两条线段之间最近的两个点

两条线段,固定其中一条:

  • 如果不相交,则最近点就是求经过动态线段两个端点中离固定线段比较近的一个端点做垂线的交点(。。。我自己也觉得绕)
  • 如果相交,就是交点。

膨胀或缩小多边形

圆角化膨胀或缩小

@tool
extends Control

@export var offset:int = 0:
	set(val):
		offset = val
		queue_redraw()


var polygon:PackedVector2Array = ShapePoints.star(0,5,50,30,Vector2(400,200))
var pos:Vector2

func _draw():
	var off_polygon = Geometry2D.offset_polygon(polygon,offset,Geometry2D.JOIN_ROUND)[0]
	draw_polygon(off_polygon,[Color.AQUAMARINE])

最初的五角星
通过圆角化膨胀后的五角星

保持尖角的膨胀和缩小

@tool
extends Control

@export var offset:int = 0:
	set(val):
		offset = val
		queue_redraw()


var polygon:PackedVector2Array = ShapePoints.star(0,5,50,30,Vector2(400,200))
var pos:Vector2

func _draw():
	var off_polygon = Geometry2D.offset_polygon(polygon,offset,Geometry2D.JOIN_MITER)[0]
	draw_polygon(off_polygon,[Color.AQUAMARINE])

保持尖角膨胀后的五角星

切角化的膨胀或缩小

@tool
extends Control

@export var offset:int = 0:
	set(val):
		offset = val
		queue_redraw()


var polygon:PackedVector2Array = ShapePoints.star(0,5,50,30,Vector2(400,200))
var pos:Vector2

func _draw():
	var off_polygon = Geometry2D.offset_polygon(polygon,offset,Geometry2D.JOIN_SQUARE)[0]
	draw_polygon(off_polygon,[Color.AQUAMARINE])

切角化膨胀后的五角星
切角化收缩后的五角星

分解凹多边形为几个凸多边形

var result := Geometry2D.decompose_polygon_in_convex(p1)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
可以看到,如果一个多边形是凹多边形(Concave Polygon),那么使用decompose_polygon_in_convex方法,会将其拆分为若干个凸多边形。

从凹多边形求凸多边形

var result := Geometry2D.convex_hull(p1)
print(p1)
print(result)

在这里插入图片描述

[(195, 152), (257, 74), (345, 141), (448, 107), (409, 216), (461, 272), (380, 323), (325, 274), (251, 296), (270, 200)]
[(195, 152), (257, 74), (448, 107), (461, 272), (380, 323), (251, 296), (195, 152)]

可以看到,其结果是删除了所有凹进去的点,最后形成一个完全包裹原来凹多边形的凸多边形。
在这里插入图片描述
其他的一些尝试:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

获取多边形的矩形尺寸

对于下图所示的多边形,我们使用make_atlas

var p1 = polygon1.polygon
print(JSON.stringify(Geometry2D.make_atlas(p1),"\t"))

在这里插入图片描述

打印内容:

{
	"points": "[(251, 940), (693, 595), (0, 294), (0, 187), (0, 0), (543, 0), (448, 272), (374, 595), (0, 319), (446, 940)]",
	"size": "(1004, 1167)"
}

可以看到结果是一个字典,包含两个键:

  • points返回的是图形顶点数据
  • size返回一个尺寸,但是我暂时没有搞懂它的含义,它的尺寸是一个远大于多边形包围盒尺寸的

在这里插入图片描述

任意多边形包围盒position、end和size求取函数

  • 通过求所有顶点中最小的xy组成的Vector2就可以得到多边形包围盒Rect2的左上角顶点position
  • 通过求所有顶点中最大的xy组成的Vector2就可以得到多边形包围盒Rect2的右下角顶点end
  • Rect2尺寸size = end - position
  • Rect2中心点center = position + size/2.0
# 求任意多边形包围盒Rect2的position
func get_polygon_Rect2_position(polygon:PackedVector2Array) -> Vector2:
	var xs = []  # 所有的x
	var ys = []  # 所有的y
	for i in range(polygon.size()):
		xs.append(polygon[i].x)
		ys.append(polygon[i].y)
	# 升序排列
	xs.sort()
	ys.sort() 
	return Vector2(xs[0],ys[0]) # 返回由最小的x和y组成的点坐标
	
# 求任意多边形包围盒Rect2的end
func get_polygon_Rect2_end(polygon:PackedVector2Array) -> Vector2:
	var xs = []  # 所有的x
	var ys = []  # 所有的y
	for i in range(polygon.size()):
		xs.append(polygon[i].x)
		ys.append(polygon[i].y)
	# 降序排列
	xs.sort()
	xs.reverse()
	ys.sort()
	ys.reverse()
	return Vector2(xs[0],ys[0]) # 返回由最大的x和y组成的点坐标

# 求任意多边形包围盒Rect2的size
func get_polygon_Rect2_size(polygon:PackedVector2Array) -> Vector2:
	var size:Vector2
	var position = get_polygon_Rect2_position(polygon)
	var end = get_polygon_Rect2_end(polygon)
	if end > position:
		size = end - position
	else:
		size =  position - end
	return size

# 求任意多边形包围盒Rect2的center
func get_polygon_Rect2_center(polygon:PackedVector2Array) -> Vector2:
	var size = get_polygon_Rect2_size(polygon)
	var position = get_polygon_Rect2_position(polygon)
	return position + size/2.0

通过设定一个空的控件尺寸,可以验证结果的正确性:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当然,上面的代码只是展示了求取的思路,实际上,我们只需要一个函数,直接返回Rect2就行。

# 求任意多边形包围盒Rect2
func get_polygon_Rect2(polygon:PackedVector2Array) -> Rect2:
	var xs = [];var ys = []  # 所有的x和y
	# 遍历所有顶点,抽离出所有的x和y坐标到单独的数组
	for i in range(polygon.size()):
		xs.append(polygon[i].x);ys.append(polygon[i].y)
	# 升序排列
	xs.sort();ys.sort()
	# 获取 position
	var pos = Vector2(xs[0],ys[0]) # Rect2.position
	# 降序排列
	xs.reverse();ys.reverse()
	# 获取 end
	var end = Vector2(xs[0],ys[0]) # Rect2.end
	# 获取 size
	var size = end - pos if end > pos else pos -end  # 计算 Rect2.size
	return  Rect2(pos,size)

测试:

var p1 = polygon1.polygon
var rect = get_polygon_Rect2(p1)
print(rect.position)
print(rect.size)
print(rect.end)
print(rect.get_center())

对多边形进行三角化

在这里插入图片描述

extends Node2D

@onready var polygon1 = $Polygon1

func _ready():
	var p1 = polygon1.polygon
	print(Geometry2D.triangulate_delaunay(p1))

打印内容:

[1, 2, 3, 2, 3, 4, 3, 4, 5, 3, 5, 6, 6, 7, 8, 1, 3, 8, 3, 6, 8, 0, 1, 8, 0, 8, 9]

其中的数字代表的是多边形的顶点索引,每三个顶点构成一个三角形。所以返回的元素数目肯定是3的整数倍。

我们编写一个函数来获得所有三角形的数据。

# 返回多边形三角化后的所有三角形数据
func polygon_triangles(polygon:PackedVector2Array) -> Array[PackedVector2Array]:
	var arr:Array[PackedVector2Array] = []
	var indexs = Geometry2D.triangulate_delaunay(polygon)
	print(indexs)
	for i in range(indexs.size()/3):
		var tag_index = indexs.slice(3 * i,3 * i + 3) 
		var tag:PackedVector2Array = []
		tag.append(polygon[tag_index[0]])
		tag.append(polygon[tag_index[1]])
		tag.append(polygon[tag_index[2]])
		arr.append(tag)
	return arr

通过遍历和绘制所有三角形:

extends Node2D

@onready var polygon1 = $Polygon1
var tags:Array[PackedVector2Array]


func _ready():
	var p1 = polygon1.polygon
	tags = polygon_triangles(p1)

func _draw():
	for tag in tags:
		var color = Color(randf(),randf(),randf())
		draw_colored_polygon(tag,color)

绘制出的结果:

在这里插入图片描述
我们使用一个更明显的凹多边形:
在这里插入图片描述

可以看到,凹多边形三角化后,会填补为一个凸多边形。

如果想要凹多边形得到完全一致的三角化效果,则需要用Geometry2Dtriangulate_polygon替代triangulate_delaunay

之前的自定义函数也就变为了:

# 返回多边形三角化后的所有三角形数据
func polygon_triangles(polygon:PackedVector2Array) -> Array[PackedVector2Array]:
	var arr:Array[PackedVector2Array] = []
	var indexs = Geometry2D.triangulate_polygon(polygon)
	for i in range(indexs.size()/3):
		var tag_index = indexs.slice(3 * i,3 * i + 3) 
		var tag:PackedVector2Array = []
		tag.append(polygon[tag_index[0]])
		tag.append(polygon[tag_index[1]])
		tag.append(polygon[tag_index[2]])
		arr.append(tag)
	return arr

凹多边形返回的三角化效果:
在这里插入图片描述

  • 27
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

巽星石

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

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

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

打赏作者

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

抵扣说明:

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

余额充值