2022-12-25 06:18:48 +00:00
package battle
2022-12-23 14:31:55 +00:00
import (
2022-12-25 06:18:48 +00:00
"github.com/kvartborg/vector"
2022-12-23 14:31:55 +00:00
"github.com/solarlune/resolv"
2022-12-25 06:18:48 +00:00
"math"
2022-12-23 14:31:55 +00:00
)
const (
2022-12-25 06:18:48 +00:00
MAX_FLOAT64 = 1.7e+308
2022-12-23 14:31:55 +00:00
COLLISION_PLAYER_INDEX_PREFIX = ( 1 << 17 )
COLLISION_BARRIER_INDEX_PREFIX = ( 1 << 16 )
COLLISION_BULLET_INDEX_PREFIX = ( 1 << 15 )
)
// These directions are chosen such that when speed is changed to "(speedX+delta, speedY+delta)" for any of them, the direction is unchanged.
var DIRECTION_DECODER = [ ] [ ] int32 {
{ 0 , 0 } ,
{ 0 , + 2 } ,
{ 0 , - 2 } ,
{ + 2 , 0 } ,
{ - 2 , 0 } ,
{ + 1 , + 1 } ,
{ - 1 , - 1 } ,
{ + 1 , - 1 } ,
{ - 1 , + 1 } ,
}
const (
ATK_CHARACTER_STATE_IDLE1 = int32 ( 0 )
ATK_CHARACTER_STATE_WALKING = int32 ( 1 )
ATK_CHARACTER_STATE_ATK1 = int32 ( 2 )
ATK_CHARACTER_STATE_ATKED1 = int32 ( 3 )
ATK_CHARACTER_STATE_INAIR_IDLE1 = int32 ( 4 )
ATK_CHARACTER_STATE_INAIR_ATK1 = int32 ( 5 )
ATK_CHARACTER_STATE_INAIR_ATKED1 = int32 ( 6 )
)
func ConvertToInputFrameId ( renderFrameId int32 , inputDelayFrames int32 , inputScaleFrames int32 ) int32 {
if renderFrameId < inputDelayFrames {
return 0
}
return ( ( renderFrameId - inputDelayFrames ) >> inputScaleFrames )
}
func DecodeInput ( encodedInput uint64 ) * InputFrameDecoded {
encodedDirection := ( encodedInput & uint64 ( 15 ) )
btnALevel := int32 ( ( encodedInput >> 4 ) & 1 )
btnBLevel := int32 ( ( encodedInput >> 5 ) & 1 )
return & InputFrameDecoded {
Dx : DIRECTION_DECODER [ encodedDirection ] [ 0 ] ,
Dy : DIRECTION_DECODER [ encodedDirection ] [ 1 ] ,
BtnALevel : btnALevel ,
BtnBLevel : btnBLevel ,
}
}
2022-12-25 06:18:48 +00:00
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 , * 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
}
}
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 ] [ 0 ] == b . Points [ 0 ] [ 0 ] && a . Points [ 0 ] [ 1 ] == b . Points [ 0 ] [ 1 ]
}
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 = MAX_FLOAT64 , - MAX_FLOAT64 , MAX_FLOAT64 , - MAX_FLOAT64
for _ , p := range a . Points {
dot := ( p [ 0 ] + a . X ) * e [ 0 ] + ( p [ 1 ] + a . Y ) * e [ 1 ]
if aStart > dot {
aStart = dot
}
if aEnd < dot {
aEnd = dot
}
}
for _ , p := range b . Points {
dot := ( p [ 0 ] + b . X ) * e [ 0 ] + ( p [ 1 ] + b . Y ) * e [ 1 ]
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 {
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 == result . Axis [ 0 ] && 0 == result . Axis [ 1 ] ) || currentOverlap > absoluteOverlap {
var sign float64 = 1
if overlap < 0 {
sign = - 1
}
result . Overlap = absoluteOverlap
result . OverlapX = e [ 0 ] * sign
result . OverlapY = e [ 1 ] * sign
}
result . Axis = e
}
// 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 WorldToPolygonColliderBLPos ( wx , wy , halfBoundingW , halfBoundingH , topPadding , bottomPadding , leftPadding , rightPadding , collisionSpaceOffsetX , collisionSpaceOffsetY float64 ) ( float64 , float64 ) {
return wx - halfBoundingW - leftPadding + collisionSpaceOffsetX , wy - halfBoundingH - bottomPadding + collisionSpaceOffsetY
}
func PolygonColliderBLToWorldPos ( cx , cy , halfBoundingW , halfBoundingH , topPadding , bottomPadding , leftPadding , rightPadding , collisionSpaceOffsetX , collisionSpaceOffsetY float64 ) ( float64 , float64 ) {
return cx + halfBoundingW + leftPadding - collisionSpaceOffsetX , cy + halfBoundingH + bottomPadding - collisionSpaceOffsetY
}
func PolygonColliderBLToVirtualGridPos ( cx , cy , halfBoundingW , halfBoundingH , topPadding , bottomPadding , leftPadding , rightPadding , collisionSpaceOffsetX , collisionSpaceOffsetY float64 , worldToVirtualGridRatio float64 ) ( int32 , int32 ) {
wx , wy := PolygonColliderBLToWorldPos ( cx , cy , halfBoundingW , halfBoundingH , topPadding , bottomPadding , leftPadding , rightPadding , collisionSpaceOffsetX , collisionSpaceOffsetY )
return WorldToVirtualGridPos ( wx , wy , worldToVirtualGridRatio )
}
func VirtualGridToPolygonColliderBLPos ( vx , vy int32 , halfBoundingW , halfBoundingH , topPadding , bottomPadding , leftPadding , rightPadding , collisionSpaceOffsetX , collisionSpaceOffsetY float64 , virtualGridToWorldRatio float64 ) ( float64 , float64 ) {
wx , wy := VirtualGridToWorldPos ( vx , vy , virtualGridToWorldRatio )
return WorldToPolygonColliderBLPos ( wx , wy , halfBoundingW , halfBoundingH , topPadding , bottomPadding , leftPadding , rightPadding , collisionSpaceOffsetX , collisionSpaceOffsetY )
}
2022-12-23 14:31:55 +00:00
func CalcHardPushbacksNorms ( playerCollider * resolv . Object , playerShape * resolv . ConvexPolygon , snapIntoPlatformOverlap float64 , pEffPushback * Vec2D ) [ ] Vec2D {
ret := make ( [ ] Vec2D , 0 , 10 ) // no one would simultaneously have more than 5 hardPushbacks
collision := playerCollider . Check ( 0 , 0 )
if nil == collision {
return ret
}
for _ , obj := range collision . Objects {
switch obj . Data . ( type ) {
case * Barrier :
barrierShape := obj . Shape . ( * resolv . ConvexPolygon )
overlapped , pushbackX , pushbackY , overlapResult := CalcPushbacks ( 0 , 0 , playerShape , barrierShape )
if ! overlapped {
continue
}
// ALWAY snap into hardPushbacks!
// [OverlapX, OverlapY] is the unit vector that points into the platform
pushbackX , pushbackY = ( overlapResult . Overlap - snapIntoPlatformOverlap ) * overlapResult . OverlapX , ( overlapResult . Overlap - snapIntoPlatformOverlap ) * overlapResult . OverlapY
ret = append ( ret , Vec2D { X : overlapResult . OverlapX , Y : overlapResult . OverlapY } )
pEffPushback . X += pushbackX
pEffPushback . Y += pushbackY
default :
}
}
return ret
}
2022-12-24 05:57:32 +00:00
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame ( delayedInputFrame , delayedInputFrameForPrevRenderFrame * InputFrameDownsync , currRenderFrame * RoomDownsyncFrame , collisionSys * resolv . Space , collisionSysMap map [ int32 ] * resolv . Object , gravityX , gravityY , jumpingInitVelY , inputDelayFrames , inputScaleFrames int32 , collisionSpaceOffsetX , collisionSpaceOffsetY , snapIntoPlatformOverlap , snapIntoPlatformThreshold , worldToVirtualGridRatio , virtualGridToWorldRatio float64 ) * RoomDownsyncFrame {
2022-12-23 14:31:55 +00:00
// [WARNING] This function MUST BE called while "InputsBufferLock" is locked!
2022-12-24 05:57:32 +00:00
roomCapacity := len ( currRenderFrame . PlayersArr )
2022-12-23 14:31:55 +00:00
nextRenderFramePlayers := make ( [ ] * PlayerDownsync , roomCapacity )
// Make a copy first
for i , currPlayerDownsync := range currRenderFrame . PlayersArr {
nextRenderFramePlayers [ i ] = & PlayerDownsync {
Id : currPlayerDownsync . Id ,
VirtualGridX : currPlayerDownsync . VirtualGridX ,
VirtualGridY : currPlayerDownsync . VirtualGridY ,
DirX : currPlayerDownsync . DirX ,
DirY : currPlayerDownsync . DirY ,
VelX : currPlayerDownsync . VelX ,
VelY : currPlayerDownsync . VelY ,
CharacterState : currPlayerDownsync . CharacterState ,
InAir : true ,
Speed : currPlayerDownsync . Speed ,
BattleState : currPlayerDownsync . BattleState ,
Score : currPlayerDownsync . Score ,
Removed : currPlayerDownsync . Removed ,
JoinIndex : currPlayerDownsync . JoinIndex ,
FramesToRecover : currPlayerDownsync . FramesToRecover - 1 ,
Hp : currPlayerDownsync . Hp ,
MaxHp : currPlayerDownsync . MaxHp ,
}
if nextRenderFramePlayers [ i ] . FramesToRecover < 0 {
nextRenderFramePlayers [ i ] . FramesToRecover = 0
}
}
effPushbacks := make ( [ ] Vec2D , roomCapacity )
hardPushbackNorms := make ( [ ] [ ] Vec2D , roomCapacity )
// 1. Process player inputs
if nil != delayedInputFrame {
inputList := delayedInputFrame . InputList
for i , currPlayerDownsync := range currRenderFrame . PlayersArr {
joinIndex := currPlayerDownsync . JoinIndex
thatPlayerInNextFrame := nextRenderFramePlayers [ i ]
if 0 < thatPlayerInNextFrame . FramesToRecover {
continue
}
decodedInput := DecodeInput ( inputList [ joinIndex - 1 ] )
prevBtnBLevel := int32 ( 0 )
if nil != delayedInputFrameForPrevRenderFrame {
prevDecodedInput := DecodeInput ( delayedInputFrameForPrevRenderFrame . InputList [ joinIndex - 1 ] )
prevBtnBLevel = prevDecodedInput . BtnBLevel
}
if decodedInput . BtnBLevel > prevBtnBLevel {
characStateAlreadyInAir := false
if ATK_CHARACTER_STATE_INAIR_IDLE1 == thatPlayerInNextFrame . CharacterState || ATK_CHARACTER_STATE_INAIR_ATK1 == thatPlayerInNextFrame . CharacterState || ATK_CHARACTER_STATE_INAIR_ATKED1 == thatPlayerInNextFrame . CharacterState {
characStateAlreadyInAir = true
}
characStateIsInterruptWaivable := false
if ATK_CHARACTER_STATE_IDLE1 == thatPlayerInNextFrame . CharacterState || ATK_CHARACTER_STATE_WALKING == thatPlayerInNextFrame . CharacterState || ATK_CHARACTER_STATE_INAIR_IDLE1 == thatPlayerInNextFrame . CharacterState {
characStateIsInterruptWaivable = true
}
if ! characStateAlreadyInAir && characStateIsInterruptWaivable {
thatPlayerInNextFrame . VelY = jumpingInitVelY
}
}
2022-12-24 05:57:32 +00:00
// Note that by now "0 == thatPlayerInNextFrame.FramesToRecover", we should change "CharacterState" to "WALKING" or "IDLE" depending on player inputs
if 0 != decodedInput . Dx || 0 != decodedInput . Dy {
thatPlayerInNextFrame . DirX = decodedInput . Dx
thatPlayerInNextFrame . DirY = decodedInput . Dy
thatPlayerInNextFrame . VelX = decodedInput . Dx * currPlayerDownsync . Speed
thatPlayerInNextFrame . CharacterState = ATK_CHARACTER_STATE_WALKING
} else {
thatPlayerInNextFrame . CharacterState = ATK_CHARACTER_STATE_IDLE1
thatPlayerInNextFrame . VelX = 0
}
2022-12-23 14:31:55 +00:00
}
}
// 2. Process player movement
for i , currPlayerDownsync := range currRenderFrame . PlayersArr {
joinIndex := currPlayerDownsync . JoinIndex
effPushbacks [ joinIndex - 1 ] . X , effPushbacks [ joinIndex - 1 ] . Y = float64 ( 0 ) , float64 ( 0 )
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
playerCollider := collisionSysMap [ collisionPlayerIndex ]
thatPlayerInNextFrame := nextRenderFramePlayers [ i ]
// Reset playerCollider position from the "virtual grid position"
newVx , newVy := currPlayerDownsync . VirtualGridX + currPlayerDownsync . VelX , currPlayerDownsync . VirtualGridY + currPlayerDownsync . VelY
if thatPlayerInNextFrame . VelY == jumpingInitVelY {
newVy += thatPlayerInNextFrame . VelY
}
2022-12-25 06:18:48 +00:00
playerCollider . X , playerCollider . Y = VirtualGridToPolygonColliderBLPos ( newVx , newVy , playerCollider . W * 0.5 , playerCollider . H * 0.5 , 0 , 0 , 0 , 0 , collisionSpaceOffsetX , collisionSpaceOffsetY , virtualGridToWorldRatio )
2022-12-23 14:31:55 +00:00
// Update in the collision system
playerCollider . Update ( )
if currPlayerDownsync . InAir {
thatPlayerInNextFrame . VelX += gravityX
thatPlayerInNextFrame . VelY += gravityY
}
}
2022-12-24 05:57:32 +00:00
// 3. Calc pushbacks for each player (after its movement) w/o bullets
2022-12-23 14:31:55 +00:00
for i , currPlayerDownsync := range currRenderFrame . PlayersArr {
joinIndex := currPlayerDownsync . JoinIndex
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
playerCollider := collisionSysMap [ collisionPlayerIndex ]
playerShape := playerCollider . Shape . ( * resolv . ConvexPolygon )
hardPushbackNorms [ joinIndex - 1 ] = CalcHardPushbacksNorms ( playerCollider , playerShape , snapIntoPlatformOverlap , & ( effPushbacks [ joinIndex - 1 ] ) )
thatPlayerInNextFrame := nextRenderFramePlayers [ i ]
fallStopping := false
if collision := playerCollider . Check ( 0 , 0 ) ; nil != collision {
for _ , obj := range collision . Objects {
isBarrier , isAnotherPlayer , isBullet := false , false , false
2022-12-24 05:57:32 +00:00
// TODO: Make this part work in JavaScript without having to expose all types Barrier/PlayerDownsync/MeleeBullet by js.MakeWrapper.
2022-12-23 14:31:55 +00:00
switch obj . Data . ( type ) {
case * Barrier :
isBarrier = true
case * PlayerDownsync :
isAnotherPlayer = true
case * MeleeBullet :
isBullet = true
}
if isBullet {
// ignore bullets for this step
continue
}
bShape := obj . Shape . ( * resolv . ConvexPolygon )
overlapped , pushbackX , pushbackY , overlapResult := CalcPushbacks ( 0 , 0 , playerShape , bShape )
if ! overlapped {
continue
}
normAlignmentWithGravity := ( overlapResult . OverlapX * float64 ( 0 ) + overlapResult . OverlapY * float64 ( - 1.0 ) )
landedOnGravityPushback := ( snapIntoPlatformThreshold < normAlignmentWithGravity ) // prevents false snapping on the lateral sides
if landedOnGravityPushback {
// kindly note that one player might land on top of another player, and snapping is also required in such case
pushbackX , pushbackY = ( overlapResult . Overlap - snapIntoPlatformOverlap ) * overlapResult . OverlapX , ( overlapResult . Overlap - snapIntoPlatformOverlap ) * overlapResult . OverlapY
thatPlayerInNextFrame . InAir = false
}
if isAnotherPlayer {
2022-12-24 05:57:32 +00:00
// [WARNING] The "zero overlap collision" might be randomly detected/missed on either frontend or backend, to have deterministic result we added paddings to all sides of a playerCollider. As each velocity component of (velX, velY) being a multiple of 0.5 at any renderFrame, each position component of (x, y) can only be a multiple of 0.5 too, thus whenever a 1-dimensional collision happens between players from [player#1: i*0.5, player#2: j*0.5, not collided yet] to [player#1: (i+k)*0.5, player#2: j*0.5, collided], the overlap becomes (i+k-j)*0.5+2*s, and after snapping subtraction the effPushback magnitude for each player is (i+k-j)*0.5, resulting in 0.5-multiples-position for the next renderFrame.
2022-12-23 14:31:55 +00:00
pushbackX , pushbackY = ( overlapResult . Overlap - snapIntoPlatformOverlap * 2 ) * overlapResult . OverlapX , ( overlapResult . Overlap - snapIntoPlatformOverlap * 2 ) * overlapResult . OverlapY
}
for _ , hardPushbackNorm := range hardPushbackNorms [ joinIndex - 1 ] {
projectedMagnitude := pushbackX * hardPushbackNorm . X + pushbackY * hardPushbackNorm . Y
if isBarrier || ( isAnotherPlayer && 0 > projectedMagnitude ) {
pushbackX -= projectedMagnitude * hardPushbackNorm . X
pushbackY -= projectedMagnitude * hardPushbackNorm . Y
}
}
effPushbacks [ joinIndex - 1 ] . X += pushbackX
effPushbacks [ joinIndex - 1 ] . Y += pushbackY
if currPlayerDownsync . InAir && landedOnGravityPushback {
fallStopping = true
}
}
}
if fallStopping {
thatPlayerInNextFrame . VelX = 0
thatPlayerInNextFrame . VelY = 0
thatPlayerInNextFrame . CharacterState = ATK_CHARACTER_STATE_IDLE1
thatPlayerInNextFrame . FramesToRecover = 0
}
if currPlayerDownsync . InAir {
oldNextCharacterState := thatPlayerInNextFrame . CharacterState
switch oldNextCharacterState {
case ATK_CHARACTER_STATE_IDLE1 , ATK_CHARACTER_STATE_WALKING :
thatPlayerInNextFrame . CharacterState = ATK_CHARACTER_STATE_INAIR_IDLE1
case ATK_CHARACTER_STATE_ATK1 :
thatPlayerInNextFrame . CharacterState = ATK_CHARACTER_STATE_INAIR_ATK1
case ATK_CHARACTER_STATE_ATKED1 :
thatPlayerInNextFrame . CharacterState = ATK_CHARACTER_STATE_INAIR_ATKED1
}
}
}
2022-12-24 05:57:32 +00:00
// 4. Get players out of stuck barriers if there's any
2022-12-23 14:31:55 +00:00
for i , currPlayerDownsync := range currRenderFrame . PlayersArr {
joinIndex := currPlayerDownsync . JoinIndex
collisionPlayerIndex := COLLISION_PLAYER_INDEX_PREFIX + joinIndex
playerCollider := collisionSysMap [ collisionPlayerIndex ]
// Update "virtual grid position"
thatPlayerInNextFrame := nextRenderFramePlayers [ i ]
2022-12-25 06:18:48 +00:00
thatPlayerInNextFrame . VirtualGridX , thatPlayerInNextFrame . VirtualGridY = PolygonColliderBLToVirtualGridPos ( playerCollider . X - effPushbacks [ joinIndex - 1 ] . X , playerCollider . Y - effPushbacks [ joinIndex - 1 ] . Y , playerCollider . W * 0.5 , playerCollider . H * 0.5 , 0 , 0 , 0 , 0 , collisionSpaceOffsetX , collisionSpaceOffsetY , worldToVirtualGridRatio )
2022-12-23 14:31:55 +00:00
}
return & RoomDownsyncFrame {
2022-12-24 05:57:32 +00:00
Id : currRenderFrame . Id + 1 ,
PlayersArr : nextRenderFramePlayers ,
2022-12-23 14:31:55 +00:00
}
}
2022-12-25 06:18:48 +00:00
func GenerateRectCollider ( wx , wy , w , h , topPadding , bottomPadding , leftPadding , rightPadding , spaceOffsetX , spaceOffsetY float64 , data interface { } , tag string ) * resolv . Object {
blX , blY := WorldToPolygonColliderBLPos ( wx , wy , w * 0.5 , h * 0.5 , topPadding , bottomPadding , leftPadding , rightPadding , spaceOffsetX , spaceOffsetY )
return generateRectColliderInCollisionSpace ( blX , blY , leftPadding + w + rightPadding , bottomPadding + h + topPadding , data , tag )
}
func generateRectColliderInCollisionSpace ( blX , blY , w , h float64 , data interface { } , tag string ) * resolv . Object {
collider := resolv . NewObject ( blX , blY , w , h , tag ) // Unlike its frontend counter part, the position of a "resolv.Object" must be specified by "bottom-left point" because "w" and "h" must be positive, see "resolv.Object.BoundsToSpace" for details
shape := resolv . NewRectangle ( 0 , 0 , w , h )
collider . SetShape ( shape )
collider . Data = data
return collider
}
func GenerateConvexPolygonCollider ( unalignedSrc * Polygon2D , spaceOffsetX , spaceOffsetY float64 , data interface { } , 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 )
collider . Data = data
return collider
}
func AlignPolygon2DToBoundingBox ( input * Polygon2D ) * Polygon2D {
// Transform again to put "anchor" at the "bottom-left point (w.r.t. world space)" of the bounding box for "resolv"
boundingBoxBL := & Vec2D {
X : MAX_FLOAT64 ,
Y : MAX_FLOAT64 ,
}
for _ , p := range input . Points {
if p . X < boundingBoxBL . X {
boundingBoxBL . X = p . X
}
if p . Y < boundingBoxBL . Y {
boundingBoxBL . Y = p . Y
}
}
// Now "input.Anchor" should move to "input.Anchor+boundingBoxBL", thus "boundingBoxBL" is also the value of the negative diff for all "input.Points"
output := & Polygon2D {
Anchor : & Vec2D {
X : input . Anchor . X + boundingBoxBL . X ,
Y : input . Anchor . Y + boundingBoxBL . Y ,
} ,
Points : make ( [ ] * Vec2D , len ( input . Points ) ) ,
}
for i , p := range input . Points {
output . Points [ i ] = & Vec2D {
X : p . X - boundingBoxBL . X ,
Y : p . Y - boundingBoxBL . Y ,
}
}
return output
}