From d97226116409765ad62db6b05a4c784913bb49cc Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Fri, 16 Jun 2023 18:47:16 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E9=83=A8=E5=88=86shape=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E8=BD=AC=E7=A7=BB=E5=88=B0rectangle=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E4=B8=94=E7=94=B1=E5=9B=BA=E5=AE=9A=E7=B1=BB=E5=9E=8B=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E6=B3=9B=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/geometry/position.go | 26 ++++++ utils/geometry/rectangle.go | 103 +++++++++++++++++++++++ utils/geometry/rectangle_example_test.go | 16 ++++ utils/geometry/rectangle_test.go | 39 +++++++++ 4 files changed, 184 insertions(+) diff --git a/utils/geometry/position.go b/utils/geometry/position.go index 3de3147..d59a44f 100644 --- a/utils/geometry/position.go +++ b/utils/geometry/position.go @@ -38,6 +38,32 @@ func (slf Point[V]) Copy() Point[V] { return CoordinateArrayCopy(slf) } +// NewPointCap 创建一个由 x、y 坐标组成的点,这个点具有一个数据容量 +func NewPointCap[V generic.Number, D any](x, y V) PointCap[V, D] { + return PointCap[V, D]{ + Point: NewPoint(x, y), + } +} + +// NewPointCapWithData 通过设置数据的方式创建一个由 x、y 坐标组成的点,这个点具有一个数据容量 +func NewPointCapWithData[V generic.Number, D any](x, y V, data D) PointCap[V, D] { + return PointCap[V, D]{ + Point: NewPoint(x, y), + Data: data, + } +} + +// PointCap 表示了一个由 x、y 坐标组成的点,这个点具有一个数据容量 +type PointCap[V generic.Number, D any] struct { + Point[V] + Data D +} + +// GetData 获取数据 +func (slf PointCap[V, D]) GetData() D { + return slf.Data +} + // CoordinateToCoordinateArray 将坐标转换为x、y的坐标数组 func CoordinateToCoordinateArray[V generic.Number](x, y V) Point[V] { return [2]V{x, y} diff --git a/utils/geometry/rectangle.go b/utils/geometry/rectangle.go index 9fb5298..205ad7b 100644 --- a/utils/geometry/rectangle.go +++ b/utils/geometry/rectangle.go @@ -189,3 +189,106 @@ func GetShapeCoverageAreaWithPos[V generic.Number](width V, positions ...V) (lef } return } + +// CoverageAreaBoundless 将一个图形覆盖矩形范围设置为无边的 +// - 无边化表示会将多余的部分进行裁剪,例如图形左边从 2 开始的时候,那么左边将会被裁剪到从 0 开始 +func CoverageAreaBoundless[V generic.Number](l, r, t, b V) (left, right, top, bottom V) { + differentX := 0 - l + differentY := 0 - t + left = l + differentX + right = r + differentX + top = t + differentY + bottom = b + differentY + return +} + +// GenerateShapeOnRectangle 生成一组二维坐标的形状 +// - 这个形状将被在一个刚好能容纳形状的矩形中表示 +// - 为 true 的位置表示了形状的每一个点 +func GenerateShapeOnRectangle[V generic.Number](xys ...Point[V]) (result []PointCap[V, bool]) { + left, r, top, b := GetShapeCoverageAreaWithCoordinateArray(xys...) + _, right, _, bottom := CoverageAreaBoundless(left, r, top, b) + w, h := right+1, bottom+1 + result = make([]PointCap[V, bool], int(w*h)) + for _, xy := range xys { + x, y := xy.GetXY() + sx := x - (r - right) + sy := y - (b - bottom) + pos := CoordinateToPos(w, sx, sy) + pointCap := &result[int(pos)] + pointCap.Point[0] = sx + pointCap.Point[1] = sy + pointCap.Data = true + } + for pos, pointCap := range result { + if !pointCap.Data { + pointCap := &result[pos] + sx, sy := PosToCoordinate(w, V(pos)) + pointCap.Point[0] = sx + pointCap.Point[1] = sy + } + } + return +} + +// GetExpressibleRectangleBySize 获取一个宽高可表达的所有特定尺寸以上的矩形形状 +// - 返回值表示了每一个矩形右下角的x,y位置(左上角始终为0, 0) +// - 矩形尺寸由大到小 +func GetExpressibleRectangleBySize[V generic.Number](width, height, minWidth, minHeight V) (result []Point[V]) { + sourceWidth := width + if width == 0 || height == 0 { + return nil + } + if width < minWidth || height < minHeight { + return nil + } + width-- + height-- + for { + rightBottom := NewPoint(width, height) + result = append(result, rightBottom) + if width == 0 && height == 0 || (width < minWidth && height < minHeight) { + return + } + if width == height { + width-- + } else if width < height { + if width+1 == sourceWidth { + height-- + } else { + width++ + height-- + } + } else if width > height { + width-- + } + } +} + +// GetExpressibleRectangle 获取一个宽高可表达的所有矩形形状 +// - 返回值表示了每一个矩形右下角的x,y位置(左上角始终为0, 0) +// - 矩形尺寸由大到小 +func GetExpressibleRectangle[V generic.Number](width, height V) (result []Point[V]) { + return GetExpressibleRectangleBySize(width, height, 1, 1) +} + +// GetRectangleFullPointsByXY 通过开始结束坐标获取一个矩形包含的所有点 +// - 例如 1,1 到 2,2 的矩形结果为 1,1 2,1 1,2 2,2 +func GetRectangleFullPointsByXY[V generic.Number](startX, startY, endX, endY V) (result []Point[V]) { + for x := startX; x <= endX; x++ { + for y := startY; y <= endY; y++ { + result = append(result, NewPoint(x, y)) + } + } + return +} + +// GetRectangleFullPoints 获取一个矩形包含的所有点 +func GetRectangleFullPoints[V generic.Number](width, height V) (result []Point[V]) { + for x := V(0); x < width; x++ { + for y := V(0); y < height; y++ { + result = append(result, NewPoint(x, y)) + } + } + return +} diff --git a/utils/geometry/rectangle_example_test.go b/utils/geometry/rectangle_example_test.go index a8b52f6..ea667b8 100644 --- a/utils/geometry/rectangle_example_test.go +++ b/utils/geometry/rectangle_example_test.go @@ -31,3 +31,19 @@ func ExampleGetShapeCoverageAreaWithPos() { // left: 1, right: 2, top: 1, bottom: 2 } + +func ExampleCoverageAreaBoundless() { + // # # # + // # X # + // # X X + + // ↓ + + // X # + // X X + + left, right, top, bottom := geometry.CoverageAreaBoundless(1, 2, 1, 2) + fmt.Println(fmt.Sprintf("left: %v, right: %v, top: %v, bottom: %v", left, right, top, bottom)) + + // left: 0, right: 1, top: 0, bottom: 1 +} diff --git a/utils/geometry/rectangle_test.go b/utils/geometry/rectangle_test.go index 0e548a2..8ac2672 100644 --- a/utils/geometry/rectangle_test.go +++ b/utils/geometry/rectangle_test.go @@ -32,3 +32,42 @@ func TestGetShapeCoverageAreaWithPos(t *testing.T) { So(bottom, ShouldEqual, 2) }) } + +func TestCoverageAreaBoundless(t *testing.T) { + Convey("TestCoverageAreaBoundless", t, func() { + left, right, top, bottom := geometry.CoverageAreaBoundless(1, 2, 1, 2) + + So(left, ShouldEqual, 0) + So(right, ShouldEqual, 1) + So(top, ShouldEqual, 0) + So(bottom, ShouldEqual, 1) + }) +} + +func TestGenerateShapeOnRectangle(t *testing.T) { + Convey("TestGenerateShapeOnRectangle", t, func() { + var points []geometry.Point[int] + points = append(points, geometry.NewPoint(1, 1)) + points = append(points, geometry.NewPoint(2, 1)) + points = append(points, geometry.NewPoint(2, 2)) + + ps := geometry.GenerateShapeOnRectangle(points...) + + So(ps[0].GetX(), ShouldEqual, 0) + So(ps[0].GetY(), ShouldEqual, 0) + So(ps[0].GetData(), ShouldEqual, true) + + So(ps[1].GetX(), ShouldEqual, 1) + So(ps[1].GetY(), ShouldEqual, 0) + So(ps[1].GetData(), ShouldEqual, true) + + So(ps[2].GetX(), ShouldEqual, 0) + So(ps[2].GetY(), ShouldEqual, 1) + So(ps[2].GetData(), ShouldEqual, false) + + So(ps[3].GetX(), ShouldEqual, 1) + So(ps[3].GetY(), ShouldEqual, 1) + So(ps[3].GetData(), ShouldEqual, true) + + }) +}