diff --git a/utils/geometry/geometry.go b/utils/geometry/geometry.go index 30d7f45..0d24c69 100644 --- a/utils/geometry/geometry.go +++ b/utils/geometry/geometry.go @@ -139,3 +139,14 @@ func CalculateNewCoordinate[V generic.SignedNumber](x, y, angle, distance V) (ne return newX, newY } + +// CalcAngleDifference 计算两个角度之间的最小角度差 +func CalcAngleDifference[V generic.Number](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 -= pi + return t +} diff --git a/utils/geometry/line.go b/utils/geometry/line.go index b4c6a6f..62bfcee 100644 --- a/utils/geometry/line.go +++ b/utils/geometry/line.go @@ -2,8 +2,41 @@ package geometry import ( "github.com/kercylan98/minotaur/utils/generic" + "github.com/kercylan98/minotaur/utils/maths" + "sort" ) +// NewLine 创建一根线段 +func NewLine[V generic.SignedNumber](start, end Point[V]) Line[V] { + if start.Equal(end) { + panic("two points of the line segment are the same") + } + return Line[V]{start, end} +} + +// Line 通过两个点表示一根线段 +type Line[V generic.SignedNumber] [2]Point[V] + +// GetPoints 获取该线段的两个点 +func (slf Line[V]) GetPoints() [2]Point[V] { + return slf +} + +// GetStart 获取该线段的开始位置 +func (slf Line[V]) GetStart() Point[V] { + return slf[0] +} + +// GetEnd 获取该线段的结束位置 +func (slf Line[V]) GetEnd() Point[V] { + return slf[1] +} + +// GetLength 获取该线段的长度 +func (slf Line[V]) GetLength() V { + return CalcDistance(DoublePointToCoordinate(slf.GetStart(), slf.GetEnd())) +} + // PointOnLineWithCoordinate 通过一个线段两个点的位置和一个点的坐标,判断这个点是否在一条线段上 func PointOnLineWithCoordinate[V generic.SignedNumber](x1, y1, x2, y2, x, y V) bool { return (x-x1)*(y2-y1) == (x2-x1)*(y-y1) @@ -48,3 +81,38 @@ func PointOnSegmentWithCoordinateArray[V generic.SignedNumber](point1, point2, p x, y := point.GetXY() return x >= x1 && x <= x2 && y >= y1 && y <= y2 && PointOnLineWithCoordinate(x1, y1, x2, y2, x, y) } + +// CalcLineIsCollinear 检查两条线段在一个误差内是否共线 +// - 共线是指两条线段在同一直线上,即它们的延长线可以重合 +func CalcLineIsCollinear[V generic.SignedNumber](line1, line2 Line[V], tolerance V) bool { + area1 := CalcTriangleTwiceArea(line1.GetStart(), line1.GetEnd(), line2.GetStart()) + area2 := CalcTriangleTwiceArea(line1.GetStart(), line1.GetEnd(), line2.GetEnd()) + return maths.Tolerance(area1, 0, tolerance) && maths.Tolerance(area2, 0, tolerance) +} + +// CalcLineIsOverlap 通过对点进行排序来检查两条共线线段是否重叠,返回重叠线段 +func CalcLineIsOverlap[V generic.SignedNumber](line1, line2 Line[V]) (line Line[V], overlap bool) { + var shapes = []Shape[V]{ + {line1.GetStart(), line1.GetEnd(), line1.GetStart()}, + {line1.GetStart(), line1.GetEnd(), line1.GetEnd()}, + {line2.GetStart(), line2.GetEnd(), line2.GetStart()}, + {line2.GetStart(), line2.GetEnd(), line2.GetEnd()}, + } + sort.Slice(shapes, func(i, j int) bool { + a, b := shapes[i], shapes[j] + if a[2].GetX() < b[2].GetX() { + return true + } else if a[2].GetX() > b[2].GetX() { + return false + } else { + return a[2].GetY() < b[2].GetY() + } + }) + + notOverlap := shapes[0][0].Equal(shapes[1][0]) && shapes[0][1].Equal(shapes[1][1]) + singlePointOverlap := shapes[1][2].Equal(shapes[2][2]) + if notOverlap || singlePointOverlap { + return line, false + } + return NewLine(shapes[1][2], shapes[2][2]), true +} diff --git a/utils/geometry/rectangle.go b/utils/geometry/rectangle.go index 58fc7fb..cef4ff1 100644 --- a/utils/geometry/rectangle.go +++ b/utils/geometry/rectangle.go @@ -322,3 +322,14 @@ func GetRectangleFullPos[V generic.SignedNumber](width, height V) (result []V) { } return } + +// CalcRectangleCentroid 计算矩形质心 +// - 非多边形质心计算,仅为顶点的平均值 - 该区域中多边形因子的适当质心 +func CalcRectangleCentroid[V generic.SignedNumber](shape Shape[V]) Point[V] { + x, y := V(0), V(0) + length := V(shape.PointCount()) + + x /= length + y /= length + return NewPoint(x, y) +} diff --git a/utils/geometry/shape.go b/utils/geometry/shape.go index f7f17e3..f9c340a 100644 --- a/utils/geometry/shape.go +++ b/utils/geometry/shape.go @@ -11,9 +11,19 @@ import ( ) var ( - ShapeStringHasBorder = false // 控制 Shape.String 是否拥有边界 + shapeStringHasBorder = false // 控制 Shape.String 是否拥有边界 ) +// SetShapeStringHasBorder 设置 Shape.String 是拥有边界的 +func SetShapeStringHasBorder() { + shapeStringHasBorder = true +} + +// SetShapeStringNotHasBorder 设置 Shape.String 是没有边界的 +func SetShapeStringNotHasBorder() { + shapeStringHasBorder = false +} + // NewShape 通过多个点生成一个形状进行返回 func NewShape[V generic.SignedNumber](points ...Point[V]) Shape[V] { return points @@ -63,7 +73,7 @@ func (slf Shape[V]) String() string { left, right, top, bottom := GetShapeCoverageAreaWithCoordinateArray(slf.Points()...) width := right - left + 1 height := bottom - top + 1 - if !ShapeStringHasBorder { + if !shapeStringHasBorder { for y := top; y < top+height; y++ { for x := left; x < left+width; x++ { exist := false @@ -334,6 +344,20 @@ func (slf Shape[V]) getAllGraphicComposition(opt *shapeSearchOptions) (result [] return result } +// Edges 获取该形状每一条边 +// - 该形状需要最少由3个点组成,否则将不会返回任意一边 +func (slf Shape[V]) Edges() (edges []Line[V]) { + if len(slf) < 3 { + return + } + for i := 1; i < slf.PointCount(); i++ { + before := slf[i-1] + edges = append(edges, NewLine(before, slf[i])) + } + edges = append(edges, NewLine(slf[0], slf[len(slf)-1])) + return edges +} + // getAllGraphicCompositionWithAsc 通过升序的方式获取该形状中包含的所有图形组合及其位置 // - 升序指标为图形包含的点数量 // - 其余内容可参考 getAllGraphicComposition @@ -355,3 +379,37 @@ func (slf Shape[V]) getAllGraphicCompositionWithDesc(opt *shapeSearchOptions) (r }) return } + +// CalcBoundingRadius 计算多边形转换为圆的半径 +func CalcBoundingRadius[V generic.SignedNumber](shape Shape[V]) V { + var boundingRadius V + var centroid = CalcRectangleCentroid(shape) + for _, point := range shape.Points() { + distance := CalcDistance(DoublePointToCoordinate(centroid, point)) + if distance > boundingRadius { + boundingRadius = distance + } + } + return boundingRadius +} + +// CalcBoundingRadiusWithCentroid 计算多边形在特定质心下圆的半径 +func CalcBoundingRadiusWithCentroid[V generic.SignedNumber](shape Shape[V], centroid Point[V]) V { + var boundingRadius V + for _, point := range shape.Points() { + distance := CalcDistance(DoublePointToCoordinate(centroid, point)) + if distance > boundingRadius { + boundingRadius = distance + } + } + return boundingRadius +} + +// CalcTriangleTwiceArea 计算由 a、b、c 三个点组成的三角形的面积的两倍 +func CalcTriangleTwiceArea[V generic.SignedNumber](a, b, c Point[V]) V { + ax := b.GetX() - a.GetX() + ay := b.GetY() - a.GetY() + bx := c.GetX() - a.GetX() + by := c.GetY() - a.GetY() + return bx*ay - ax*by +} diff --git a/utils/maths/math.go b/utils/maths/math.go index 0117adc..9864792 100644 --- a/utils/maths/math.go +++ b/utils/maths/math.go @@ -2,8 +2,18 @@ package maths import ( "github.com/kercylan98/minotaur/utils/generic" + "math" ) +const ( + DefaultTolerance = 0.0001 // 默认误差范围 +) + +// GetDefaultTolerance 获取默认误差范围 +func GetDefaultTolerance() float64 { + return DefaultTolerance +} + // Pow 整数幂运算 func Pow(a, n int) int { if a == 0 { @@ -94,3 +104,8 @@ func Clamp[V generic.Number](value, min, max V) V { } return value } + +// Tolerance 检查两个值是否在一个误差范围内 +func Tolerance[V generic.Number](value1, value2, tolerance V) bool { + return V(math.Abs(float64(value1-value2))) <= tolerance +}