mirror of
https://github.com/genxium/DelayNoMore
synced 2024-12-25 11:18:55 +00:00
Updated test cases for frontend-backend-collision-reconciliation.
This commit is contained in:
parent
cff31d295c
commit
0f4d067c06
@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/kvartborg/vector"
|
||||
"github.com/solarlune/resolv"
|
||||
"go.uber.org/zap"
|
||||
"math/rand"
|
||||
@ -329,7 +328,7 @@ func (pR *Room) ChooseStage() error {
|
||||
|
||||
var barrierLocalIdInBattle int32 = 0
|
||||
for _, polygon2DUnaligned := range barrierPolygon2DList {
|
||||
polygon2D := AlignPolygon2DToBoundingBox(polygon2DUnaligned)
|
||||
polygon2D := AlignPolygon2DToBoundingBox(polygon2DUnaligned)
|
||||
/*
|
||||
// For debug-printing only.
|
||||
Logger.Info("ChooseStage printing polygon2D for barrierPolygon2DList", zap.Any("barrierLocalIdInBattle", barrierLocalIdInBattle), zap.Any("polygon2D.Anchor", polygon2D.Anchor), zap.Any("polygon2D.Points", polygon2D.Points))
|
||||
@ -1151,35 +1150,21 @@ func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRende
|
||||
continue
|
||||
}
|
||||
baseChange := player.Speed * pR.RollbackEstimatedDt * decodedInputSpeedFactor
|
||||
oldDx, oldDy := baseChange * float64(decodedInput[0]), baseChange * float64(decodedInput[1])
|
||||
dx, dy := oldDx, oldDy
|
||||
oldDx, oldDy := baseChange*float64(decodedInput[0]), baseChange*float64(decodedInput[1])
|
||||
dx, dy := oldDx, oldDy
|
||||
|
||||
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
|
||||
playerCollider := pR.CollisionSysMap[collisionPlayerIndex]
|
||||
if collision := playerCollider.Check(oldDx, oldDy, "Barrier"); collision != nil {
|
||||
playerShape := playerCollider.Shape.(*resolv.ConvexPolygon)
|
||||
barrierShape := collision.Objects[0].Shape.(*resolv.ConvexPolygon)
|
||||
origX, origY := playerShape.Position()
|
||||
playerShape.SetPosition(origX+oldDx, origY+oldDy)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
playerShape.SetPosition(origX, origY)
|
||||
if overlapped, pushbackX, pushbackY := CalcPushbacks(oldDx, oldDy, playerShape, barrierShape); overlapped {
|
||||
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))
|
||||
dx -= pushbackX
|
||||
dy -= pushbackY
|
||||
} else {
|
||||
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)))
|
||||
}
|
||||
}
|
||||
playerCollider.X += dx
|
||||
playerCollider.Y += dy
|
||||
@ -1216,7 +1201,7 @@ func (pR *Room) refreshColliders() {
|
||||
spaceOffsetX := float64(spaceW) * 0.5
|
||||
spaceOffsetY := float64(spaceH) * 0.5
|
||||
|
||||
minStep := int(3) // the approx minimum distance a player can move per frame
|
||||
minStep := int(3) // the approx minimum distance a player can move per frame
|
||||
space := resolv.NewSpace(int(spaceW), int(spaceH), minStep, minStep) // allocate a new collision space everytime after a battle is settled
|
||||
for _, player := range pR.Players {
|
||||
playerCollider := GenerateRectCollider(player.X, player.Y, playerColliderRadius*2, playerColliderRadius*2, spaceOffsetX, spaceOffsetY, "Player")
|
||||
|
@ -85,7 +85,7 @@ type Game struct {
|
||||
|
||||
func NewGame() *Game {
|
||||
|
||||
stageName := "simple" // Use this for calibration
|
||||
stageName := "simple" // Use this for calibration
|
||||
// stageName := "richsoil"
|
||||
stageDiscreteW, stageDiscreteH, stageTileW, stageTileH, playerPosMap, barrierMap, err := parseStage(stageName)
|
||||
if nil != err {
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
. "dnmshared"
|
||||
"fmt"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/kvartborg/vector"
|
||||
"github.com/solarlune/resolv"
|
||||
"go.uber.org/zap"
|
||||
"image/color"
|
||||
@ -34,67 +33,46 @@ func NewWorldColliderDisplay(game *Game, stageDiscreteW, stageDiscreteH, stageTi
|
||||
spaceOffsetY := float64(spaceH) * 0.5
|
||||
|
||||
playerColliderRadius := float64(32)
|
||||
playerColliders := make([]*resolv.Object, len(playerList))
|
||||
playerColliders := make([]*resolv.Object, len(playerList))
|
||||
space := resolv.NewSpace(int(spaceW), int(spaceH), 16, 16)
|
||||
for i, player := range playerList {
|
||||
playerCollider := GenerateRectCollider(player.X, player.Y, playerColliderRadius*2, playerColliderRadius*2, spaceOffsetX, spaceOffsetY, "Player") // [WARNING] Deliberately not using a circle because "resolv v0.5.1" doesn't yet align circle center with space cell center, regardless of the "specified within-object offset"
|
||||
Logger.Info(fmt.Sprintf("Player Collider#%d: player.X=%v, player.Y=%v, radius=%v, spaceOffsetX=%v, spaceOffsetY=%v, shape=%v; calibrationCheckX=player.X-radius+spaceOffsetX=%v", i, player.X, player.Y, playerColliderRadius, spaceOffsetX, spaceOffsetY, playerCollider.Shape, player.X-playerColliderRadius+spaceOffsetX))
|
||||
playerColliders[i] = playerCollider
|
||||
Logger.Info(fmt.Sprintf("Player Collider#%d: player.X=%v, player.Y=%v, radius=%v, spaceOffsetX=%v, spaceOffsetY=%v, shape=%v; calibrationCheckX=player.X-radius+spaceOffsetX=%v", i, player.X, player.Y, playerColliderRadius, spaceOffsetX, spaceOffsetY, playerCollider.Shape, player.X-playerColliderRadius+spaceOffsetX))
|
||||
playerColliders[i] = playerCollider
|
||||
space.Add(playerCollider)
|
||||
}
|
||||
|
||||
barrierLocalId := 0
|
||||
for _, barrierUnaligned := range barrierList {
|
||||
barrierCollider := GenerateConvexPolygonCollider(barrierUnaligned, spaceOffsetX, spaceOffsetY, "Barrier")
|
||||
Logger.Info(fmt.Sprintf("Added barrier: shape=%v", barrierCollider.Shape))
|
||||
Logger.Info(fmt.Sprintf("Added barrier: shape=%v", barrierCollider.Shape))
|
||||
space.Add(barrierCollider)
|
||||
barrierLocalId++
|
||||
}
|
||||
|
||||
world.Space = space
|
||||
|
||||
moveToCollide := true
|
||||
if moveToCollide {
|
||||
toTestPlayerCollider := playerColliders[0]
|
||||
oldDx, oldDy := 135.0, 135.0
|
||||
dx, dy := oldDx, oldDy
|
||||
if collision := toTestPlayerCollider.Check(oldDx, oldDy, "Barrier"); collision != nil {
|
||||
playerShape := toTestPlayerCollider.Shape.(*resolv.ConvexPolygon)
|
||||
barrierShape := collision.Objects[0].Shape.(*resolv.ConvexPolygon)
|
||||
origX, origY := playerShape.Position()
|
||||
playerShape.SetPosition(origX+oldDx, origY+oldDy)
|
||||
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("Not collided: playerShape=%v, oldDx=%v, oldDy=%v, toCheckBarrier=%v", playerShape, oldDx, oldDy, barrierShape))
|
||||
}
|
||||
moveToCollide := true
|
||||
if moveToCollide {
|
||||
toTestPlayerCollider := playerColliders[0]
|
||||
oldDx, oldDy := -2.98, -50.0
|
||||
dx, dy := oldDx, oldDy
|
||||
if collision := toTestPlayerCollider.Check(oldDx, oldDy, "Barrier"); collision != nil {
|
||||
playerShape := toTestPlayerCollider.Shape.(*resolv.ConvexPolygon)
|
||||
barrierShape := collision.Objects[0].Shape.(*resolv.ConvexPolygon)
|
||||
if overlapped, pushbackX, pushbackY := CalcPushbacks(oldDx, oldDy, playerShape, barrierShape); overlapped {
|
||||
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))
|
||||
dx -= pushbackX
|
||||
dy -= pushbackY
|
||||
} else {
|
||||
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)))
|
||||
}
|
||||
}
|
||||
|
||||
playerShape.SetPosition(origX, origY)
|
||||
|
||||
toTestPlayerCollider.X += dx
|
||||
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.X += dx
|
||||
toTestPlayerCollider.Y += dy
|
||||
toTestPlayerCollider.Update()
|
||||
}
|
||||
|
||||
return world
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ type Vec2D struct {
|
||||
}
|
||||
|
||||
func NormVec2D(dx, dy float64) Vec2D {
|
||||
return Vec2D{dy, -dx}
|
||||
return Vec2D{dy, -dx}
|
||||
}
|
||||
|
||||
type Polygon2D struct {
|
||||
|
@ -1,11 +1,22 @@
|
||||
package dnmshared
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kvartborg/vector"
|
||||
"github.com/solarlune/resolv"
|
||||
"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 {
|
||||
collider := resolv.NewObject(origX-w*0.5+spaceOffsetX, origY-h*0.5+spaceOffsetY, w, h, tag)
|
||||
shape := resolv.NewRectangle(0, 0, w, h)
|
||||
@ -43,16 +54,38 @@ func GenerateConvexPolygonCollider(unalignedSrc *Polygon2D, spaceOffsetX, spaceO
|
||||
return collider
|
||||
}
|
||||
|
||||
type SatResult struct {
|
||||
Overlap float64
|
||||
OverlapX float64
|
||||
OverlapY float64
|
||||
AContainedInB bool
|
||||
BContainedInA bool
|
||||
Axis vector.Vector
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
func IsPolygonPairColliding(a, b *resolv.ConvexPolygon, result *SatResult) bool {
|
||||
type SatResult struct {
|
||||
Overlap float64
|
||||
OverlapX float64
|
||||
OverlapY float64
|
||||
AContainedInB bool
|
||||
BContainedInA bool
|
||||
Axis vector.Vector
|
||||
}
|
||||
|
||||
func IsPolygonPairOverlapped(a, b *resolv.ConvexPolygon, result *SatResult) bool {
|
||||
aCnt, bCnt := len(a.Points), len(b.Points)
|
||||
// Single point case
|
||||
if 1 == aCnt && 1 == bCnt {
|
||||
@ -64,7 +97,7 @@ func IsPolygonPairColliding(a, b *resolv.ConvexPolygon, result *SatResult) bool
|
||||
|
||||
if 1 < aCnt {
|
||||
for _, axis := range a.SATAxes() {
|
||||
if IsPolygonPairSeparatedByDir(a, b, axis.Unit(), result) {
|
||||
if isPolygonPairSeparatedByDir(a, b, axis.Unit(), result) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -72,7 +105,7 @@ func IsPolygonPairColliding(a, b *resolv.ConvexPolygon, result *SatResult) bool
|
||||
|
||||
if 1 < bCnt {
|
||||
for _, axis := range b.SATAxes() {
|
||||
if IsPolygonPairSeparatedByDir(a, b, axis.Unit(), result) {
|
||||
if isPolygonPairSeparatedByDir(a, b, axis.Unit(), result) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -81,10 +114,26 @@ func IsPolygonPairColliding(a, b *resolv.ConvexPolygon, result *SatResult) bool
|
||||
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
|
||||
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 {
|
||||
aStart = dot
|
||||
@ -96,7 +145,7 @@ func IsPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e vector.Vector, re
|
||||
}
|
||||
|
||||
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 {
|
||||
bStart = dot
|
||||
@ -113,7 +162,7 @@ func IsPolygonPairSeparatedByDir(a, b *resolv.ConvexPolygon, e vector.Vector, re
|
||||
}
|
||||
|
||||
if nil != result {
|
||||
result.Axis = e
|
||||
result.Axis = e
|
||||
overlap := float64(0)
|
||||
|
||||
if aStart < bStart {
|
||||
|
39
frontend/assets/scripts/collision_test_nodejs.js
Normal file
39
frontend/assets/scripts/collision_test_nodejs.js
Normal 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);
|
||||
}
|
9
frontend/assets/scripts/collision_test_nodejs.js.meta
Normal file
9
frontend/assets/scripts/collision_test_nodejs.js.meta
Normal 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
Loading…
Reference in New Issue
Block a user