Updated test cases for frontend-backend-collision-reconciliation.

This commit is contained in:
yflu 2022-10-22 13:38:10 +08:00
parent cff31d295c
commit 0f4d067c06
8 changed files with 157 additions and 97 deletions

View File

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/kvartborg/vector"
"github.com/solarlune/resolv" "github.com/solarlune/resolv"
"go.uber.org/zap" "go.uber.org/zap"
"math/rand" "math/rand"
@ -1159,27 +1158,13 @@ func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRende
if collision := playerCollider.Check(oldDx, oldDy, "Barrier"); collision != nil { if collision := playerCollider.Check(oldDx, oldDy, "Barrier"); collision != nil {
playerShape := playerCollider.Shape.(*resolv.ConvexPolygon) playerShape := playerCollider.Shape.(*resolv.ConvexPolygon)
barrierShape := collision.Objects[0].Shape.(*resolv.ConvexPolygon) barrierShape := collision.Objects[0].Shape.(*resolv.ConvexPolygon)
origX, origY := playerShape.Position() if overlapped, pushbackX, pushbackY := CalcPushbacks(oldDx, oldDy, playerShape, barrierShape); overlapped {
playerShape.SetPosition(origX+oldDx, origY+oldDy) Logger.Info(fmt.Sprintf("Collided & overlapped: player.X=%v, player.Y=%v, oldDx=%v, oldDy=%v, playerShape=%v, toCheckBarrier=%v, pushbackX=%v, pushbackY=%v", playerCollider.X, playerCollider.Y, oldDx, oldDy, ConvexPolygonStr(playerShape), ConvexPolygonStr(barrierShape), pushbackX, pushbackY))
if colliding := IsPolygonPairColliding(playerShape, barrierShape, nil); colliding { dx -= pushbackX
Logger.Info(fmt.Sprintf("Collided: playerShape=%v, oldDx=%v, oldDy=%v", playerShape, oldDx, oldDy)) dy -= pushbackY
overlapResult := &SatResult{ } else {
Overlap: 0, Logger.Info(fmt.Sprintf("Collider BUT not overlapped: player.X=%v, player.Y=%v, oldDx=%v, oldDy=%v, playerShape=%v, toCheckBarrier=%v", playerCollider.X, playerCollider.Y, oldDx, oldDy, ConvexPolygonStr(playerShape), ConvexPolygonStr(barrierShape)))
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
}
}
playerShape.SetPosition(origX, origY)
} }
playerCollider.X += dx playerCollider.X += dx
playerCollider.Y += dy playerCollider.Y += dy

View File

@ -4,7 +4,6 @@ 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,43 +55,22 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi
moveToCollide := true moveToCollide := true
if moveToCollide { if moveToCollide {
toTestPlayerCollider := playerColliders[0] toTestPlayerCollider := playerColliders[0]
oldDx, oldDy := 135.0, 135.0 oldDx, oldDy := -2.98, -50.0
dx, dy := oldDx, oldDy dx, dy := oldDx, oldDy
if collision := toTestPlayerCollider.Check(oldDx, oldDy, "Barrier"); collision != nil { if collision := toTestPlayerCollider.Check(oldDx, oldDy, "Barrier"); collision != nil {
playerShape := toTestPlayerCollider.Shape.(*resolv.ConvexPolygon) playerShape := toTestPlayerCollider.Shape.(*resolv.ConvexPolygon)
barrierShape := collision.Objects[0].Shape.(*resolv.ConvexPolygon) barrierShape := collision.Objects[0].Shape.(*resolv.ConvexPolygon)
origX, origY := playerShape.Position() if overlapped, pushbackX, pushbackY := CalcPushbacks(oldDx, oldDy, playerShape, barrierShape); overlapped {
playerShape.SetPosition(origX+oldDx, origY+oldDy) Logger.Info(fmt.Sprintf("Collided & overlapped: player.X=%v, player.Y=%v, oldDx=%v, oldDy=%v, playerShape=%v, toCheckBarrier=%v, pushbackX=%v, pushbackY=%v", toTestPlayerCollider.X, toTestPlayerCollider.Y, oldDx, oldDy, ConvexPolygonStr(playerShape), ConvexPolygonStr(barrierShape), pushbackX, pushbackY))
if colliding := IsPolygonPairColliding(playerShape, barrierShape, nil); colliding { dx -= pushbackX
Logger.Info(fmt.Sprintf("Collided: playerShape=%v, oldDx=%v, oldDy=%v", playerShape, oldDx, oldDy)) dy -= pushbackY
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("Not Collided: playerShape=%v, oldDx=%v, oldDy=%v, toCheckBarrier=%v, e=%v", playerShape, oldDx, oldDy, barrierShape, e)) Logger.Info(fmt.Sprintf("Collider BUT not overlapped: player.X=%v, player.Y=%v, oldDx=%v, oldDy=%v, playerShape=%v, toCheckBarrier=%v", toTestPlayerCollider.X, toTestPlayerCollider.Y, oldDx, oldDy, ConvexPolygonStr(playerShape), ConvexPolygonStr(barrierShape)))
} }
} else {
Logger.Info(fmt.Sprintf("Not collided: playerShape=%v, oldDx=%v, oldDy=%v, toCheckBarrier=%v", playerShape, oldDx, oldDy, barrierShape))
} }
playerShape.SetPosition(origX, origY)
toTestPlayerCollider.X += dx toTestPlayerCollider.X += dx
toTestPlayerCollider.Y += dy toTestPlayerCollider.Y += dy
} else {
Logger.Info(fmt.Sprintf("Collision Test: shape=%v, oldDx=%v, oldDy=%v, not colliding with any Barrier", toTestPlayerCollider.Shape, oldDx, oldDy))
}
toTestPlayerCollider.Update() toTestPlayerCollider.Update()
} }

View File

@ -1,11 +1,22 @@
package dnmshared package dnmshared
import ( import (
"fmt"
"github.com/kvartborg/vector" "github.com/kvartborg/vector"
"github.com/solarlune/resolv" "github.com/solarlune/resolv"
"math" "math"
"strings"
) )
func ConvexPolygonStr(body *resolv.ConvexPolygon) string {
var s []string = make([]string, len(body.Points))
for i, p := range body.Points {
s[i] = fmt.Sprintf("[%v, %v]", p[0]+body.X, p[1]+body.Y)
}
return fmt.Sprintf("[%s]", strings.Join(s, ", "))
}
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)
@ -43,6 +54,28 @@ func GenerateConvexPolygonCollider(unalignedSrc *Polygon2D, spaceOffsetX, spaceO
return collider return collider
} }
func CalcPushbacks(oldDx, oldDy float64, playerShape, barrierShape *resolv.ConvexPolygon) (bool, float64, float64) {
origX, origY := playerShape.Position()
defer func() {
playerShape.SetPosition(origX, origY)
}()
playerShape.SetPosition(origX+oldDx, origY+oldDy)
overlapResult := &SatResult{
Overlap: 0,
OverlapX: 0,
OverlapY: 0,
AContainedInB: true,
BContainedInA: true,
Axis: vector.Vector{0, 0},
}
if overlapped := IsPolygonPairOverlapped(playerShape, barrierShape, overlapResult); overlapped {
pushbackX, pushbackY := overlapResult.Overlap*overlapResult.OverlapX, overlapResult.Overlap*overlapResult.OverlapY
return true, pushbackX, pushbackY
} else {
return false, 0, 0
}
}
type SatResult struct { type SatResult struct {
Overlap float64 Overlap float64
OverlapX float64 OverlapX float64
@ -52,7 +85,7 @@ type SatResult struct {
Axis vector.Vector Axis vector.Vector
} }
func IsPolygonPairColliding(a, b *resolv.ConvexPolygon, result *SatResult) bool { func IsPolygonPairOverlapped(a, b *resolv.ConvexPolygon, result *SatResult) bool {
aCnt, bCnt := len(a.Points), len(b.Points) aCnt, bCnt := len(a.Points), len(b.Points)
// Single point case // Single point case
if 1 == aCnt && 1 == bCnt { if 1 == aCnt && 1 == bCnt {
@ -64,7 +97,7 @@ func IsPolygonPairColliding(a, b *resolv.ConvexPolygon, result *SatResult) bool
if 1 < aCnt { if 1 < aCnt {
for _, axis := range a.SATAxes() { for _, axis := range a.SATAxes() {
if IsPolygonPairSeparatedByDir(a, b, axis.Unit(), result) { if isPolygonPairSeparatedByDir(a, b, axis.Unit(), result) {
return false return false
} }
} }
@ -72,7 +105,7 @@ func IsPolygonPairColliding(a, b *resolv.ConvexPolygon, result *SatResult) bool
if 1 < bCnt { if 1 < bCnt {
for _, axis := range b.SATAxes() { for _, axis := range b.SATAxes() {
if IsPolygonPairSeparatedByDir(a, b, axis.Unit(), result) { if isPolygonPairSeparatedByDir(a, b, axis.Unit(), result) {
return false return false
} }
} }
@ -81,10 +114,26 @@ func IsPolygonPairColliding(a, b *resolv.ConvexPolygon, result *SatResult) bool
return true return true
} }
func IsPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e vector.Vector, result *SatResult) bool { func isPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e vector.Vector, result *SatResult) bool {
/*
[WARNING] This function is deliberately made private, it shouldn't be used alone (i.e. not along the norms of a polygon), otherwise the pushbacks calculated would be meaningless.
Consider the following example
a: {
anchor: [1337.19 1696.74]
points: [[0 0] [24 0] [24 24] [0 24]]
},
b: {
anchor: [1277.72 1570.56]
points: [[642.57 319.16] [0 319.16] [5.73 0] [643.75 0.90]]
}
e = (-2.98, 1.49).Unit()
*/
var aStart, aEnd, bStart, bEnd float64 = math.MaxFloat64, -math.MaxFloat64, math.MaxFloat64, -math.MaxFloat64 var aStart, aEnd, bStart, bEnd float64 = math.MaxFloat64, -math.MaxFloat64, math.MaxFloat64, -math.MaxFloat64
for _, p := range a.Points { for _, p := range a.Points {
dot := p.X()*e.X() + p.Y()*e.Y() dot := (p.X()+a.X)*e.X() + (p.Y()+a.Y)*e.Y()
if aStart > dot { if aStart > dot {
aStart = dot aStart = dot
@ -96,7 +145,7 @@ func IsPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e vector.Vector, re
} }
for _, p := range b.Points { for _, p := range b.Points {
dot := p.X()*e.X() + p.Y()*e.Y() dot := (p.X()+b.X)*e.X() + (p.Y()+b.Y)*e.Y()
if bStart > dot { if bStart > dot {
bStart = dot bStart = dot

View File

@ -0,0 +1,39 @@
const collisions = require('./modules/Collisions');
const collisionSys = new collisions.Collisions();
/*
Backend result reference
2022-10-22T12:11:25.156+0800 INFO collider_visualizer/worldColliderDisplay.go:77 Collided: player.X=1257.665, player.Y=1415.335, oldDx=-2.98, oldDy=-50, playerShape=&{[[0 0] [64 0] [64 64] [0 64]] 1254.685 1365.335 true}, toCheckBarrier=&{[[628.626 54.254500000000064] [0 56.03250000000003] [0.42449999999999477 1.1229999999999905] [625.9715000000001 0]] 1289.039 1318.0805 true}, pushbackX=-0.15848054013127655, pushbackY=-56.03205175509715, result=&{56.03227587710039 -0.0028283794946841584 -0.9999960001267175 false false [0.9988052279193613 -0.04886836073527201]}
*/
function polygonStr(body) {
let coords = [];
let cnt = body._coords.length;
for (let ix = 0, iy = 1; ix < cnt; ix += 2, iy += 2) {
coords.push([body._coords[ix], body._coords[iy]]);
}
return JSON.stringify(coords);
}
const playerCollider = collisionSys.createPolygon(1257.665, 1415.335, [[0, 0], [64, 0], [64, 64], [0, 64]]);
const barrierCollider = collisionSys.createPolygon(1289.039, 1318.0805, [[628.626, 54.254500000000064], [0, 56.03250000000003], [0.42449999999999477, 1.1229999999999905], [625.9715000000001, 0]]);
const oldDx = -2.98;
const oldDy = -50.0;
playerCollider.x += oldDx;
playerCollider.y += oldDy;
collisionSys.update();
const result = collisionSys.createResult();
const potentials = playerCollider.potentials();
let overlapCheckId = 0;
for (const barrier of potentials) {
if (!playerCollider.collides(barrier, result)) continue;
const pushbackX = result.overlap * result.overlap_x;
const pushbackY = result.overlap * result.overlap_y;
console.log("For overlapCheckId=" + overlapCheckId + ", the overlap: a=", polygonStr(result.a), ", b=", polygonStr(result.b), ", pushbackX=", pushbackX, ", pushbackY=", pushbackY);
}

View File

@ -0,0 +1,9 @@
{
"ver": "1.0.5",
"uuid": "fce86138-76fc-44d5-8eac-2731b3b0cefd",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

File diff suppressed because one or more lines are too long