mirror of
https://github.com/genxium/DelayNoMore
synced 2024-12-26 03:39:00 +00:00
297 lines
9.6 KiB
Go
297 lines
9.6 KiB
Go
package dnmshared
|
|
|
|
import (
|
|
. "dnmshared/sharedprotos"
|
|
"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("[%.2f, %.2f]", p[0]+body.X, p[1]+body.Y)
|
|
}
|
|
|
|
return fmt.Sprintf("{\n%s\n}", strings.Join(s, ",\n"))
|
|
}
|
|
|
|
func GenerateRectCollider(origX, origY, w, h, spaceOffsetX, spaceOffsetY float64, tag string) *resolv.Object {
|
|
cx, cy := WorldToPolygonColliderAnchorPos(origX, origY, w*0.5, h*0.5, spaceOffsetX, spaceOffsetY)
|
|
return GenerateRectColliderInCollisionSpace(cx, cy, w, h, tag)
|
|
}
|
|
|
|
func GenerateRectColliderInCollisionSpace(cx, cy, w, h float64, tag string) *resolv.Object {
|
|
collider := resolv.NewObject(cx, cy, 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
|
|
|
|
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)
|
|
}
|
|
|
|
collider := resolv.NewObject(aligned.Anchor.X+spaceOffsetX, aligned.Anchor.Y+spaceOffsetY, w, h, tag)
|
|
collider.SetShape(shape)
|
|
|
|
return collider
|
|
}
|
|
|
|
func CalcPushbacksWithGravitySnapping(oldDx, oldDy float64, playerShape, barrierShape *resolv.ConvexPolygon, currentInAir bool, snapIntoPlatformOverlap, snapIntoPlatformThreshold float64) (bool, float64, float64, *SatResult, float64, float64, bool) {
|
|
origX, origY := playerShape.Position()
|
|
snappedIntoPlatformEx, snappedIntoPlatformEy := float64(0), float64(0)
|
|
localFallStopping := false
|
|
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
|
|
normAlignmentWithGravity := (overlapResult.OverlapX * 0 + overlapResult.OverlapX * (-1.0))
|
|
flatEnough := (snapIntoPlatformThreshold < normAlignmentWithGravity) // prevents false snapping on the lateral sides
|
|
remainsNotInAir := (!currentInAir && flatEnough)
|
|
localFallStopping = (currentInAir && flatEnough)
|
|
if remainsNotInAir || localFallStopping {
|
|
// [OverlayX, OverlapY] is the unit vector that points into the platform; FIXME: Should only assign to [snappedIntoPlatformEx, snappedIntoPlatformEy] at most once!
|
|
snappedIntoPlatformEx, snappedIntoPlatformEy = -overlapResult.OverlapY, overlapResult.OverlapX
|
|
pushbackX, pushbackY = (overlapResult.Overlap - snapIntoPlatformOverlap) * overlapResult.OverlapX, (overlapResult.Overlap - snapIntoPlatformOverlap) * overlapResult.OverlapY
|
|
if (snappedIntoPlatformEx * currPlayerDownsync.dirX + snappedIntoPlatformEy * currPlayerDownsync.dirY < 0) {
|
|
// snapped dir should have a positive projection from player facing dir
|
|
snappedIntoPlatformEx, snappedIntoPlatformEy = -snappedIntoPlatformEx, -snappedIntoPlatformEy
|
|
}
|
|
}
|
|
return true, pushbackX, pushbackY, overlapResult, snappedIntoPlatformEx, snappedIntoPlatformEy, localFallStopping
|
|
} else {
|
|
return false, 0, 0, overlapResult, 0, 0, false
|
|
}
|
|
}
|
|
|
|
func CalcPushbacks(oldDx, oldDy float64, playerShape, barrierShape *resolv.ConvexPolygon) (bool, float64, float64, *SatResult) {
|
|
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, overlapResult
|
|
} else {
|
|
return false, 0, 0, overlapResult
|
|
}
|
|
}
|
|
|
|
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 {
|
|
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 {
|
|
/*
|
|
[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()+a.X)*e.X() + (p.Y()+a.Y)*e.Y()
|
|
|
|
if aStart > dot {
|
|
aStart = dot
|
|
}
|
|
|
|
if aEnd < dot {
|
|
aEnd = dot
|
|
}
|
|
}
|
|
|
|
for _, p := range b.Points {
|
|
dot := (p.X()+b.X)*e.X() + (p.Y()+b.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
|
|
}
|
|
|
|
func WorldToVirtualGridPos(wx, wy, worldToVirtualGridRatio float64) (int32, int32) {
|
|
// [WARNING] Introduces loss of precision!
|
|
// In JavaScript floating numbers suffer from seemingly non-deterministic arithmetics, and even if certain libs solved this issue by approaches such as fixed-point-number, they might not be used in other libs -- e.g. the "collision libs" we're interested in -- thus couldn't kill all pains.
|
|
var virtualGridX int32 = int32(math.Round(wx * worldToVirtualGridRatio))
|
|
var virtualGridY int32 = int32(math.Round(wy * worldToVirtualGridRatio))
|
|
return virtualGridX, virtualGridY
|
|
}
|
|
|
|
func VirtualGridToWorldPos(vx, vy int32, virtualGridToWorldRatio float64) (float64, float64) {
|
|
// No loss of precision
|
|
var wx float64 = float64(vx) * virtualGridToWorldRatio
|
|
var wy float64 = float64(vy) * virtualGridToWorldRatio
|
|
return wx, wy
|
|
}
|
|
|
|
func WorldToPolygonColliderAnchorPos(wx, wy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (float64, float64) {
|
|
return wx - halfBoundingW + collisionSpaceOffsetX, wy - halfBoundingH + collisionSpaceOffsetY
|
|
}
|
|
|
|
func PolygonColliderAnchorToWorldPos(cx, cy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64) (float64, float64) {
|
|
return cx + halfBoundingW - collisionSpaceOffsetX, cy + halfBoundingH - collisionSpaceOffsetY
|
|
}
|
|
|
|
func PolygonColliderAnchorToVirtualGridPos(cx, cy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64, worldToVirtualGridRatio float64) (int32, int32) {
|
|
wx, wy := PolygonColliderAnchorToWorldPos(cx, cy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY)
|
|
return WorldToVirtualGridPos(wx, wy, worldToVirtualGridRatio)
|
|
}
|
|
|
|
func VirtualGridToPolygonColliderAnchorPos(vx, vy int32, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY float64, virtualGridToWorldRatio float64) (float64, float64) {
|
|
wx, wy := VirtualGridToWorldPos(vx, vy, virtualGridToWorldRatio)
|
|
return WorldToPolygonColliderAnchorPos(wx, wy, halfBoundingW, halfBoundingH, collisionSpaceOffsetX, collisionSpaceOffsetY)
|
|
}
|