概述
最近积累了一些图形重叠检测,以及求图形的轴对齐包围盒Rect2,还有求Rect2的外接圆等函数。感觉可以作为一个单独的函数库,提供日常的使用,所以汇总成了ShapeTests
。
注意:函数名和写法可能会不断改进。
代码
# ==============================================
# ShapeTests
# 一个用于求取基础图形轴对齐包围盒、外接圆以及重叠检测的函数库
# 巽星石 v4.3.stable.steam [77dcf97d8]
# 2024年10月2日15:02:18
# 2024年10月2日21:52:23
# ==============================================
class_name ShapeTests
# ======================= 求Rect2 =======================
# 获取点集的轴对齐包围盒(Rect2)
static func get_points_rect2(points:PackedVector2Array) -> Rect2:
var rect = Rect2()
var xs = Array(points).map(func(num):return num.x)
var ys = Array(points).map(func(num):return num.y)
rect.position = Vector2(xs.min(),ys.min())
rect.end = Vector2(xs.max(),ys.max())
return rect
# 获取圆的Rect2
static func get_circle_rect2(center:Vector2,radius:float) -> Rect2:
var rect = Rect2()
rect.position = center - Vector2.ONE * radius
rect.size = Vector2.ONE * radius * 2
return rect
# ======================= 求Rect2的外接圆 =======================
class Circle:
var center:Vector2
var radius:float
# 返回 Rect2 的外接圆
static func get_rect2_out_circle(rect2:Rect2) -> Circle:
var c = Circle.new()
c.center = rect2.get_center()
c.radius = (rect2.get_center() - rect2.position).length()
return c
# 返回 points 的外接圆
static func get_points_out_circle(points:PackedVector2Array) -> Circle:
var rect:Rect2 = get_points_rect2(points)
return get_rect2_out_circle(rect)
# ======================= 判断图形是否完全在Rect2内 =======================
# 判断圆是否完全在 rect2 内
static func is_circle_in_rect2(center:Vector2,radius:float,rect2:Rect2) -> bool:
var rect1 = get_circle_rect2(center,radius)
return is_rect_in_rect2(rect1,rect2)
# 判断 rect1 是否完全在 rect2 内
static func is_rect_in_rect2(rect1:Rect2,rect2:Rect2) -> bool:
return rect2.has_point(rect1.position) and rect2.has_point(rect1.end)
# ======================= 图形间的重叠检测 =======================
# 判断两个圆形是否重叠
static func is_circle_circle_overlap(c1:Vector2,r1:float,c2:Vector2,r2:float):
var dis = c1.distance_to(c2) # 距离
return dis <= r1 + r2
# 获取两个圆形重叠部分的向量
static func get_circle_circle_overlap(c1:Vector2,r1:float,c2:Vector2,r2:float):
var dis = c1.distance_to(c2) # 距离
var dir = c1.direction_to(c2) # 方向
var min_dis = r1 + r2
var over_dis := 0.0 # 重叠部分的距离
if dis < min_dis:
over_dis = min_dis - dis
return dir * over_dis
# 返回矩形四个角点
static func get_rect_points(rect:Rect2) -> PackedVector2Array:
var pots:PackedVector2Array
var pos = rect.position; var end = rect.end; var size = rect.size
pots = [ pos,pos + Vector2(size.x,0), end,end - Vector2(size.x,0)]
return pots
# 判断rect2和rect1是否有重叠
static func is_rect_rect_overlap(rect1:Rect2,rect2:Rect2) -> bool:
var points = Array(get_rect_points(rect2))
# 矩形1的任何一个点在矩形2内
var bol = points.any(func(p):
return rect1.has_point(p)
)
return bol
# 返回rect2和rect1是重叠的点集合
static func get_rect_rect_overlap_points(rect1:Rect2,rect2:Rect2) -> PackedVector2Array:
var pots:PackedVector2Array
var points = Array(get_rect_points(rect2))
# 矩形1的任何一个点在矩形2内
for p in points:
if rect1.has_point(p):
pots.append(p)
return pots
# 获取Rect2的4分矩形集合
static func get_rect_4_parts(rect:Rect2) -> Array[Rect2]:
var rects:Array[Rect2]
var c = rect.get_center()
var half_size = rect.size/2.0
var r1 = Rect2(rect.position,half_size)
rects.append(r1)
rects.append(Transform2D(0,Vector2(half_size.x,0)) * r1)
rects.append(Transform2D(0,half_size) * r1)
rects.append(Transform2D(0,Vector2(0,half_size.y)) * r1)
return rects
# 获取两个矩形重叠部分的向量
static func get_rect_rect_overlap(rect1:Rect2,rect2:Rect2) -> PackedVector2Array:
var c1 = rect1.get_center()
var c2 = rect2.get_center()
var vec12 = c2 - c1
var half_size = (rect1.size + rect2.size)/2.0
var ove_points = get_rect_rect_overlap_points(rect1,rect2) # rect2进入rect1的点集合
var points2 = Array(get_rect_points(rect2))
var points1 = Array(get_rect_points(rect1))
var len = 0
var vec = Vector2() # 方向
var pos = Vector2() # 位置
var sub_rects = get_rect_4_parts(rect1) # 将rect1划分为四个象限
match ove_points.size():
4: # rect2完全进入rect1
if sub_rects[0].has_point(c2):
vec = points1[0] - points2[2]
pos = points2[2]
if sub_rects[1].has_point(c2):
vec = points1[1] - points2[3]
pos = points2[3]
if sub_rects[2].has_point(c2):
vec = points1[2] - points2[0]
pos = points2[0]
if sub_rects[3].has_point(c2):
vec = points1[3] - points2[1]
pos = points2[1]
2: # rect2一侧进入rect1
var dw = half_size.x - abs(vec12.x)
var dh = half_size.y - abs(vec12.y)
if ove_points == PackedVector2Array([points2[0],points2[1]]):
vec = Vector2.DOWN * dh
pos = points2[0].lerp(points2[1],0.5)
if ove_points == PackedVector2Array([points2[2],points2[3]]):
vec = Vector2.UP * dh
pos = points2[2].lerp(points2[3],0.5)
if ove_points == PackedVector2Array([points2[0],points2[3]]):
vec = Vector2.RIGHT * dw
pos = points2[0].lerp(points2[3],0.5)
if ove_points == PackedVector2Array([points2[1],points2[2]]):
vec = Vector2.LEFT * dw
pos = points2[1].lerp(points2[2],0.5)
1: # rect2仅一个角进入rect1
if ove_points == PackedVector2Array([points2[3]]):
vec = points1[1] - points2[3]
pos = points2[3]
if ove_points == PackedVector2Array([points2[2]]):
vec = points1[0] - points2[2]
pos = points2[2]
if ove_points == PackedVector2Array([points2[1]]):
vec = points1[3] - points2[1]
pos = points2[1]
if ove_points == PackedVector2Array([points2[0]]):
vec = points1[2] - points2[0]
pos = points2[0]
return PackedVector2Array([pos,pos + vec])
使用测试
获取圆的Rect2
extends Node2D
var c = Vector2(300,300) # 圆心
var r = 100 # 半径
func _draw() -> void:
var rect = ShapeTests.get_circle_rect2(c,r) # 获取圆的Rect2
draw_rect(rect,Color.AQUAMARINE,false,1) # 绘制圆的Rect2
draw_circle(c,r,Color.ORANGE_RED,false,1) # 绘制圆
求点集的Rect2
extends Node2D
var points = Points2D.Vec2Arr("100,200 300,400 200,100") # 定义点集
func _draw() -> void:
var rect = ShapeTests.get_points_rect2(points) # 求点集的Rect2
draw_rect(rect,Color.AQUAMARINE,false,1) # 绘制Rect2
draw_polyline(points,Color.YELLOW_GREEN,1) # 绘制折线
# 绘制顶点
for p in points:
draw_circle(p,3,Color.ORANGE_RED)
求Rect2的外接圆
extends Node2D
var rect = Rect2(100,100,300,200) # 定义Rect2
func _draw() -> void:
var c = ShapeTests.get_rect2_out_circle(rect) # 求Rect2的外接圆
draw_circle(c.center,c.radius,Color.ORANGE_RED,false,1) # 绘制外接圆
draw_rect(rect,Color.AQUAMARINE,false,1) # 绘制Rect2
求任意点集的外接圆
extends Node2D
var points = Points2D.Vec2Arr("100,200 300,400 200,100")
func _draw() -> void:
var c = ShapeTests.get_points_out_circle(points)
draw_circle(c.center,c.radius,Color.ORANGE_RED,false,1)
draw_polyline(points,Color.YELLOW_GREEN,1)
for p in points:
draw_circle(p,3,Color.ORANGE_RED)
可以看到,实际上是先求点集的Rect2
然后求Rect2
的外接圆,这里并不太精确,是因为它是基于Rect2的,但是已经满足无论如何旋转,所有点都始终在外接圆内的要求。
判断圆是否在矩形内
extends Node2D
var rect = Rect2(100,100,900,400) # 定义边界矩形
var c = Vector2(300,300) # 圆心
var r = 100 # 半径
func _process(delta: float) -> void:
c = get_global_mouse_position() # 用鼠标位置更新圆的位置
queue_redraw()
func _draw() -> void:
draw_rect(rect,Color.AQUAMARINE,false,1) # 绘制边界矩形
if ShapeTests.is_circle_in_rect2(c,r,rect):
draw_circle(c,r,Color.ORANGE_RED,false,1) # 绘制圆
else:
draw_circle(c,r,Color.ORANGE_RED,true) # 绘制圆
判断矩形是否在矩形内
extends Node2D
var rect = Rect2(100,100,900,400) # 定义边界矩形
var rect2 = Rect2(200,200,100,80) # 小矩形
func _process(delta: float) -> void:
# 用鼠标位置更新矩形的位置
rect2.position = get_global_mouse_position() - rect2.size/2.0
queue_redraw()
func _draw() -> void:
draw_rect(rect,Color.AQUAMARINE,false,1) # 绘制边界矩形
if ShapeTests.is_rect_in_rect2(rect2,rect):
draw_rect(rect2,Color.ORANGE_RED,false,1) # 绘制非填充矩形
else:
draw_rect(rect2,Color.ORANGE_RED,true) # 绘制填充矩形
注意这里是判断小矩形是否完完全全在大矩形内:
圆与圆的重叠向量
extends Node2D
var c1 = Vector2(300,300) # 圆心
var r1 = 100 # 半径
var c2 = Vector2(300,300) # 圆心
var r2 = 60 # 半径
# 箭头
var arrow:PackedVector2Array = [
Vector2.LEFT.rotated(deg_to_rad(-30)) * 5,
Vector2(),
Vector2.LEFT.rotated(deg_to_rad(30)) * 5,
]
func _process(delta: float) -> void:
# 用鼠标位置圆2的圆心
c2 = get_global_mouse_position()
queue_redraw()
func _draw() -> void:
var overlap = ShapeTests.get_circle_circle_overlap(c1,r1,c2,r2)
var a = c1.distance_to(c2) - r2
draw_circle(c1,r1,Color.YELLOW_GREEN,false,1) # 绘制圆1
draw_circle(c2,r2,Color.YELLOW_GREEN,false,1) # 绘制圆2
var p1 = c1.move_toward(c2,a)
draw_line(p1,p1+overlap,Color.ORANGE_RED,1) # 绘制重叠向量
draw_polyline( # 绘制箭头
Transform2D(c1.direction_to(c2).angle(),p1+overlap) * arrow,
Color.ORANGE_RED,1
)
重叠向量是我自己发明的一个词,是能够表现一个图形与另一个图形重叠程度的向量。可以使用反向重叠向量移动被动物体,或者使用重叠向量将移动物体排出被动物体。
矩形与矩形的重叠检测
判断一个举行与另一个矩形是否重合
矩形A的4个点,有任意一个点在另一个矩形B中,就认为矩形A与矩形B重叠。
# 判断rect2和rect1是否有重叠
static func is_rect_rect_overlap(rect1:Rect2,rect2:Rect2) -> bool:
var pos = rect2.position
var end = rect2.end
var size = rect2.size
var points = [
pos,pos + Vector2(size.x,0),
end,end - Vector2(size.x,0)
]
# 矩形1的任何一个点在矩形2内
var bol = points.any(func(p):
return rect1.has_point(p)
)
return bol
测试:
extends Node2D
var rect1 = Rect2(100,100,400,350)
var rect2 = Rect2(200,200,100,80)
# 箭头
var arrow:PackedVector2Array = [
Vector2.LEFT.rotated(deg_to_rad(-30)) * 5,
Vector2(),
Vector2.LEFT.rotated(deg_to_rad(30)) * 5,
]
func _process(delta: float) -> void:
# 用鼠标位置控制Rect2的位置
rect2.position = get_global_mouse_position() - rect2.size/2.0
queue_redraw()
func _draw() -> void:
var overlap = ShapeTests.get_rect_rect_overlap(rect1,rect2)
var c1 = rect1.get_center()
var c2 = rect2.get_center()
for rect in ShapeTests.get_rect_4_parts(rect1):
draw_rect(rect,Color.YELLOW_GREEN,false,1) # 绘制矩形1
#draw_rect(rect1,Color.YELLOW_GREEN,false,1) # 绘制矩形1
draw_rect(rect2,Color.YELLOW_GREEN,false,1) # 绘制矩形2
draw_line(overlap[0],overlap[1],Color.ORANGE_RED,1) # 绘制重叠向量
draw_polyline( # 绘制箭头
Transform2D(overlap[0].direction_to(overlap[1]).angle(),overlap[1]) * arrow,
Color.ORANGE_RED,1
)