From 7a8b07d1c186fbad6a16f16e1f85ab9070dcb0e3 Mon Sep 17 00:00:00 2001 From: gravestench Date: Tue, 1 Dec 2020 22:23:49 -0800 Subject: [PATCH] adding a full-featured rectangle implementation to d2geom, adding an ecs component for it --- d2common/d2geom/rectangle/area.go | 6 + d2common/d2geom/rectangle/ceil.go | 12 + d2common/d2geom/rectangle/ceil_all.go | 14 + d2common/d2geom/rectangle/center_on.go | 6 + d2common/d2geom/rectangle/contains.go | 10 + .../d2geom/rectangle/contains_rectangle.go | 13 + d2common/d2geom/rectangle/copy_from.go | 6 + d2common/d2geom/rectangle/deconstruct.go | 19 + d2common/d2geom/rectangle/equals.go | 16 + d2common/d2geom/rectangle/fit_inside.go | 17 + d2common/d2geom/rectangle/get_aspect_ratio.go | 16 + d2common/d2geom/rectangle/get_center.go | 8 + d2common/d2geom/rectangle/get_point.go | 38 ++ d2common/d2geom/rectangle/get_points.go | 31 ++ d2common/d2geom/rectangle/get_random_point.go | 19 + .../rectangle/get_random_point_outside.go | 44 +++ .../rectangle/get_rectangle_from_points.go | 9 + d2common/d2geom/rectangle/get_size.go | 9 + d2common/d2geom/rectangle/inflate.go | 11 + d2common/d2geom/rectangle/intersection.go | 29 ++ d2common/d2geom/rectangle/marching_ants.go | 89 +++++ d2common/d2geom/rectangle/merge_points.go | 23 ++ d2common/d2geom/rectangle/merge_rectangle.go | 7 + d2common/d2geom/rectangle/merge_xy.go | 9 + d2common/d2geom/rectangle/namespace.go | 184 +++++++++ d2common/d2geom/rectangle/offset.go | 9 + d2common/d2geom/rectangle/offset_point.go | 11 + d2common/d2geom/rectangle/overlaps.go | 10 + d2common/d2geom/rectangle/perimeter.go | 6 + d2common/d2geom/rectangle/perimeter_point.go | 24 ++ d2common/d2geom/rectangle/rectangle.go | 367 ++++++++++++++++++ d2common/d2geom/rectangle/same_dimensions.go | 12 + d2common/d2geom/rectangle/scale.go | 9 + d2common/d2geom/rectangle/union.go | 12 + d2core/d2components/rectangle.go | 43 ++ 35 files changed, 1148 insertions(+) create mode 100644 d2common/d2geom/rectangle/area.go create mode 100644 d2common/d2geom/rectangle/ceil.go create mode 100644 d2common/d2geom/rectangle/ceil_all.go create mode 100644 d2common/d2geom/rectangle/center_on.go create mode 100644 d2common/d2geom/rectangle/contains.go create mode 100644 d2common/d2geom/rectangle/contains_rectangle.go create mode 100644 d2common/d2geom/rectangle/copy_from.go create mode 100644 d2common/d2geom/rectangle/deconstruct.go create mode 100644 d2common/d2geom/rectangle/equals.go create mode 100644 d2common/d2geom/rectangle/fit_inside.go create mode 100644 d2common/d2geom/rectangle/get_aspect_ratio.go create mode 100644 d2common/d2geom/rectangle/get_center.go create mode 100644 d2common/d2geom/rectangle/get_point.go create mode 100644 d2common/d2geom/rectangle/get_points.go create mode 100644 d2common/d2geom/rectangle/get_random_point.go create mode 100644 d2common/d2geom/rectangle/get_random_point_outside.go create mode 100644 d2common/d2geom/rectangle/get_rectangle_from_points.go create mode 100644 d2common/d2geom/rectangle/get_size.go create mode 100644 d2common/d2geom/rectangle/inflate.go create mode 100644 d2common/d2geom/rectangle/intersection.go create mode 100644 d2common/d2geom/rectangle/marching_ants.go create mode 100644 d2common/d2geom/rectangle/merge_points.go create mode 100644 d2common/d2geom/rectangle/merge_rectangle.go create mode 100644 d2common/d2geom/rectangle/merge_xy.go create mode 100644 d2common/d2geom/rectangle/namespace.go create mode 100644 d2common/d2geom/rectangle/offset.go create mode 100644 d2common/d2geom/rectangle/offset_point.go create mode 100644 d2common/d2geom/rectangle/overlaps.go create mode 100644 d2common/d2geom/rectangle/perimeter.go create mode 100644 d2common/d2geom/rectangle/perimeter_point.go create mode 100644 d2common/d2geom/rectangle/rectangle.go create mode 100644 d2common/d2geom/rectangle/same_dimensions.go create mode 100644 d2common/d2geom/rectangle/scale.go create mode 100644 d2common/d2geom/rectangle/union.go create mode 100644 d2core/d2components/rectangle.go diff --git a/d2common/d2geom/rectangle/area.go b/d2common/d2geom/rectangle/area.go new file mode 100644 index 00000000..00059514 --- /dev/null +++ b/d2common/d2geom/rectangle/area.go @@ -0,0 +1,6 @@ +package rectangle + +// Area calculates the area of the given rectangle. +func Area(r *Rectangle) float64 { + return r.Width * r.Height +} diff --git a/d2common/d2geom/rectangle/ceil.go b/d2common/d2geom/rectangle/ceil.go new file mode 100644 index 00000000..22665ae3 --- /dev/null +++ b/d2common/d2geom/rectangle/ceil.go @@ -0,0 +1,12 @@ +package rectangle + +import "math" + +// Ceil rounds a Rectangle's position up to the smallest integer greater than or equal to each +// current coordinate. +func Ceil(r *Rectangle) *Rectangle { + r.X = math.Ceil(r.X) + r.Y = math.Ceil(r.Y) + + return r +} diff --git a/d2common/d2geom/rectangle/ceil_all.go b/d2common/d2geom/rectangle/ceil_all.go new file mode 100644 index 00000000..d3c127a1 --- /dev/null +++ b/d2common/d2geom/rectangle/ceil_all.go @@ -0,0 +1,14 @@ +package rectangle + +import "math" + +// CeilAll rounds a Rectangle's position and size up to the smallest +// integer greater than or equal to each respective value. +func CeilAll(r *Rectangle) *Rectangle { + r.X = math.Ceil(r.X) + r.Y = math.Ceil(r.Y) + r.Width = math.Ceil(r.Width) + r.Height = math.Ceil(r.Height) + + return r +} diff --git a/d2common/d2geom/rectangle/center_on.go b/d2common/d2geom/rectangle/center_on.go new file mode 100644 index 00000000..99f72e6d --- /dev/null +++ b/d2common/d2geom/rectangle/center_on.go @@ -0,0 +1,6 @@ +package rectangle + +// CenterOn moves the top-left corner of a Rectangle so that its center is at the given coordinates. +func CenterOn(r *Rectangle, x, y float64) *Rectangle { + return r.SetCenterX(x).SetCenterY(y) +} diff --git a/d2common/d2geom/rectangle/contains.go b/d2common/d2geom/rectangle/contains.go new file mode 100644 index 00000000..cfa70cb1 --- /dev/null +++ b/d2common/d2geom/rectangle/contains.go @@ -0,0 +1,10 @@ +package rectangle + +// Contains checks if the given x, y is inside the Rectangle's bounds. +func Contains(r *Rectangle, x, y float64) bool { + if r.Width <= 0 || r.Height <= 0 { + return false + } + + return r.X <= x && r.X+r.Width >= x && r.Y <= y && r.Y+r.Height >= y +} diff --git a/d2common/d2geom/rectangle/contains_rectangle.go b/d2common/d2geom/rectangle/contains_rectangle.go new file mode 100644 index 00000000..cecb4b54 --- /dev/null +++ b/d2common/d2geom/rectangle/contains_rectangle.go @@ -0,0 +1,13 @@ +package rectangle + +// ContainsRectangle checks if one rectangle fully contains another. +func ContainsRectangle(a, b *Rectangle) bool { + if b.Area() > a.Area() { + return false + } + + return (b.X > a.X && b.X < a.Right()) && + (b.Right() > a.X && b.Right() < a.Right()) && + (b.Y > a.Y && b.Y < a.Bottom()) && + (b.Bottom() > a.Y && b.Bottom() < a.Bottom()) +} diff --git a/d2common/d2geom/rectangle/copy_from.go b/d2common/d2geom/rectangle/copy_from.go new file mode 100644 index 00000000..74c40cde --- /dev/null +++ b/d2common/d2geom/rectangle/copy_from.go @@ -0,0 +1,6 @@ +package rectangle + +// CopyFrom copies the values of one Rectangle to a destination Rectangle. +func CopyFrom(source, dest *Rectangle) *Rectangle { + return dest.SetTo(source.X, source.Y, source.Width, source.Height) +} diff --git a/d2common/d2geom/rectangle/deconstruct.go b/d2common/d2geom/rectangle/deconstruct.go new file mode 100644 index 00000000..5dc40fae --- /dev/null +++ b/d2common/d2geom/rectangle/deconstruct.go @@ -0,0 +1,19 @@ +package rectangle + +import "github.com/gravestench/pho/geom/point" + +// Deconstruct creates a slice of points for each corner of a Rectangle. +// If a slice is specified, each point object will be added to the end of the slice, +// otherwise a new slice will be created. +func Deconstruct(r *Rectangle, to []*point.Point) []*point.Point { + if to == nil { + to = make([]*point.Point, 0) + } + + to = append(to, point.New(r.X, r.Y)) + to = append(to, point.New(r.Right(), r.Y)) + to = append(to, point.New(r.Right(), r.Bottom())) + to = append(to, point.New(r.X, r.Bottom())) + + return to +} diff --git a/d2common/d2geom/rectangle/equals.go b/d2common/d2geom/rectangle/equals.go new file mode 100644 index 00000000..b4b3256a --- /dev/null +++ b/d2common/d2geom/rectangle/equals.go @@ -0,0 +1,16 @@ +package rectangle + +import "github.com/gravestench/pho/phomath" + +// Equals compares the `x`, `y`, `width` and `height` properties of two rectangles. +func Equals(a, b *Rectangle) bool { + dx := (a.X - b.X) * (a.X - b.X) + dy := (a.Y - b.Y) * (a.Y - b.Y) + dw := (a.Width - b.Width) * (a.Width - b.Width) + dh := (a.Height - b.Height) * (a.Height - b.Height) + + return dx < phomath.Epsilon && + dy < phomath.Epsilon && + dw < phomath.Epsilon && + dh < phomath.Epsilon +} diff --git a/d2common/d2geom/rectangle/fit_inside.go b/d2common/d2geom/rectangle/fit_inside.go new file mode 100644 index 00000000..3e9af8db --- /dev/null +++ b/d2common/d2geom/rectangle/fit_inside.go @@ -0,0 +1,17 @@ +package rectangle + +// Adjusts rectangle `a`, changing its width, height and position, +// so that it fits inside the area of rectangle `b`, while maintaining its original +// aspect ratio. +func FitInside(a, b *Rectangle) *Rectangle { + aRatio := GetAspectRatio(a) + bRatio := GetAspectRatio(b) + + if aRatio < bRatio { + a.SetSize(b.Height*aRatio, b.Height) + } else { + a.SetSize(b.Width, b.Width/aRatio) + } + + return a.CenterOn(b.CenterX(), b.CenterY()) +} diff --git a/d2common/d2geom/rectangle/get_aspect_ratio.go b/d2common/d2geom/rectangle/get_aspect_ratio.go new file mode 100644 index 00000000..e63905c1 --- /dev/null +++ b/d2common/d2geom/rectangle/get_aspect_ratio.go @@ -0,0 +1,16 @@ +package rectangle + +import ( + "math" + + "github.com/gravestench/pho/phomath" +) + +// GetAspectRatio returns the aspect ratio (width/height) of the given rectangle +func GetAspectRatio(r *Rectangle) float64 { + if r.Height < phomath.Epsilon { + return math.NaN() + } + + return r.Width / r.Height +} diff --git a/d2common/d2geom/rectangle/get_center.go b/d2common/d2geom/rectangle/get_center.go new file mode 100644 index 00000000..d4f5606d --- /dev/null +++ b/d2common/d2geom/rectangle/get_center.go @@ -0,0 +1,8 @@ +package rectangle + +import "github.com/gravestench/pho/geom/point" + +// GetCenter returns the center of the Rectangle as a Point. +func GetCenter(r *Rectangle) *point.Point { + return point.New(r.CenterX(), r.CenterY()) +} diff --git a/d2common/d2geom/rectangle/get_point.go b/d2common/d2geom/rectangle/get_point.go new file mode 100644 index 00000000..642ae313 --- /dev/null +++ b/d2common/d2geom/rectangle/get_point.go @@ -0,0 +1,38 @@ +package rectangle + +import "github.com/gravestench/pho/geom/point" + +// GetPoint calculates the coordinates of a point at a certain `position` on the +// Rectangle's perimeter, assigns to and returns the given point, or creates a point if nil. +func GetPoint(r *Rectangle, position float64, p *point.Point) *point.Point { + if p == nil { + p = point.New(0, 0) + } + + if position <= 0 || position >= 1 { + p.X, p.Y = r.X, r.Y + return p + } + + perimeter := Perimeter(r) * position + + if position > 0.5 { + perimeter -= r.Width + r.Height + + if perimeter <= r.Width { + // face 3 + p.X, p.Y = r.Right()-perimeter, r.Bottom() + } else { + // face 4 + p.X, p.Y = r.X, r.Bottom()-(perimeter-r.Width) + } + } else if position <= r.Width { + // face 1 + p.X, p.Y = r.X+perimeter, r.Y + } else { + // face 2 + p.X, p.Y = r.Right(), r.Y+(perimeter-r.Width) + } + + return p +} diff --git a/d2common/d2geom/rectangle/get_points.go b/d2common/d2geom/rectangle/get_points.go new file mode 100644 index 00000000..652685bc --- /dev/null +++ b/d2common/d2geom/rectangle/get_points.go @@ -0,0 +1,31 @@ +package rectangle + +import ( + "github.com/gravestench/pho/geom/point" +) + +const ByStepRate = -1 + +// GetPoints returns a slice of points from the perimeter of the Rectangle, +// each spaced out based on the quantity or step required. +func GetPoints(r *Rectangle, quantity int, stepRate float64, points []*point.Point) []*point.Point { + if quantity == ByStepRate { + quantity = int(Perimeter(r) / stepRate) + } + + if points == nil { + points = make([]*point.Point, 0) + } + + for len(points) < quantity { + points = append(points, nil) + } + + for idx := 0; idx < quantity; idx++ { + position := float64(idx) / float64(quantity) + + points[idx] = GetPoint(r, position, nil) + } + + return points +} diff --git a/d2common/d2geom/rectangle/get_random_point.go b/d2common/d2geom/rectangle/get_random_point.go new file mode 100644 index 00000000..1f37eeff --- /dev/null +++ b/d2common/d2geom/rectangle/get_random_point.go @@ -0,0 +1,19 @@ +package rectangle + +import ( + "math/rand" + + "github.com/gravestench/pho/geom/point" +) + +// GetRandomPoint returns a random point within the Rectangle's bounds. +func GetRandomPoint(r *Rectangle, p *point.Point) *point.Point { + if p == nil { + p = point.New(0, 0) + } + + p.X = r.X + (rand.Float64() * r.Width) + p.Y = r.Y + (rand.Float64() * r.Height) + + return p +} diff --git a/d2common/d2geom/rectangle/get_random_point_outside.go b/d2common/d2geom/rectangle/get_random_point_outside.go new file mode 100644 index 00000000..456614c7 --- /dev/null +++ b/d2common/d2geom/rectangle/get_random_point_outside.go @@ -0,0 +1,44 @@ +package rectangle + +import ( + "math/rand" + + "github.com/gravestench/pho/geom/point" + "github.com/gravestench/pho/phomath" +) + +// Calculates a random point that lies within the `outer` Rectangle, but outside of the `inner` +// Rectangle. The inner Rectangle must be fully contained within the outer rectangle. +func GetRandomPointOutside(outer, inner *Rectangle, out *point.Point) *point.Point { + if out == nil { + out = point.New(0, 0) + } + + if !ContainsRectangle(outer, inner) { + return out + } + + const ( + top = iota + right + bottom + left + ) + + switch phomath.Between(top, left) { + case top: + out.X = outer.X + (rand.Float64() * (inner.Right() - outer.X)) + out.Y = outer.Y + (rand.Float64() * (inner.Top() - outer.Y)) + case right: + out.X = inner.X + (rand.Float64() * (outer.Right() - inner.X)) + out.Y = inner.Y + (rand.Float64() * (outer.Bottom() - inner.Bottom())) + case bottom: + out.X = outer.X + (rand.Float64() * (inner.X - outer.X)) + out.Y = inner.Y + (rand.Float64() * (outer.Bottom() - inner.Y)) + case left: + out.X = inner.Right() + (rand.Float64() * (outer.Right() - inner.Right())) + out.Y = outer.Y + (rand.Float64() * (inner.Bottom() - outer.Y)) + } + + return out +} diff --git a/d2common/d2geom/rectangle/get_rectangle_from_points.go b/d2common/d2geom/rectangle/get_rectangle_from_points.go new file mode 100644 index 00000000..0bca359a --- /dev/null +++ b/d2common/d2geom/rectangle/get_rectangle_from_points.go @@ -0,0 +1,9 @@ +package rectangle + +import "github.com/gravestench/pho/geom/point" + +// GetRectangleFromPoints calculates the Axis Aligned Bounding Box ( or aabb) from an array of +// points. +func GetRectangleFromPoints(points []*point.Point) *Rectangle { + return New(0, 0, 0, 0).MergePoints(points) +} diff --git a/d2common/d2geom/rectangle/get_size.go b/d2common/d2geom/rectangle/get_size.go new file mode 100644 index 00000000..40022784 --- /dev/null +++ b/d2common/d2geom/rectangle/get_size.go @@ -0,0 +1,9 @@ +package rectangle + +import "github.com/gravestench/pho/geom/point" + +// GetSize returns the size of the Rectangle, expressed as a Point object. +// With the value of the `width` as the `x` property and the `height` as the `y` property. +func GetSize(r *Rectangle) *point.Point { + return point.New(r.Width, r.Height) +} diff --git a/d2common/d2geom/rectangle/inflate.go b/d2common/d2geom/rectangle/inflate.go new file mode 100644 index 00000000..59feda57 --- /dev/null +++ b/d2common/d2geom/rectangle/inflate.go @@ -0,0 +1,11 @@ +package rectangle + +// Inflate increases the size of a Rectangle by a specified amount. +// The center of the Rectangle stays the same. The amounts are added to each side, +// so the actual increase in width or height is two times bigger than the respective argument. +func Inflate(r *Rectangle, x, y float64) *Rectangle { + cx, cy := r.CenterX(), r.CenterY() + r.Width, r.Height = r.Width+(2*x), r.Height+(2*y) + + return r.CenterOn(cx, cy) +} diff --git a/d2common/d2geom/rectangle/intersection.go b/d2common/d2geom/rectangle/intersection.go new file mode 100644 index 00000000..bfce60e1 --- /dev/null +++ b/d2common/d2geom/rectangle/intersection.go @@ -0,0 +1,29 @@ +package rectangle + +import ( + "math" + + "github.com/gravestench/pho/geom/intersects" +) + +// Takes two Rectangles and first checks to see if they intersect. +// If they intersect it will return the area of intersection in the `out` Rectangle. +// If they do not intersect, the `out` Rectangle will have a width and height of zero. +// The given `output` rectangle will be assigned the intsersect values and returned. +// A new rectangle will be created if it is nil. +func Intersection(a, b, output *Rectangle) *Rectangle { + if output == nil { + output = New(0, 0, 0, 0) + } + + if intersects.RectangleToRectangle(a, b) { + output.X = math.Max(a.X, b.X) + output.Y = math.Max(a.Y, b.Y) + output.Width = math.Min(a.Right(), b.Right()) - output.X + output.Height = math.Min(a.Bottom(), b.Bottom()) - output.Y + } else { + output.SetEmpty() + } + + return output +} diff --git a/d2common/d2geom/rectangle/marching_ants.go b/d2common/d2geom/rectangle/marching_ants.go new file mode 100644 index 00000000..374477c7 --- /dev/null +++ b/d2common/d2geom/rectangle/marching_ants.go @@ -0,0 +1,89 @@ +package rectangle + +import ( + "math" + + "github.com/gravestench/pho/geom/point" +) + +const ( + AutoStep = -1 + AutoQuantity = -1 +) + +// Returns an array of points from the perimeter of the Rectangle, +// where each point is spaced out based on either the `step` value, or the `quantity`. +func MarchingAnts(r *Rectangle, step float64, quantity int, out []*point.Point) []*point.Point { + if step <= 0 { + step = AutoStep + } + + if quantity <= 0 { + quantity = AutoQuantity + } + + if out == nil { + out = make([]*point.Point, quantity) + } + + if step == AutoStep && quantity == AutoQuantity { + return /* bail */ out + } + + if step == AutoStep { + step = Perimeter(r) / float64(quantity) + } else { + quantity = int(math.Round(Perimeter(r) / step)) + } + + const ( + top = iota + right + bottom + left + numFaces + ) + + x, y := r.X, r.Y + face := top + + for idx := 0; idx < quantity; idx++ { + out = append(out, point.New(x, y)) + + switch face { + case top: + x += step + + if x >= r.Right() { + face = (face + 1) % numFaces + y += x - r.Right() + x = r.Right() + } + case right: + y += step + + if y >= r.Bottom() { + face = (face + 1) % numFaces + x -= y - r.Bottom() + y = r.Bottom() + } + case bottom: + x -= step + + if x <= r.Left() { + face = (face + 1) % numFaces + y -= r.Left() - x + x = r.Left() + } + case left: + y -= step + + if y <= r.Top() { + face = (face + 1) % numFaces + y = r.Top() + } + } + } + + return out +} diff --git a/d2common/d2geom/rectangle/merge_points.go b/d2common/d2geom/rectangle/merge_points.go new file mode 100644 index 00000000..6db566c1 --- /dev/null +++ b/d2common/d2geom/rectangle/merge_points.go @@ -0,0 +1,23 @@ +package rectangle + +import ( + "math" + + "github.com/gravestench/pho/geom/point" +) + +// Merges a Rectangle with a list of points by repositioning and/or resizing +// it such that all points are located on or within its bounds. +func MergePoints(r *Rectangle, points []*point.Point) *Rectangle { + minX, maxX, minY, maxY := r.X, r.Right(), r.Y, r.Bottom() + + for idx := range points { + minX, maxX = math.Min(minX, points[idx].X), math.Max(maxX, points[idx].X) + minY, maxY = math.Min(minY, points[idx].Y), math.Max(maxY, points[idx].Y) + } + + r.X, r.Y = minX, minY + r.Width, r.Height = maxX-minX, maxY-minY + + return r +} diff --git a/d2common/d2geom/rectangle/merge_rectangle.go b/d2common/d2geom/rectangle/merge_rectangle.go new file mode 100644 index 00000000..a0fbf9c0 --- /dev/null +++ b/d2common/d2geom/rectangle/merge_rectangle.go @@ -0,0 +1,7 @@ +package rectangle + +// MergeRectangle merges the source rectangle into the target rectangle and returns the target. +// Neither rectangle should have a negative width or height. +func MergeRectangle(target, source *Rectangle) *Rectangle { + return MergePoints(target, source.Deconstruct(nil)) +} diff --git a/d2common/d2geom/rectangle/merge_xy.go b/d2common/d2geom/rectangle/merge_xy.go new file mode 100644 index 00000000..a5ba818a --- /dev/null +++ b/d2common/d2geom/rectangle/merge_xy.go @@ -0,0 +1,9 @@ +package rectangle + +import "github.com/gravestench/pho/geom/point" + +// MergeXY merges a Rectangle with a point by repositioning and/or resizing it +// so that the point is on or within its bounds. +func MergeXY(r *Rectangle, x, y float64) *Rectangle { + return r.MergePoints([]*point.Point{point.New(x, y)}) +} diff --git a/d2common/d2geom/rectangle/namespace.go b/d2common/d2geom/rectangle/namespace.go new file mode 100644 index 00000000..130ef355 --- /dev/null +++ b/d2common/d2geom/rectangle/namespace.go @@ -0,0 +1,184 @@ +package rectangle + +import ( + "github.com/gravestench/pho/geom/point" +) + +type RectangleNamespace interface { + Contains(r *Rectangle, x, y float64) bool + GetPoint(r *Rectangle, position float64, p *point.Point) *point.Point + GetPoints(r *Rectangle, quantity int, stepRate float64, points []*point.Point) []*point.Point + GetRandomPoint(r *Rectangle, p *point.Point) *point.Point + ContainsPoint(r *Rectangle, p *point.Point) bool + ContainsRectangle(r *Rectangle, other *Rectangle) bool + Deconstruct(r *Rectangle, to []*point.Point) []*point.Point + Equals(r *Rectangle, other *Rectangle) bool + FitInside(r *Rectangle, other *Rectangle) *Rectangle + Inflate(r *Rectangle, x, y float64) *Rectangle + Intersection(r *Rectangle, other, intersect *Rectangle) *Rectangle + MergePoints(r *Rectangle, points []*point.Point) *Rectangle + MergeRectangle(r *Rectangle, other *Rectangle) *Rectangle + MergeXY(r *Rectangle, x, y float64) *Rectangle + Offset(r *Rectangle, x, y float64) *Rectangle + OffsetPoint(r *Rectangle, p *point.Point) *Rectangle + Overlaps(r *Rectangle, other *Rectangle) bool + PerimeterPoint(r *Rectangle, angle float64, p *point.Point) *point.Point + GetRandomPointOutside(r *Rectangle, other *Rectangle, out *point.Point) *point.Point + SameDimensions(r *Rectangle, other *Rectangle) bool + Scale(r *Rectangle, x, y float64) *Rectangle + Union(r *Rectangle, other *Rectangle) *Rectangle +} + +type Namespace struct{} + +// Contains checks if the given x, y is inside the Rectangle's bounds. +func (*Namespace) Contains(r *Rectangle, x, y float64) bool { + return Contains(r, x, y) +} + +// GetPoint calculates the coordinates of a point at a certain `position` on the +// Rectangle's perimeter, assigns to and returns the given point, or creates a point if nil. +func (*Namespace) GetPoint(r *Rectangle, position float64, p *point.Point) *point.Point { + return GetPoint(r, position, p) +} + +// GetPoints returns a slice of points from the perimeter of the Rectangle, +// each spaced out based on the quantity or step required. +func (*Namespace) GetPoints(r *Rectangle, quantity int, stepRate float64, + points []*point.Point) []*point.Point { + return GetPoints(r, quantity, stepRate, points) +} + +// GetRandomPoint returns a random point within the Rectangle's bounds. +func (*Namespace) GetRandomPoint(r *Rectangle, p *point.Point) *point.Point { + return GetRandomPoint(r, p) +} + +// Ceil rounds a Rectangle's position up to the smallest integer greater than or equal to each +// current coordinate. +func (*Namespace) Ceil(r *Rectangle) *Rectangle { + return Ceil(r) +} + +// CeilAll rounds a Rectangle's position and size up to the smallest +// integer greater than or equal to each respective value. +func (*Namespace) CeilAll(r *Rectangle) *Rectangle { + return CeilAll(r) +} + +// ContainsPoint checks if a given point is inside a Rectangle's bounds. +func (*Namespace) ContainsPoint(r *Rectangle, p *point.Point) bool { + return Contains(r, p.X, p.Y) +} + +// ContainsRect checks if a given point is inside a Rectangle's bounds. +func (*Namespace) ContainsRectangle(r, other *Rectangle) bool { + return ContainsRectangle(r, other) +} + +// CopyFrom copies the values of the given rectangle. +func (*Namespace) CopyFrom(target, source *Rectangle) *Rectangle { + return CopyFrom(source, target) +} + +// Deconstruct creates a slice of points for each corner of a Rectangle. +// If a slice is specified, each point object will be added to the end of the slice, +// otherwise a new slice will be created. +func (*Namespace) Deconstruct(r *Rectangle, to []*point.Point) []*point.Point { + return Deconstruct(r, to) +} + +// Equals compares the `x`, `y`, `width` and `height` properties of two rectangles. +func (*Namespace) Equals(a, b *Rectangle) bool { + return Equals(a, b) +} + +// Adjusts rectangle, changing its width, height and position, +// so that it fits inside the area of the source rectangle, while maintaining its original +// aspect ratio. +func (*Namespace) FitInside(inner, outer *Rectangle) *Rectangle { + return FitInside(inner, outer) +} + +func (*Namespace) Inflate(r *Rectangle, x, y float64) *Rectangle { + return Inflate(r, x, y) +} + +// Takes two Rectangles and first checks to see if they intersect. +// If they intersect it will return the area of intersection in the `out` Rectangle. +// If they do not intersect, the `out` Rectangle will have a width and height of zero. +// The given `intersect` rectangle will be assigned the intsersect values and returned. +// A new rectangle will be created if `intersect` is nil. +func (*Namespace) Intersection(r, other, intersect *Rectangle) *Rectangle { + return Intersection(r, other, intersect) +} + +// MergePoints adjusts this rectangle using a list of points by repositioning and/or resizing +// it such that all points are located on or within its bounds. +func (*Namespace) MergePoints(r *Rectangle, points []*point.Point) *Rectangle { + return MergePoints(r, points) +} + +// MergeRectangle merges the given rectangle into this rectangle and returns this rectangle. +// Neither rectangle should have a negative width or height. +func (*Namespace) MergeRectangle(r *Rectangle, other *Rectangle) *Rectangle { + return MergeRectangle(r, other) +} + +// MergeXY merges this rectangle with a point by repositioning and/or resizing it so that the +// point is/on or/within its bounds. +func (*Namespace) MergeXY(r *Rectangle, x, y float64) *Rectangle { + return MergeXY(r, x, y) +} + +// Offset nudges (translates) the top left corner of this Rectangle by a given offset. +func (*Namespace) Offset(r *Rectangle, x, y float64) *Rectangle { + return Offset(r, x, y) +} + +// OffsetPoint nudges (translates) the top left corner of this Rectangle by the coordinates of a +// point. +func (*Namespace) OffsetPoint(r *Rectangle, p *point.Point) *Rectangle { + return OffsetPoint(r, p) +} + +// Checks if this Rectangle overlaps with another rectangle. +func (*Namespace) Overlaps(r *Rectangle, other *Rectangle) bool { + return Overlaps(r, other) +} + +// PerimeterPoint returns a Point from the perimeter of the Rectangle based on the given angle. +func (*Namespace) PerimeterPoint(r *Rectangle, angle float64, p *point.Point) *point.Point { + return PerimeterPoint(r, angle, p) +} + +// Calculates a random point that lies within the `outer` Rectangle, but outside of the `inner` +// Rectangle. The inner Rectangle must be fully contained within the outer rectangle. +func (*Namespace) GetRandomPointOutside(r, other *Rectangle, out *point.Point) *point.Point { + var outer, inner *Rectangle + + if r.ContainsRectangle(other) { + outer, inner = r, other + } else { + outer, inner = other, r + } + + return GetRandomPointOutside(outer, inner, out) +} + +// SameDimensions determines if the two objects (either Rectangles or Rectangle-like) have the same +// width and height values under strict equality. +func (*Namespace) SameDimensions(r, other *Rectangle) bool { + return SameDimensions(r, other) +} + +// Scale the width and height of this Rectangle by the given amounts. +func (*Namespace) Scale(r *Rectangle, x, y float64) *Rectangle { + return Scale(r, x, y) +} + +// Union creates a new Rectangle or repositions and/or resizes an existing Rectangle so that it +// encompasses the two given Rectangles, i.e. calculates their union. +func (*Namespace) Union(r *Rectangle, other *Rectangle) *Rectangle { + return Union(r, other, r) +} diff --git a/d2common/d2geom/rectangle/offset.go b/d2common/d2geom/rectangle/offset.go new file mode 100644 index 00000000..04d1149e --- /dev/null +++ b/d2common/d2geom/rectangle/offset.go @@ -0,0 +1,9 @@ +package rectangle + +// Offset nudges (translates) the top left corner of a Rectangle by a given offset. +func Offset(r *Rectangle, x, y float64) *Rectangle { + r.X += x + r.Y += y + + return r +} diff --git a/d2common/d2geom/rectangle/offset_point.go b/d2common/d2geom/rectangle/offset_point.go new file mode 100644 index 00000000..5b3748ac --- /dev/null +++ b/d2common/d2geom/rectangle/offset_point.go @@ -0,0 +1,11 @@ +package rectangle + +import "github.com/gravestench/pho/geom/point" + +// OffsetPoint nudges (translates) the top left corner of a Rectangle by the coordinates of a point. +func OffsetPoint(r *Rectangle, p *point.Point) *Rectangle { + r.X += p.X + r.Y += p.Y + + return r +} diff --git a/d2common/d2geom/rectangle/overlaps.go b/d2common/d2geom/rectangle/overlaps.go new file mode 100644 index 00000000..0685c23a --- /dev/null +++ b/d2common/d2geom/rectangle/overlaps.go @@ -0,0 +1,10 @@ +package rectangle + +// Checks if two Rectangles overlap. If a Rectangle is within another Rectangle, +// the two will be considered overlapping. Thus, the Rectangles are treated as "solid". +func Overlaps(a, b *Rectangle) bool { + return a.X < b.Right() && + a.Right() > b.X && + a.Y < b.Bottom() && + a.Bottom() > b.Y +} diff --git a/d2common/d2geom/rectangle/perimeter.go b/d2common/d2geom/rectangle/perimeter.go new file mode 100644 index 00000000..9a7ff621 --- /dev/null +++ b/d2common/d2geom/rectangle/perimeter.go @@ -0,0 +1,6 @@ +package rectangle + +// Perimeter calculates the perimeter of a Rectangle. +func Perimeter(r *Rectangle) float64 { + return 2 * (r.Width + r.Height) +} diff --git a/d2common/d2geom/rectangle/perimeter_point.go b/d2common/d2geom/rectangle/perimeter_point.go new file mode 100644 index 00000000..466f72a8 --- /dev/null +++ b/d2common/d2geom/rectangle/perimeter_point.go @@ -0,0 +1,24 @@ +package rectangle + +import ( + "math" + + "github.com/gravestench/pho/geom/point" + "github.com/gravestench/pho/phomath" +) + +func PerimeterPoint(r *Rectangle, angle float64, out *point.Point) *point.Point { + if out == nil { + out = point.New(0, 0) + } + + angle = phomath.DegToRad(angle) + polarity := map[bool]float64{true: 1, false: -1} + s, c := math.Sin(angle), math.Cos(angle) + dx, dy := r.Width/2*polarity[c > 0], r.Height/2*polarity[s > 0] + + out.X = dx + r.CenterX() + out.Y = dy + r.CenterY() + + return out +} diff --git a/d2common/d2geom/rectangle/rectangle.go b/d2common/d2geom/rectangle/rectangle.go new file mode 100644 index 00000000..57d5b599 --- /dev/null +++ b/d2common/d2geom/rectangle/rectangle.go @@ -0,0 +1,367 @@ +package rectangle + +import ( + "github.com/gravestench/pho/geom" + "github.com/gravestench/pho/geom/line" + "github.com/gravestench/pho/geom/point" + "github.com/gravestench/pho/phomath" +) + +// New creates a new rectangle +func New(x, y, w, h float64) *Rectangle { + return &Rectangle{ + Type: geom.Rectangle, + X: x, + Y: y, + Width: w, + Height: h, + } +} + +// Encapsulates a 2D rectangle defined by its corner point in the top-left and its extends +// in x (width) and y (height) +type Rectangle struct { + Type geom.ShapeType + X, Y float64 + Width, Height float64 +} + +// Area calculates the area of the given rectangle. +func (r *Rectangle) Area() float64 { + return r.Width * r.Height +} + +// Left returns the left position of the rectangle +func (r *Rectangle) Left() float64 { + return r.X +} + +// SetLeft sets the left position of the rectangle, which also sets the x coordinate +func (r *Rectangle) SetLeft(value float64) *Rectangle { + if value >= r.Right() { + r.Width = 0 + } else { + r.Width = r.Right() - value + } + + r.X = value + return r +} + +// Right returns the right position of the rectangle +func (r *Rectangle) Right() float64 { + return r.X + r.Width +} + +// SetRight sets the right position of the rectangle, which adjusts the width +func (r *Rectangle) SetRight(value float64) *Rectangle { + if value <= r.X { + r.Width = 0 + } else { + r.Width = value - r.X + } + + r.X = value + + return r +} + +// Top returns the top position of the rectangle +func (r *Rectangle) Top() float64 { + return r.Y +} + +// SetTop sets the top position of the rectangle, which also adjusts the Y coordinate +func (r *Rectangle) SetTop(value float64) *Rectangle { + if value >= r.Bottom() { + r.Height = 0 + } else { + r.Height = r.Bottom() - value + } + + return r +} + +// Bottom returns the bottom position of the rectangle +func (r *Rectangle) Bottom() float64 { + return r.Y + r.Height +} + +// SetBottom sets the bottom position of the rectangle, which also adjusts the height +func (r *Rectangle) SetBottom(value float64) *Rectangle { + if value <= r.Y { + r.Height = 0 + } else { + r.Height = value - r.Y + } + + return r +} + +func (r *Rectangle) CenterX() float64 { + return r.X + r.Width/2 +} + +func (r *Rectangle) SetCenterX(value float64) *Rectangle { + r.X = value - r.Width/2 + return r +} + +func (r *Rectangle) CenterY() float64 { + return r.Y + r.Height/2 +} + +func (r *Rectangle) SetCenterY(value float64) *Rectangle { + r.Y = value - r.Height/2 + + return r +} + +// CenterOn moves the top-left corner of a Rectangle so that its center is at the given coordinates. +func (r *Rectangle) CenterOn(x, y float64) *Rectangle { + return CenterOn(r, x, y) +} + +// Contains checks if the given x, y is inside the Rectangle's bounds. +func (r *Rectangle) Contains(x, y float64) bool { + return Contains(r, x, y) +} + +// GetPoint calculates the coordinates of a point at a certain `position` on the +// Rectangle's perimeter, assigns to and returns the given point, or creates a point if nil. +func (r *Rectangle) GetPoint(position float64, p *point.Point) *point.Point { + return GetPoint(r, position, p) +} + +// GetPoints returns a slice of points from the perimeter of the Rectangle, +// each spaced out based on the quantity or step required. +func (r *Rectangle) GetPoints(quantity int, stepRate float64, points []*point.Point) []*point.Point { + return GetPoints(r, quantity, stepRate, points) +} + +// GetRandomPoint returns a random point within the Rectangle's bounds. +func (r *Rectangle) GetRandomPoint(p *point.Point) *point.Point { + return GetRandomPoint(r, p) +} + +// SetTo sets the position, width, and height of the Rectangle. +func (r *Rectangle) SetTo(x, y, w, h float64) *Rectangle { + r.X, r.Y, r.Width, r.Height = x, y, w, h + return r +} + +// SetEmpty resets the position, width, and height of the Rectangle to 0. +func (r *Rectangle) SetEmpty() *Rectangle { + return r.SetTo(0, 0, 0, 0) +} + +// SetPosition sets the position of the rectangle. +func (r *Rectangle) SetPosition(x, y float64) *Rectangle { + r.X, r.Y = x, y + return r +} + +// SetSize sets the width and height of the rectangle. +func (r *Rectangle) SetSize(w, h float64) *Rectangle { + r.Width, r.Height = w, h + return r +} + +// IsEmpty determines if the Rectangle is empty. +// A Rectangle is empty if its width or height is less than or equal to 0. +func (r *Rectangle) IsEmpty() bool { + return r.Width <= phomath.Epsilon || r.Height <= phomath.Epsilon +} + +// GetLineA returns a line object that corresponds to the top side of this rectangle. +// Assigns to the given line and returns it, or creates a new line if nil. +func (r *Rectangle) GetLineA(l *line.Line) *line.Line { + if l == nil { + l = line.New(0, 0, 0, 0) + } + + l.SetTo(r.X, r.Y, r.Right(), r.Y) + + return l +} + +// GetLineB returns a line object that corresponds to the right side of this rectangle. +// Assigns to the given line and returns it, or creates a new line if nil. +func (r *Rectangle) GetLineB(l *line.Line) *line.Line { + if l == nil { + l = line.New(0, 0, 0, 0) + } + + l.SetTo(r.Right(), r.Y, r.Right(), r.Bottom()) + + return l +} + +// GetLineC returns a line object that corresponds to the bottom side of this rectangle. +// Assigns to the given line and returns it, or creates a new line if nil. +func (r *Rectangle) GetLineC(l *line.Line) *line.Line { + if l == nil { + l = line.New(0, 0, 0, 0) + } + + l.SetTo(r.X, r.Bottom(), r.X, r.Y) + + return l +} + +// GetLineD returns a line object that corresponds to the left side of this rectangle. +// Assigns to the given line and returns it, or creates a new line if nil. +func (r *Rectangle) GetLineD(l *line.Line) *line.Line { + if l == nil { + l = line.New(0, 0, 0, 0) + } + + l.SetTo(r.X, r.Y, r.X, r.Bottom()) + + return l +} + +// Ceil rounds a Rectangle's position up to the smallest integer greater than or equal to each +// current coordinate. +func (r *Rectangle) Ceil() *Rectangle { + return Ceil(r) +} + +// CeilAll rounds a Rectangle's position and size up to the smallest +// integer greater than or equal to each respective value. +func (r *Rectangle) CeilAll() *Rectangle { + return CeilAll(r) +} + +// Clone this rectangle to a new rectangle instance +func (r *Rectangle) Clone() *Rectangle { + return New(r.X, r.Y, r.Width, r.Height) +} + +// ContainsPoint checks if a given point is inside a Rectangle's bounds. +func (r *Rectangle) ContainsPoint(p *point.Point) bool { + return Contains(r, p.X, p.Y) +} + +// ContainsRect checks if a given point is inside a Rectangle's bounds. +func (r *Rectangle) ContainsRectangle(other *Rectangle) bool { + return ContainsRectangle(r, other) +} + +// CopyFrom copies the values of the given rectangle. +func (r *Rectangle) CopyFrom(source *Rectangle) *Rectangle { + return CopyFrom(source, r) +} + +// Deconstruct creates a slice of points for each corner of a Rectangle. +// If a slice is specified, each point object will be added to the end of the slice, +// otherwise a new slice will be created. +func (r *Rectangle) Deconstruct(to []*point.Point) []*point.Point { + return Deconstruct(r, to) +} + +// Equals compares the `x`, `y`, `width` and `height` properties of two rectangles. +func (r *Rectangle) Equals(other *Rectangle) bool { + return Equals(r, other) +} + +// Adjusts rectangle, changing its width, height and position, +// so that it fits inside the area of the source rectangle, while maintaining its original +// aspect ratio. +func (r *Rectangle) FitInside(other *Rectangle) *Rectangle { + return FitInside(r, other) +} + +// GetCenter returns the center of the Rectangle as a Point. +func (r *Rectangle) GetCenter() *point.Point { + return GetCenter(r) +} + +// GetSize returns the size of the Rectangle, expressed as a Point object. +// With the value of the `width` as the `x` property and the `height` as the `y` property. +func (r *Rectangle) GetSize() *point.Point { + return GetSize(r) +} + +func (r *Rectangle) Inflate(x, y float64) *Rectangle { + return Inflate(r, x, y) +} + +// Takes two Rectangles and first checks to see if they intersect. +// If they intersect it will return the area of intersection in the `out` Rectangle. +// If they do not intersect, the `out` Rectangle will have a width and height of zero. +// The given `intersect` rectangle will be assigned the intsersect values and returned. +// A new rectangle will be created if `intersect` is nil. +func (r *Rectangle) Intersection(other, intersect *Rectangle) *Rectangle { + return Intersection(r, other, intersect) +} + +// MergePoints adjusts this rectangle using a list of points by repositioning and/or resizing +// it such that all points are located on or within its bounds. +func (r *Rectangle) MergePoints(points []*point.Point) *Rectangle { + return MergePoints(r, points) +} + +// MergeRectangle merges the given rectangle into this rectangle and returns this rectangle. +// Neither rectangle should have a negative width or height. +func (r *Rectangle) MergeRectangle(other *Rectangle) *Rectangle { + return MergeRectangle(r, other) +} + +// MergeXY merges this rectangle with a point by repositioning and/or resizing it so that the +// point is/on or/within its bounds. +func (r *Rectangle) MergeXY(x, y float64) *Rectangle { + return MergeXY(r, x, y) +} + +// Offset nudges (translates) the top left corner of this Rectangle by a given offset. +func (r *Rectangle) Offset(x, y float64) *Rectangle { + return Offset(r, x, y) +} + +// OffsetPoint nudges (translates) the top left corner of this Rectangle by the coordinates of a +// point. +func (r *Rectangle) OffsetPoint(p *point.Point) *Rectangle { + return OffsetPoint(r, p) +} + +// Checks if this Rectangle overlaps with another rectangle. +func (r *Rectangle) Overlaps(other *Rectangle) bool { + return Overlaps(r, other) +} + +// PerimeterPoint returns a Point from the perimeter of the Rectangle based on the given angle. +func (r *Rectangle) PerimeterPoint(angle float64, p *point.Point) *point.Point { + return PerimeterPoint(r, angle, p) +} + +// Calculates a random point that lies within the `outer` Rectangle, but outside of the `inner` +// Rectangle. The inner Rectangle must be fully contained within the outer rectangle. +func (r *Rectangle) GetRandomPointOutside(other *Rectangle, out *point.Point) *point.Point { + var outer, inner *Rectangle + + if r.ContainsRectangle(other) { + outer, inner = r, other + } else { + outer, inner = other, r + } + + return GetRandomPointOutside(outer, inner, out) +} + +// SameDimensions determines if the two objects (either Rectangles or Rectangle-like) have the same +// width and height values under strict equality. +func (r *Rectangle) SameDimensions(other *Rectangle) bool { + return SameDimensions(r, other) +} + +// Scale the width and height of this Rectangle by the given amounts. +func (r *Rectangle) Scale(x, y float64) *Rectangle { + return Scale(r, x, y) +} + +// Union creates a new Rectangle or repositions and/or resizes an existing Rectangle so that it +// encompasses the two given Rectangles, i.e. calculates their union. +func (r *Rectangle) Union(other *Rectangle) *Rectangle { + return Union(r, other, r) +} diff --git a/d2common/d2geom/rectangle/same_dimensions.go b/d2common/d2geom/rectangle/same_dimensions.go new file mode 100644 index 00000000..7632c374 --- /dev/null +++ b/d2common/d2geom/rectangle/same_dimensions.go @@ -0,0 +1,12 @@ +package rectangle + +import "github.com/gravestench/pho/phomath" + +// SameDimensions determines if the two objects (either Rectangles or Rectangle-like) have the same +// width and height values under strict equality. +func SameDimensions(a, b *Rectangle) bool { + dw := (a.Width - b.Width) * (a.Width - b.Width) + dh := (a.Height - b.Height) * (a.Height - b.Height) + + return dw < phomath.Epsilon && dh < phomath.Epsilon +} diff --git a/d2common/d2geom/rectangle/scale.go b/d2common/d2geom/rectangle/scale.go new file mode 100644 index 00000000..24bb1c37 --- /dev/null +++ b/d2common/d2geom/rectangle/scale.go @@ -0,0 +1,9 @@ +package rectangle + +// Scale the width and height of this Rectangle by the given amounts. +func Scale(r *Rectangle, x, y float64) *Rectangle { + r.Width *= x + r.Height *= y + + return r +} diff --git a/d2common/d2geom/rectangle/union.go b/d2common/d2geom/rectangle/union.go new file mode 100644 index 00000000..acbaa8dc --- /dev/null +++ b/d2common/d2geom/rectangle/union.go @@ -0,0 +1,12 @@ +package rectangle + +import "math" + +// Union creates a new Rectangle or repositions and/or resizes an existing Rectangle so that it +// encompasses the two given Rectangles, i.e. calculates their union. +func Union(a, b, out *Rectangle) *Rectangle { + x, y := math.Min(a.X, b.X), math.Min(a.Y, b.Y) + w, h := math.Min(a.Width, b.Width), math.Min(a.Height, b.Height) + + return out.SetTo(x, y, w, h) +} diff --git a/d2core/d2components/rectangle.go b/d2core/d2components/rectangle.go new file mode 100644 index 00000000..c909a6ca --- /dev/null +++ b/d2core/d2components/rectangle.go @@ -0,0 +1,43 @@ +//nolint:dupl,golint,stylecheck // component declarations are supposed to look the same +package d2components + +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2geom/rectangle" + "github.com/gravestench/akara" +) + +// static check that Rectangle implements Component +var _ akara.Component = &Rectangle{} + +// Rectangle represents an entities x,y axis scale as a vector +type Rectangle struct { + rectangle.Rectangle +} + +// New creates a new Rectangle instance. By default, the scale is (1,1) +func (*Rectangle) New() akara.Component { + return &Rectangle{ + Rectangle: rectangle.Rectangle{}, + } +} + +// RectangleFactory is a wrapper for the generic component factory that returns Rectangle component instances. +// This can be embedded inside of a system to give them the methods for adding, retrieving, and removing a Rectangle. +type RectangleFactory struct { + Rectangle *akara.ComponentFactory +} + +// AddRectangle adds a Rectangle component to the given entity and returns it +func (m *RectangleFactory) AddRectangle(id akara.EID) *Rectangle { + return m.Rectangle.Add(id).(*Rectangle) +} + +// GetRectangle returns the Rectangle component for the given entity, and a bool for whether or not it exists +func (m *RectangleFactory) GetRectangle(id akara.EID) (*Rectangle, bool) { + component, found := m.Rectangle.Get(id) + if !found { + return nil, found + } + + return component.(*Rectangle), found +}