feat: geometry 包新增 SimpleCircle 结构体,用于表示仅由圆心及半径组成的圆形,包含投影、距离等常用函数。优化 geometry 中的计算函数,所有计算入参均会转换为 float64 运算,输出时转换回原有的泛型类型
This commit is contained in:
parent
7fa0e68636
commit
6846c9dfc7
|
@ -5,7 +5,7 @@ import (
|
|||
"math"
|
||||
)
|
||||
|
||||
// Circle 圆形
|
||||
// Circle 由多个点组成的圆形数据结构
|
||||
type Circle[V generic.SignedNumber] struct {
|
||||
Shape[V]
|
||||
}
|
||||
|
|
|
@ -32,6 +32,8 @@ func GetOppositionDirection(direction Direction) Direction {
|
|||
return DirectionRight
|
||||
case DirectionRight:
|
||||
return DirectionLeft
|
||||
case DirectionUnknown:
|
||||
return DirectionUnknown
|
||||
}
|
||||
return DirectionUnknown
|
||||
}
|
||||
|
@ -89,21 +91,14 @@ func GetDirectionNextWithPos[V generic.SignedNumber](direction Direction, width,
|
|||
|
||||
// CalcDirection 计算点2位于点1的方向
|
||||
func CalcDirection[V generic.SignedNumber](x1, y1, x2, y2 V) Direction {
|
||||
var oneEighty = 180
|
||||
var fortyFive = 45
|
||||
var oneThirtyFive = 135
|
||||
var twoTwentyFive = 225
|
||||
var threeFifteen = 315
|
||||
var end = 360
|
||||
var start = 0
|
||||
angle := CalcAngle(x1, y1, x2, y2) + V(oneEighty)
|
||||
if angle > V(oneThirtyFive) && angle <= V(twoTwentyFive) {
|
||||
angle := CalcAngle(float64(x1), float64(y1), float64(x2), float64(y2)) + 180
|
||||
if angle > 45 && angle <= 225 {
|
||||
return DirectionRight
|
||||
} else if (angle > V(threeFifteen) && angle <= V(end)) || (angle >= V(start) && angle <= V(fortyFive)) {
|
||||
} else if (angle > 315 && angle <= 360) || (angle >= 0 && angle <= 45) {
|
||||
return DirectionLeft
|
||||
} else if angle > V(twoTwentyFive) && angle <= V(threeFifteen) {
|
||||
} else if angle > 225 && angle <= 315 {
|
||||
return DirectionUp
|
||||
} else if angle > V(fortyFive) && angle <= V(oneThirtyFive) {
|
||||
} else if angle > 45 && angle <= 134 {
|
||||
return DirectionDown
|
||||
}
|
||||
return DirectionUnknown
|
||||
|
@ -141,20 +136,18 @@ func CalcNewCoordinate[V generic.SignedNumber](x, y, angle, distance V) (newX, n
|
|||
|
||||
// CalcRadianWithAngle 根据角度 angle 计算弧度
|
||||
func CalcRadianWithAngle[V generic.SignedNumber](angle V) V {
|
||||
var pi = math.Pi
|
||||
var dividend = 180.0
|
||||
return angle * V(pi) / V(dividend)
|
||||
return V(float64(angle) * math.Pi / 180)
|
||||
}
|
||||
|
||||
// CalcAngleDifference 计算两个角度之间的最小角度差
|
||||
func CalcAngleDifference[V generic.SignedNumber](angleA, angleB V) V {
|
||||
pi := math.Pi
|
||||
t := angleA - angleB
|
||||
a := t + V(pi)
|
||||
b := V(pi) * 2
|
||||
t = V(math.Floor(float64(a/b))) * b
|
||||
t -= V(pi)
|
||||
return t
|
||||
t := float64(angleA) - float64(angleB)
|
||||
a := t + math.Pi
|
||||
b := math.Pi * 2
|
||||
t = math.Floor(a/b) * b
|
||||
t -= pi
|
||||
return V(t)
|
||||
}
|
||||
|
||||
// CalcRayIsIntersect 根据给定的位置和角度生成射线,检测射线是否与多边形发生碰撞
|
||||
|
|
|
@ -172,7 +172,7 @@ func CalcLineSegmentIsIntersect[V generic.SignedNumber](line1, line2 LineSegment
|
|||
|
||||
// CalcLineSegmentSlope 计算线段的斜率
|
||||
func CalcLineSegmentSlope[V generic.SignedNumber](line LineSegment[V]) V {
|
||||
return (line.GetEnd().GetY() - line.GetStart().GetY()) / (line.GetEnd().GetX() - line.GetStart().GetX())
|
||||
return V((float64(line.GetEnd().GetY()) - float64(line.GetStart().GetY())) / (float64(line.GetEnd().GetX()) - float64(line.GetStart().GetX())))
|
||||
}
|
||||
|
||||
// CalcLineSegmentIntercept 计算线段的截距
|
||||
|
|
|
@ -84,6 +84,18 @@ func (slf Point[V]) Abs() Point[V] {
|
|||
return NewPoint(V(math.Abs(float64(slf.GetX()))), V(math.Abs(float64(slf.GetY()))))
|
||||
}
|
||||
|
||||
// Distance 返回两个点之间的距离
|
||||
func (slf Point[V]) Distance(point Point[V]) float64 {
|
||||
return math.Sqrt(float64(slf.DistanceSquared(point)))
|
||||
}
|
||||
|
||||
// DistanceSquared 返回两个点之间的距离的平方
|
||||
func (slf Point[V]) DistanceSquared(point Point[V]) V {
|
||||
x, y := slf.GetXY()
|
||||
px, py := point.GetXY()
|
||||
return (x-px)*(x-px) + (y-py)*(y-py)
|
||||
}
|
||||
|
||||
// Max 返回两个位置中每个维度的最大值组成的新的位置
|
||||
func (slf Point[V]) Max(point Point[V]) Point[V] {
|
||||
x, y := slf.GetXY()
|
||||
|
|
|
@ -4,19 +4,20 @@ import "github.com/kercylan98/minotaur/utils/generic"
|
|||
|
||||
// GetAdjacentTranslatePos 获取一个连续位置的矩阵中,特定位置相邻的最多四个平移方向(上下左右)的位置
|
||||
func GetAdjacentTranslatePos[T any, P generic.SignedNumber](matrix []T, width, pos P) (result []P) {
|
||||
size := P(len(matrix))
|
||||
currentRow := pos / width
|
||||
if up := pos - width; up >= 0 {
|
||||
result = append(result, up)
|
||||
wf, pf := float64(width), float64(pos)
|
||||
size := float64(len(matrix))
|
||||
currentRow := pf / wf
|
||||
if up := -wf; up >= 0 {
|
||||
result = append(result, P(up))
|
||||
}
|
||||
if down := pos + width; down < size {
|
||||
result = append(result, down)
|
||||
if down := pf + wf; down < size {
|
||||
result = append(result, P(down))
|
||||
}
|
||||
if left := pos - 1; left >= 0 && currentRow == (left/width) {
|
||||
result = append(result, left)
|
||||
if left := pf - 1; left >= 0 && currentRow == (left/wf) {
|
||||
result = append(result, P(left))
|
||||
}
|
||||
if right := pos + 1; right < size && currentRow == (right/width) {
|
||||
result = append(result, right)
|
||||
if right := pf + 1; right < size && currentRow == (right/wf) {
|
||||
result = append(result, P(right))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -61,19 +62,20 @@ func GetAdjacentTranslateCoordinateYX[T any, P generic.SignedNumber](matrix [][]
|
|||
|
||||
// GetAdjacentDiagonalsPos 获取一个连续位置的矩阵中,特定位置相邻的对角线最多四个方向的位置
|
||||
func GetAdjacentDiagonalsPos[T any, P generic.SignedNumber](matrix []T, width, pos P) (result []P) {
|
||||
size := P(len(matrix))
|
||||
currentRow := pos / width
|
||||
if topLeft := pos - width - 1; topLeft >= 0 && currentRow-1 == (topLeft/width) {
|
||||
result = append(result, topLeft)
|
||||
size := float64(len(matrix))
|
||||
wf, pf := float64(width), float64(pos)
|
||||
currentRow := pf / wf
|
||||
if topLeft := pf - wf - 1; topLeft >= 0 && currentRow-1 == (topLeft/wf) {
|
||||
result = append(result, P(topLeft))
|
||||
}
|
||||
if topRight := pos - width + 1; topRight >= 0 && currentRow-1 == (topRight/width) {
|
||||
result = append(result, topRight)
|
||||
if topRight := pf - wf + 1; topRight >= 0 && currentRow-1 == (topRight/wf) {
|
||||
result = append(result, P(topRight))
|
||||
}
|
||||
if bottomLeft := pos + width - 1; bottomLeft < size && currentRow+1 == (bottomLeft/width) {
|
||||
result = append(result, bottomLeft)
|
||||
if bottomLeft := pf + wf - 1; bottomLeft < size && currentRow+1 == (bottomLeft/wf) {
|
||||
result = append(result, P(bottomLeft))
|
||||
}
|
||||
if bottomRight := pos + width + 1; bottomRight < size && currentRow+1 == (bottomRight/width) {
|
||||
result = append(result, bottomRight)
|
||||
if bottomRight := pf + wf + 1; bottomRight < size && currentRow+1 == (bottomRight/wf) {
|
||||
result = append(result, P(bottomRight))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -326,13 +328,13 @@ func GetRectangleFullPos[V generic.SignedNumber](width, height V) (result []V) {
|
|||
// CalcRectangleCentroid 计算矩形质心
|
||||
// - 非多边形质心计算,仅为顶点的平均值 - 该区域中多边形因子的适当质心
|
||||
func CalcRectangleCentroid[V generic.SignedNumber](shape Shape[V]) Point[V] {
|
||||
x, y := V(0), V(0)
|
||||
length := V(shape.PointCount())
|
||||
var x, y float64
|
||||
length := float64(shape.PointCount())
|
||||
for _, point := range shape.Points() {
|
||||
x += point.GetX()
|
||||
y += point.GetY()
|
||||
x += float64(point.GetX())
|
||||
y += float64(point.GetY())
|
||||
}
|
||||
x /= length
|
||||
y /= length
|
||||
return NewPoint(x, y)
|
||||
return NewPoint(V(x), V(x))
|
||||
}
|
||||
|
|
|
@ -413,7 +413,7 @@ func (slf Shape[V]) getAllGraphicCompositionWithDesc(opt *shapeSearchOptions) (r
|
|||
return
|
||||
}
|
||||
|
||||
// CalcBoundingRadius 计算多边形转换为圆的半径
|
||||
// CalcBoundingRadius 计算多边形转换为圆的半径,即外接圆的半径
|
||||
func CalcBoundingRadius[V generic.SignedNumber](shape Shape[V]) V {
|
||||
var boundingRadius V
|
||||
var centroid = CalcRectangleCentroid(shape)
|
||||
|
@ -426,6 +426,11 @@ func CalcBoundingRadius[V generic.SignedNumber](shape Shape[V]) V {
|
|||
return boundingRadius
|
||||
}
|
||||
|
||||
// CalcBoundingRadiusWithWidthAndHeight 计算多边形转换为圆的半径,即外接圆的半径
|
||||
func CalcBoundingRadiusWithWidthAndHeight[V generic.SignedNumber](width, height V) V {
|
||||
return V(math.Sqrt(float64(width*width+height*height)) / 2)
|
||||
}
|
||||
|
||||
// CalcBoundingRadiusWithCentroid 计算多边形在特定质心下圆的半径
|
||||
func CalcBoundingRadiusWithCentroid[V generic.SignedNumber](shape Shape[V], centroid Point[V]) V {
|
||||
var boundingRadius V
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
package geometry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/utils/generic"
|
||||
"github.com/kercylan98/minotaur/utils/random"
|
||||
"math"
|
||||
)
|
||||
|
||||
// NewSimpleCircle 通过传入圆的半径和圆心位置,生成一个圆
|
||||
func NewSimpleCircle[V generic.SignedNumber](radius V, centroid Point[V]) SimpleCircle[V] {
|
||||
if radius <= 0 {
|
||||
panic(fmt.Errorf("radius must be greater than 0, but got %v", radius))
|
||||
}
|
||||
return SimpleCircle[V]{
|
||||
centroid: centroid,
|
||||
radius: radius,
|
||||
}
|
||||
}
|
||||
|
||||
// SimpleCircle 仅由位置和半径组成的圆形数据结构
|
||||
type SimpleCircle[V generic.SignedNumber] struct {
|
||||
centroid Point[V] // 圆心位置
|
||||
radius V // 半径
|
||||
}
|
||||
|
||||
// String 获取圆形的字符串表示
|
||||
func (sc SimpleCircle[V]) String() string {
|
||||
return fmt.Sprintf("SimpleCircle{centroid: %v, %v, radius: %v}", sc.centroid.GetX(), sc.centroid.GetY(), sc.radius)
|
||||
}
|
||||
|
||||
// Centroid 获取圆形质心位置
|
||||
func (sc SimpleCircle[V]) Centroid() Point[V] {
|
||||
return sc.centroid
|
||||
}
|
||||
|
||||
// CentroidX 获取圆形质心位置的 X 坐标
|
||||
func (sc SimpleCircle[V]) CentroidX() V {
|
||||
return sc.centroid.GetX()
|
||||
}
|
||||
|
||||
// CentroidY 获取圆形质心位置的 Y 坐标
|
||||
func (sc SimpleCircle[V]) CentroidY() V {
|
||||
return sc.centroid.GetY()
|
||||
}
|
||||
|
||||
// CentroidXY 获取圆形质心位置的 X、Y 坐标
|
||||
func (sc SimpleCircle[V]) CentroidXY() (V, V) {
|
||||
return sc.centroid.GetXY()
|
||||
}
|
||||
|
||||
// PointIsIn 检查特定点是否位于圆内
|
||||
func (sc SimpleCircle[V]) PointIsIn(pos Point[V]) bool {
|
||||
return V(pos.Distance(sc.centroid)) <= sc.radius
|
||||
}
|
||||
|
||||
// CentroidDistance 计算与另一个圆的质心距离
|
||||
func (sc SimpleCircle[V]) CentroidDistance(circle SimpleCircle[V]) float64 {
|
||||
return sc.centroid.Distance(circle.centroid)
|
||||
}
|
||||
|
||||
// Radius 获取圆形半径
|
||||
func (sc SimpleCircle[V]) Radius() V {
|
||||
return sc.radius
|
||||
}
|
||||
|
||||
// ZoomRadius 获取缩放后的半径
|
||||
func (sc SimpleCircle[V]) ZoomRadius(zoom float64) V {
|
||||
return V(float64(sc.radius) * zoom)
|
||||
}
|
||||
|
||||
// Area 获取圆形面积
|
||||
func (sc SimpleCircle[V]) Area() V {
|
||||
return sc.radius * sc.radius
|
||||
}
|
||||
|
||||
// Projection 获取圆形投影到另一个圆形的特定比例下的位置和半径
|
||||
func (sc SimpleCircle[V]) Projection(circle SimpleCircle[V], ratio float64) SimpleCircle[V] {
|
||||
// 计算圆心朝目标按比例移动后的位置
|
||||
distance := float64(sc.Centroid().Distance(circle.centroid))
|
||||
moveDistance := distance * ratio
|
||||
newX := float64(sc.CentroidX()) + moveDistance*(float64(circle.CentroidX())-float64(sc.CentroidX()))/distance
|
||||
newY := float64(sc.CentroidY()) + moveDistance*(float64(circle.CentroidY())-float64(sc.CentroidY()))/distance
|
||||
|
||||
return NewSimpleCircle(V(float64(sc.radius)*ratio), NewPoint(V(newX), V(newY)))
|
||||
}
|
||||
|
||||
// Length 获取圆的周长
|
||||
func (sc SimpleCircle[V]) Length() V {
|
||||
return 2 * sc.radius
|
||||
}
|
||||
|
||||
// Overlap 与另一个圆是否发生重叠
|
||||
func (sc SimpleCircle[V]) Overlap(circle SimpleCircle[V]) bool {
|
||||
return sc.centroid.Distance(circle.centroid) < float64(sc.radius+circle.radius)
|
||||
}
|
||||
|
||||
// RandomPoint 获取圆内随机点
|
||||
func (sc SimpleCircle[V]) RandomPoint() Point[V] {
|
||||
rx := V(random.Float64() * float64(sc.radius))
|
||||
ry := V(random.Float64() * float64(sc.radius))
|
||||
if random.Bool() {
|
||||
rx = -rx
|
||||
}
|
||||
if random.Bool() {
|
||||
ry = -ry
|
||||
}
|
||||
return sc.centroid.GetOffset(rx, ry)
|
||||
}
|
||||
|
||||
// RandomPointWithinCircle 获取圆内随机点,且该圆在 radius 小于父圆时不会超出父圆
|
||||
func (sc SimpleCircle[V]) RandomPointWithinCircle(radius V) Point[V] {
|
||||
// 生成随机角度
|
||||
angle := random.Float64() * 2 * math.Pi
|
||||
|
||||
// 限制坐标随机范围
|
||||
var rx, ry float64
|
||||
if radius < sc.radius {
|
||||
r := float64(sc.radius - radius)
|
||||
rx = random.Float64() * r
|
||||
ry = random.Float64() * r
|
||||
} else {
|
||||
r := float64(sc.radius)
|
||||
rx = random.Float64() * r
|
||||
ry = random.Float64() * r
|
||||
}
|
||||
|
||||
// 生成随机点
|
||||
return sc.centroid.GetOffset(V(rx*math.Cos(angle)), V(ry*math.Sin(angle)))
|
||||
}
|
||||
|
||||
// RandomPointWithinRadius 获取圆内随机点,且该点与圆心的距离小于等于指定半径
|
||||
func (sc SimpleCircle[V]) RandomPointWithinRadius(radius V) Point[V] {
|
||||
if radius > sc.radius {
|
||||
panic("radius must be less than or equal to the circle radius")
|
||||
}
|
||||
rx := V(random.Float64() * float64(radius))
|
||||
ry := V(random.Float64() * float64(radius))
|
||||
if random.Bool() {
|
||||
rx = -rx
|
||||
}
|
||||
if random.Bool() {
|
||||
ry = -ry
|
||||
}
|
||||
return sc.centroid.GetOffset(rx, ry)
|
||||
}
|
||||
|
||||
// RandomPointWithinRadiusAndSector 获取圆内随机点,且距离圆心小于等于指定半径,且角度在指定范围内
|
||||
// - startAngle: 起始角度,取值范围为 0 到 360 度
|
||||
// - endAngle: 结束角度,取值范围为 0 到 360 度,且大于起始角度
|
||||
func (sc SimpleCircle[V]) RandomPointWithinRadiusAndSector(radius, startAngle, endAngle V) Point[V] {
|
||||
var full = 360
|
||||
if radius > sc.radius {
|
||||
panic("radius must be less than or equal to the circle radius")
|
||||
}
|
||||
if startAngle < 0 || startAngle > V(full) {
|
||||
panic("startAngle must be in the range 0 to 360 degrees")
|
||||
}
|
||||
if endAngle < 0 || endAngle > V(full) {
|
||||
panic("endAngle must be in the range 0 to 360 degrees")
|
||||
}
|
||||
if startAngle > endAngle {
|
||||
panic("startAngle must be less than or equal to endAngle")
|
||||
}
|
||||
angle := V(random.Float64() * float64(endAngle-startAngle))
|
||||
return sc.centroid.GetOffset(radius*V(math.Cos(float64(angle))), radius*V(math.Sin(float64(angle))))
|
||||
}
|
||||
|
||||
// RandomCircleWithinParent 根据指定半径,生成一个圆内随机子圆,该圆不会超出父圆
|
||||
func (sc SimpleCircle[V]) RandomCircleWithinParent(radius V) SimpleCircle[V] {
|
||||
if radius > sc.radius {
|
||||
panic("radius must be less than or equal to the circle radius")
|
||||
}
|
||||
return NewSimpleCircle(radius, sc.RandomPointWithinCircle(radius))
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package geometry_test
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/geometry"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSimpleCircle_RandomSubCircle(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
sc := geometry.NewSimpleCircle(10, geometry.NewPoint(0, 0))
|
||||
sub := sc.RandomCircleWithinParent(8)
|
||||
|
||||
t.Log(sc)
|
||||
t.Log(sub)
|
||||
t.Log(sc.CentroidDistance(sub))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue