diff --git a/shape.go b/shape.go index df6f799..2aff47d 100755 --- a/shape.go +++ b/shape.go @@ -19,6 +19,7 @@ type IShape interface { Bounds() (Vector, Vector) // Position returns the X and Y position of the Shape. Position() Vector + transformedCenter() Vector // specifically for convex shapes // SetPosition allows you to place a Shape at another location. SetPosition(x, y float64) // SetPositionVec allows you to place a Shape at another location using a Vector. @@ -387,6 +388,10 @@ func (cp *ConvexPolygon) Center() Vector { } +func (cp *ConvexPolygon) transformedCenter() Vector { + return cp.Center() +} + // Project projects (i.e. flattens) the ConvexPolygon onto the provided axis. func (cp *ConvexPolygon) Project(axis Vector) Projection { axis = axis.Unit() @@ -475,6 +480,7 @@ 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. + Normal Vector } func NewContactSet() *ContactSet { @@ -671,22 +677,57 @@ func (cp *ConvexPolygon) calculateMTV(contactSet *ContactSet, otherShape IShape) } + // If the direction from target to source points opposite to the separation, invert the separation vector. + if cp.Center().Sub(other.Center()).Dot(smallest) < 0 { + smallest = smallest.Invert() + } + 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 := other.position sort.Slice(verts, func(i, j int) bool { return verts[i].Sub(center).Magnitude() < verts[j].Sub(center).Magnitude() }) - smallest = Vector{center.X - verts[0].X, center.Y - verts[0].Y} - smallest = smallest.Unit().Scale(smallest.Magnitude() - other.radius) + axis := Vector{center.X - verts[0].X, center.Y - verts[0].Y} + pa := cp.Project(axis) + pb := other.Project(axis) + overlap := pa.Overlap(pb) + if overlap <= 0 { + return Vector{}, false + } + smallest = axis.Unit().Scale(overlap) + + for _, axis := range cp.SATAxes() { + pa := cp.Project(axis) + pb := other.Project(axis) + + overlap := pa.Overlap(pb) + + if overlap <= 0 { + return Vector{}, false + } + + if smallest.Magnitude() > overlap { + smallest = axis.Scale(overlap) + } + + } + + // If the direction from target to source points opposite to the separation, invert the separation vector + if cp.Center().Sub(other.position).Dot(smallest) < 0 { + smallest = smallest.Invert() + } } delta.X = smallest.X delta.Y = smallest.Y + pointingDirection := otherShape.transformedCenter().Sub(cp.transformedCenter()) + if pointingDirection.Dot(delta) > 0 { + delta = delta.Invert() + } + return delta, true } @@ -830,6 +871,21 @@ func (circle *Circle) Bounds() (Vector, Vector) { return Vector{circle.position.X - circle.radius, circle.position.Y - circle.radius}, Vector{circle.position.X + circle.radius, circle.position.Y + circle.radius} } +func (circle *Circle) Project(axis Vector) Projection { + axis = axis.Unit() + projectedCenter := axis.Dot(circle.position) + projectedRadius := axis.Magnitude() * circle.radius * circle.scale + + min := projectedCenter - projectedRadius + max := projectedCenter + projectedRadius + + if min > max { + min, max = max, min + } + + return Projection{min, max} +} + // 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 @@ -945,6 +1001,10 @@ func (circle *Circle) Position() Vector { return circle.position } +func (circle *Circle) transformedCenter() Vector { + return circle.Position() +} + // PointInside returns if the given Vector is inside of the circle. func (circle *Circle) PointInside(point Vector) bool { return point.DistanceSquared(circle.position) <= circle.radius*circle.radius @@ -1188,7 +1248,7 @@ func (projection Projection) Overlapping(other Projection) bool { // 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) + return math.Min(projection.Max - other.Min, other.Max - projection.Min) } // IsInside returns whether the Projection is wholly inside of the other, provided Projection. diff --git a/space.go b/space.go index 21ab038..a964f41 100755 --- a/space.go +++ b/space.go @@ -8,6 +8,7 @@ import ( // Objects occupy those spaces. type Space struct { Cells [][]*Cell + objects []*Object CellWidth, CellHeight int // Width and Height of each Cell in "world-space" / pixels / whatever } @@ -33,29 +34,22 @@ func NewSpace(spaceWidth, spaceHeight, cellWidth, cellHeight int) *Space { // Add adds the specified Objects to the Space, updating the Space's cells to refer to the Object. func (sp *Space) Add(objects ...*Object) { - if sp == nil { - panic("ERROR: space is nil") - } - for _, obj := range objects { obj.Space = sp - // We call Update() once to make sure the object gets its cells added. obj.Update() } + sp.objects = append(sp.objects, objects...) + } // Remove removes the specified Objects from being associated with the Space. This should be done whenever an Object is removed from the // game. func (sp *Space) Remove(objects ...*Object) { - if sp == nil { - panic("ERROR: space is nil") - } - for _, obj := range objects { for _, cell := range obj.TouchingCells { @@ -66,36 +60,30 @@ func (sp *Space) Remove(objects ...*Object) { obj.Space = nil + for i, o := range sp.objects { + if o == obj { + sp.objects[i] = nil + sp.objects = append(sp.objects[:i], sp.objects[i+1:]...) + break + } + } + } } -// Objects loops through all Cells in the Space (from top to bottom, and from left to right) to return all Objects -// that exist in the Space. Of course, each Object is counted only once. -func (sp *Space) Objects() []*Object { - - objectsAdded := map[*Object]bool{} - objects := []*Object{} - - for cy := range sp.Cells { - - for cx := range sp.Cells[cy] { - - for _, o := range sp.Cells[cy][cx].Objects { - - if _, added := objectsAdded[o]; !added { - objects = append(objects, o) - objectsAdded[o] = true - } +// Objects returns a new slice of the objects in the Space. +func (s *Space) Objects() []*Object { + return append(make([]*Object, 0, len(s.objects)), s.objects...) +} - } - - } +// ForEachObject iterates through each Object in the Space and runs the provided function on them. +func (s *Space) ForEachObject(forEach func(o *Object)) { + for _, o := range s.objects { + forEach(o) } - return objects - } // Resize resizes the internal Cells array.