Merge pull request #157 from bcvery1/addcircle
Added circle geometry - small feature/improvement
This commit is contained in:
commit
8563c28493
233
geometry.go
233
geometry.go
|
@ -318,6 +318,239 @@ func (r Rect) Intersect(s Rect) Rect {
|
|||
return t
|
||||
}
|
||||
|
||||
// IntersectCircle returns a minimal required Vector, such that moving the circle by that vector would stop the Circle
|
||||
// and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only
|
||||
// the perimeters touch.
|
||||
//
|
||||
// This function will return a non-zero vector if:
|
||||
// - The Rect contains the Circle, partially or fully
|
||||
// - The Circle contains the Rect, partially of fully
|
||||
func (r Rect) IntersectCircle(c Circle) Vec {
|
||||
return c.IntersectRect(r).Scaled(-1)
|
||||
}
|
||||
|
||||
// Circle is a 2D circle. It is defined by two properties:
|
||||
// - Center vector
|
||||
// - Radius float64
|
||||
type Circle struct {
|
||||
Center Vec
|
||||
Radius float64
|
||||
}
|
||||
|
||||
// C returns a new Circle with the given radius and center coordinates.
|
||||
//
|
||||
// Note that a negative radius is valid.
|
||||
func C(center Vec, radius float64) Circle {
|
||||
return Circle{
|
||||
Center: center,
|
||||
Radius: radius,
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the string representation of the Circle.
|
||||
//
|
||||
// c := pixel.C(10.1234, pixel.ZV)
|
||||
// c.String() // returns "Circle(10.12, Vec(0, 0))"
|
||||
// fmt.Println(c) // Circle(10.12, Vec(0, 0))
|
||||
func (c Circle) String() string {
|
||||
return fmt.Sprintf("Circle(%s, %.2f)", c.Center, c.Radius)
|
||||
}
|
||||
|
||||
// Norm returns the Circle in normalized form - this sets the radius to its absolute value.
|
||||
//
|
||||
// c := pixel.C(-10, pixel.ZV)
|
||||
// c.Norm() // returns pixel.Circle{pixel.Vec{0, 0}, 10}
|
||||
func (c Circle) Norm() Circle {
|
||||
return Circle{
|
||||
Center: c.Center,
|
||||
Radius: math.Abs(c.Radius),
|
||||
}
|
||||
}
|
||||
|
||||
// Area returns the area of the Circle.
|
||||
func (c Circle) Area() float64 {
|
||||
return math.Pi * math.Pow(c.Radius, 2)
|
||||
}
|
||||
|
||||
// Moved returns the Circle moved by the given vector delta.
|
||||
func (c Circle) Moved(delta Vec) Circle {
|
||||
return Circle{
|
||||
Center: c.Center.Add(delta),
|
||||
Radius: c.Radius,
|
||||
}
|
||||
}
|
||||
|
||||
// Resized returns the Circle resized by the given delta. The Circles center is use as the anchor.
|
||||
//
|
||||
// c := pixel.C(pixel.ZV, 10)
|
||||
// c.Resized(-5) // returns pixel.Circle{pixel.Vec{0, 0}, 5}
|
||||
// c.Resized(25) // returns pixel.Circle{pixel.Vec{0, 0}, 35}
|
||||
func (c Circle) Resized(radiusDelta float64) Circle {
|
||||
return Circle{
|
||||
Center: c.Center,
|
||||
Radius: c.Radius + radiusDelta,
|
||||
}
|
||||
}
|
||||
|
||||
// Contains checks whether a vector `u` is contained within this Circle (including it's perimeter).
|
||||
func (c Circle) Contains(u Vec) bool {
|
||||
toCenter := c.Center.To(u)
|
||||
return c.Radius >= toCenter.Len()
|
||||
}
|
||||
|
||||
// maxCircle will return the larger circle based on the radius.
|
||||
func maxCircle(c, d Circle) Circle {
|
||||
if c.Radius < d.Radius {
|
||||
return d
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// minCircle will return the smaller circle based on the radius.
|
||||
func minCircle(c, d Circle) Circle {
|
||||
if c.Radius < d.Radius {
|
||||
return c
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// Union returns the minimal Circle which covers both `c` and `d`.
|
||||
func (c Circle) Union(d Circle) Circle {
|
||||
biggerC := maxCircle(c.Norm(), d.Norm())
|
||||
smallerC := minCircle(c.Norm(), d.Norm())
|
||||
|
||||
// Get distance between centers
|
||||
dist := c.Center.To(d.Center).Len()
|
||||
|
||||
// If the bigger Circle encompasses the smaller one, we have the result
|
||||
if dist+smallerC.Radius <= biggerC.Radius {
|
||||
return biggerC
|
||||
}
|
||||
|
||||
// Calculate radius for encompassing Circle
|
||||
r := (dist + biggerC.Radius + smallerC.Radius) / 2
|
||||
|
||||
// Calculate center for encompassing Circle
|
||||
theta := .5 + (biggerC.Radius-smallerC.Radius)/(2*dist)
|
||||
center := Lerp(smallerC.Center, biggerC.Center, theta)
|
||||
|
||||
return Circle{
|
||||
Center: center,
|
||||
Radius: r,
|
||||
}
|
||||
}
|
||||
|
||||
// Intersect returns the maximal Circle which is covered by both `c` and `d`.
|
||||
//
|
||||
// If `c` and `d` don't overlap, this function returns a zero-sized circle at the centerpoint between the two Circle's
|
||||
// centers.
|
||||
func (c Circle) Intersect(d Circle) Circle {
|
||||
// Check if one of the circles encompasses the other; if so, return that one
|
||||
biggerC := maxCircle(c.Norm(), d.Norm())
|
||||
smallerC := minCircle(c.Norm(), d.Norm())
|
||||
|
||||
if biggerC.Radius >= biggerC.Center.To(smallerC.Center).Len()+smallerC.Radius {
|
||||
return biggerC
|
||||
}
|
||||
|
||||
// Calculate the midpoint between the two radii
|
||||
// Distance between centers
|
||||
dist := c.Center.To(d.Center).Len()
|
||||
// Difference between radii
|
||||
diff := dist - (c.Radius + d.Radius)
|
||||
// Distance from c.Center to the weighted midpoint
|
||||
distToMidpoint := c.Radius + 0.5*diff
|
||||
// Weighted midpoint
|
||||
center := Lerp(c.Center, d.Center, distToMidpoint/dist)
|
||||
|
||||
// No need to calculate radius if the circles do not overlap
|
||||
if c.Center.To(d.Center).Len() >= c.Radius+d.Radius {
|
||||
return C(center, 0)
|
||||
}
|
||||
|
||||
radius := c.Center.To(d.Center).Len() - (c.Radius + d.Radius)
|
||||
|
||||
return Circle{
|
||||
Center: center,
|
||||
Radius: math.Abs(radius),
|
||||
}
|
||||
}
|
||||
|
||||
// IntersectRect returns a minimal required Vector, such that moving the circle by that vector would stop the Circle
|
||||
// and the Rect intersecting. This function returns a zero-vector if the Circle and Rect do not overlap, and if only
|
||||
// the perimeters touch.
|
||||
//
|
||||
// This function will return a non-zero vector if:
|
||||
// - The Rect contains the Circle, partially or fully
|
||||
// - The Circle contains the Rect, partially of fully
|
||||
func (c Circle) IntersectRect(r Rect) Vec {
|
||||
// h and v will hold the minimum horizontal and vertical distances (respectively) to avoid overlapping
|
||||
var h, v float64
|
||||
|
||||
// Checks if the c.Center is not in the diagonal quadrants of the rectangle
|
||||
if (r.Min.X <= c.Center.X && c.Center.X <= r.Max.X) || (r.Min.Y <= c.Center.Y && c.Center.Y <= r.Max.Y) {
|
||||
// 'grow' the Rect by c.Radius in each orthagonal
|
||||
grown := Rect{Min: r.Min.Sub(V(c.Radius, c.Radius)), Max: r.Max.Add(V(c.Radius, c.Radius))}
|
||||
if !grown.Contains(c.Center) {
|
||||
// c.Center not close enough to overlap, return zero-vector
|
||||
return ZV
|
||||
}
|
||||
|
||||
// Get minimum distance to travel out of Rect
|
||||
rToC := r.Center().To(c.Center)
|
||||
h = c.Radius - math.Abs(rToC.X) + (r.W() / 2)
|
||||
v = c.Radius - math.Abs(rToC.Y) + (r.H() / 2)
|
||||
|
||||
if rToC.X < 0 {
|
||||
h = -h
|
||||
}
|
||||
if rToC.Y < 0 {
|
||||
v = -v
|
||||
}
|
||||
} else {
|
||||
// The center is in the diagonal quadrants
|
||||
if c.Center.To(r.Min).Len() <= c.Radius {
|
||||
// Closest to bottom-left
|
||||
cornerToCenter := r.Min.To(c.Center)
|
||||
// Get the horizontal and vertical overlaps
|
||||
h = c.Radius - math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.Y, 2))
|
||||
v = -1 * (c.Radius + math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.X, 2)))
|
||||
}
|
||||
if c.Center.To(r.Max).Len() <= c.Radius {
|
||||
// Closest to top-right
|
||||
cornerToCenter := r.Max.To(c.Center)
|
||||
// Get the horizontal and vertical overlaps
|
||||
h = c.Radius - math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.Y, 2))
|
||||
v = c.Radius - math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.X, 2))
|
||||
}
|
||||
if c.Center.To(V(r.Min.X, r.Max.Y)).Len() <= c.Radius {
|
||||
// Closest to top-left
|
||||
cornerToCenter := V(r.Min.X, r.Max.Y).To(c.Center)
|
||||
// Get the horizontal and vertical overlaps
|
||||
h = -1 * (c.Radius + math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.Y, 2)))
|
||||
v = c.Radius - math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.X, 2))
|
||||
}
|
||||
if c.Center.To(V(r.Max.X, r.Min.Y)).Len() <= c.Radius {
|
||||
// Closest to bottom-right
|
||||
cornerToCenter := V(r.Max.X, r.Min.Y).To(c.Center)
|
||||
// Get the horizontal and vertical overlaps
|
||||
h = -1 * (c.Radius + math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.Y, 2)))
|
||||
v = -1 * (c.Radius + math.Sqrt(math.Pow(c.Radius, 2)-math.Pow(cornerToCenter.X, 2)))
|
||||
}
|
||||
}
|
||||
|
||||
// No intersect
|
||||
if h == 0 && v == 0 {
|
||||
return ZV
|
||||
}
|
||||
|
||||
if math.Abs(h) > math.Abs(v) {
|
||||
// Vertical distance shorter
|
||||
return V(0, v)
|
||||
}
|
||||
return V(h, 0)
|
||||
}
|
||||
|
||||
// Matrix is a 2x3 affine matrix that can be used for all kinds of spatial transforms, such
|
||||
// as movement, scaling and rotations.
|
||||
//
|
||||
|
|
528
geometry_test.go
528
geometry_test.go
|
@ -2,19 +2,19 @@ package pixel_test
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/faiface/pixel"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type rectTestTransform struct {
|
||||
name string
|
||||
f func(pixel.Rect) pixel.Rect
|
||||
}
|
||||
|
||||
func TestResizeRect(t *testing.T) {
|
||||
func TestRect_Resize(t *testing.T) {
|
||||
type rectTestTransform struct {
|
||||
name string
|
||||
f func(pixel.Rect) pixel.Rect
|
||||
}
|
||||
|
||||
// rectangles
|
||||
squareAroundOrigin := pixel.R(-10, -10, 10, 10)
|
||||
|
@ -162,3 +162,517 @@ func TestMatrix_Unproject(t *testing.T) {
|
|||
assert.True(t, math.IsNaN(unprojected.Y))
|
||||
})
|
||||
}
|
||||
|
||||
func TestC(t *testing.T) {
|
||||
type args struct {
|
||||
radius float64
|
||||
center pixel.Vec
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want pixel.Circle
|
||||
}{
|
||||
{
|
||||
name: "C(): positive radius",
|
||||
args: args{radius: 10, center: pixel.ZV},
|
||||
want: pixel.Circle{Radius: 10, Center: pixel.ZV},
|
||||
},
|
||||
{
|
||||
name: "C(): zero radius",
|
||||
args: args{radius: 0, center: pixel.ZV},
|
||||
want: pixel.Circle{Radius: 0, Center: pixel.ZV},
|
||||
},
|
||||
{
|
||||
name: "C(): negative radius",
|
||||
args: args{radius: -5, center: pixel.ZV},
|
||||
want: pixel.Circle{Radius: -5, Center: pixel.ZV},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := pixel.C(tt.args.center, tt.args.radius); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("C() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCircle_String(t *testing.T) {
|
||||
type fields struct {
|
||||
radius float64
|
||||
center pixel.Vec
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "Circle.String(): positive radius",
|
||||
fields: fields{radius: 10, center: pixel.ZV},
|
||||
want: "Circle(Vec(0, 0), 10.00)",
|
||||
},
|
||||
{
|
||||
name: "Circle.String(): zero radius",
|
||||
fields: fields{radius: 0, center: pixel.ZV},
|
||||
want: "Circle(Vec(0, 0), 0.00)",
|
||||
},
|
||||
{
|
||||
name: "Circle.String(): negative radius",
|
||||
fields: fields{radius: -5, center: pixel.ZV},
|
||||
want: "Circle(Vec(0, 0), -5.00)",
|
||||
},
|
||||
{
|
||||
name: "Circle.String(): irrational radius",
|
||||
fields: fields{radius: math.Pi, center: pixel.ZV},
|
||||
want: "Circle(Vec(0, 0), 3.14)",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := pixel.C(tt.fields.center, tt.fields.radius)
|
||||
if got := c.String(); got != tt.want {
|
||||
t.Errorf("Circle.String() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCircle_Norm(t *testing.T) {
|
||||
type fields struct {
|
||||
radius float64
|
||||
center pixel.Vec
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want pixel.Circle
|
||||
}{
|
||||
{
|
||||
name: "Circle.Norm(): positive radius",
|
||||
fields: fields{radius: 10, center: pixel.ZV},
|
||||
want: pixel.C(pixel.ZV, 10),
|
||||
},
|
||||
{
|
||||
name: "Circle.Norm(): zero radius",
|
||||
fields: fields{radius: 0, center: pixel.ZV},
|
||||
want: pixel.C(pixel.ZV, 0),
|
||||
},
|
||||
{
|
||||
name: "Circle.Norm(): negative radius",
|
||||
fields: fields{radius: -5, center: pixel.ZV},
|
||||
want: pixel.C(pixel.ZV, 5),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := pixel.C(tt.fields.center, tt.fields.radius)
|
||||
if got := c.Norm(); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Circle.Norm() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCircle_Area(t *testing.T) {
|
||||
type fields struct {
|
||||
radius float64
|
||||
center pixel.Vec
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want float64
|
||||
}{
|
||||
{
|
||||
name: "Circle.Area(): positive radius",
|
||||
fields: fields{radius: 10, center: pixel.ZV},
|
||||
want: 100 * math.Pi,
|
||||
},
|
||||
{
|
||||
name: "Circle.Area(): zero radius",
|
||||
fields: fields{radius: 0, center: pixel.ZV},
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "Circle.Area(): negative radius",
|
||||
fields: fields{radius: -5, center: pixel.ZV},
|
||||
want: 25 * math.Pi,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := pixel.C(tt.fields.center, tt.fields.radius)
|
||||
if got := c.Area(); got != tt.want {
|
||||
t.Errorf("Circle.Area() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCircle_Moved(t *testing.T) {
|
||||
type fields struct {
|
||||
radius float64
|
||||
center pixel.Vec
|
||||
}
|
||||
type args struct {
|
||||
delta pixel.Vec
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want pixel.Circle
|
||||
}{
|
||||
{
|
||||
name: "Circle.Moved(): positive movement",
|
||||
fields: fields{radius: 10, center: pixel.ZV},
|
||||
args: args{delta: pixel.V(10, 20)},
|
||||
want: pixel.C(pixel.V(10, 20), 10),
|
||||
},
|
||||
{
|
||||
name: "Circle.Moved(): zero movement",
|
||||
fields: fields{radius: 10, center: pixel.ZV},
|
||||
args: args{delta: pixel.ZV},
|
||||
want: pixel.C(pixel.V(0, 0), 10),
|
||||
},
|
||||
{
|
||||
name: "Circle.Moved(): negative movement",
|
||||
fields: fields{radius: 10, center: pixel.ZV},
|
||||
args: args{delta: pixel.V(-5, -10)},
|
||||
want: pixel.C(pixel.V(-5, -10), 10),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := pixel.C(tt.fields.center, tt.fields.radius)
|
||||
if got := c.Moved(tt.args.delta); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Circle.Moved() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCircle_Resized(t *testing.T) {
|
||||
type fields struct {
|
||||
radius float64
|
||||
center pixel.Vec
|
||||
}
|
||||
type args struct {
|
||||
radiusDelta float64
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want pixel.Circle
|
||||
}{
|
||||
{
|
||||
name: "Circle.Resized(): positive delta",
|
||||
fields: fields{radius: 10, center: pixel.ZV},
|
||||
args: args{radiusDelta: 5},
|
||||
want: pixel.C(pixel.V(0, 0), 15),
|
||||
},
|
||||
{
|
||||
name: "Circle.Resized(): zero delta",
|
||||
fields: fields{radius: 10, center: pixel.ZV},
|
||||
args: args{radiusDelta: 0},
|
||||
want: pixel.C(pixel.V(0, 0), 10),
|
||||
},
|
||||
{
|
||||
name: "Circle.Resized(): negative delta",
|
||||
fields: fields{radius: 10, center: pixel.ZV},
|
||||
args: args{radiusDelta: -5},
|
||||
want: pixel.C(pixel.V(0, 0), 5),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := pixel.C(tt.fields.center, tt.fields.radius)
|
||||
if got := c.Resized(tt.args.radiusDelta); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Circle.Resized() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCircle_Contains(t *testing.T) {
|
||||
type fields struct {
|
||||
radius float64
|
||||
center pixel.Vec
|
||||
}
|
||||
type args struct {
|
||||
u pixel.Vec
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "Circle.Contains(): point on cicles' center",
|
||||
fields: fields{radius: 10, center: pixel.ZV},
|
||||
args: args{u: pixel.ZV},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Circle.Contains(): point offcenter",
|
||||
fields: fields{radius: 10, center: pixel.V(5, 0)},
|
||||
args: args{u: pixel.ZV},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Circle.Contains(): point on circumference",
|
||||
fields: fields{radius: 10, center: pixel.V(10, 0)},
|
||||
args: args{u: pixel.ZV},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Circle.Contains(): point outside circle",
|
||||
fields: fields{radius: 10, center: pixel.V(15, 0)},
|
||||
args: args{u: pixel.ZV},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := pixel.C(tt.fields.center, tt.fields.radius)
|
||||
if got := c.Contains(tt.args.u); got != tt.want {
|
||||
t.Errorf("Circle.Contains() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCircle_Union(t *testing.T) {
|
||||
type fields struct {
|
||||
radius float64
|
||||
center pixel.Vec
|
||||
}
|
||||
type args struct {
|
||||
d pixel.Circle
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want pixel.Circle
|
||||
}{
|
||||
{
|
||||
name: "Circle.Union(): overlapping circles",
|
||||
fields: fields{radius: 5, center: pixel.ZV},
|
||||
args: args{d: pixel.C(pixel.ZV, 5)},
|
||||
want: pixel.C(pixel.ZV, 5),
|
||||
},
|
||||
{
|
||||
name: "Circle.Union(): separate circles",
|
||||
fields: fields{radius: 1, center: pixel.ZV},
|
||||
args: args{d: pixel.C(pixel.V(0, 2), 1)},
|
||||
want: pixel.C(pixel.V(0, 1), 2),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := pixel.C(tt.fields.center, tt.fields.radius)
|
||||
if got := c.Union(tt.args.d); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Circle.Union() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCircle_Intersect(t *testing.T) {
|
||||
type fields struct {
|
||||
radius float64
|
||||
center pixel.Vec
|
||||
}
|
||||
type args struct {
|
||||
d pixel.Circle
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want pixel.Circle
|
||||
}{
|
||||
{
|
||||
name: "Circle.Intersect(): intersecting circles",
|
||||
fields: fields{radius: 1, center: pixel.ZV},
|
||||
args: args{d: pixel.C(pixel.V(1, 0), 1)},
|
||||
want: pixel.C(pixel.V(0.5, 0), 1),
|
||||
},
|
||||
{
|
||||
name: "Circle.Intersect(): non-intersecting circles",
|
||||
fields: fields{radius: 1, center: pixel.ZV},
|
||||
args: args{d: pixel.C(pixel.V(3, 3), 1)},
|
||||
want: pixel.C(pixel.V(1.5, 1.5), 0),
|
||||
},
|
||||
{
|
||||
name: "Circle.Intersect(): first circle encompassing second",
|
||||
fields: fields{radius: 10, center: pixel.ZV},
|
||||
args: args{d: pixel.C(pixel.V(3, 3), 1)},
|
||||
want: pixel.C(pixel.ZV, 10),
|
||||
},
|
||||
{
|
||||
name: "Circle.Intersect(): second circle encompassing first",
|
||||
fields: fields{radius: 1, center: pixel.V(-1, -4)},
|
||||
args: args{d: pixel.C(pixel.ZV, 10)},
|
||||
want: pixel.C(pixel.ZV, 10),
|
||||
},
|
||||
{
|
||||
name: "Circle.Intersect(): matching circles",
|
||||
fields: fields{radius: 1, center: pixel.ZV},
|
||||
args: args{d: pixel.C(pixel.ZV, 1)},
|
||||
want: pixel.C(pixel.ZV, 1),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := pixel.C(
|
||||
tt.fields.center,
|
||||
tt.fields.radius,
|
||||
)
|
||||
if got := c.Intersect(tt.args.d); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Circle.Intersect() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRect_IntersectCircle(t *testing.T) {
|
||||
type fields struct {
|
||||
Min pixel.Vec
|
||||
Max pixel.Vec
|
||||
}
|
||||
type args struct {
|
||||
c pixel.Circle
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want pixel.Vec
|
||||
}{
|
||||
{
|
||||
name: "Rect.IntersectCircle(): no overlap",
|
||||
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
||||
args: args{c: pixel.C(pixel.V(50, 50), 1)},
|
||||
want: pixel.ZV,
|
||||
},
|
||||
{
|
||||
name: "Rect.IntersectCircle(): circle contains rect",
|
||||
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
||||
args: args{c: pixel.C(pixel.V(5, 5), 10)},
|
||||
want: pixel.V(-15, 0),
|
||||
},
|
||||
{
|
||||
name: "Rect.IntersectCircle(): rect contains circle",
|
||||
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
||||
args: args{c: pixel.C(pixel.V(5, 5), 1)},
|
||||
want: pixel.V(-6, 0),
|
||||
},
|
||||
{
|
||||
name: "Rect.IntersectCircle(): circle overlaps bottom-left corner",
|
||||
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
||||
args: args{c: pixel.C(pixel.V(0, 0), 1)},
|
||||
want: pixel.V(1, 0),
|
||||
},
|
||||
{
|
||||
name: "Rect.IntersectCircle(): circle overlaps top-left corner",
|
||||
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
||||
args: args{c: pixel.C(pixel.V(0, 10), 1)},
|
||||
want: pixel.V(1, 0),
|
||||
},
|
||||
{
|
||||
name: "Rect.IntersectCircle(): circle overlaps bottom-right corner",
|
||||
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
||||
args: args{c: pixel.C(pixel.V(10, 0), 1)},
|
||||
want: pixel.V(-1, 0),
|
||||
},
|
||||
{
|
||||
name: "Rect.IntersectCircle(): circle overlaps top-right corner",
|
||||
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
||||
args: args{c: pixel.C(pixel.V(10, 10), 1)},
|
||||
want: pixel.V(-1, 0),
|
||||
},
|
||||
{
|
||||
name: "Rect.IntersectCircle(): circle overlaps two corners",
|
||||
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
||||
args: args{c: pixel.C(pixel.V(0, 5), 6)},
|
||||
want: pixel.V(6, 0),
|
||||
},
|
||||
{
|
||||
name: "Rect.IntersectCircle(): circle overlaps left edge",
|
||||
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
||||
args: args{c: pixel.C(pixel.V(0, 5), 1)},
|
||||
want: pixel.V(1, 0),
|
||||
},
|
||||
{
|
||||
name: "Rect.IntersectCircle(): circle overlaps bottom edge",
|
||||
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
||||
args: args{c: pixel.C(pixel.V(5, 0), 1)},
|
||||
want: pixel.V(0, 1),
|
||||
},
|
||||
{
|
||||
name: "Rect.IntersectCircle(): circle overlaps right edge",
|
||||
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
||||
args: args{c: pixel.C(pixel.V(10, 5), 1)},
|
||||
want: pixel.V(-1, 0),
|
||||
},
|
||||
{
|
||||
name: "Rect.IntersectCircle(): circle overlaps top edge",
|
||||
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
||||
args: args{c: pixel.C(pixel.V(5, 10), 1)},
|
||||
want: pixel.V(0, -1),
|
||||
},
|
||||
{
|
||||
name: "Rect.IntersectCircle(): edge is tangent of left side",
|
||||
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
||||
args: args{c: pixel.C(pixel.V(-1, 5), 1)},
|
||||
want: pixel.ZV,
|
||||
},
|
||||
{
|
||||
name: "Rect.IntersectCircle(): edge is tangent of top side",
|
||||
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
||||
args: args{c: pixel.C(pixel.V(5, -1), 1)},
|
||||
want: pixel.ZV,
|
||||
},
|
||||
{
|
||||
name: "Rect.IntersectCircle(): circle above rectangle",
|
||||
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
||||
args: args{c: pixel.C(pixel.V(5, 12), 1)},
|
||||
want: pixel.ZV,
|
||||
},
|
||||
{
|
||||
name: "Rect.IntersectCircle(): circle below rectangle",
|
||||
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
||||
args: args{c: pixel.C(pixel.V(5, -2), 1)},
|
||||
want: pixel.ZV,
|
||||
},
|
||||
{
|
||||
name: "Rect.IntersectCircle(): circle left of rectangle",
|
||||
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
||||
args: args{c: pixel.C(pixel.V(-1, 5), 1)},
|
||||
want: pixel.ZV,
|
||||
},
|
||||
{
|
||||
name: "Rect.IntersectCircle(): circle right of rectangle",
|
||||
fields: fields{Min: pixel.ZV, Max: pixel.V(10, 10)},
|
||||
args: args{c: pixel.C(pixel.V(11, 5), 1)},
|
||||
want: pixel.ZV,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := pixel.Rect{
|
||||
Min: tt.fields.Min,
|
||||
Max: tt.fields.Max,
|
||||
}
|
||||
if got := r.IntersectCircle(tt.args.c); got != tt.want {
|
||||
t.Errorf("Rect.IntersectCircle() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue