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"
"fmt"
"github.com/hajimehoshi/ebiten/v2"
"github.com/kvartborg/vector"
"github.com/solarlune/resolv"
"go.uber.org/zap"
"image/color"
@ -56,7 +57,7 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi
if moveToCollide {
toTestPlayerCollider := playerColliders[0]
oldDx := 0.0
oldDy := 180.0
oldDy := 135.0
dx := oldDx
dy := oldDy
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)
origX, origY := playerShape.Position()
playerShape.SetPosition(origX+oldDx, origY+oldDy)
if mtv := CalculateMTVForConvexPolygon(playerShape, barrierShape); mtv != nil {
Logger.Info(fmt.Sprintf("Collided: shape=%v, oldDx=%v, oldDy=%v, MTV=%v", toTestPlayerCollider.Shape, oldDx, oldDy, mtv))
//dx, dy = mtv[0], mtv[1]
if colliding := IsPolygonPairColliding(playerShape, barrierShape, nil); colliding {
Logger.Info(fmt.Sprintf("Collided: playerShape=%v, oldDx=%v, oldDy=%v", playerShape, oldDx, oldDy))
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 {
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("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", playerShape, oldDx, oldDy, barrierShape))
}
playerShape.SetPosition(origX, origY)

View File

@ -15,6 +15,10 @@ type Vec2D struct {
Y float64 `json:"y,omitempty"`
}
func NormVec2D(dx, dy float64) Vec2D {
return Vec2D{dy, -dx}
}
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.
Points []*Vec2D `json:"-"`

View File

@ -3,80 +3,169 @@ package dnmshared
import (
"github.com/kvartborg/vector"
"github.com/solarlune/resolv"
"math"
"math"
)
func GenerateRectCollider(origX, origY, w, h, spaceOffsetX, spaceOffsetY float64, tag string) *resolv.Object {
collider := resolv.NewObject(origX-w*0.5+spaceOffsetX, origY-h*0.5+spaceOffsetY, w, h, tag)
shape := resolv.NewRectangle(0, 0, w, h)
collider.SetShape(shape)
return collider
collider := resolv.NewObject(origX-w*0.5+spaceOffsetX, origY-h*0.5+spaceOffsetY, w, h, tag)
shape := resolv.NewRectangle(0, 0, w, h)
collider.SetShape(shape)
return collider
}
func GenerateConvexPolygonCollider(unalignedSrc *Polygon2D, spaceOffsetX, spaceOffsetY float64, tag string) *resolv.Object {
aligned := AlignPolygon2DToBoundingBox(unalignedSrc)
var w, h float64 = 0, 0
aligned := AlignPolygon2DToBoundingBox(unalignedSrc)
var w, h float64 = 0, 0
shape := resolv.NewConvexPolygon()
for i, pi := range aligned.Points {
for j, pj := range aligned.Points {
if i == j {
continue
}
if math.Abs(pj.X-pi.X) > w {
w = math.Abs(pj.X - pi.X)
}
if math.Abs(pj.Y-pi.Y) > h {
h = math.Abs(pj.Y - pi.Y)
}
}
}
shape := resolv.NewConvexPolygon()
for i, pi := range aligned.Points {
for j, pj := range aligned.Points {
if i == j {
continue
}
if math.Abs(pj.X-pi.X) > w {
w = math.Abs(pj.X - pi.X)
}
if math.Abs(pj.Y-pi.Y) > h {
h = math.Abs(pj.Y - pi.Y)
}
}
}
for i := 0; i < len(aligned.Points); i++ {
p := aligned.Points[i]
shape.AddPoints(p.X, p.Y)
}
for i := 0; i < len(aligned.Points); i++ {
p := aligned.Points[i]
shape.AddPoints(p.X, p.Y)
}
collider := resolv.NewObject(aligned.Anchor.X+spaceOffsetX, aligned.Anchor.Y+spaceOffsetY, w, h, tag)
collider.SetShape(shape)
collider := resolv.NewObject(aligned.Anchor.X+spaceOffsetX, aligned.Anchor.Y+spaceOffsetY, w, h, tag)
collider.SetShape(shape)
return collider
return collider
}
func CalculateMTVForConvexPolygon(cp *resolv.ConvexPolygon, other *resolv.ConvexPolygon) vector.Vector {
delta := vector.Vector{0, 0}
smallest := vector.Vector{math.MaxFloat64, 0}
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)
}
}
delta[0] = smallest[0]
delta[1] = smallest[1]
return delta
type SatResult struct {
Overlap float64
OverlapX float64
OverlapY float64
AContainedInB bool
BContainedInA bool
Axis vector.Vector
}
func IsPolygonPairColliding(a, b *resolv.ConvexPolygon, result *SatResult) bool {
aCnt, bCnt := len(a.Points), len(b.Points)
// Single point case
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
}
}
}
if 1 < bCnt {
for _, axis := range b.SATAxes() {
if IsPolygonPairSeparatedByDir(a, b, axis.Unit(), result) {
return false
}
}
}
return true
}
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
}
if aEnd < dot {
aEnd = dot
}
}
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
}

View File

@ -444,38 +444,38 @@ func (pTmxMapIns *TmxMap) continuousObjLayerOffsetToContinuousMapNodePos(continu
}
func AlignPolygon2DToBoundingBox(input *Polygon2D) *Polygon2D {
// Transform again to put "anchor" at the top-left point of the bounding box for "resolv"
float64Max := float64(99999999999999.9)
boundingBoxTL := &Vec2D{
X: float64Max,
Y: float64Max,
}
for _, p := range input.Points {
if p.X < boundingBoxTL.X {
boundingBoxTL.X = p.X
}
if p.Y < boundingBoxTL.Y {
boundingBoxTL.Y = p.Y
}
}
// Now "input.Anchor" should move to "input.Anchor+boundingBoxTL", thus "boundingBoxTL" is also the value of the negative diff for all "input.Points"
output := &Polygon2D{
Anchor: &Vec2D{
X: input.Anchor.X+boundingBoxTL.X,
Y: input.Anchor.Y+boundingBoxTL.Y,
},
Points: make([]*Vec2D, len(input.Points)),
TileWidth: input.TileWidth,
TileHeight: input.TileHeight,
// Transform again to put "anchor" at the top-left point of the bounding box for "resolv"
float64Max := float64(99999999999999.9)
boundingBoxTL := &Vec2D{
X: float64Max,
Y: float64Max,
}
for _, p := range input.Points {
if p.X < boundingBoxTL.X {
boundingBoxTL.X = p.X
}
if p.Y < boundingBoxTL.Y {
boundingBoxTL.Y = p.Y
}
}
for i, p := range input.Points {
output.Points[i] = &Vec2D{
X: p.X-boundingBoxTL.X,
Y: p.Y-boundingBoxTL.Y,
}
}
// Now "input.Anchor" should move to "input.Anchor+boundingBoxTL", thus "boundingBoxTL" is also the value of the negative diff for all "input.Points"
output := &Polygon2D{
Anchor: &Vec2D{
X: input.Anchor.X + boundingBoxTL.X,
Y: input.Anchor.Y + boundingBoxTL.Y,
},
Points: make([]*Vec2D, len(input.Points)),
TileWidth: input.TileWidth,
TileHeight: input.TileHeight,
}
return output
for i, p := range input.Points {
output.Points[i] = &Vec2D{
X: p.X - boundingBoxTL.X,
Y: p.Y - boundingBoxTL.Y,
}
}
return output
}