mirror of
https://github.com/genxium/DelayNoMore
synced 2024-12-26 03:39:00 +00:00
Refactored use of SAT collision checking.
This commit is contained in:
parent
3baaf1d52c
commit
1959a7fd9a
@ -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 {
|
||||||
|
Logger.Info(fmt.Sprintf("Not Collided: playerShape=%v, oldDx=%v, oldDy=%v, toCheckBarrier=%v, e=%v", playerShape, oldDx, oldDy, barrierShape, e))
|
||||||
|
}
|
||||||
} 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", playerShape, oldDx, oldDy, barrierShape))
|
||||||
}
|
}
|
||||||
|
|
||||||
playerShape.SetPosition(origX, origY)
|
playerShape.SetPosition(origX, origY)
|
||||||
|
@ -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:"-"`
|
||||||
|
@ -3,80 +3,169 @@ package dnmshared
|
|||||||
import (
|
import (
|
||||||
"github.com/kvartborg/vector"
|
"github.com/kvartborg/vector"
|
||||||
"github.com/solarlune/resolv"
|
"github.com/solarlune/resolv"
|
||||||
"math"
|
"math"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenerateRectCollider(origX, origY, w, h, spaceOffsetX, spaceOffsetY float64, tag string) *resolv.Object {
|
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)
|
collider := resolv.NewObject(origX-w*0.5+spaceOffsetX, origY-h*0.5+spaceOffsetY, w, h, tag)
|
||||||
shape := resolv.NewRectangle(0, 0, w, h)
|
shape := resolv.NewRectangle(0, 0, w, h)
|
||||||
collider.SetShape(shape)
|
collider.SetShape(shape)
|
||||||
return collider
|
return collider
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateConvexPolygonCollider(unalignedSrc *Polygon2D, spaceOffsetX, spaceOffsetY float64, tag string) *resolv.Object {
|
func GenerateConvexPolygonCollider(unalignedSrc *Polygon2D, spaceOffsetX, spaceOffsetY float64, tag string) *resolv.Object {
|
||||||
aligned := AlignPolygon2DToBoundingBox(unalignedSrc)
|
aligned := AlignPolygon2DToBoundingBox(unalignedSrc)
|
||||||
var w, h float64 = 0, 0
|
var w, h float64 = 0, 0
|
||||||
|
|
||||||
shape := resolv.NewConvexPolygon()
|
shape := resolv.NewConvexPolygon()
|
||||||
for i, pi := range aligned.Points {
|
for i, pi := range aligned.Points {
|
||||||
for j, pj := range aligned.Points {
|
for j, pj := range aligned.Points {
|
||||||
if i == j {
|
if i == j {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if math.Abs(pj.X-pi.X) > w {
|
if math.Abs(pj.X-pi.X) > w {
|
||||||
w = math.Abs(pj.X - pi.X)
|
w = math.Abs(pj.X - pi.X)
|
||||||
}
|
}
|
||||||
if math.Abs(pj.Y-pi.Y) > h {
|
if math.Abs(pj.Y-pi.Y) > h {
|
||||||
h = math.Abs(pj.Y - pi.Y)
|
h = math.Abs(pj.Y - pi.Y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len(aligned.Points); i++ {
|
for i := 0; i < len(aligned.Points); i++ {
|
||||||
p := aligned.Points[i]
|
p := aligned.Points[i]
|
||||||
shape.AddPoints(p.X, p.Y)
|
shape.AddPoints(p.X, p.Y)
|
||||||
}
|
}
|
||||||
|
|
||||||
collider := resolv.NewObject(aligned.Anchor.X+spaceOffsetX, aligned.Anchor.Y+spaceOffsetY, w, h, tag)
|
collider := resolv.NewObject(aligned.Anchor.X+spaceOffsetX, aligned.Anchor.Y+spaceOffsetY, w, h, tag)
|
||||||
collider.SetShape(shape)
|
collider.SetShape(shape)
|
||||||
|
|
||||||
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
|
}
|
||||||
}
|
|
||||||
|
func IsPolygonPairColliding(a, b *resolv.ConvexPolygon, result *SatResult) bool {
|
||||||
overlap := cp.Project(axis).Overlap(other.Project(axis))
|
aCnt, bCnt := len(a.Points), len(b.Points)
|
||||||
|
// Single point case
|
||||||
if smallest.Magnitude() > overlap {
|
if 1 == aCnt && 1 == bCnt {
|
||||||
smallest = axis.Scale(overlap)
|
if nil != result {
|
||||||
}
|
result.Overlap = 0
|
||||||
|
}
|
||||||
}
|
return a.Points[0].X() == b.Points[0].X() && a.Points[0].Y() == b.Points[0].Y()
|
||||||
|
}
|
||||||
for _, axis := range other.SATAxes() {
|
|
||||||
|
if 1 < aCnt {
|
||||||
if !cp.Project(axis).Overlapping(other.Project(axis)) {
|
for _, axis := range a.SATAxes() {
|
||||||
return nil
|
if IsPolygonPairSeparatedByDir(a, b, axis.Unit(), result) {
|
||||||
}
|
return false
|
||||||
|
}
|
||||||
overlap := cp.Project(axis).Overlap(other.Project(axis))
|
}
|
||||||
|
}
|
||||||
if smallest.Magnitude() > overlap {
|
|
||||||
smallest = axis.Scale(overlap)
|
if 1 < bCnt {
|
||||||
}
|
for _, axis := range b.SATAxes() {
|
||||||
|
if IsPolygonPairSeparatedByDir(a, b, axis.Unit(), result) {
|
||||||
}
|
return false
|
||||||
|
}
|
||||||
delta[0] = smallest[0]
|
}
|
||||||
delta[1] = smallest[1]
|
}
|
||||||
|
|
||||||
return delta
|
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
|
||||||
}
|
}
|
||||||
|
@ -444,38 +444,38 @@ func (pTmxMapIns *TmxMap) continuousObjLayerOffsetToContinuousMapNodePos(continu
|
|||||||
}
|
}
|
||||||
|
|
||||||
func AlignPolygon2DToBoundingBox(input *Polygon2D) *Polygon2D {
|
func AlignPolygon2DToBoundingBox(input *Polygon2D) *Polygon2D {
|
||||||
// Transform again to put "anchor" at the top-left point of the bounding box for "resolv"
|
// Transform again to put "anchor" at the top-left point of the bounding box for "resolv"
|
||||||
float64Max := float64(99999999999999.9)
|
float64Max := float64(99999999999999.9)
|
||||||
boundingBoxTL := &Vec2D{
|
boundingBoxTL := &Vec2D{
|
||||||
X: float64Max,
|
X: float64Max,
|
||||||
Y: float64Max,
|
Y: float64Max,
|
||||||
}
|
}
|
||||||
for _, p := range input.Points {
|
for _, p := range input.Points {
|
||||||
if p.X < boundingBoxTL.X {
|
if p.X < boundingBoxTL.X {
|
||||||
boundingBoxTL.X = p.X
|
boundingBoxTL.X = p.X
|
||||||
}
|
}
|
||||||
if p.Y < boundingBoxTL.Y {
|
if p.Y < boundingBoxTL.Y {
|
||||||
boundingBoxTL.Y = p.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"
|
// 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{
|
output := &Polygon2D{
|
||||||
Anchor: &Vec2D{
|
Anchor: &Vec2D{
|
||||||
X: input.Anchor.X+boundingBoxTL.X,
|
X: input.Anchor.X + boundingBoxTL.X,
|
||||||
Y: input.Anchor.Y+boundingBoxTL.Y,
|
Y: input.Anchor.Y + boundingBoxTL.Y,
|
||||||
},
|
},
|
||||||
Points: make([]*Vec2D, len(input.Points)),
|
Points: make([]*Vec2D, len(input.Points)),
|
||||||
TileWidth: input.TileWidth,
|
TileWidth: input.TileWidth,
|
||||||
TileHeight: input.TileHeight,
|
TileHeight: input.TileHeight,
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, p := range input.Points {
|
for i, p := range input.Points {
|
||||||
output.Points[i] = &Vec2D{
|
output.Points[i] = &Vec2D{
|
||||||
X: p.X-boundingBoxTL.X,
|
X: p.X - boundingBoxTL.X,
|
||||||
Y: p.Y-boundingBoxTL.Y,
|
Y: p.Y - boundingBoxTL.Y,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user