1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2025-02-09 10:06:35 -05:00

adding a full-featured rectangle implementation to d2geom, adding an ecs component for it

This commit is contained in:
gravestench 2020-12-01 22:23:49 -08:00
parent 0472233949
commit 7a8b07d1c1
35 changed files with 1148 additions and 0 deletions

View File

@ -0,0 +1,6 @@
package rectangle
// Area calculates the area of the given rectangle.
func Area(r *Rectangle) float64 {
return r.Width * r.Height
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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())
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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())
}

View File

@ -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
}

View File

@ -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())
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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))
}

View File

@ -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)})
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -0,0 +1,6 @@
package rectangle
// Perimeter calculates the perimeter of a Rectangle.
func Perimeter(r *Rectangle) float64 {
return 2 * (r.Width + r.Height)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}