Refactored use of SAT collision checking.

This commit is contained in:
genxium 2022-10-19 17:32:18 +08:00
parent 3baaf1d52c
commit 1959a7fd9a
4 changed files with 211 additions and 102 deletions

View File

@ -4,6 +4,7 @@ import (
. "dnmshared" . "dnmshared"
"fmt" "fmt"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
"github.com/kvartborg/vector"
"github.com/solarlune/resolv" "github.com/solarlune/resolv"
"go.uber.org/zap" "go.uber.org/zap"
"image/color" "image/color"
@ -56,7 +57,7 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi
if moveToCollide { if moveToCollide {
toTestPlayerCollider := playerColliders[0] toTestPlayerCollider := playerColliders[0]
oldDx := 0.0 oldDx := 0.0
oldDy := 180.0 oldDy := 135.0
dx := oldDx dx := oldDx
dy := oldDy dy := oldDy
if collision := toTestPlayerCollider.Check(oldDx, oldDy, "Barrier"); collision != nil { if collision := toTestPlayerCollider.Check(oldDx, oldDy, "Barrier"); collision != nil {
@ -64,11 +65,26 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi
barrierShape := collision.Objects[0].Shape.(*resolv.ConvexPolygon) barrierShape := collision.Objects[0].Shape.(*resolv.ConvexPolygon)
origX, origY := playerShape.Position() origX, origY := playerShape.Position()
playerShape.SetPosition(origX+oldDx, origY+oldDy) playerShape.SetPosition(origX+oldDx, origY+oldDy)
if mtv := CalculateMTVForConvexPolygon(playerShape, barrierShape); mtv != nil { if colliding := IsPolygonPairColliding(playerShape, barrierShape, nil); colliding {
Logger.Info(fmt.Sprintf("Collided: shape=%v, oldDx=%v, oldDy=%v, MTV=%v", toTestPlayerCollider.Shape, oldDx, oldDy, mtv)) Logger.Info(fmt.Sprintf("Collided: playerShape=%v, oldDx=%v, oldDy=%v", playerShape, oldDx, oldDy))
//dx, dy = mtv[0], mtv[1] overlapResult := &SatResult{
Overlap: 0,
OverlapX: 0,
OverlapY: 0,
AContainedInB: true,
BContainedInA: true,
Axis: vector.Vector{0, 0},
}
e := vector.Vector{oldDx, oldDy}.Unit()
if separatableAlongMovement := IsPolygonPairSeparatedByDir(playerShape, barrierShape, e, overlapResult); !separatableAlongMovement {
pushbackX, pushbackY := overlapResult.Overlap*overlapResult.OverlapX, overlapResult.Overlap*overlapResult.OverlapY
Logger.Info(fmt.Sprintf("Collided: playerShape=%v, oldDx=%v, oldDy=%v, toCheckBarrier=%v, pushbackX=%v, pushbackY=%v", playerShape, oldDx, oldDy, barrierShape, pushbackX, pushbackY))
dx, dy = oldDx-pushbackX, oldDy-pushbackY
} else { } else {
Logger.Info(fmt.Sprintf("Collided: shape=%v, oldDx=%v, oldDy=%v, toCheckBarrier=%v, not intersecting", toTestPlayerCollider.Shape, oldDx, oldDy, barrierShape)) Logger.Info(fmt.Sprintf("Not Collided: playerShape=%v, oldDx=%v, oldDy=%v, toCheckBarrier=%v, e=%v", playerShape, oldDx, oldDy, barrierShape, e))
}
} else {
Logger.Info(fmt.Sprintf("Not collided: playerShape=%v, oldDx=%v, oldDy=%v, toCheckBarrier=%v", playerShape, oldDx, oldDy, barrierShape))
} }
playerShape.SetPosition(origX, origY) playerShape.SetPosition(origX, origY)

View File

@ -15,6 +15,10 @@ type Vec2D struct {
Y float64 `json:"y,omitempty"` Y float64 `json:"y,omitempty"`
} }
func NormVec2D(dx, dy float64) Vec2D {
return Vec2D{dy, -dx}
}
type Polygon2D struct { type Polygon2D struct {
Anchor *Vec2D `json:"-"` // This "Polygon2D.Anchor" is used to be assigned to "B2BodyDef.Position", which in turn is used as the position of the FIRST POINT of the polygon. Anchor *Vec2D `json:"-"` // This "Polygon2D.Anchor" is used to be assigned to "B2BodyDef.Position", which in turn is used as the position of the FIRST POINT of the polygon.
Points []*Vec2D `json:"-"` Points []*Vec2D `json:"-"`

View File

@ -43,40 +43,129 @@ func GenerateConvexPolygonCollider(unalignedSrc *Polygon2D, spaceOffsetX, spaceO
return collider return collider
} }
func CalculateMTVForConvexPolygon(cp *resolv.ConvexPolygon, other *resolv.ConvexPolygon) vector.Vector { type SatResult struct {
delta := vector.Vector{0, 0} Overlap float64
OverlapX float64
smallest := vector.Vector{math.MaxFloat64, 0} OverlapY float64
AContainedInB bool
for _, axis := range cp.SATAxes() { BContainedInA bool
if !cp.Project(axis).Overlapping(other.Project(axis)) { Axis vector.Vector
return nil
} }
overlap := cp.Project(axis).Overlap(other.Project(axis)) func IsPolygonPairColliding(a, b *resolv.ConvexPolygon, result *SatResult) bool {
aCnt, bCnt := len(a.Points), len(b.Points)
if smallest.Magnitude() > overlap { // Single point case
smallest = axis.Scale(overlap) if 1 == aCnt && 1 == bCnt {
if nil != result {
result.Overlap = 0
}
return a.Points[0].X() == b.Points[0].X() && a.Points[0].Y() == b.Points[0].Y()
} }
if 1 < aCnt {
for _, axis := range a.SATAxes() {
if IsPolygonPairSeparatedByDir(a, b, axis.Unit(), result) {
return false
}
}
} }
for _, axis := range other.SATAxes() { if 1 < bCnt {
for _, axis := range b.SATAxes() {
if !cp.Project(axis).Overlapping(other.Project(axis)) { if IsPolygonPairSeparatedByDir(a, b, axis.Unit(), result) {
return nil return false
}
}
} }
overlap := cp.Project(axis).Overlap(other.Project(axis)) return true
if smallest.Magnitude() > overlap {
smallest = axis.Scale(overlap)
} }
func IsPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e vector.Vector, result *SatResult) bool {
var aStart, aEnd, bStart, bEnd float64 = math.MaxFloat64, -math.MaxFloat64, math.MaxFloat64, -math.MaxFloat64
for _, p := range a.Points {
dot := p.X()*e.X() + p.Y()*e.Y()
if aStart > dot {
aStart = dot
} }
delta[0] = smallest[0] if aEnd < dot {
delta[1] = smallest[1] aEnd = dot
}
return delta }
for _, p := range b.Points {
dot := p.X()*e.X() + p.Y()*e.Y()
if bStart > dot {
bStart = dot
}
if bEnd < dot {
bEnd = dot
}
}
if aStart > bEnd || aEnd < bStart {
// Separated by unit vector "e"
return true
}
if nil != result {
result.Axis = e
overlap := float64(0)
if aStart < bStart {
result.AContainedInB = false
if aEnd < bEnd {
overlap = aEnd - bStart
result.BContainedInA = false
} else {
option1 := aEnd - bStart
option2 := bEnd - aStart
if option1 < option2 {
overlap = option1
} else {
overlap = -option2
}
}
} else {
result.BContainedInA = false
if aEnd > bEnd {
overlap = aStart - bEnd
result.AContainedInB = false
} else {
option1 := aEnd - bStart
option2 := bEnd - aStart
if option1 < option2 {
overlap = option1
} else {
overlap = -option2
}
}
}
currentOverlap := result.Overlap
absoluteOverlap := overlap
if overlap < 0 {
absoluteOverlap = -overlap
}
if 0 == currentOverlap || currentOverlap > absoluteOverlap {
var sign float64 = 1
if overlap < 0 {
sign = -1
}
result.Overlap = absoluteOverlap
result.OverlapX = e.X() * sign
result.OverlapY = e.Y() * sign
}
}
// the specified unit vector "e" doesn't separate "a" and "b", overlap result is generated
return false
} }