diff --git a/geometry.go b/geometry.go index 82ff17b..61b93ca 100644 --- a/geometry.go +++ b/geometry.go @@ -345,7 +345,7 @@ func (c Circle) String() string { return fmt.Sprintf("Circle(%.2f, %s)", c.Radius, c.Center) } -// Norm returns the Circle in normalized form - this is that the radius is set to an absolute version. +// 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{10, pixel.Vec{0, 0}} @@ -374,7 +374,7 @@ func (c Circle) Moved(delta Vec) Circle { } } -// Resized returns the Circle resized by the given delta. +// Resized returns the Circle resized by the given delta. The Circles center is use as the anchor. // // c := pixel.C(10, pixel.ZV) // c.Resized(-5) // returns pixel.Circle{5, pixel.Vec{0, 0}} @@ -392,14 +392,26 @@ func (c Circle) Contains(u Vec) bool { 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 := c - smallerC := d - if c.Radius < d.Radius { - biggerC = d - smallerC = c - } + biggerC := MaxCircle(c, d) + smallerC := MinCircle(c, d) // Get distance between centers dist := c.Center.To(d.Center).Len() @@ -427,7 +439,28 @@ func (c Circle) Union(d Circle) Circle { // 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 { - center := Lerp(c.Center, d.Center, 0.5) + // Check if one of the circles encompasses the other; if so, return that one + biggerC := MaxCircle(c, d) + smallerC := MinCircle(c, d) + + 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(0, center) + } radius := math.Min(0, c.Center.To(d.Center).Len()-(c.Radius+d.Radius)) diff --git a/geometry_test.go b/geometry_test.go index 7010620..4cab36f 100644 --- a/geometry_test.go +++ b/geometry_test.go @@ -545,6 +545,24 @@ func TestCircle_Intersect(t *testing.T) { args: args{d: pixel.C(1, pixel.V(3, 3))}, want: pixel.C(0, pixel.V(1.5, 1.5)), }, + { + name: "Circle.Intersect(): first circle encompassing second", + fields: fields{radius: 10, center: pixel.V(0, 0)}, + args: args{d: pixel.C(1, pixel.V(3, 3))}, + want: pixel.C(10, pixel.V(0, 0)), + }, + { + name: "Circle.Intersect(): second circle encompassing first", + fields: fields{radius: 1, center: pixel.V(-1, -4)}, + args: args{d: pixel.C(10, pixel.V(0, 0))}, + want: pixel.C(10, pixel.V(0, 0)), + }, + { + name: "Circle.Intersect(): matching circles", + fields: fields{radius: 1, center: pixel.V(0, 0)}, + args: args{d: pixel.C(1, pixel.V(0, 0))}, + want: pixel.C(1, pixel.V(0, 0)), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -558,3 +576,79 @@ func TestCircle_Intersect(t *testing.T) { }) } } + +func TestMaxCircle(t *testing.T) { + bigCircle := pixel.C(10, pixel.ZV) + smallCircle := pixel.C(1, pixel.ZV) + + type args struct { + c pixel.Circle + d pixel.Circle + } + tests := []struct { + name string + args args + want pixel.Circle + }{ + { + name: "MaxCircle(): first bigger", + args: args{c: bigCircle, d: smallCircle}, + want: bigCircle, + }, + { + name: "MaxCircle(): first smaller", + args: args{c: smallCircle, d: bigCircle}, + want: bigCircle, + }, + { + name: "MaxCircle(): both same size", + args: args{c: smallCircle, d: smallCircle}, + want: smallCircle, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pixel.MaxCircle(tt.args.c, tt.args.d); !reflect.DeepEqual(got, tt.want) { + t.Errorf("MaxCircle() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestMinCircle(t *testing.T) { + bigCircle := pixel.C(10, pixel.ZV) + smallCircle := pixel.C(1, pixel.ZV) + + type args struct { + c pixel.Circle + d pixel.Circle + } + tests := []struct { + name string + args args + want pixel.Circle + }{ + { + name: "MinCircle(): first bigger", + args: args{c: bigCircle, d: smallCircle}, + want: smallCircle, + }, + { + name: "MinCircle(): first smaller", + args: args{c: smallCircle, d: bigCircle}, + want: smallCircle, + }, + { + name: "MinCircle(): both same size", + args: args{c: smallCircle, d: smallCircle}, + want: smallCircle, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := pixel.MinCircle(tt.args.c, tt.args.d); !reflect.DeepEqual(got, tt.want) { + t.Errorf("MinCircle() = %v, want %v", got, tt.want) + } + }) + } +}