mirror of
https://github.com/genxium/DelayNoMore
synced 2024-12-26 03:39:00 +00:00
767 lines
21 KiB
Go
767 lines
21 KiB
Go
package resolv
|
|
|
|
import (
|
|
"math"
|
|
"sort"
|
|
)
|
|
|
|
type Shape interface {
|
|
// Intersection tests to see if a Shape intersects with the other given Shape. dx and dy are delta movement variables indicating
|
|
// movement to be applied before the intersection check (thereby allowing you to see if a Shape would collide with another if it
|
|
// were in a different relative location). If an Intersection is found, a ContactSet will be returned, giving information regarding
|
|
// the intersection.
|
|
Intersection(dx, dy float64, other Shape) *ContactSet
|
|
// Bounds returns the top-left and bottom-right points of the Shape.
|
|
Bounds() (Vector, Vector)
|
|
// Position returns the X and Y position of the Shape.
|
|
Position() (float64, float64)
|
|
// SetPosition allows you to place a Shape at another location.
|
|
SetPosition(x, y float64)
|
|
// Clone duplicates the Shape.
|
|
Clone() Shape
|
|
}
|
|
|
|
// A Line is a helper shape used to determine if two ConvexPolygon lines intersect; you can't create a Line to use as a Shape.
|
|
// Instead, you can create a ConvexPolygon, specify two points, and set its Closed value to false.
|
|
type Line struct {
|
|
Start, End Vector
|
|
}
|
|
|
|
func NewLine(x, y, x2, y2 float64) *Line {
|
|
return &Line{
|
|
Start: Vector{x, y},
|
|
End: Vector{x2, y2},
|
|
}
|
|
}
|
|
|
|
func (line *Line) Project(axis Vector) Vector {
|
|
return line.Vector().Scale(axis.Dot(line.Start.Sub(line.End)))
|
|
}
|
|
|
|
func (line *Line) Normal() Vector {
|
|
v := line.Vector()
|
|
return Vector{v[1], -v[0]}.Unit()
|
|
}
|
|
|
|
func (line *Line) Vector() Vector {
|
|
return line.End.Clone().Sub(line.Start).Unit()
|
|
}
|
|
|
|
// IntersectionPointsLine returns the intersection point of a Line with another Line as a Vector. If no intersection is found, it will return nil.
|
|
func (line *Line) IntersectionPointsLine(other *Line) Vector {
|
|
|
|
det := (line.End[0]-line.Start[0])*(other.End[1]-other.Start[1]) - (other.End[0]-other.Start[0])*(line.End[1]-line.Start[1])
|
|
|
|
if det != 0 {
|
|
|
|
// MAGIC MATH; the extra + 1 here makes it so that corner cases (literally, lines going through corners) works.
|
|
|
|
// lambda := (float32(((line.Y-b.Y)*(b.X2-b.X))-((line.X-b.X)*(b.Y2-b.Y))) + 1) / float32(det)
|
|
lambda := (((line.Start[1] - other.Start[1]) * (other.End[0] - other.Start[0])) - ((line.Start[0] - other.Start[0]) * (other.End[1] - other.Start[1])) + 1) / det
|
|
|
|
// gamma := (float32(((line.Y-b.Y)*(line.X2-line.X))-((line.X-b.X)*(line.Y2-line.Y))) + 1) / float32(det)
|
|
gamma := (((line.Start[1] - other.Start[1]) * (line.End[0] - line.Start[0])) - ((line.Start[0] - other.Start[0]) * (line.End[1] - line.Start[1])) + 1) / det
|
|
|
|
if (0 < lambda && lambda < 1) && (0 < gamma && gamma < 1) {
|
|
|
|
// Delta
|
|
dx := line.End[0] - line.Start[0]
|
|
dy := line.End[1] - line.Start[1]
|
|
|
|
// dx, dy := line.GetDelta()
|
|
|
|
return Vector{line.Start[0] + (lambda * dx), line.Start[1] + (lambda * dy)}
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// IntersectionPointsCircle returns a slice of Vectors, each indicating the intersection point. If no intersection is found, it will return an empty slice.
|
|
func (line *Line) IntersectionPointsCircle(circle *Circle) []Vector {
|
|
|
|
points := []Vector{}
|
|
|
|
cp := Vector{circle.X, circle.Y}
|
|
lStart := line.Start.Sub(cp)
|
|
lEnd := line.End.Sub(cp)
|
|
diff := lEnd.Sub(lStart)
|
|
|
|
a := diff[0]*diff[0] + diff[1]*diff[1]
|
|
b := 2 * ((diff[0] * lStart[0]) + (diff[1] * lStart[1]))
|
|
c := (lStart[0] * lStart[0]) + (lStart[1] * lStart[1]) - (circle.Radius * circle.Radius)
|
|
|
|
det := b*b - (4 * a * c)
|
|
|
|
if det < 0 {
|
|
// Do nothing, no intersections
|
|
} else if det == 0 {
|
|
|
|
t := -b / (2 * a)
|
|
|
|
if t >= 0 && t <= 1 {
|
|
points = append(points, Vector{line.Start[0] + t*diff[0], line.Start[1] + t*diff[1]})
|
|
}
|
|
|
|
} else {
|
|
|
|
t := (-b + math.Sqrt(det)) / (2 * a)
|
|
|
|
// We have to ensure t is between 0 and 1; otherwise, the collision points are on the circle as though the lines were infinite in length.
|
|
if t >= 0 && t <= 1 {
|
|
points = append(points, Vector{line.Start[0] + t*diff[0], line.Start[1] + t*diff[1]})
|
|
}
|
|
t = (-b - math.Sqrt(det)) / (2 * a)
|
|
if t >= 0 && t <= 1 {
|
|
points = append(points, Vector{line.Start[0] + t*diff[0], line.Start[1] + t*diff[1]})
|
|
}
|
|
|
|
}
|
|
|
|
return points
|
|
|
|
}
|
|
|
|
type ConvexPolygon struct {
|
|
Points []Vector
|
|
X, Y float64
|
|
Closed bool
|
|
}
|
|
|
|
// NewConvexPolygon creates a new convex polygon from the provided set of X and Y positions of 2D points (or vertices). Should generally be ordered clockwise,
|
|
// from X and Y of the first, to X and Y of the last. For example: NewConvexPolygon(0, 0, 10, 0, 10, 10, 0, 10) would create a 10x10 convex
|
|
// polygon square, with the vertices at {0,0}, {10,0}, {10, 10}, and {0, 10}.
|
|
func NewConvexPolygon(points ...float64) *ConvexPolygon {
|
|
|
|
// if len(points)/2 < 2 {
|
|
// return nil
|
|
// }
|
|
|
|
cp := &ConvexPolygon{Points: []Vector{}, Closed: true}
|
|
|
|
cp.AddPoints(points...)
|
|
|
|
return cp
|
|
}
|
|
|
|
func (cp *ConvexPolygon) Clone() Shape {
|
|
|
|
points := []Vector{}
|
|
|
|
for _, point := range cp.Points {
|
|
points = append(points, point.Clone())
|
|
}
|
|
|
|
newPoly := NewConvexPolygon()
|
|
newPoly.X = cp.X
|
|
newPoly.Y = cp.Y
|
|
newPoly.AddPointsVec(points...)
|
|
newPoly.Closed = cp.Closed
|
|
return newPoly
|
|
}
|
|
|
|
// AddPointsVec allows you to add points to the ConvexPolygon with a slice of Vectors, each indicating a point / vertex.
|
|
func (cp *ConvexPolygon) AddPointsVec(points ...Vector) {
|
|
cp.Points = append(cp.Points, points...)
|
|
}
|
|
|
|
// AddPoints allows you to add points to the ConvexPolygon with a slice or selection of float64s, with each pair indicating an X or Y value for
|
|
// a point / vertex (i.e. AddPoints(0, 1, 2, 3) would add two points - one at {0, 1}, and another at {2, 3}).
|
|
func (cp *ConvexPolygon) AddPoints(vertexPositions ...float64) {
|
|
for v := 0; v < len(vertexPositions); v += 2 {
|
|
cp.Points = append(cp.Points, Vector{vertexPositions[v], vertexPositions[v+1]})
|
|
}
|
|
}
|
|
|
|
// Lines returns a slice of transformed Lines composing the ConvexPolygon.
|
|
func (cp *ConvexPolygon) Lines() []*Line {
|
|
|
|
lines := []*Line{}
|
|
|
|
vertices := cp.Transformed()
|
|
|
|
for i := 0; i < len(vertices); i++ {
|
|
|
|
start, end := vertices[i], vertices[0]
|
|
|
|
if i < len(vertices)-1 {
|
|
end = vertices[i+1]
|
|
} else if !cp.Closed {
|
|
break
|
|
}
|
|
|
|
line := NewLine(start[0], start[1], end[0], end[1])
|
|
|
|
lines = append(lines, line)
|
|
|
|
}
|
|
|
|
return lines
|
|
|
|
}
|
|
|
|
// Transformed returns the ConvexPolygon's points / vertices, transformed according to the ConvexPolygon's position.
|
|
func (cp *ConvexPolygon) Transformed() []Vector {
|
|
transformed := []Vector{}
|
|
for _, point := range cp.Points {
|
|
transformed = append(transformed, Vector{point[0] + cp.X, point[1] + cp.Y})
|
|
}
|
|
return transformed
|
|
}
|
|
|
|
// Bounds returns two Vectors, comprising the top-left and bottom-right positions of the bounds of the
|
|
// ConvexPolygon, post-transformation.
|
|
func (cp *ConvexPolygon) Bounds() (Vector, Vector) {
|
|
|
|
transformed := cp.Transformed()
|
|
|
|
topLeft := Vector{transformed[0][0], transformed[0][1]}
|
|
bottomRight := topLeft.Clone()
|
|
|
|
for i := 0; i < len(transformed); i++ {
|
|
|
|
point := transformed[i]
|
|
|
|
if point[0] < topLeft[0] {
|
|
topLeft[0] = point[0]
|
|
} else if point[0] > bottomRight[0] {
|
|
bottomRight[0] = point[0]
|
|
}
|
|
|
|
if point[1] < topLeft[1] {
|
|
topLeft[1] = point[1]
|
|
} else if point[1] > bottomRight[1] {
|
|
bottomRight[1] = point[1]
|
|
}
|
|
|
|
}
|
|
return topLeft, bottomRight
|
|
}
|
|
|
|
// Position returns the position of the ConvexPolygon.
|
|
func (cp *ConvexPolygon) Position() (float64, float64) {
|
|
return cp.X, cp.Y
|
|
}
|
|
|
|
// SetPosition sets the position of the ConvexPolygon. The offset of the vertices compared to the X and Y position is relative to however
|
|
// you initially defined the polygon and added the vertices.
|
|
func (cp *ConvexPolygon) SetPosition(x, y float64) {
|
|
cp.X = x
|
|
cp.Y = y
|
|
}
|
|
|
|
// SetPositionVec allows you to set the position of the ConvexPolygon using a Vector. The offset of the vertices compared to the X and Y
|
|
// position is relative to however you initially defined the polygon and added the vertices.
|
|
func (cp *ConvexPolygon) SetPositionVec(vec Vector) {
|
|
cp.X = vec.X()
|
|
cp.Y = vec.Y()
|
|
}
|
|
|
|
// Move translates the ConvexPolygon by the designated X and Y values.
|
|
func (cp *ConvexPolygon) Move(x, y float64) {
|
|
cp.X += x
|
|
cp.Y += y
|
|
}
|
|
|
|
// MoveVec translates the ConvexPolygon by the designated Vector.
|
|
func (cp *ConvexPolygon) MoveVec(vec Vector) {
|
|
cp.X += vec.X()
|
|
cp.Y += vec.Y()
|
|
}
|
|
|
|
// Center returns the transformed Center of the ConvexPolygon.
|
|
func (cp *ConvexPolygon) Center() Vector {
|
|
|
|
pos := Vector{0, 0}
|
|
|
|
for _, v := range cp.Transformed() {
|
|
pos.Add(v)
|
|
}
|
|
|
|
pos[0] /= float64(len(cp.Transformed()))
|
|
pos[1] /= float64(len(cp.Transformed()))
|
|
|
|
return pos
|
|
|
|
}
|
|
|
|
// Project projects (i.e. flattens) the ConvexPolygon onto the provided axis.
|
|
func (cp *ConvexPolygon) Project(axis Vector) Projection {
|
|
axis = axis.Unit()
|
|
vertices := cp.Transformed()
|
|
min := axis.Dot(Vector{vertices[0][0], vertices[0][1]})
|
|
max := min
|
|
for i := 1; i < len(vertices); i++ {
|
|
p := axis.Dot(Vector{vertices[i][0], vertices[i][1]})
|
|
if p < min {
|
|
min = p
|
|
} else if p > max {
|
|
max = p
|
|
}
|
|
}
|
|
return Projection{min, max}
|
|
}
|
|
|
|
// SATAxes returns the axes of the ConvexPolygon for SAT intersection testing.
|
|
func (cp *ConvexPolygon) SATAxes() []Vector {
|
|
|
|
axes := []Vector{}
|
|
for _, line := range cp.Lines() {
|
|
axes = append(axes, line.Normal())
|
|
}
|
|
return axes
|
|
|
|
}
|
|
|
|
// PointInside returns if a Point (a Vector) is inside the ConvexPolygon.
|
|
func (polygon *ConvexPolygon) PointInside(point Vector) bool {
|
|
|
|
pointLine := NewLine(point[0], point[1], point[0]+999999999999, point[1])
|
|
|
|
contactCount := 0
|
|
|
|
for _, line := range polygon.Lines() {
|
|
|
|
if line.IntersectionPointsLine(pointLine) != nil {
|
|
contactCount++
|
|
}
|
|
|
|
}
|
|
|
|
return contactCount == 1
|
|
}
|
|
|
|
type ContactSet struct {
|
|
Points []Vector // Slice of Points indicating contact between the two Shapes.
|
|
MTV Vector // Minimum Translation Vector; this is the vector to move a Shape on to move it outside of its contacting Shape.
|
|
Center Vector // Center of the Contact set; this is the average of all Points contained within the Contact Set.
|
|
}
|
|
|
|
func NewContactSet() *ContactSet {
|
|
return &ContactSet{
|
|
Points: []Vector{},
|
|
MTV: Vector{0, 0},
|
|
Center: Vector{0, 0},
|
|
}
|
|
}
|
|
|
|
// LeftmostPoint returns the left-most point out of the ContactSet's Points slice. If the Points slice is empty somehow, this returns nil.
|
|
func (cs *ContactSet) LeftmostPoint() Vector {
|
|
|
|
var left Vector
|
|
|
|
for _, point := range cs.Points {
|
|
|
|
if left == nil || point[0] < left[0] {
|
|
left = point
|
|
}
|
|
|
|
}
|
|
|
|
return left
|
|
|
|
}
|
|
|
|
// RightmostPoint returns the right-most point out of the ContactSet's Points slice. If the Points slice is empty somehow, this returns nil.
|
|
func (cs *ContactSet) RightmostPoint() Vector {
|
|
|
|
var right Vector
|
|
|
|
for _, point := range cs.Points {
|
|
|
|
if right == nil || point[0] > right[0] {
|
|
right = point
|
|
}
|
|
|
|
}
|
|
|
|
return right
|
|
|
|
}
|
|
|
|
// TopmostPoint returns the top-most point out of the ContactSet's Points slice. If the Points slice is empty somehow, this returns nil.
|
|
func (cs *ContactSet) TopmostPoint() Vector {
|
|
|
|
var top Vector
|
|
|
|
for _, point := range cs.Points {
|
|
|
|
if top == nil || point[1] < top[1] {
|
|
top = point
|
|
}
|
|
|
|
}
|
|
|
|
return top
|
|
|
|
}
|
|
|
|
// BottommostPoint returns the bottom-most point out of the ContactSet's Points slice. If the Points slice is empty somehow, this returns nil.
|
|
func (cs *ContactSet) BottommostPoint() Vector {
|
|
|
|
var bottom Vector
|
|
|
|
for _, point := range cs.Points {
|
|
|
|
if bottom == nil || point[1] > bottom[1] {
|
|
bottom = point
|
|
}
|
|
|
|
}
|
|
|
|
return bottom
|
|
|
|
}
|
|
|
|
// Intersection tests to see if a ConvexPolygon intersects with the other given Shape. dx and dy are delta movement variables indicating
|
|
// movement to be applied before the intersection check (thereby allowing you to see if a Shape would collide with another if it
|
|
// were in a different relative location). If an Intersection is found, a ContactSet will be returned, giving information regarding
|
|
// the intersection.
|
|
func (cp *ConvexPolygon) Intersection(dx, dy float64, other Shape) *ContactSet {
|
|
|
|
contactSet := NewContactSet()
|
|
|
|
ogX := cp.X
|
|
ogY := cp.Y
|
|
cp.X += dx
|
|
cp.Y += dy
|
|
|
|
if circle, isCircle := other.(*Circle); isCircle {
|
|
|
|
for _, line := range cp.Lines() {
|
|
contactSet.Points = append(contactSet.Points, line.IntersectionPointsCircle(circle)...)
|
|
}
|
|
|
|
} else if poly, isPoly := other.(*ConvexPolygon); isPoly {
|
|
|
|
for _, line := range cp.Lines() {
|
|
|
|
for _, otherLine := range poly.Lines() {
|
|
|
|
if point := line.IntersectionPointsLine(otherLine); point != nil {
|
|
contactSet.Points = append(contactSet.Points, point)
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(contactSet.Points) > 0 {
|
|
|
|
for _, point := range contactSet.Points {
|
|
contactSet.Center = contactSet.Center.Add(point)
|
|
}
|
|
|
|
contactSet.Center[0] /= float64(len(contactSet.Points))
|
|
contactSet.Center[1] /= float64(len(contactSet.Points))
|
|
|
|
if mtv := cp.calculateMTV(contactSet, other); mtv != nil {
|
|
contactSet.MTV = mtv
|
|
}
|
|
|
|
} else {
|
|
contactSet = nil
|
|
}
|
|
|
|
// If dx or dy aren't 0, then the MTV will be greater to compensate; this adjusts the vector back.
|
|
if contactSet != nil && (dx != 0 || dy != 0) {
|
|
deltaMagnitude := Vector{dx, dy}.Magnitude()
|
|
ogMagnitude := contactSet.MTV.Magnitude()
|
|
contactSet.MTV = contactSet.MTV.Unit().Scale(ogMagnitude - deltaMagnitude)
|
|
}
|
|
|
|
cp.X = ogX
|
|
cp.Y = ogY
|
|
|
|
return contactSet
|
|
|
|
}
|
|
|
|
// calculateMTV returns the MTV, if possible, and a bool indicating whether it was possible or not.
|
|
func (cp *ConvexPolygon) calculateMTV(contactSet *ContactSet, otherShape Shape) Vector {
|
|
|
|
delta := Vector{0, 0}
|
|
|
|
smallest := Vector{math.MaxFloat64, 0}
|
|
|
|
switch other := otherShape.(type) {
|
|
|
|
case *ConvexPolygon:
|
|
|
|
for _, axis := range cp.SATAxes() {
|
|
if !cp.Project(axis).Overlapping(other.Project(axis)) {
|
|
return nil
|
|
}
|
|
|
|
overlap := cp.Project(axis).Overlap(other.Project(axis))
|
|
|
|
if smallest.Magnitude() > overlap {
|
|
smallest = axis.Scale(overlap)
|
|
}
|
|
|
|
}
|
|
|
|
for _, axis := range other.SATAxes() {
|
|
|
|
if !cp.Project(axis).Overlapping(other.Project(axis)) {
|
|
return nil
|
|
}
|
|
|
|
overlap := cp.Project(axis).Overlap(other.Project(axis))
|
|
|
|
if smallest.Magnitude() > overlap {
|
|
smallest = axis.Scale(overlap)
|
|
}
|
|
|
|
}
|
|
|
|
case *Circle:
|
|
|
|
verts := append([]Vector{}, cp.Transformed()...)
|
|
// The center point of a contact could also be closer than the verts, particularly if we're testing from a Circle to another Shape.
|
|
verts = append(verts, contactSet.Center)
|
|
center := Vector{other.X, other.Y}
|
|
sort.Slice(verts, func(i, j int) bool { return verts[i].Sub(center).Magnitude() < verts[j].Sub(center).Magnitude() })
|
|
|
|
smallest = Vector{center[0] - verts[0][0], center[1] - verts[0][1]}
|
|
smallest = smallest.Unit().Scale(smallest.Magnitude() - other.Radius)
|
|
|
|
}
|
|
|
|
delta[0] = smallest[0]
|
|
delta[1] = smallest[1]
|
|
|
|
return delta
|
|
}
|
|
|
|
// ContainedBy returns if the ConvexPolygon is wholly contained by the other shape provided.
|
|
func (cp *ConvexPolygon) ContainedBy(otherShape Shape) bool {
|
|
|
|
switch other := otherShape.(type) {
|
|
|
|
case *ConvexPolygon:
|
|
|
|
for _, axis := range cp.SATAxes() {
|
|
if !cp.Project(axis).IsInside(other.Project(axis)) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
for _, axis := range other.SATAxes() {
|
|
if !cp.Project(axis).IsInside(other.Project(axis)) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// FlipH flips the ConvexPolygon's vertices horizontally according to their initial offset when adding the points.
|
|
func (cp *ConvexPolygon) FlipH() {
|
|
|
|
for _, v := range cp.Points {
|
|
v[0] = -v[0]
|
|
}
|
|
// We have to reverse vertex order after flipping the vertices to ensure the winding order is consistent between Objects (so that the normals are consistently outside or inside, which is important
|
|
// when doing Intersection tests). If we assume that the normal of a line, going from vertex A to vertex B, is one direction, then the normal would be inverted if the vertices were flipped in position,
|
|
// but not in order. This would make Intersection tests drive objects into each other, instead of giving the delta to move away.
|
|
cp.ReverseVertexOrder()
|
|
|
|
}
|
|
|
|
// FlipV flips the ConvexPolygon's vertices vertically according to their initial offset when adding the points.
|
|
func (cp *ConvexPolygon) FlipV() {
|
|
|
|
for _, v := range cp.Points {
|
|
v[1] = -v[1]
|
|
}
|
|
cp.ReverseVertexOrder()
|
|
|
|
}
|
|
|
|
// ReverseVertexOrder reverses the vertex ordering of the ConvexPolygon.
|
|
func (cp *ConvexPolygon) ReverseVertexOrder() {
|
|
|
|
verts := []Vector{cp.Points[0]}
|
|
|
|
for i := len(cp.Points) - 1; i >= 1; i-- {
|
|
verts = append(verts, cp.Points[i])
|
|
}
|
|
|
|
cp.Points = verts
|
|
|
|
}
|
|
|
|
// NewRectangle returns a rectangular ConvexPolygon with the vertices in clockwise order. In actuality, an AABBRectangle should be its own
|
|
// "thing" with its own optimized Intersection code check.
|
|
func NewRectangle(x, y, w, h float64) *ConvexPolygon {
|
|
return NewConvexPolygon(
|
|
x, y,
|
|
x+w, y,
|
|
x+w, y+h,
|
|
x, y+h,
|
|
)
|
|
}
|
|
|
|
type Circle struct {
|
|
X, Y, Radius float64
|
|
}
|
|
|
|
// NewCircle returns a new Circle, with its center at the X and Y position given, and with the defined radius.
|
|
func NewCircle(x, y, radius float64) *Circle {
|
|
circle := &Circle{
|
|
X: x,
|
|
Y: y,
|
|
Radius: radius,
|
|
}
|
|
return circle
|
|
}
|
|
|
|
func (circle *Circle) Clone() Shape {
|
|
return NewCircle(circle.X, circle.Y, circle.Radius)
|
|
}
|
|
|
|
// Bounds returns the top-left and bottom-right corners of the Circle.
|
|
func (circle *Circle) Bounds() (Vector, Vector) {
|
|
return Vector{circle.X - circle.Radius, circle.Y - circle.Radius}, Vector{circle.X + circle.Radius, circle.Y + circle.Radius}
|
|
}
|
|
|
|
// Intersection tests to see if a Circle intersects with the other given Shape. dx and dy are delta movement variables indicating
|
|
// movement to be applied before the intersection check (thereby allowing you to see if a Shape would collide with another if it
|
|
// were in a different relative location). If an Intersection is found, a ContactSet will be returned, giving information regarding
|
|
// the intersection.
|
|
func (circle *Circle) Intersection(dx, dy float64, other Shape) *ContactSet {
|
|
|
|
var contactSet *ContactSet
|
|
|
|
ox := circle.X
|
|
oy := circle.Y
|
|
|
|
circle.X += dx
|
|
circle.Y += dy
|
|
|
|
// here
|
|
|
|
switch shape := other.(type) {
|
|
case *ConvexPolygon:
|
|
// Maybe this would work?
|
|
contactSet = shape.Intersection(-dx, -dy, circle)
|
|
if contactSet != nil {
|
|
contactSet.MTV = contactSet.MTV.Scale(-1)
|
|
}
|
|
case *Circle:
|
|
|
|
contactSet = NewContactSet()
|
|
|
|
contactSet.Points = circle.IntersectionPointsCircle(shape)
|
|
|
|
if len(contactSet.Points) == 0 {
|
|
return nil
|
|
}
|
|
|
|
contactSet.MTV = Vector{circle.X - shape.X, circle.Y - shape.Y}
|
|
dist := contactSet.MTV.Magnitude()
|
|
contactSet.MTV = contactSet.MTV.Unit().Scale(circle.Radius + shape.Radius - dist)
|
|
|
|
for _, point := range contactSet.Points {
|
|
contactSet.Center = contactSet.Center.Add(point)
|
|
}
|
|
|
|
contactSet.Center[0] /= float64(len(contactSet.Points))
|
|
contactSet.Center[1] /= float64(len(contactSet.Points))
|
|
|
|
// if contactSet != nil {
|
|
// contactSet.MTV[0] -= dx
|
|
// contactSet.MTV[1] -= dy
|
|
// }
|
|
|
|
// contactSet.MTV = Vector{circle.X - shape.X, circle.Y - shape.Y}
|
|
}
|
|
|
|
circle.X = ox
|
|
circle.Y = oy
|
|
|
|
return contactSet
|
|
}
|
|
|
|
// Move translates the Circle by the designated X and Y values.
|
|
func (circle *Circle) Move(x, y float64) {
|
|
circle.X += x
|
|
circle.Y += y
|
|
}
|
|
|
|
// MoveVec translates the Circle by the designated Vector.
|
|
func (circle *Circle) MoveVec(vec Vector) {
|
|
circle.X += vec.X()
|
|
circle.Y += vec.Y()
|
|
}
|
|
|
|
// SetPosition sets the center position of the Circle using the X and Y values given.
|
|
func (circle *Circle) SetPosition(x, y float64) {
|
|
circle.X = x
|
|
circle.Y = y
|
|
}
|
|
|
|
// SetPosition sets the center position of the Circle using the Vector given.
|
|
func (circle *Circle) SetPositionVec(vec Vector) {
|
|
circle.X = vec.X()
|
|
circle.Y = vec.Y()
|
|
}
|
|
|
|
// Position() returns the X and Y position of the Circle.
|
|
func (circle *Circle) Position() (float64, float64) {
|
|
return circle.X, circle.Y
|
|
}
|
|
|
|
// PointInside returns if the given Vector is inside of the circle.
|
|
func (circle *Circle) PointInside(point Vector) bool {
|
|
return point.Sub(Vector{circle.X, circle.Y}).Magnitude() <= circle.Radius
|
|
}
|
|
|
|
// IntersectionPointsCircle returns the intersection points of the two circles provided.
|
|
func (circle *Circle) IntersectionPointsCircle(other *Circle) []Vector {
|
|
|
|
d := math.Sqrt(math.Pow(other.X-circle.X, 2) + math.Pow(other.Y-circle.Y, 2))
|
|
|
|
if d > circle.Radius+other.Radius || d < math.Abs(circle.Radius-other.Radius) || d == 0 && circle.Radius == other.Radius {
|
|
return nil
|
|
}
|
|
|
|
a := (math.Pow(circle.Radius, 2) - math.Pow(other.Radius, 2) + math.Pow(d, 2)) / (2 * d)
|
|
h := math.Sqrt(math.Pow(circle.Radius, 2) - math.Pow(a, 2))
|
|
|
|
x2 := circle.X + a*(other.X-circle.X)/d
|
|
y2 := circle.Y + a*(other.Y-circle.Y)/d
|
|
|
|
return []Vector{
|
|
{x2 + h*(other.Y-circle.Y)/d, y2 - h*(other.X-circle.X)/d},
|
|
{x2 - h*(other.Y-circle.Y)/d, y2 + h*(other.X-circle.X)/d},
|
|
}
|
|
|
|
}
|
|
|
|
type Projection struct {
|
|
Min, Max float64
|
|
}
|
|
|
|
// Overlapping returns whether a Projection is overlapping with the other, provided Projection. Credit to https://www.sevenson.com.au/programming/sat/
|
|
func (projection Projection) Overlapping(other Projection) bool {
|
|
return projection.Overlap(other) > 0
|
|
}
|
|
|
|
// Overlap returns the amount that a Projection is overlapping with the other, provided Projection. Credit to https://dyn4j.org/2010/01/sat/#sat-nointer
|
|
func (projection Projection) Overlap(other Projection) float64 {
|
|
return math.Min(projection.Max, other.Max) - math.Max(projection.Min, other.Min)
|
|
}
|
|
|
|
// IsInside returns whether the Projection is wholly inside of the other, provided Projection.
|
|
func (projection Projection) IsInside(other Projection) bool {
|
|
return projection.Min >= other.Min && projection.Max <= other.Max
|
|
}
|