【Godot4.3】Geometry2D总结
概述
之前写过一篇总结:【Godot4.2】2D辅助类Geometry2D入门
本文是去年9月份新总结的。
Godot提供了一个名叫Geometry2D
的类,它提供了一些用于2D几何图形(包括Polygon、PolyLine等)相关的函数,可以实现诸如多边形与多边形或多边形与折线的布尔运算等。本篇就是简单研究和总结这部分内容的一个简要笔记。
多边形与多边形的布尔运算
多边形与多边形可以进行四种布尔运算:交集、差集、并集和亦或,返回的结果可能是0个,一个或多个多边形。
测试场景
我们创建一个2D测试场景,添加3个Polygon2D
节点,其中PolygonA
和PolygonB
是用于布尔运算的多边形。lab
用于显示布尔运算的结果的信息。
自定义多边形布尔运算函数
Geometry2D的布尔运算方法名起的优点随意,反倒是代表布尔运算的PolyBooleanOperation
枚举常量更符合原意,所以简单封装一个函数polygon_boolean()
,代表多边形的布尔运算,并用PolyBooleanOperation
枚举常量值,来进行运算的区分,根据运算类型调用不同的方法。
# ============================ 自定义函数 ============================
# 多边形布尔运算
func polygon_boolean(
polygon_a:PackedVector2Array, # 多边形A
polygon_b:PackedVector2Array, # 多边形B
operation:int = Geometry2D.PolyBooleanOperation.OPERATION_INTERSECTION # 布尔运算形式
) -> Array[PackedVector2Array]:
var result:Array[PackedVector2Array]
# 根据运算类型调用不同的方法
match operation:
Geometry2D.OPERATION_INTERSECTION: # 交集
result = Geometry2D.intersect_polygons(polygon_a,polygon_b)
Geometry2D.OPERATION_DIFFERENCE: # 差集
result = Geometry2D.clip_polygons(polygon_a,polygon_b)
Geometry2D.OPERATION_UNION: # 并集
result = Geometry2D.merge_polygons(polygon_a,polygon_b)
Geometry2D.OPERATION_XOR: # 亦或集
result = Geometry2D.exclude_polygons(polygon_a,polygon_b)
return result
下面是对应的Geometry2D
方法和对应的PolyBooleanOperation
枚举值的对照表:
布尔运算 | 方法 | PolyBooleanOperation枚举值 |
---|---|---|
交集 | intersect_polygons() | OPERATION_INTERSECTION |
并集 | merge_polygons() | OPERATION_UNION |
差集 | clip_polygons() | OPERATION_DIFFERENCE |
亦或 | exclude_polygons() | OPERATION_XOR |
求交集
intersect_polygons()
将 polygon_a 与 polygon_b 相交,并返回一组相交的多边形。这会在多边形之间执行 OPERATION_INTERSECTION。换句话说,返回由各多边形共享的公共区域。如果没有交集,则返回一个空数组。- 该操作可能会产生一个外多边形(边界)和一个内多边形(孔),这可以通过调用 is_polygon_clockwise() 来区分。
# =============================================================
# Geometry2D 测试
# Godot v4.3.stable.steam [77dcf97d8]
# 2024年9月4日00:17:00
# =============================================================
extends Node2D
# ============================ 参数 ============================
@export var polygon_a_color:Color = Color.WHITE
@export var polygon_b_color:Color = Color.AQUA
@export var polygon_result_color:Color = Color.ORANGE_RED
# ============================ 节点引用 ============================
@onready var polygon_a: Polygon2D = $PolygonA
@onready var polygon_b: Polygon2D = $PolygonB
@onready var lab: Label = $lab
# ============================ 测试代码 ============================
func _ready() -> void:
polygon_a.color = polygon_a_color
polygon_b.color = polygon_b_color
# 求PolygonA和PolygonB交集
var result = polygon_boolean(polygon_a.polygon,polygon_b.polygon)
# 显示信息
lab.text = "result = %s\nresult.size = %d" % [str(result),result.size()]
# 显示运算结果
if result.size() > 0: # 运算结果不为空
# 遍历结果动态生成Polygon2D节点显示布尔运算结果
for polygon in result:
var polygon_node = Polygon2D.new()
polygon_node.polygon = polygon
polygon_node.color = polygon_result_color
add_child(polygon_node)
在上面的代码中:
polygon_boolean()
默认进行求交集运算,传入PolygonA
和PolygonB
的顶点数据后,返回的是一个Array[PackedVector2Array]
类型,根据PolygonA
和PolygonB
的形状和位置不同,其运算获得的结果可能为0个、1个或多个。- 所以我们需要判断运算结果(也就是返回的
Array[PackedVector2Array]
)的元素数目来进行结果的显示:- 等于0代表交集为空,不需要绘制
- 大于0代表至少有一个交集图形,可以进行绘制。
- 因为可能有一个以上的布尔运算结果,所以遍历结果动态生成
Polygon2D
节点显示布尔运算结果
以下是各种可能性下的运算结果,其中:
- 白色为PolygonA,海蓝色为PolygonB,布尔运算结果为橘红色
:::color3
注意
这里有一个需要注意的点:PolygonA
和PolygonB
的原点需要对齐,也就是坐标系原点一致,也就是没有发生相对偏移,否则会出现不准确的结果。对其他布尔操作也是一样。
:::
求并集
有了上面的polygon_boolean()
函数,以及_ready
中动态创建Polygon2D
显示结果的代码。我们则只需要修改polygon_boolean()
的布尔运算类型参数就可以查看其他布尔运算的结果了
var result = polygon_boolean(polygon_a.polygon,polygon_b.polygon)
以下是一些并集测试结果:
求差集
var result = polygon_boolean(polygon_a.polygon,polygon_b.polygon,Geometry2D.OPERATION_DIFFERENCE)
亦或
- exclude_polygons相互排除由 polygon_a 和 polygon_b 的交集(参见 intersect_polygons())定义的公共区域,并返回一组排除的多边形。这会在多边形之间执行 OPERATION_XOR。换句话说,返回各多边形之间除公共区域之外的所有区域。
- 该操作可能会产生一个外多边形(边界)和一个内多边形(孔),这可以通过调用 is_polygon_clockwise() 来区分。
var result = polygon_boolean(polygon_a.polygon,polygon_b.polygon,Geometry2D.OPERATION_XOR)
在第三个例子中,因为存在两个区域重叠的结果,所以修改部分代码如下:
func _ready() -> void:
polygon_a.color = polygon_a_color
polygon_b.color = polygon_b_color
# 求PolygonA和PolygonB交集
var result = polygon_boolean(polygon_a.polygon,polygon_b.polygon,Geometry2D.OPERATION_XOR)
# 显示信息
lab.text = "result = %s\nresult.size = %d" % [str(result),result.size()]
# 显示运算结果
if result.size() > 0: # 运算结果不为空
# 遍历结果动态生成Polygon2D节点显示布尔运算结果
for i in range(result.size()):
var polygon_node = Polygon2D.new()
polygon_node.polygon = result[i]
polygon_node.color = polygon_result_color.darkened(0.2 * i)
add_child(polygon_node)
PolyLine和Polygon的布尔运算
自定义函数
同样我们自定义一个函数来封装Geometry2D
对PolyLine和Polygon的布尔运算。
# ============================ 自定义函数 ============================
# 多边形与折线段布尔运算
func polygon_boolean_polyline(
polyline:PackedVector2Array, # 多边形
polygon:PackedVector2Array, # 折线段
operation:int = Geometry2D.PolyBooleanOperation.OPERATION_INTERSECTION # 布尔运算形式
) -> Array[PackedVector2Array]:
var result:Array[PackedVector2Array]
# 根据运算类型调用不同的方法
match operation:
Geometry2D.OPERATION_INTERSECTION: # 交集
result = Geometry2D.intersect_polyline_with_polygon(polyline,polygon)
Geometry2D.OPERATION_DIFFERENCE: # 差集
result = Geometry2D.clip_polyline_with_polygon(polyline,polygon)
return result
可以看到PolyLine和Polygon的布尔运算只有交集和差集两种。
布尔运算 | 方法 | 布尔操作 |
---|---|---|
交集 | intersect_polyline_with_polygon | OPERATION_INTERSECTION |
差集 | clip_polyline_with_polygon | OPERATION_DIFFERENCE |
修改测试场景
我们更改场景节点如下:
然后简单绘制一段折线与一个多边形:
修改根节点代码如下:
# =============================================================
# Geometry2D 测试
# Godot v4.3.stable.steam [77dcf97d8]
# 2024年9月4日00:17:00
# =============================================================
extends Node2D
# ============================ 参数 ============================
@export var polygon_color:Color = Color.WHITE
@export var polyline_color:Color = Color.AQUA
@export var result_color:Color = Color.ORANGE_RED
# ============================ 节点引用 ============================
@onready var polygon: Polygon2D = $Polygon
@onready var polyline: Line2D = $Polyline
@onready var lab: Label = $lab
# ============================ 测试代码 ============================
func _ready() -> void:
polygon.color = polygon_color
polyline.default_color = polyline_color
var result = polygon_boolean_polyline(polyline.points,polygon.polygon)
# 显示信息
lab.text = "result = %s\nresult.size = %d" % [str(result),result.size()]
# 显示运算结果
if result.size() > 0: # 运算结果不为空
# 遍历结果动态生成Line2D节点显示布尔运算结果
for i in range(result.size()):
var polyline_node = Line2D.new()
polyline_node.points = result[i]
polyline_node.default_color = result_color.darkened(0.2 * i)
add_child(polyline_node)
求交集
var result = polygon_boolean_polyline(polyline.points,polygon.polygon)
注意
不要传错顺序,第一个参数传入Polyline的顶点数据,第二个参数传入Polygon的。
求差集
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
2024年9月4日02:05:09 以下内容未修改
获取凸多边形
convex_hull()
可以用于获取任意包围折线段或多边形的凸多边形
获取任意多边形的凸多边形
# =============================================================
# Geometry2D 测试
# Godot v4.3.stable.steam [77dcf97d8]
# 2024年9月4日20:53:23
# =============================================================
extends Node2D
# ============================ 参数 ============================
@export var polygon_color:Color = Color.WHITE
@export var result_color:Color = Color.ORANGE_RED
# ============================ 节点引用 ============================
@onready var polygon: Polygon2D = $Polygon
@onready var polygon_result: Polygon2D = $PolygonResult
@onready var lab: Label = $lab
# ============================ 测试代码 ============================
func _ready() -> void:
polygon.color = polygon_color
var result = Geometry2D.convex_hull(polygon.polygon)
# 显示信息
lab.text = "result = %s\nresult.size = %d" % [str(result),result.size()]
# 显示运算结果
if result.size() > 0: # 运算结果不为空
polygon_result.polygon = result
polygon_result.color = result_color
获取任意折线段的凸多边形
分解为凸多边形
# =============================================================
# Geometry2D 测试
# Godot v4.3.stable.steam [77dcf97d8]
# 2024年9月4日20:53:23
# =============================================================
extends Node2D
# ============================ 参数 ============================
@export var polygon_color:Color = Color.WHITE
@export var result_color:Color = Color.ORANGE_RED
# ============================ 节点引用 ============================
@onready var polygon: Polygon2D = $Polygon
@onready var marker2d: Marker2D = $Marker2D
@onready var lab: Label = $lab
# ============================ 测试代码 ============================
func _ready() -> void:
polygon.color = polygon_color
var result = Geometry2D.decompose_polygon_in_convex(polygon.polygon)
# 显示信息
lab.text = "result = %s\nresult.size = %d" % [str(result),result.size()]
# 显示运算结果
if result.size() > 0: # 运算结果不为空
for i in range(result.size()):
var polygon_node = Polygon2D.new()
polygon_node.polygon = result[i]
polygon_node.color = result_color.darkened(0.2 * i)
polygon_node.position = marker2d.position
add_child(polygon_node)
判断两条直线是否相交以及获得交点
@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)
判断点是否在一个几何图形内
判断点是否在圆内
@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])
获取折线偏移一定距离后的多边形
offset_polyline()
可以基础Polyline,经过一定的偏移,获取对应的多边形数据。
# 折线段转多边形测试
# 作者:巽星石
# 2024年9月7日23:46:30
@tool
extends Node2D
# 折线顶点数据
var line:PackedVector2Array = [
Vector2(20,20),
Vector2(100,100),
Vector2(200,150)
]
# ================== 参数 ==================
# 折线的绘制颜色
@export var line_color:=Color.WHITE:
set(val):
line_color = val
queue_redraw()
# 折线的绘制颜色
@export_range(0.1,50.0,0.1) var offset:=1.0:
set(val):
offset = val
queue_redraw()
# ================== 绘制 ==================
func _draw() -> void:
var line_polygon = Geometry2D.offset_polyline(line,offset)
if line_polygon.size() > 0:
line_polygon[0].append(line_polygon[0][0]) # 闭合
draw_polyline(line_polygon[0],line_color,1)
draw_polyline(line,Color.RED,1)
pass
通过设定连接处和端点处的样式,我们可以获取不同的效果:
- JOIN_ROUND和END_ROUND分别代表连接处使用圆角或弧线形式
var line_polygon = Geometry2D.offset_polyline(line,offset,Geometry2D.JOIN_ROUND,Geometry2D.END_ROUND)
在偏移参数不变的情况下,得到的多边形如下: