diff --git a/battle_srv/models/room.go b/battle_srv/models/room.go index bc26f5f..a8ffca1 100644 --- a/battle_srv/models/room.go +++ b/battle_srv/models/room.go @@ -7,7 +7,6 @@ import ( "github.com/gorilla/websocket" "github.com/solarlune/resolv" "go.uber.org/zap" - "math" "math/rand" . "server/common" "server/common/utils" @@ -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,19 +1150,21 @@ func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRende continue } baseChange := player.Speed * pR.RollbackEstimatedDt * decodedInputSpeedFactor - dx := baseChange * float64(decodedInput[0]) - dy := baseChange * float64(decodedInput[1]) + 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(dx, dy, "Barrier"); collision != nil { - changeWithCollision := collision.ContactWithObject(collision.Objects[0]) - Logger.Info(fmt.Sprintf("Collided: roomId=%v, playerId=%v, orig dx=%v, orig dy=%v, proposed new dx =%v, proposed new dy=%v", pR.Id, player.Id, dx, dy, changeWithCollision.X(), changeWithCollision.Y())) - // FIXME: Use a mechanism equivalent to that of the frontend! - // dx = changeWithCollision.X() - // dy = changeWithCollision.Y() - dx = 0 - dy = 0 + if collision := playerCollider.Check(oldDx, oldDy, "Barrier"); collision != nil { + playerShape := playerCollider.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", 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 @@ -1200,12 +1201,10 @@ 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 := resolv.NewObject(player.X-playerColliderRadius+spaceOffsetX, player.Y-playerColliderRadius+spaceOffsetY, playerColliderRadius*2, playerColliderRadius*2) - playerColliderShape := resolv.NewCircle(+playerColliderRadius, +playerColliderRadius, playerColliderRadius) - playerCollider.SetShape(playerColliderShape) + playerCollider := GenerateRectCollider(player.X, player.Y, playerColliderRadius*2, playerColliderRadius*2, spaceOffsetX, spaceOffsetY, "Player") space.Add(playerCollider) // Keep track of the collider in "pR.CollisionSysMap" joinIndex := player.JoinIndex @@ -1215,31 +1214,8 @@ func (pR *Room) refreshColliders() { } for _, barrier := range pR.Barriers { - - var w float64 = 0 - var h float64 = 0 - - for i, pi := range barrier.Boundary.Points { - for j, pj := range barrier.Boundary.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) - } - } - } - - barrierColliderShape := resolv.NewConvexPolygon() - for _, p := range barrier.Boundary.Points { - barrierColliderShape.AddPoints(p.X, p.Y) - } - - barrierCollider := resolv.NewObject(barrier.Boundary.Anchor.X+spaceOffsetX, barrier.Boundary.Anchor.Y+spaceOffsetY, w, h, "Barrier") - barrierCollider.SetShape(barrierColliderShape) + boundaryUnaligned := barrier.Boundary + barrierCollider := GenerateConvexPolygonCollider(boundaryUnaligned, spaceOffsetX, spaceOffsetY, "Barrier") space.Add(barrierCollider) } } diff --git a/collider_visualizer/main.go b/collider_visualizer/main.go index c57b61f..a098106 100644 --- a/collider_visualizer/main.go +++ b/collider_visualizer/main.go @@ -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 { diff --git a/collider_visualizer/worldColliderDisplay.go b/collider_visualizer/worldColliderDisplay.go index de2879a..21f301a 100644 --- a/collider_visualizer/worldColliderDisplay.go +++ b/collider_visualizer/worldColliderDisplay.go @@ -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,69 +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 := 135.0 - oldDy := 135.0 - dx := oldDx - dy := 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 } diff --git a/dnmshared/geometry.go b/dnmshared/geometry.go index 575d5f8..638d8b6 100644 --- a/dnmshared/geometry.go +++ b/dnmshared/geometry.go @@ -16,7 +16,7 @@ type Vec2D struct { } func NormVec2D(dx, dy float64) Vec2D { - return Vec2D{dy, -dx} + return Vec2D{dy, -dx} } type Polygon2D struct { diff --git a/dnmshared/resolv_helper.go b/dnmshared/resolv_helper.go index a99a9e0..f4b5d28 100644 --- a/dnmshared/resolv_helper.go +++ b/dnmshared/resolv_helper.go @@ -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 { diff --git a/frontend/assets/scenes/default_map.fire b/frontend/assets/scenes/default_map.fire index 58dd111..7d809bc 100644 --- a/frontend/assets/scenes/default_map.fire +++ b/frontend/assets/scenes/default_map.fire @@ -539,7 +539,7 @@ "array": [ 0, 0, - 210.4441731196186, + 342.9460598986377, 0, 0, 0, diff --git a/frontend/assets/scenes/login.fire b/frontend/assets/scenes/login.fire index 31ccba3..4cfb6f3 100644 --- a/frontend/assets/scenes/login.fire +++ b/frontend/assets/scenes/login.fire @@ -440,7 +440,7 @@ "array": [ 0, 0, - 209.73151519075364, + 344.6781107062066, 0, 0, 0, diff --git a/frontend/assets/scripts/Map.js b/frontend/assets/scripts/Map.js index 826d110..7aa45b0 100644 --- a/frontend/assets/scripts/Map.js +++ b/frontend/assets/scripts/Map.js @@ -740,9 +740,13 @@ cc.Class({ newPlayerNode.setPosition(cc.v2(x, y)); newPlayerNode.getComponent("SelfPlayer").mapNode = self.node; const currentSelfColliderCircle = newPlayerNode.getComponent(cc.CircleCollider); + const r = currentSelfColliderCircle.radius, d = 2*r; + // The collision box of an individual player is a polygon instead of a circle, because the backend collision engine doesn't handle circle alignment well. + const x0 = x-r, y0 = y-r; + let pts = [[0, 0], [d, 0], [d, d], [0, d]]; - const newPlayerColliderLatest = self.latestCollisionSys.createCircle(x, y, currentSelfColliderCircle.radius); - const newPlayerColliderChaser = self.chaserCollisionSys.createCircle(x, y, currentSelfColliderCircle.radius); + const newPlayerColliderLatest = self.latestCollisionSys.createPolygon(x0, y0, pts); + const newPlayerColliderChaser = self.chaserCollisionSys.createPolygon(x0, y0, pts); const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; self.latestCollisionSysMap.set(collisionPlayerIndex, newPlayerColliderLatest); self.chaserCollisionSysMap.set(collisionPlayerIndex, newPlayerColliderChaser); @@ -952,10 +956,12 @@ cc.Class({ const joinIndex = playerRichInfo.joinIndex; const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; const playerCollider = collisionSysMap.get(collisionPlayerIndex); + const currentSelfColliderCircle = playerRichInfo.node.getComponent(cc.CircleCollider); + const r = currentSelfColliderCircle.radius; rdf.players[playerRichInfo.id] = { id: playerRichInfo.id, - x: playerCollider.x, - y: playerCollider.y, + x: playerCollider.x + r, // [WARNING] the (x, y) of "playerCollider" is offset to the anchor (i.e. first point of all points) of the polygon shape + y: playerCollider.y + r, dir: self.ctrl.decodeDirection(null == inputFrameAppliedOnPrevRenderFrame ? 0 : inputFrameAppliedOnPrevRenderFrame.inputList[joinIndex - 1]), speed: (null == speedRefRenderFrame ? playerRichInfo.speed : speedRefRenderFrame.players[playerRichInfo.id].speed), joinIndex: joinIndex @@ -1025,8 +1031,11 @@ cc.Class({ const collisionPlayerIndex = self.collisionPlayerIndexPrefix + joinIndex; const playerCollider = collisionSysMap.get(collisionPlayerIndex); const player = latestRdf.players[playerId]; - playerCollider.x = player.x; - playerCollider.y = player.y; + + const currentSelfColliderCircle = playerRichInfo.node.getComponent(cc.CircleCollider); + const r = currentSelfColliderCircle.radius; + playerCollider.x = player.x - r; + playerCollider.y = player.y - r; }); /* diff --git a/frontend/assets/scripts/WsSessionMgr.js b/frontend/assets/scripts/WsSessionMgr.js index a57244b..c5763b4 100644 --- a/frontend/assets/scripts/WsSessionMgr.js +++ b/frontend/assets/scripts/WsSessionMgr.js @@ -189,10 +189,11 @@ window.initPersistentSessionClient = function(onopenCb, expectedRoomId) { const inputFrameIdConsecutive = (resp.inputFrameDownsyncBatch[0].inputFrameId == mapIns.lastAllConfirmedInputFrameId + 1); const renderFrameIdConsecutive = (resp.rdf.id <= mapIns.renderFrameId + mapIns.renderFrameIdLagTolerance); if (inputFrameIdConsecutive && renderFrameIdConsecutive) { - console.log("Got consecutive resync@localRenderFrameId=", mapIns.renderFrameId, ", @lastAllConfirmedRenderFrameId=", mapIns.lastAllConfirmedRenderFrameId, "@lastAllConfirmedInputFrameId=", mapIns.lastAllConfirmedInputFrameId, ", @localRecentInputCache=", mapIns._stringifyRecentInputCache(false), ", the incoming resp=\n", JSON.stringify(resp)); + // console.log("Got consecutive resync@localRenderFrameId=", mapIns.renderFrameId, ", @lastAllConfirmedRenderFrameId=", mapIns.lastAllConfirmedRenderFrameId, "@lastAllConfirmedInputFrameId=", mapIns.lastAllConfirmedInputFrameId, ", @localRecentInputCache=", mapIns._stringifyRecentInputCache(false), ", the incoming resp=\n", JSON.stringify(resp)); mapIns.onInputFrameDownsyncBatch(resp.inputFrameDownsyncBatch); } else { - console.warn("Got forced resync@localRenderFrameId=", mapIns.renderFrameId, ", @lastAllConfirmedRenderFrameId=", mapIns.lastAllConfirmedRenderFrameId, "@lastAllConfirmedInputFrameId=", mapIns.lastAllConfirmedInputFrameId, ", @localRecentInputCache=", mapIns._stringifyRecentInputCache(false), ", the incoming resp=\n", JSON.stringify(resp, null, 2)); + // console.warn("Got forced resync@localRenderFrameId=", mapIns.renderFrameId, ", @lastAllConfirmedRenderFrameId=", mapIns.lastAllConfirmedRenderFrameId, "@lastAllConfirmedInputFrameId=", mapIns.lastAllConfirmedInputFrameId, ", @localRecentInputCache=", mapIns._stringifyRecentInputCache(false), ", the incoming resp=\n", JSON.stringify(resp, null, 2)); + console.warn("Got forced resync@localRenderFrameId=", mapIns.renderFrameId, ", @lastAllConfirmedRenderFrameId=", mapIns.lastAllConfirmedRenderFrameId, "@lastAllConfirmedInputFrameId=", mapIns.lastAllConfirmedInputFrameId, ", @localRecentInputCache=", mapIns._stringifyRecentInputCache(false), ", inputFrameIdConsecutive=", inputFrameIdConsecutive, ", renderFrameIdConsecutive=", renderFrameIdConsecutive); // The following order of execution is important mapIns.onRoomDownsyncFrame(resp.rdf); mapIns.onInputFrameDownsyncBatch(resp.inputFrameDownsyncBatch); diff --git a/frontend/assets/scripts/collision_test_nodejs.js b/frontend/assets/scripts/collision_test_nodejs.js new file mode 100644 index 0000000..43ccb40 --- /dev/null +++ b/frontend/assets/scripts/collision_test_nodejs.js @@ -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); +} diff --git a/frontend/assets/scripts/collision_test_nodejs.js.meta b/frontend/assets/scripts/collision_test_nodejs.js.meta new file mode 100644 index 0000000..a64f7e4 --- /dev/null +++ b/frontend/assets/scripts/collision_test_nodejs.js.meta @@ -0,0 +1,9 @@ +{ + "ver": "1.0.5", + "uuid": "fce86138-76fc-44d5-8eac-2731b3b0cefd", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/frontend/assets/scripts/modules/Collisions.js b/frontend/assets/scripts/modules/Collisions.js index 6805e83..a1e8477 100644 --- a/frontend/assets/scripts/modules/Collisions.js +++ b/frontend/assets/scripts/modules/Collisions.js @@ -94,7 +94,7 @@ /***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _modules_BVH_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./modules/BVH.mjs */ \"./src/modules/BVH.mjs\");\n/* harmony import */ var _modules_Circle_mjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./modules/Circle.mjs */ \"./src/modules/Circle.mjs\");\n/* harmony import */ var _modules_Polygon_mjs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./modules/Polygon.mjs */ \"./src/modules/Polygon.mjs\");\n/* harmony import */ var _modules_Point_mjs__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./modules/Point.mjs */ \"./src/modules/Point.mjs\");\n/* harmony import */ var _modules_Result_mjs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./modules/Result.mjs */ \"./src/modules/Result.mjs\");\n/* harmony import */ var _modules_SAT_mjs__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./modules/SAT.mjs */ \"./src/modules/SAT.mjs\");\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n/**\r\n * A collision system used to track bodies in order to improve collision detection performance\r\n * @class\r\n */\r\nclass Collisions {\r\n\t/**\r\n\t * @constructor\r\n\t */\r\n\tconstructor() {\r\n\t\t/** @private */\r\n\t\tthis._bvh = new _modules_BVH_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"]();\r\n\t}\r\n\r\n\t/**\r\n\t * Creates a {@link Circle} and inserts it into the collision system\r\n\t * @param {Number} [x = 0] The starting X coordinate\r\n\t * @param {Number} [y = 0] The starting Y coordinate\r\n\t * @param {Number} [radius = 0] The radius\r\n\t * @param {Number} [scale = 1] The scale\r\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\r\n\t * @returns {Circle}\r\n\t */\r\n\tcreateCircle(x = 0, y = 0, radius = 0, scale = 1, padding = 0) {\r\n\t\tconst body = new _modules_Circle_mjs__WEBPACK_IMPORTED_MODULE_1__[\"default\"](x, y, radius, scale, padding);\r\n\r\n\t\tthis._bvh.insert(body);\r\n\r\n\t\treturn body;\r\n\t}\r\n\r\n\t/**\r\n\t * Creates a {@link Polygon} and inserts it into the collision system\r\n\t * @param {Number} [x = 0] The starting X coordinate\r\n\t * @param {Number} [y = 0] The starting Y coordinate\r\n\t * @param {Array} [points = []] An array of coordinate pairs making up the polygon - [[x1, y1], [x2, y2], ...]\r\n\t * @param {Number} [angle = 0] The starting rotation in radians\r\n\t * @param {Number} [scale_x = 1] The starting scale along the X axis\r\n\t * @param {Number} [scale_y = 1] The starting scale long the Y axis\r\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\r\n\t * @returns {Polygon}\r\n\t */\r\n\tcreatePolygon(x = 0, y = 0, points = [[0, 0]], angle = 0, scale_x = 1, scale_y = 1, padding = 0) {\r\n\t\tconst body = new _modules_Polygon_mjs__WEBPACK_IMPORTED_MODULE_2__[\"default\"](x, y, points, angle, scale_x, scale_y, padding);\r\n\r\n\t\tthis._bvh.insert(body);\r\n\r\n\t\treturn body;\r\n\t}\r\n\r\n\t/**\r\n\t * Creates a {@link Point} and inserts it into the collision system\r\n\t * @param {Number} [x = 0] The starting X coordinate\r\n\t * @param {Number} [y = 0] The starting Y coordinate\r\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\r\n\t * @returns {Point}\r\n\t */\r\n\tcreatePoint(x = 0, y = 0, padding = 0) {\r\n\t\tconst body = new _modules_Point_mjs__WEBPACK_IMPORTED_MODULE_3__[\"default\"](x, y, padding);\r\n\r\n\t\tthis._bvh.insert(body);\r\n\r\n\t\treturn body;\r\n\t}\r\n\r\n\t/**\r\n\t * Creates a {@link Result} used to collect the detailed results of a collision test\r\n\t */\r\n\tcreateResult() {\r\n\t\treturn new _modules_Result_mjs__WEBPACK_IMPORTED_MODULE_4__[\"default\"]();\r\n\t}\r\n\r\n\t/**\r\n\t * Creates a Result used to collect the detailed results of a collision test\r\n\t */\r\n\tstatic createResult() {\r\n\t\treturn new _modules_Result_mjs__WEBPACK_IMPORTED_MODULE_4__[\"default\"]();\r\n\t}\r\n\r\n\t/**\r\n\t * Inserts bodies into the collision system\r\n\t * @param {...Circle|...Polygon|...Point} bodies\r\n\t */\r\n\tinsert(...bodies) {\r\n\t\tfor(const body of bodies) {\r\n\t\t\tthis._bvh.insert(body, false);\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * Removes bodies from the collision system\r\n\t * @param {...Circle|...Polygon|...Point} bodies\r\n\t */\r\n\tremove(...bodies) {\r\n\t\tfor(const body of bodies) {\r\n\t\t\tthis._bvh.remove(body, false);\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * Updates the collision system. This should be called before any collisions are tested.\r\n\t */\r\n\tupdate() {\r\n\t\tthis._bvh.update();\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * Draws the bodies within the system to a CanvasRenderingContext2D's current path\r\n\t * @param {CanvasRenderingContext2D} context The context to draw to\r\n\t */\r\n\tdraw(context) {\r\n\t\treturn this._bvh.draw(context);\r\n\t}\r\n\r\n\t/**\r\n\t * Draws the system's BVH to a CanvasRenderingContext2D's current path. This is useful for testing out different padding values for bodies.\r\n\t * @param {CanvasRenderingContext2D} context The context to draw to\r\n\t */\r\n\tdrawBVH(context) {\r\n\t\treturn this._bvh.drawBVH(context);\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a list of potential collisions for a body\r\n\t * @param {Circle|Polygon|Point} body The body to test for potential collisions against\r\n\t * @returns {Array}\r\n\t */\r\n\tpotentials(body) {\r\n\t\treturn this._bvh.potentials(body);\r\n\t}\r\n\r\n\t/**\r\n\t * Determines if two bodies are colliding\r\n\t * @param {Circle|Polygon|Point} target The target body to test against\r\n\t * @param {Result} [result = null] A Result object on which to store information about the collision\r\n\t * @param {Boolean} [aabb = true] Set to false to skip the AABB test (useful if you use your own potential collision heuristic)\r\n\t * @returns {Boolean}\r\n\t */\r\n\tcollides(source, target, result = null, aabb = true) {\r\n\t\treturn Object(_modules_SAT_mjs__WEBPACK_IMPORTED_MODULE_5__[\"default\"])(source, target, result, aabb);\r\n\t}\r\n};\r\n\r\nconst toExport = {\r\n\tCollisions,\r\n\tResult: _modules_Result_mjs__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\r\n\tCircle: _modules_Circle_mjs__WEBPACK_IMPORTED_MODULE_1__[\"default\"],\r\n\tPolygon: _modules_Polygon_mjs__WEBPACK_IMPORTED_MODULE_2__[\"default\"],\r\n\tPoint: _modules_Point_mjs__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\r\n};\r\n\r\nmodule.exports = toExport; \r\n\n\n//# sourceURL=webpack:///./src/Collisions.mjs?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _modules_BVH_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./modules/BVH.mjs */ \"./src/modules/BVH.mjs\");\n/* harmony import */ var _modules_Circle_mjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./modules/Circle.mjs */ \"./src/modules/Circle.mjs\");\n/* harmony import */ var _modules_Polygon_mjs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./modules/Polygon.mjs */ \"./src/modules/Polygon.mjs\");\n/* harmony import */ var _modules_Point_mjs__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./modules/Point.mjs */ \"./src/modules/Point.mjs\");\n/* harmony import */ var _modules_Result_mjs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./modules/Result.mjs */ \"./src/modules/Result.mjs\");\n/* harmony import */ var _modules_SAT_mjs__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./modules/SAT.mjs */ \"./src/modules/SAT.mjs\");\n\n\n\n\n\n\n\n/**\n * A collision system used to track bodies in order to improve collision detection performance\n * @class\n */\nclass Collisions {\n\t/**\n\t * @constructor\n\t */\n\tconstructor() {\n\t\t/** @private */\n\t\tthis._bvh = new _modules_BVH_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"]();\n\t}\n\n\t/**\n\t * Creates a {@link Circle} and inserts it into the collision system\n\t * @param {Number} [x = 0] The starting X coordinate\n\t * @param {Number} [y = 0] The starting Y coordinate\n\t * @param {Number} [radius = 0] The radius\n\t * @param {Number} [scale = 1] The scale\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\n\t * @returns {Circle}\n\t */\n\tcreateCircle(x = 0, y = 0, radius = 0, scale = 1, padding = 0) {\n\t\tconst body = new _modules_Circle_mjs__WEBPACK_IMPORTED_MODULE_1__[\"default\"](x, y, radius, scale, padding);\n\n\t\tthis._bvh.insert(body);\n\n\t\treturn body;\n\t}\n\n\t/**\n\t * Creates a {@link Polygon} and inserts it into the collision system\n\t * @param {Number} [x = 0] The starting X coordinate\n\t * @param {Number} [y = 0] The starting Y coordinate\n\t * @param {Array} [points = []] An array of coordinate pairs making up the polygon - [[x1, y1], [x2, y2], ...]\n\t * @param {Number} [angle = 0] The starting rotation in radians\n\t * @param {Number} [scale_x = 1] The starting scale along the X axis\n\t * @param {Number} [scale_y = 1] The starting scale long the Y axis\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\n\t * @returns {Polygon}\n\t */\n\tcreatePolygon(x = 0, y = 0, points = [[0, 0]], angle = 0, scale_x = 1, scale_y = 1, padding = 0) {\n\t\tconst body = new _modules_Polygon_mjs__WEBPACK_IMPORTED_MODULE_2__[\"default\"](x, y, points, angle, scale_x, scale_y, padding);\n\n\t\tthis._bvh.insert(body);\n\n\t\treturn body;\n\t}\n\n\t/**\n\t * Creates a {@link Point} and inserts it into the collision system\n\t * @param {Number} [x = 0] The starting X coordinate\n\t * @param {Number} [y = 0] The starting Y coordinate\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\n\t * @returns {Point}\n\t */\n\tcreatePoint(x = 0, y = 0, padding = 0) {\n\t\tconst body = new _modules_Point_mjs__WEBPACK_IMPORTED_MODULE_3__[\"default\"](x, y, padding);\n\n\t\tthis._bvh.insert(body);\n\n\t\treturn body;\n\t}\n\n\t/**\n\t * Creates a {@link Result} used to collect the detailed results of a collision test\n\t */\n\tcreateResult() {\n\t\treturn new _modules_Result_mjs__WEBPACK_IMPORTED_MODULE_4__[\"default\"]();\n\t}\n\n\t/**\n\t * Creates a Result used to collect the detailed results of a collision test\n\t */\n\tstatic createResult() {\n\t\treturn new _modules_Result_mjs__WEBPACK_IMPORTED_MODULE_4__[\"default\"]();\n\t}\n\n\t/**\n\t * Inserts bodies into the collision system\n\t * @param {...Circle|...Polygon|...Point} bodies\n\t */\n\tinsert(...bodies) {\n\t\tfor(const body of bodies) {\n\t\t\tthis._bvh.insert(body, false);\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Removes bodies from the collision system\n\t * @param {...Circle|...Polygon|...Point} bodies\n\t */\n\tremove(...bodies) {\n\t\tfor(const body of bodies) {\n\t\t\tthis._bvh.remove(body, false);\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Updates the collision system. This should be called before any collisions are tested.\n\t */\n\tupdate() {\n\t\tthis._bvh.update();\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Draws the bodies within the system to a CanvasRenderingContext2D's current path\n\t * @param {CanvasRenderingContext2D} context The context to draw to\n\t */\n\tdraw(context) {\n\t\treturn this._bvh.draw(context);\n\t}\n\n\t/**\n\t * Draws the system's BVH to a CanvasRenderingContext2D's current path. This is useful for testing out different padding values for bodies.\n\t * @param {CanvasRenderingContext2D} context The context to draw to\n\t */\n\tdrawBVH(context) {\n\t\treturn this._bvh.drawBVH(context);\n\t}\n\n\t/**\n\t * Returns a list of potential collisions for a body\n\t * @param {Circle|Polygon|Point} body The body to test for potential collisions against\n\t * @returns {Array}\n\t */\n\tpotentials(body) {\n\t\treturn this._bvh.potentials(body);\n\t}\n\n\t/**\n\t * Determines if two bodies are colliding\n\t * @param {Circle|Polygon|Point} target The target body to test against\n\t * @param {Result} [result = null] A Result object on which to store information about the collision\n\t * @param {Boolean} [aabb = true] Set to false to skip the AABB test (useful if you use your own potential collision heuristic)\n\t * @returns {Boolean}\n\t */\n\tcollides(source, target, result = null, aabb = true) {\n\t\treturn Object(_modules_SAT_mjs__WEBPACK_IMPORTED_MODULE_5__[\"default\"])(source, target, result, aabb);\n\t}\n};\n\nconst toExport = {\n\tCollisions,\n\tResult: _modules_Result_mjs__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\n\tCircle: _modules_Circle_mjs__WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n\tPolygon: _modules_Polygon_mjs__WEBPACK_IMPORTED_MODULE_2__[\"default\"],\n\tPoint: _modules_Point_mjs__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n};\n\nmodule.exports = toExport; \n\n\n//# sourceURL=webpack:///./src/Collisions.mjs?"); /***/ }), @@ -106,7 +106,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _mod /***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return BVH; });\n/* harmony import */ var _BVHBranch_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./BVHBranch.mjs */ \"./src/modules/BVHBranch.mjs\");\n\r\n\r\n/**\r\n * A Bounding Volume Hierarchy (BVH) used to find potential collisions quickly\r\n * @class\r\n * @private\r\n */\r\nclass BVH {\r\n\t/**\r\n\t * @constructor\r\n\t */\r\n\tconstructor() {\r\n\t\t/** @private */\r\n\t\tthis._hierarchy = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bodies = [];\r\n\r\n\t\t/** @private */\r\n\t\tthis._dirty_branches = [];\r\n\t}\r\n\r\n\t/**\r\n\t * Inserts a body into the BVH\r\n\t * @param {Circle|Polygon|Point} body The body to insert\r\n\t * @param {Boolean} [updating = false] Set to true if the body already exists in the BVH (used internally when updating the body's position)\r\n\t */\r\n\tinsert(body, updating = false) {\r\n\t\tif(!updating) {\r\n\t\t\tconst bvh = body._bvh;\r\n\r\n\t\t\tif(bvh && bvh !== this) {\r\n\t\t\t\tthrow new Error('Body belongs to another collision system');\r\n\t\t\t}\r\n\r\n\t\t\tbody._bvh = this;\r\n\t\t\tthis._bodies.push(body);\r\n\t\t}\r\n\r\n\t\tconst polygon = body._polygon;\r\n\t\tconst body_x = body.x;\r\n\t\tconst body_y = body.y;\r\n\r\n\t\tif(polygon) {\r\n\t\t\tif(\r\n\t\t\t\tbody._dirty_coords ||\r\n\t\t\t\tbody.x !== body._x ||\r\n\t\t\t\tbody.y !== body._y ||\r\n\t\t\t\tbody.angle !== body._angle ||\r\n\t\t\t\tbody.scale_x !== body._scale_x ||\r\n\t\t\t\tbody.scale_y !== body._scale_y\r\n\t\t\t) {\r\n\t\t\t\tbody._calculateCoords();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tconst padding = body._bvh_padding;\r\n\t\tconst radius = polygon ? 0 : body.radius * body.scale;\r\n\t\tconst body_min_x = (polygon ? body._min_x : body_x - radius) - padding;\r\n\t\tconst body_min_y = (polygon ? body._min_y : body_y - radius) - padding;\r\n\t\tconst body_max_x = (polygon ? body._max_x : body_x + radius) + padding;\r\n\t\tconst body_max_y = (polygon ? body._max_y : body_y + radius) + padding;\r\n\r\n\t\tbody._bvh_min_x = body_min_x;\r\n\t\tbody._bvh_min_y = body_min_y;\r\n\t\tbody._bvh_max_x = body_max_x;\r\n\t\tbody._bvh_max_y = body_max_y;\r\n\r\n\t\tlet current = this._hierarchy;\r\n\t\tlet sort = 0;\r\n\r\n\t\tif(!current) {\r\n\t\t\tthis._hierarchy = body;\r\n\t\t}\r\n\t\telse {\r\n\t\t\twhile(true) {\r\n\t\t\t\t// Branch\r\n\t\t\t\tif(current._bvh_branch) {\r\n\t\t\t\t\tconst left = current._bvh_left;\r\n\t\t\t\t\tconst left_min_y = left._bvh_min_y;\r\n\t\t\t\t\tconst left_max_x = left._bvh_max_x;\r\n\t\t\t\t\tconst left_max_y = left._bvh_max_y;\r\n\t\t\t\t\tconst left_new_min_x = body_min_x < left._bvh_min_x ? body_min_x : left._bvh_min_x;\r\n\t\t\t\t\tconst left_new_min_y = body_min_y < left_min_y ? body_min_y : left_min_y;\r\n\t\t\t\t\tconst left_new_max_x = body_max_x > left_max_x ? body_max_x : left_max_x;\r\n\t\t\t\t\tconst left_new_max_y = body_max_y > left_max_y ? body_max_y : left_max_y;\r\n\t\t\t\t\tconst left_volume = (left_max_x - left._bvh_min_x) * (left_max_y - left_min_y);\r\n\t\t\t\t\tconst left_new_volume = (left_new_max_x - left_new_min_x) * (left_new_max_y - left_new_min_y);\r\n\t\t\t\t\tconst left_difference = left_new_volume - left_volume;\r\n\r\n\t\t\t\t\tconst right = current._bvh_right;\r\n\t\t\t\t\tconst right_min_x = right._bvh_min_x;\r\n\t\t\t\t\tconst right_min_y = right._bvh_min_y;\r\n\t\t\t\t\tconst right_max_x = right._bvh_max_x;\r\n\t\t\t\t\tconst right_max_y = right._bvh_max_y;\r\n\t\t\t\t\tconst right_new_min_x = body_min_x < right_min_x ? body_min_x : right_min_x;\r\n\t\t\t\t\tconst right_new_min_y = body_min_y < right_min_y ? body_min_y : right_min_y;\r\n\t\t\t\t\tconst right_new_max_x = body_max_x > right_max_x ? body_max_x : right_max_x;\r\n\t\t\t\t\tconst right_new_max_y = body_max_y > right_max_y ? body_max_y : right_max_y;\r\n\t\t\t\t\tconst right_volume = (right_max_x - right_min_x) * (right_max_y - right_min_y);\r\n\t\t\t\t\tconst right_new_volume = (right_new_max_x - right_new_min_x) * (right_new_max_y - right_new_min_y);\r\n\t\t\t\t\tconst right_difference = right_new_volume - right_volume;\r\n\r\n\t\t\t\t\tcurrent._bvh_sort = sort++;\r\n\t\t\t\t\tcurrent._bvh_min_x = left_new_min_x < right_new_min_x ? left_new_min_x : right_new_min_x;\r\n\t\t\t\t\tcurrent._bvh_min_y = left_new_min_y < right_new_min_y ? left_new_min_y : right_new_min_y;\r\n\t\t\t\t\tcurrent._bvh_max_x = left_new_max_x > right_new_max_x ? left_new_max_x : right_new_max_x;\r\n\t\t\t\t\tcurrent._bvh_max_y = left_new_max_y > right_new_max_y ? left_new_max_y : right_new_max_y;\r\n\r\n\t\t\t\t\tcurrent = left_difference <= right_difference ? left : right;\r\n\t\t\t\t}\r\n\t\t\t\t// Leaf\r\n\t\t\t\telse {\r\n\t\t\t\t\tconst grandparent = current._bvh_parent;\r\n\t\t\t\t\tconst parent_min_x = current._bvh_min_x;\r\n\t\t\t\t\tconst parent_min_y = current._bvh_min_y;\r\n\t\t\t\t\tconst parent_max_x = current._bvh_max_x;\r\n\t\t\t\t\tconst parent_max_y = current._bvh_max_y;\r\n\t\t\t\t\tconst new_parent = current._bvh_parent = body._bvh_parent = _BVHBranch_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"].getBranch();\r\n\r\n\t\t\t\t\tnew_parent._bvh_parent = grandparent;\r\n\t\t\t\t\tnew_parent._bvh_left = current;\r\n\t\t\t\t\tnew_parent._bvh_right = body;\r\n\t\t\t\t\tnew_parent._bvh_sort = sort++;\r\n\t\t\t\t\tnew_parent._bvh_min_x = body_min_x < parent_min_x ? body_min_x : parent_min_x;\r\n\t\t\t\t\tnew_parent._bvh_min_y = body_min_y < parent_min_y ? body_min_y : parent_min_y;\r\n\t\t\t\t\tnew_parent._bvh_max_x = body_max_x > parent_max_x ? body_max_x : parent_max_x;\r\n\t\t\t\t\tnew_parent._bvh_max_y = body_max_y > parent_max_y ? body_max_y : parent_max_y;\r\n\r\n\t\t\t\t\tif(!grandparent) {\r\n\t\t\t\t\t\tthis._hierarchy = new_parent;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if(grandparent._bvh_left === current) {\r\n\t\t\t\t\t\tgrandparent._bvh_left = new_parent;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse {\r\n\t\t\t\t\t\tgrandparent._bvh_right = new_parent;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Removes a body from the BVH\r\n\t * @param {Circle|Polygon|Point} body The body to remove\r\n\t * @param {Boolean} [updating = false] Set to true if this is a temporary removal (used internally when updating the body's position)\r\n\t */\r\n\tremove(body, updating = false) {\r\n\t\tif(!updating) {\r\n\t\t\tconst bvh = body._bvh;\r\n\r\n\t\t\tif(bvh && bvh !== this) {\r\n\t\t\t\tthrow new Error('Body belongs to another collision system');\r\n\t\t\t}\r\n\r\n\t\t\tbody._bvh = null;\r\n\t\t\tthis._bodies.splice(this._bodies.indexOf(body), 1);\r\n\t\t}\r\n\r\n\t\tif(this._hierarchy === body) {\r\n\t\t\tthis._hierarchy = null;\r\n\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tconst parent = body._bvh_parent;\r\n\t\tconst grandparent = parent._bvh_parent;\r\n\t\tconst parent_left = parent._bvh_left;\r\n\t\tconst sibling = parent_left === body ? parent._bvh_right : parent_left;\r\n\r\n\t\tsibling._bvh_parent = grandparent;\r\n\r\n\t\tif(sibling._bvh_branch) {\r\n\t\t\tsibling._bvh_sort = parent._bvh_sort;\r\n\t\t}\r\n\r\n\t\tif(grandparent) {\r\n\t\t\tif(grandparent._bvh_left === parent) {\r\n\t\t\t\tgrandparent._bvh_left = sibling;\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tgrandparent._bvh_right = sibling;\r\n\t\t\t}\r\n\r\n\t\t\tlet branch = grandparent;\r\n\r\n\t\t\twhile(branch) {\r\n\t\t\t\tconst left = branch._bvh_left;\r\n\t\t\t\tconst left_min_x = left._bvh_min_x;\r\n\t\t\t\tconst left_min_y = left._bvh_min_y;\r\n\t\t\t\tconst left_max_x = left._bvh_max_x;\r\n\t\t\t\tconst left_max_y = left._bvh_max_y;\r\n\r\n\t\t\t\tconst right = branch._bvh_right;\r\n\t\t\t\tconst right_min_x = right._bvh_min_x;\r\n\t\t\t\tconst right_min_y = right._bvh_min_y;\r\n\t\t\t\tconst right_max_x = right._bvh_max_x;\r\n\t\t\t\tconst right_max_y = right._bvh_max_y;\r\n\r\n\t\t\t\tbranch._bvh_min_x = left_min_x < right_min_x ? left_min_x : right_min_x;\r\n\t\t\t\tbranch._bvh_min_y = left_min_y < right_min_y ? left_min_y : right_min_y;\r\n\t\t\t\tbranch._bvh_max_x = left_max_x > right_max_x ? left_max_x : right_max_x;\r\n\t\t\t\tbranch._bvh_max_y = left_max_y > right_max_y ? left_max_y : right_max_y;\r\n\r\n\t\t\t\tbranch = branch._bvh_parent;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse {\r\n\t\t\tthis._hierarchy = sibling;\r\n\t\t}\r\n\r\n\t\t_BVHBranch_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"].releaseBranch(parent);\r\n\t}\r\n\r\n\t/**\r\n\t * Updates the BVH. Moved bodies are removed/inserted.\r\n\t */\r\n\tupdate() {\r\n\t\tconst bodies = this._bodies;\r\n\t\tconst count = bodies.length;\r\n\r\n\t\tfor(let i = 0; i < count; ++i) {\r\n\t\t\tconst body = bodies[i];\r\n\r\n\t\t\tlet update = false;\r\n\r\n\t\t\tif(!update && body.padding !== body._bvh_padding) {\r\n\t\t\t\tbody._bvh_padding = body.padding;\r\n\t\t\t\tupdate = true;\r\n\t\t\t}\r\n\r\n\t\t\tif(!update) {\r\n\t\t\t\tconst polygon = body._polygon;\r\n\r\n\t\t\t\tif(polygon) {\r\n\t\t\t\t\tif(\r\n\t\t\t\t\t\tbody._dirty_coords ||\r\n\t\t\t\t\t\tbody.x !== body._x ||\r\n\t\t\t\t\t\tbody.y !== body._y ||\r\n\t\t\t\t\t\tbody.angle !== body._angle ||\r\n\t\t\t\t\t\tbody.scale_x !== body._scale_x ||\r\n\t\t\t\t\t\tbody.scale_y !== body._scale_y\r\n\t\t\t\t\t) {\r\n\t\t\t\t\t\tbody._calculateCoords();\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tconst x = body.x;\r\n\t\t\t\tconst y = body.y;\r\n\t\t\t\tconst radius = polygon ? 0 : body.radius * body.scale;\r\n\t\t\t\tconst min_x = polygon ? body._min_x : x - radius;\r\n\t\t\t\tconst min_y = polygon ? body._min_y : y - radius;\r\n\t\t\t\tconst max_x = polygon ? body._max_x : x + radius;\r\n\t\t\t\tconst max_y = polygon ? body._max_y : y + radius;\r\n\r\n\t\t\t\tupdate = min_x < body._bvh_min_x || min_y < body._bvh_min_y || max_x > body._bvh_max_x || max_y > body._bvh_max_y;\r\n\t\t\t}\r\n\r\n\t\t\tif(update) {\r\n\t\t\t\tthis.remove(body, true);\r\n\t\t\t\tthis.insert(body, true);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a list of potential collisions for a body\r\n\t * @param {Circle|Polygon|Point} body The body to test\r\n\t * @returns {Array}\r\n\t */\r\n\tpotentials(body) {\r\n\t\tconst results = [];\r\n\t\tconst min_x = body._bvh_min_x;\r\n\t\tconst min_y = body._bvh_min_y;\r\n\t\tconst max_x = body._bvh_max_x;\r\n\t\tconst max_y = body._bvh_max_y;\r\n\r\n\t\tlet current = this._hierarchy;\r\n\t\tlet traverse_left = true;\r\n\r\n\t\tif(!current || !current._bvh_branch) {\r\n\t\t\treturn results;\r\n\t\t}\r\n\r\n\t\twhile(current) {\r\n\t\t\tif(traverse_left) {\r\n\t\t\t\ttraverse_left = false;\r\n\r\n\t\t\t\tlet left = current._bvh_branch ? current._bvh_left : null;\r\n\r\n\t\t\t\twhile(\r\n\t\t\t\t\tleft &&\r\n\t\t\t\t\tleft._bvh_max_x >= min_x &&\r\n\t\t\t\t\tleft._bvh_max_y >= min_y &&\r\n\t\t\t\t\tleft._bvh_min_x <= max_x &&\r\n\t\t\t\t\tleft._bvh_min_y <= max_y\r\n\t\t\t\t) {\r\n\t\t\t\t\tcurrent = left;\r\n\t\t\t\t\tleft = current._bvh_branch ? current._bvh_left : null;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tconst branch = current._bvh_branch;\r\n\t\t\tconst right = branch ? current._bvh_right : null;\r\n\r\n\t\t\tif(\r\n\t\t\t\tright &&\r\n\t\t\t\tright._bvh_max_x > min_x &&\r\n\t\t\t\tright._bvh_max_y > min_y &&\r\n\t\t\t\tright._bvh_min_x < max_x &&\r\n\t\t\t\tright._bvh_min_y < max_y\r\n\t\t\t) {\r\n\t\t\t\tcurrent = right;\r\n\t\t\t\ttraverse_left = true;\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tif(!branch && current !== body) {\r\n\t\t\t\t\tresults.push(current);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tlet parent = current._bvh_parent;\r\n\r\n\t\t\t\tif(parent) {\r\n\t\t\t\t\twhile(parent && parent._bvh_right === current) {\r\n\t\t\t\t\t\tcurrent = parent;\r\n\t\t\t\t\t\tparent = current._bvh_parent;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tcurrent = parent;\r\n\t\t\t\t}\r\n\t\t\t\telse {\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn results;\r\n\t}\r\n\r\n\t/**\r\n\t * Draws the bodies within the BVH to a CanvasRenderingContext2D's current path\r\n\t * @param {CanvasRenderingContext2D} context The context to draw to\r\n\t */\r\n\tdraw(context) {\r\n\t\tconst bodies = this._bodies;\r\n\t\tconst count = bodies.length;\r\n\r\n\t\tfor(let i = 0; i < count; ++i) {\r\n\t\t\tbodies[i].draw(context);\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Draws the BVH to a CanvasRenderingContext2D's current path. This is useful for testing out different padding values for bodies.\r\n\t * @param {CanvasRenderingContext2D} context The context to draw to\r\n\t */\r\n\tdrawBVH(context) {\r\n\t\tlet current = this._hierarchy;\r\n\t\tlet traverse_left = true;\r\n\r\n\t\twhile(current) {\r\n\t\t\tif(traverse_left) {\r\n\t\t\t\ttraverse_left = false;\r\n\r\n\t\t\t\tlet left = current._bvh_branch ? current._bvh_left : null;\r\n\r\n\t\t\t\twhile(left) {\r\n\t\t\t\t\tcurrent = left;\r\n\t\t\t\t\tleft = current._bvh_branch ? current._bvh_left : null;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tconst branch = current._bvh_branch;\r\n\t\t\tconst min_x = current._bvh_min_x;\r\n\t\t\tconst min_y = current._bvh_min_y;\r\n\t\t\tconst max_x = current._bvh_max_x;\r\n\t\t\tconst max_y = current._bvh_max_y;\r\n\t\t\tconst right = branch ? current._bvh_right : null;\r\n\r\n\t\t\tcontext.moveTo(min_x, min_y);\r\n\t\t\tcontext.lineTo(max_x, min_y);\r\n\t\t\tcontext.lineTo(max_x, max_y);\r\n\t\t\tcontext.lineTo(min_x, max_y);\r\n\t\t\tcontext.lineTo(min_x, min_y);\r\n\r\n\t\t\tif(right) {\r\n\t\t\t\tcurrent = right;\r\n\t\t\t\ttraverse_left = true;\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tlet parent = current._bvh_parent;\r\n\r\n\t\t\t\tif(parent) {\r\n\t\t\t\t\twhile(parent && parent._bvh_right === current) {\r\n\t\t\t\t\t\tcurrent = parent;\r\n\t\t\t\t\t\tparent = current._bvh_parent;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tcurrent = parent;\r\n\t\t\t\t}\r\n\t\t\t\telse {\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\n\n//# sourceURL=webpack:///./src/modules/BVH.mjs?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return BVH; });\n/* harmony import */ var _BVHBranch_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./BVHBranch.mjs */ \"./src/modules/BVHBranch.mjs\");\n\n\n/**\n * A Bounding Volume Hierarchy (BVH) used to find potential collisions quickly\n * @class\n * @private\n */\nclass BVH {\n\t/**\n\t * @constructor\n\t */\n\tconstructor() {\n\t\t/** @private */\n\t\tthis._hierarchy = null;\n\n\t\t/** @private */\n\t\tthis._bodies = [];\n\n\t\t/** @private */\n\t\tthis._dirty_branches = [];\n\t}\n\n\t/**\n\t * Inserts a body into the BVH\n\t * @param {Circle|Polygon|Point} body The body to insert\n\t * @param {Boolean} [updating = false] Set to true if the body already exists in the BVH (used internally when updating the body's position)\n\t */\n\tinsert(body, updating = false) {\n\t\tif(!updating) {\n\t\t\tconst bvh = body._bvh;\n\n\t\t\tif(bvh && bvh !== this) {\n\t\t\t\tthrow new Error('Body belongs to another collision system');\n\t\t\t}\n\n\t\t\tbody._bvh = this;\n\t\t\tthis._bodies.push(body);\n\t\t}\n\n\t\tconst polygon = body._polygon;\n\t\tconst body_x = body.x;\n\t\tconst body_y = body.y;\n\n\t\tif(polygon) {\n\t\t\tif(\n\t\t\t\tbody._dirty_coords ||\n\t\t\t\tbody.x !== body._x ||\n\t\t\t\tbody.y !== body._y ||\n\t\t\t\tbody.angle !== body._angle ||\n\t\t\t\tbody.scale_x !== body._scale_x ||\n\t\t\t\tbody.scale_y !== body._scale_y\n\t\t\t) {\n\t\t\t\tbody._calculateCoords();\n\t\t\t}\n\t\t}\n\n\t\tconst padding = body._bvh_padding;\n\t\tconst radius = polygon ? 0 : body.radius * body.scale;\n\t\tconst body_min_x = (polygon ? body._min_x : body_x - radius) - padding;\n\t\tconst body_min_y = (polygon ? body._min_y : body_y - radius) - padding;\n\t\tconst body_max_x = (polygon ? body._max_x : body_x + radius) + padding;\n\t\tconst body_max_y = (polygon ? body._max_y : body_y + radius) + padding;\n\n\t\tbody._bvh_min_x = body_min_x;\n\t\tbody._bvh_min_y = body_min_y;\n\t\tbody._bvh_max_x = body_max_x;\n\t\tbody._bvh_max_y = body_max_y;\n\n\t\tlet current = this._hierarchy;\n\t\tlet sort = 0;\n\n\t\tif(!current) {\n\t\t\tthis._hierarchy = body;\n\t\t}\n\t\telse {\n\t\t\twhile(true) {\n\t\t\t\t// Branch\n\t\t\t\tif(current._bvh_branch) {\n\t\t\t\t\tconst left = current._bvh_left;\n\t\t\t\t\tconst left_min_y = left._bvh_min_y;\n\t\t\t\t\tconst left_max_x = left._bvh_max_x;\n\t\t\t\t\tconst left_max_y = left._bvh_max_y;\n\t\t\t\t\tconst left_new_min_x = body_min_x < left._bvh_min_x ? body_min_x : left._bvh_min_x;\n\t\t\t\t\tconst left_new_min_y = body_min_y < left_min_y ? body_min_y : left_min_y;\n\t\t\t\t\tconst left_new_max_x = body_max_x > left_max_x ? body_max_x : left_max_x;\n\t\t\t\t\tconst left_new_max_y = body_max_y > left_max_y ? body_max_y : left_max_y;\n\t\t\t\t\tconst left_volume = (left_max_x - left._bvh_min_x) * (left_max_y - left_min_y);\n\t\t\t\t\tconst left_new_volume = (left_new_max_x - left_new_min_x) * (left_new_max_y - left_new_min_y);\n\t\t\t\t\tconst left_difference = left_new_volume - left_volume;\n\n\t\t\t\t\tconst right = current._bvh_right;\n\t\t\t\t\tconst right_min_x = right._bvh_min_x;\n\t\t\t\t\tconst right_min_y = right._bvh_min_y;\n\t\t\t\t\tconst right_max_x = right._bvh_max_x;\n\t\t\t\t\tconst right_max_y = right._bvh_max_y;\n\t\t\t\t\tconst right_new_min_x = body_min_x < right_min_x ? body_min_x : right_min_x;\n\t\t\t\t\tconst right_new_min_y = body_min_y < right_min_y ? body_min_y : right_min_y;\n\t\t\t\t\tconst right_new_max_x = body_max_x > right_max_x ? body_max_x : right_max_x;\n\t\t\t\t\tconst right_new_max_y = body_max_y > right_max_y ? body_max_y : right_max_y;\n\t\t\t\t\tconst right_volume = (right_max_x - right_min_x) * (right_max_y - right_min_y);\n\t\t\t\t\tconst right_new_volume = (right_new_max_x - right_new_min_x) * (right_new_max_y - right_new_min_y);\n\t\t\t\t\tconst right_difference = right_new_volume - right_volume;\n\n\t\t\t\t\tcurrent._bvh_sort = sort++;\n\t\t\t\t\tcurrent._bvh_min_x = left_new_min_x < right_new_min_x ? left_new_min_x : right_new_min_x;\n\t\t\t\t\tcurrent._bvh_min_y = left_new_min_y < right_new_min_y ? left_new_min_y : right_new_min_y;\n\t\t\t\t\tcurrent._bvh_max_x = left_new_max_x > right_new_max_x ? left_new_max_x : right_new_max_x;\n\t\t\t\t\tcurrent._bvh_max_y = left_new_max_y > right_new_max_y ? left_new_max_y : right_new_max_y;\n\n\t\t\t\t\tcurrent = left_difference <= right_difference ? left : right;\n\t\t\t\t}\n\t\t\t\t// Leaf\n\t\t\t\telse {\n\t\t\t\t\tconst grandparent = current._bvh_parent;\n\t\t\t\t\tconst parent_min_x = current._bvh_min_x;\n\t\t\t\t\tconst parent_min_y = current._bvh_min_y;\n\t\t\t\t\tconst parent_max_x = current._bvh_max_x;\n\t\t\t\t\tconst parent_max_y = current._bvh_max_y;\n\t\t\t\t\tconst new_parent = current._bvh_parent = body._bvh_parent = _BVHBranch_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"].getBranch();\n\n\t\t\t\t\tnew_parent._bvh_parent = grandparent;\n\t\t\t\t\tnew_parent._bvh_left = current;\n\t\t\t\t\tnew_parent._bvh_right = body;\n\t\t\t\t\tnew_parent._bvh_sort = sort++;\n\t\t\t\t\tnew_parent._bvh_min_x = body_min_x < parent_min_x ? body_min_x : parent_min_x;\n\t\t\t\t\tnew_parent._bvh_min_y = body_min_y < parent_min_y ? body_min_y : parent_min_y;\n\t\t\t\t\tnew_parent._bvh_max_x = body_max_x > parent_max_x ? body_max_x : parent_max_x;\n\t\t\t\t\tnew_parent._bvh_max_y = body_max_y > parent_max_y ? body_max_y : parent_max_y;\n\n\t\t\t\t\tif(!grandparent) {\n\t\t\t\t\t\tthis._hierarchy = new_parent;\n\t\t\t\t\t}\n\t\t\t\t\telse if(grandparent._bvh_left === current) {\n\t\t\t\t\t\tgrandparent._bvh_left = new_parent;\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tgrandparent._bvh_right = new_parent;\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Removes a body from the BVH\n\t * @param {Circle|Polygon|Point} body The body to remove\n\t * @param {Boolean} [updating = false] Set to true if this is a temporary removal (used internally when updating the body's position)\n\t */\n\tremove(body, updating = false) {\n\t\tif(!updating) {\n\t\t\tconst bvh = body._bvh;\n\n\t\t\tif(bvh && bvh !== this) {\n\t\t\t\tthrow new Error('Body belongs to another collision system');\n\t\t\t}\n\n\t\t\tbody._bvh = null;\n\t\t\tthis._bodies.splice(this._bodies.indexOf(body), 1);\n\t\t}\n\n\t\tif(this._hierarchy === body) {\n\t\t\tthis._hierarchy = null;\n\n\t\t\treturn;\n\t\t}\n\n\t\tconst parent = body._bvh_parent;\n\t\tconst grandparent = parent._bvh_parent;\n\t\tconst parent_left = parent._bvh_left;\n\t\tconst sibling = parent_left === body ? parent._bvh_right : parent_left;\n\n\t\tsibling._bvh_parent = grandparent;\n\n\t\tif(sibling._bvh_branch) {\n\t\t\tsibling._bvh_sort = parent._bvh_sort;\n\t\t}\n\n\t\tif(grandparent) {\n\t\t\tif(grandparent._bvh_left === parent) {\n\t\t\t\tgrandparent._bvh_left = sibling;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tgrandparent._bvh_right = sibling;\n\t\t\t}\n\n\t\t\tlet branch = grandparent;\n\n\t\t\twhile(branch) {\n\t\t\t\tconst left = branch._bvh_left;\n\t\t\t\tconst left_min_x = left._bvh_min_x;\n\t\t\t\tconst left_min_y = left._bvh_min_y;\n\t\t\t\tconst left_max_x = left._bvh_max_x;\n\t\t\t\tconst left_max_y = left._bvh_max_y;\n\n\t\t\t\tconst right = branch._bvh_right;\n\t\t\t\tconst right_min_x = right._bvh_min_x;\n\t\t\t\tconst right_min_y = right._bvh_min_y;\n\t\t\t\tconst right_max_x = right._bvh_max_x;\n\t\t\t\tconst right_max_y = right._bvh_max_y;\n\n\t\t\t\tbranch._bvh_min_x = left_min_x < right_min_x ? left_min_x : right_min_x;\n\t\t\t\tbranch._bvh_min_y = left_min_y < right_min_y ? left_min_y : right_min_y;\n\t\t\t\tbranch._bvh_max_x = left_max_x > right_max_x ? left_max_x : right_max_x;\n\t\t\t\tbranch._bvh_max_y = left_max_y > right_max_y ? left_max_y : right_max_y;\n\n\t\t\t\tbranch = branch._bvh_parent;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tthis._hierarchy = sibling;\n\t\t}\n\n\t\t_BVHBranch_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"].releaseBranch(parent);\n\t}\n\n\t/**\n\t * Updates the BVH. Moved bodies are removed/inserted.\n\t */\n\tupdate() {\n\t\tconst bodies = this._bodies;\n\t\tconst count = bodies.length;\n\n\t\tfor(let i = 0; i < count; ++i) {\n\t\t\tconst body = bodies[i];\n\n\t\t\tlet update = false;\n\n\t\t\tif(!update && body.padding !== body._bvh_padding) {\n\t\t\t\tbody._bvh_padding = body.padding;\n\t\t\t\tupdate = true;\n\t\t\t}\n\n\t\t\tif(!update) {\n\t\t\t\tconst polygon = body._polygon;\n\n\t\t\t\tif(polygon) {\n\t\t\t\t\tif(\n\t\t\t\t\t\tbody._dirty_coords ||\n\t\t\t\t\t\tbody.x !== body._x ||\n\t\t\t\t\t\tbody.y !== body._y ||\n\t\t\t\t\t\tbody.angle !== body._angle ||\n\t\t\t\t\t\tbody.scale_x !== body._scale_x ||\n\t\t\t\t\t\tbody.scale_y !== body._scale_y\n\t\t\t\t\t) {\n\t\t\t\t\t\tbody._calculateCoords();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst x = body.x;\n\t\t\t\tconst y = body.y;\n\t\t\t\tconst radius = polygon ? 0 : body.radius * body.scale;\n\t\t\t\tconst min_x = polygon ? body._min_x : x - radius;\n\t\t\t\tconst min_y = polygon ? body._min_y : y - radius;\n\t\t\t\tconst max_x = polygon ? body._max_x : x + radius;\n\t\t\t\tconst max_y = polygon ? body._max_y : y + radius;\n\n\t\t\t\tupdate = min_x < body._bvh_min_x || min_y < body._bvh_min_y || max_x > body._bvh_max_x || max_y > body._bvh_max_y;\n\t\t\t}\n\n\t\t\tif(update) {\n\t\t\t\tthis.remove(body, true);\n\t\t\t\tthis.insert(body, true);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Returns a list of potential collisions for a body\n\t * @param {Circle|Polygon|Point} body The body to test\n\t * @returns {Array}\n\t */\n\tpotentials(body) {\n\t\tconst results = [];\n\t\tconst min_x = body._bvh_min_x;\n\t\tconst min_y = body._bvh_min_y;\n\t\tconst max_x = body._bvh_max_x;\n\t\tconst max_y = body._bvh_max_y;\n\n\t\tlet current = this._hierarchy;\n\t\tlet traverse_left = true;\n\n\t\tif(!current || !current._bvh_branch) {\n\t\t\treturn results;\n\t\t}\n\n\t\twhile(current) {\n\t\t\tif(traverse_left) {\n\t\t\t\ttraverse_left = false;\n\n\t\t\t\tlet left = current._bvh_branch ? current._bvh_left : null;\n\n\t\t\t\twhile(\n\t\t\t\t\tleft &&\n\t\t\t\t\tleft._bvh_max_x >= min_x &&\n\t\t\t\t\tleft._bvh_max_y >= min_y &&\n\t\t\t\t\tleft._bvh_min_x <= max_x &&\n\t\t\t\t\tleft._bvh_min_y <= max_y\n\t\t\t\t) {\n\t\t\t\t\tcurrent = left;\n\t\t\t\t\tleft = current._bvh_branch ? current._bvh_left : null;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst branch = current._bvh_branch;\n\t\t\tconst right = branch ? current._bvh_right : null;\n\n\t\t\tif(\n\t\t\t\tright &&\n\t\t\t\tright._bvh_max_x > min_x &&\n\t\t\t\tright._bvh_max_y > min_y &&\n\t\t\t\tright._bvh_min_x < max_x &&\n\t\t\t\tright._bvh_min_y < max_y\n\t\t\t) {\n\t\t\t\tcurrent = right;\n\t\t\t\ttraverse_left = true;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif(!branch && current !== body) {\n\t\t\t\t\tresults.push(current);\n\t\t\t\t}\n\n\t\t\t\tlet parent = current._bvh_parent;\n\n\t\t\t\tif(parent) {\n\t\t\t\t\twhile(parent && parent._bvh_right === current) {\n\t\t\t\t\t\tcurrent = parent;\n\t\t\t\t\t\tparent = current._bvh_parent;\n\t\t\t\t\t}\n\n\t\t\t\t\tcurrent = parent;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\t/**\n\t * Draws the bodies within the BVH to a CanvasRenderingContext2D's current path\n\t * @param {CanvasRenderingContext2D} context The context to draw to\n\t */\n\tdraw(context) {\n\t\tconst bodies = this._bodies;\n\t\tconst count = bodies.length;\n\n\t\tfor(let i = 0; i < count; ++i) {\n\t\t\tbodies[i].draw(context);\n\t\t}\n\t}\n\n\t/**\n\t * Draws the BVH to a CanvasRenderingContext2D's current path. This is useful for testing out different padding values for bodies.\n\t * @param {CanvasRenderingContext2D} context The context to draw to\n\t */\n\tdrawBVH(context) {\n\t\tlet current = this._hierarchy;\n\t\tlet traverse_left = true;\n\n\t\twhile(current) {\n\t\t\tif(traverse_left) {\n\t\t\t\ttraverse_left = false;\n\n\t\t\t\tlet left = current._bvh_branch ? current._bvh_left : null;\n\n\t\t\t\twhile(left) {\n\t\t\t\t\tcurrent = left;\n\t\t\t\t\tleft = current._bvh_branch ? current._bvh_left : null;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst branch = current._bvh_branch;\n\t\t\tconst min_x = current._bvh_min_x;\n\t\t\tconst min_y = current._bvh_min_y;\n\t\t\tconst max_x = current._bvh_max_x;\n\t\t\tconst max_y = current._bvh_max_y;\n\t\t\tconst right = branch ? current._bvh_right : null;\n\n\t\t\tcontext.moveTo(min_x, min_y);\n\t\t\tcontext.lineTo(max_x, min_y);\n\t\t\tcontext.lineTo(max_x, max_y);\n\t\t\tcontext.lineTo(min_x, max_y);\n\t\t\tcontext.lineTo(min_x, min_y);\n\n\t\t\tif(right) {\n\t\t\t\tcurrent = right;\n\t\t\t\ttraverse_left = true;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tlet parent = current._bvh_parent;\n\n\t\t\t\tif(parent) {\n\t\t\t\t\twhile(parent && parent._bvh_right === current) {\n\t\t\t\t\t\tcurrent = parent;\n\t\t\t\t\t\tparent = current._bvh_parent;\n\t\t\t\t\t}\n\n\t\t\t\t\tcurrent = parent;\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n};\n\n\n//# sourceURL=webpack:///./src/modules/BVH.mjs?"); /***/ }), @@ -118,7 +118,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) * /***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return BVHBranch; });\n/**\r\n * @private\r\n */\r\nconst branch_pool = [];\r\n\r\n/**\r\n * A branch within a BVH\r\n * @class\r\n * @private\r\n */\r\nclass BVHBranch {\r\n\t/**\r\n\t * @constructor\r\n\t */\r\n\tconstructor() {\r\n\t\t/** @private */\r\n\t\tthis._bvh_parent = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_branch = true;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_left = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_right = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_sort = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_min_x = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_min_y = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_max_x = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_max_y = 0;\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a branch from the branch pool or creates a new branch\r\n\t * @returns {BVHBranch}\r\n\t */\r\n\tstatic getBranch() {\r\n\t\tif(branch_pool.length) {\r\n\t\t\treturn branch_pool.pop();\r\n\t\t}\r\n\r\n\t\treturn new BVHBranch();\r\n\t}\r\n\r\n\t/**\r\n\t * Releases a branch back into the branch pool\r\n\t * @param {BVHBranch} branch The branch to release\r\n\t */\r\n\tstatic releaseBranch(branch) {\r\n\t\tbranch_pool.push(branch);\r\n\t}\r\n\r\n\t/**\r\n\t * Sorting callback used to sort branches by deepest first\r\n\t * @param {BVHBranch} a The first branch\r\n\t * @param {BVHBranch} b The second branch\r\n\t * @returns {Number}\r\n\t */\r\n\tstatic sortBranches(a, b) {\r\n\t\treturn a.sort > b.sort ? -1 : 1;\r\n\t}\r\n};\r\n\n\n//# sourceURL=webpack:///./src/modules/BVHBranch.mjs?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return BVHBranch; });\n/**\n * @private\n */\nconst branch_pool = [];\n\n/**\n * A branch within a BVH\n * @class\n * @private\n */\nclass BVHBranch {\n\t/**\n\t * @constructor\n\t */\n\tconstructor() {\n\t\t/** @private */\n\t\tthis._bvh_parent = null;\n\n\t\t/** @private */\n\t\tthis._bvh_branch = true;\n\n\t\t/** @private */\n\t\tthis._bvh_left = null;\n\n\t\t/** @private */\n\t\tthis._bvh_right = null;\n\n\t\t/** @private */\n\t\tthis._bvh_sort = 0;\n\n\t\t/** @private */\n\t\tthis._bvh_min_x = 0;\n\n\t\t/** @private */\n\t\tthis._bvh_min_y = 0;\n\n\t\t/** @private */\n\t\tthis._bvh_max_x = 0;\n\n\t\t/** @private */\n\t\tthis._bvh_max_y = 0;\n\t}\n\n\t/**\n\t * Returns a branch from the branch pool or creates a new branch\n\t * @returns {BVHBranch}\n\t */\n\tstatic getBranch() {\n\t\tif(branch_pool.length) {\n\t\t\treturn branch_pool.pop();\n\t\t}\n\n\t\treturn new BVHBranch();\n\t}\n\n\t/**\n\t * Releases a branch back into the branch pool\n\t * @param {BVHBranch} branch The branch to release\n\t */\n\tstatic releaseBranch(branch) {\n\t\tbranch_pool.push(branch);\n\t}\n\n\t/**\n\t * Sorting callback used to sort branches by deepest first\n\t * @param {BVHBranch} a The first branch\n\t * @param {BVHBranch} b The second branch\n\t * @returns {Number}\n\t */\n\tstatic sortBranches(a, b) {\n\t\treturn a.sort > b.sort ? -1 : 1;\n\t}\n};\n\n\n//# sourceURL=webpack:///./src/modules/BVHBranch.mjs?"); /***/ }), @@ -130,7 +130,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) * /***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Body; });\n/* harmony import */ var _Result_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Result.mjs */ \"./src/modules/Result.mjs\");\n/* harmony import */ var _SAT_mjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./SAT.mjs */ \"./src/modules/SAT.mjs\");\n\r\n\r\n\r\n/**\r\n * The base class for bodies used to detect collisions\r\n * @class\r\n * @protected\r\n */\r\nclass Body {\r\n\t/**\r\n\t * @constructor\r\n\t * @param {Number} [x = 0] The starting X coordinate\r\n\t * @param {Number} [y = 0] The starting Y coordinate\r\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\r\n\t */\r\n\tconstructor(x = 0, y = 0, padding = 0) {\r\n\t\t/**\r\n\t\t * @desc The X coordinate of the body\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.x = x;\r\n\r\n\t\t/**\r\n\t\t * @desc The Y coordinate of the body\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.y = y;\r\n\r\n\t\t/**\r\n\t\t * @desc The amount to pad the bounding volume when testing for potential collisions\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.padding = padding;\r\n\r\n\t\t/** @private */\r\n\t\tthis._circle = false;\r\n\r\n\t\t/** @private */\r\n\t\tthis._polygon = false;\r\n\r\n\t\t/** @private */\r\n\t\tthis._point = false;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_parent = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_branch = false;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_padding = padding;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_min_x = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_min_y = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_max_x = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._bvh_max_y = 0;\r\n\t}\r\n\r\n\t/**\r\n\t * Determines if the body is colliding with another body\r\n\t * @param {Circle|Polygon|Point} target The target body to test against\r\n\t * @param {Result} [result = null] A Result object on which to store information about the collision\r\n\t * @param {Boolean} [aabb = true] Set to false to skip the AABB test (useful if you use your own potential collision heuristic)\r\n\t * @returns {Boolean}\r\n\t */\r\n\tcollides(target, result = null, aabb = true) {\r\n\t\treturn Object(_SAT_mjs__WEBPACK_IMPORTED_MODULE_1__[\"default\"])(this, target, result, aabb);\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a list of potential collisions\r\n\t * @returns {Array}\r\n\t */\r\n\tpotentials() {\r\n\t\tconst bvh = this._bvh;\r\n\r\n\t\tif(bvh === null) {\r\n\t\t\tthrow new Error('Body does not belong to a collision system');\r\n\t\t}\r\n\r\n\t\treturn bvh.potentials(this);\r\n\t}\r\n\r\n\t/**\r\n\t * Removes the body from its current collision system\r\n\t */\r\n\tremove() {\r\n\t\tconst bvh = this._bvh;\r\n\r\n\t\tif(bvh) {\r\n\t\t\tbvh.remove(this, false);\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Creates a {@link Result} used to collect the detailed results of a collision test\r\n\t */\r\n\tcreateResult() {\r\n\t\treturn new _Result_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"]();\r\n\t}\r\n\r\n\t/**\r\n\t * Creates a Result used to collect the detailed results of a collision test\r\n\t */\r\n\tstatic createResult() {\r\n\t\treturn new _Result_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"]();\r\n\t}\r\n};\r\n\n\n//# sourceURL=webpack:///./src/modules/Body.mjs?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Body; });\n/* harmony import */ var _Result_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Result.mjs */ \"./src/modules/Result.mjs\");\n/* harmony import */ var _SAT_mjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./SAT.mjs */ \"./src/modules/SAT.mjs\");\n\n\n\n/**\n * The base class for bodies used to detect collisions\n * @class\n * @protected\n */\nclass Body {\n\t/**\n\t * @constructor\n\t * @param {Number} [x = 0] The starting X coordinate\n\t * @param {Number} [y = 0] The starting Y coordinate\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\n\t */\n\tconstructor(x = 0, y = 0, padding = 0) {\n\t\t/**\n\t\t * @desc The X coordinate of the body\n\t\t * @type {Number}\n\t\t */\n\t\tthis.x = x;\n\n\t\t/**\n\t\t * @desc The Y coordinate of the body\n\t\t * @type {Number}\n\t\t */\n\t\tthis.y = y;\n\n\t\t/**\n\t\t * @desc The amount to pad the bounding volume when testing for potential collisions\n\t\t * @type {Number}\n\t\t */\n\t\tthis.padding = padding;\n\n\t\t/** @private */\n\t\tthis._circle = false;\n\n\t\t/** @private */\n\t\tthis._polygon = false;\n\n\t\t/** @private */\n\t\tthis._point = false;\n\n\t\t/** @private */\n\t\tthis._bvh = null;\n\n\t\t/** @private */\n\t\tthis._bvh_parent = null;\n\n\t\t/** @private */\n\t\tthis._bvh_branch = false;\n\n\t\t/** @private */\n\t\tthis._bvh_padding = padding;\n\n\t\t/** @private */\n\t\tthis._bvh_min_x = 0;\n\n\t\t/** @private */\n\t\tthis._bvh_min_y = 0;\n\n\t\t/** @private */\n\t\tthis._bvh_max_x = 0;\n\n\t\t/** @private */\n\t\tthis._bvh_max_y = 0;\n\t}\n\n\t/**\n\t * Determines if the body is colliding with another body\n\t * @param {Circle|Polygon|Point} target The target body to test against\n\t * @param {Result} [result = null] A Result object on which to store information about the collision\n\t * @param {Boolean} [aabb = true] Set to false to skip the AABB test (useful if you use your own potential collision heuristic)\n\t * @returns {Boolean}\n\t */\n\tcollides(target, result = null, aabb = true) {\n\t\treturn Object(_SAT_mjs__WEBPACK_IMPORTED_MODULE_1__[\"default\"])(this, target, result, aabb);\n\t}\n\n\t/**\n\t * Returns a list of potential collisions\n\t * @returns {Array}\n\t */\n\tpotentials() {\n\t\tconst bvh = this._bvh;\n\n\t\tif(bvh === null) {\n\t\t\tthrow new Error('Body does not belong to a collision system');\n\t\t}\n\n\t\treturn bvh.potentials(this);\n\t}\n\n\t/**\n\t * Removes the body from its current collision system\n\t */\n\tremove() {\n\t\tconst bvh = this._bvh;\n\n\t\tif(bvh) {\n\t\t\tbvh.remove(this, false);\n\t\t}\n\t}\n\n\t/**\n\t * Creates a {@link Result} used to collect the detailed results of a collision test\n\t */\n\tcreateResult() {\n\t\treturn new _Result_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"]();\n\t}\n\n\t/**\n\t * Creates a Result used to collect the detailed results of a collision test\n\t */\n\tstatic createResult() {\n\t\treturn new _Result_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"]();\n\t}\n};\n\n\n//# sourceURL=webpack:///./src/modules/Body.mjs?"); /***/ }), @@ -142,7 +142,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) * /***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Circle; });\n/* harmony import */ var _Body_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Body.mjs */ \"./src/modules/Body.mjs\");\n\r\n\r\n/**\r\n * A circle used to detect collisions\r\n * @class\r\n */\r\nclass Circle extends _Body_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\r\n\t/**\r\n\t * @constructor\r\n\t * @param {Number} [x = 0] The starting X coordinate\r\n\t * @param {Number} [y = 0] The starting Y coordinate\r\n\t * @param {Number} [radius = 0] The radius\r\n\t * @param {Number} [scale = 1] The scale\r\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\r\n\t */\r\n\tconstructor(x = 0, y = 0, radius = 0, scale = 1, padding = 0) {\r\n\t\tsuper(x, y, padding);\r\n\r\n\t\t/**\r\n\t\t * @desc\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.radius = radius;\r\n\r\n\t\t/**\r\n\t\t * @desc\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.scale = scale;\r\n\t}\r\n\r\n\t/**\r\n\t * Draws the circle to a CanvasRenderingContext2D's current path\r\n\t * @param {CanvasRenderingContext2D} context The context to add the arc to\r\n\t */\r\n\tdraw(context) {\r\n\t\tconst x = this.x;\r\n\t\tconst y = this.y;\r\n\t\tconst radius = this.radius * this.scale;\r\n\r\n\t\tcontext.moveTo(x + radius, y);\r\n\t\tcontext.arc(x, y, radius, 0, Math.PI * 2);\r\n\t}\r\n};\r\n\n\n//# sourceURL=webpack:///./src/modules/Circle.mjs?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Circle; });\n/* harmony import */ var _Body_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Body.mjs */ \"./src/modules/Body.mjs\");\n\n\n/**\n * A circle used to detect collisions\n * @class\n */\nclass Circle extends _Body_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\n\t/**\n\t * @constructor\n\t * @param {Number} [x = 0] The starting X coordinate\n\t * @param {Number} [y = 0] The starting Y coordinate\n\t * @param {Number} [radius = 0] The radius\n\t * @param {Number} [scale = 1] The scale\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\n\t */\n\tconstructor(x = 0, y = 0, radius = 0, scale = 1, padding = 0) {\n\t\tsuper(x, y, padding);\n\n\t\t/**\n\t\t * @desc\n\t\t * @type {Number}\n\t\t */\n\t\tthis.radius = radius;\n\n\t\t/**\n\t\t * @desc\n\t\t * @type {Number}\n\t\t */\n\t\tthis.scale = scale;\n\t}\n\n\t/**\n\t * Draws the circle to a CanvasRenderingContext2D's current path\n\t * @param {CanvasRenderingContext2D} context The context to add the arc to\n\t */\n\tdraw(context) {\n\t\tconst x = this.x;\n\t\tconst y = this.y;\n\t\tconst radius = this.radius * this.scale;\n\n\t\tcontext.moveTo(x + radius, y);\n\t\tcontext.arc(x, y, radius, 0, Math.PI * 2);\n\t}\n};\n\n\n//# sourceURL=webpack:///./src/modules/Circle.mjs?"); /***/ }), @@ -154,7 +154,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) * /***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Point; });\n/* harmony import */ var _Polygon_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Polygon.mjs */ \"./src/modules/Polygon.mjs\");\n\r\n\r\n/**\r\n * A point used to detect collisions\r\n * @class\r\n */\r\nclass Point extends _Polygon_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\r\n\t/**\r\n\t * @constructor\r\n\t * @param {Number} [x = 0] The starting X coordinate\r\n\t * @param {Number} [y = 0] The starting Y coordinate\r\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\r\n\t */\r\n\tconstructor(x = 0, y = 0, padding = 0) {\r\n\t\tsuper(x, y, [[0, 0]], 0, 1, 1, padding);\r\n\r\n\t\t/** @private */\r\n\t\tthis._point = true;\r\n\t}\r\n};\r\n\r\nPoint.prototype.setPoints = undefined;\r\n\n\n//# sourceURL=webpack:///./src/modules/Point.mjs?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Point; });\n/* harmony import */ var _Polygon_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Polygon.mjs */ \"./src/modules/Polygon.mjs\");\n\n\n/**\n * A point used to detect collisions\n * @class\n */\nclass Point extends _Polygon_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\n\t/**\n\t * @constructor\n\t * @param {Number} [x = 0] The starting X coordinate\n\t * @param {Number} [y = 0] The starting Y coordinate\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\n\t */\n\tconstructor(x = 0, y = 0, padding = 0) {\n\t\tsuper(x, y, [[0, 0]], 0, 1, 1, padding);\n\n\t\t/** @private */\n\t\tthis._point = true;\n\t}\n};\n\nPoint.prototype.setPoints = undefined;\n\n\n//# sourceURL=webpack:///./src/modules/Point.mjs?"); /***/ }), @@ -166,7 +166,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) * /***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Polygon; });\n/* harmony import */ var _Body_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Body.mjs */ \"./src/modules/Body.mjs\");\n\r\n\r\n/**\r\n * A polygon used to detect collisions\r\n * @class\r\n */\r\nclass Polygon extends _Body_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\r\n\t/**\r\n\t * @constructor\r\n\t * @param {Number} [x = 0] The starting X coordinate\r\n\t * @param {Number} [y = 0] The starting Y coordinate\r\n\t * @param {Array} [points = []] An array of coordinate pairs making up the polygon - [[x1, y1], [x2, y2], ...]\r\n\t * @param {Number} [angle = 0] The starting rotation in radians\r\n\t * @param {Number} [scale_x = 1] The starting scale along the X axis\r\n\t * @param {Number} [scale_y = 1] The starting scale long the Y axis\r\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\r\n\t */\r\n\tconstructor(x = 0, y = 0, points = [], angle = 0, scale_x = 1, scale_y = 1, padding = 0) {\r\n\t\tsuper(x, y, padding);\r\n\r\n\t\t/**\r\n\t\t * @desc The angle of the body in radians\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.angle = angle;\r\n\r\n\t\t/**\r\n\t\t * @desc The scale of the body along the X axis\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.scale_x = scale_x;\r\n\r\n\t\t/**\r\n\t\t * @desc The scale of the body along the Y axis\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.scale_y = scale_y;\r\n\r\n\r\n\t\t/** @private */\r\n\t\tthis._polygon = true;\r\n\r\n\t\t/** @private */\r\n\t\tthis._x = x;\r\n\r\n\t\t/** @private */\r\n\t\tthis._y = y;\r\n\r\n\t\t/** @private */\r\n\t\tthis._angle = angle;\r\n\r\n\t\t/** @private */\r\n\t\tthis._scale_x = scale_x;\r\n\r\n\t\t/** @private */\r\n\t\tthis._scale_y = scale_y;\r\n\r\n\t\t/** @private */\r\n\t\tthis._min_x = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._min_y = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._max_x = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._max_y = 0;\r\n\r\n\t\t/** @private */\r\n\t\tthis._points = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._coords = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._edges = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._normals = null;\r\n\r\n\t\t/** @private */\r\n\t\tthis._dirty_coords = true;\r\n\r\n\t\t/** @private */\r\n\t\tthis._dirty_normals = true;\r\n\r\n\t\tPolygon.prototype.setPoints.call(this, points);\r\n\t}\r\n\r\n\t/**\r\n\t * Draws the polygon to a CanvasRenderingContext2D's current path\r\n\t * @param {CanvasRenderingContext2D} context The context to add the shape to\r\n\t */\r\n\tdraw(context) {\r\n\t\tif(\r\n\t\t\tthis._dirty_coords ||\r\n\t\t\tthis.x !== this._x ||\r\n\t\t\tthis.y !== this._y ||\r\n\t\t\tthis.angle !== this._angle ||\r\n\t\t\tthis.scale_x !== this._scale_x ||\r\n\t\t\tthis.scale_y !== this._scale_y\r\n\t\t) {\r\n\t\t\tthis._calculateCoords();\r\n\t\t}\r\n\r\n\t\tconst coords = this._coords;\r\n\r\n\t\tif(coords.length === 2) {\r\n\t\t\tcontext.moveTo(coords[0], coords[1]);\r\n\t\t\tcontext.arc(coords[0], coords[1], 1, 0, Math.PI * 2);\r\n\t\t}\r\n\t\telse {\r\n\t\t\tcontext.moveTo(coords[0], coords[1]);\r\n\r\n\t\t\tfor(let i = 2; i < coords.length; i += 2) {\r\n\t\t\t\tcontext.lineTo(coords[i], coords[i + 1]);\r\n\t\t\t}\r\n\r\n\t\t\tif(coords.length > 4) {\r\n\t\t\t\tcontext.lineTo(coords[0], coords[1]);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Sets the points making up the polygon. It's important to use this function when changing the polygon's shape to ensure internal data is also updated.\r\n\t * @param {Array} new_points An array of coordinate pairs making up the polygon - [[x1, y1], [x2, y2], ...]\r\n\t */\r\n\tsetPoints(new_points) {\r\n\t\tconst count = new_points.length;\r\n\r\n\t\tthis._points = new Float64Array(count * 2);\r\n\t\tthis._coords = new Float64Array(count * 2);\r\n\t\tthis._edges = new Float64Array(count * 2);\r\n\t\tthis._normals = new Float64Array(count * 2);\r\n\r\n\t\tconst points = this._points;\r\n\r\n\t\tfor(let i = 0, ix = 0, iy = 1; i < count; ++i, ix += 2, iy += 2) {\r\n\t\t\tconst new_point = new_points[i];\r\n\r\n\t\t\tpoints[ix] = new_point[0];\r\n\t\t\tpoints[iy] = new_point[1];\r\n\t\t}\r\n\r\n\t\tthis._dirty_coords = true;\r\n\t}\r\n\r\n\t/**\r\n\t * Calculates and caches the polygon's world coordinates based on its points, angle, and scale\r\n\t */\r\n\t_calculateCoords() {\r\n\t\tconst x = this.x;\r\n\t\tconst y = this.y;\r\n\t\tconst angle = this.angle;\r\n\t\tconst scale_x = this.scale_x;\r\n\t\tconst scale_y = this.scale_y;\r\n\t\tconst points = this._points;\r\n\t\tconst coords = this._coords;\r\n\t\tconst count = points.length;\r\n\r\n\t\tlet min_x;\r\n\t\tlet max_x;\r\n\t\tlet min_y;\r\n\t\tlet max_y;\r\n\r\n\t\tfor(let ix = 0, iy = 1; ix < count; ix += 2, iy += 2) {\r\n\t\t\tlet coord_x = points[ix] * scale_x;\r\n\t\t\tlet coord_y = points[iy] * scale_y;\r\n\r\n\t\t\tif(angle) {\r\n\t\t\t\tconst cos = Math.cos(angle);\r\n\t\t\t\tconst sin = Math.sin(angle);\r\n\t\t\t\tconst tmp_x = coord_x;\r\n\t\t\t\tconst tmp_y = coord_y;\r\n\r\n\t\t\t\tcoord_x = tmp_x * cos - tmp_y * sin;\r\n\t\t\t\tcoord_y = tmp_x * sin + tmp_y * cos;\r\n\t\t\t}\r\n\r\n\t\t\tcoord_x += x;\r\n\t\t\tcoord_y += y;\r\n\r\n\t\t\tcoords[ix] = coord_x;\r\n\t\t\tcoords[iy] = coord_y;\r\n\r\n\t\t\tif(ix === 0) {\r\n\t\t\t\tmin_x = max_x = coord_x;\r\n\t\t\t\tmin_y = max_y = coord_y;\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tif(coord_x < min_x) {\r\n\t\t\t\t\tmin_x = coord_x;\r\n\t\t\t\t}\r\n\t\t\t\telse if(coord_x > max_x) {\r\n\t\t\t\t\tmax_x = coord_x;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif(coord_y < min_y) {\r\n\t\t\t\t\tmin_y = coord_y;\r\n\t\t\t\t}\r\n\t\t\t\telse if(coord_y > max_y) {\r\n\t\t\t\t\tmax_y = coord_y;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis._x = x;\r\n\t\tthis._y = y;\r\n\t\tthis._angle = angle;\r\n\t\tthis._scale_x = scale_x;\r\n\t\tthis._scale_y = scale_y;\r\n\t\tthis._min_x = min_x;\r\n\t\tthis._min_y = min_y;\r\n\t\tthis._max_x = max_x;\r\n\t\tthis._max_y = max_y;\r\n\t\tthis._dirty_coords = false;\r\n\t\tthis._dirty_normals = true;\r\n\t}\r\n\r\n\t/**\r\n\t * Calculates the normals and edges of the polygon's sides\r\n\t */\r\n\t_calculateNormals() {\r\n\t\tconst coords = this._coords;\r\n\t\tconst edges = this._edges;\r\n\t\tconst normals = this._normals;\r\n\t\tconst count = coords.length;\r\n\r\n\t\tfor(let ix = 0, iy = 1; ix < count; ix += 2, iy += 2) {\r\n\t\t\tconst next = ix + 2 < count ? ix + 2 : 0;\r\n\t\t\tconst x = coords[next] - coords[ix];\r\n\t\t\tconst y = coords[next + 1] - coords[iy];\r\n\t\t\tconst length = x || y ? Math.sqrt(x * x + y * y) : 0;\r\n\r\n\t\t\tedges[ix] = x;\r\n\t\t\tedges[iy] = y;\r\n\t\t\tnormals[ix] = length ? y / length : 0;\r\n\t\t\tnormals[iy] = length ? -x / length : 0;\r\n\t\t}\r\n\r\n\t\tthis._dirty_normals = false;\r\n\t}\r\n};\r\n\n\n//# sourceURL=webpack:///./src/modules/Polygon.mjs?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Polygon; });\n/* harmony import */ var _Body_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Body.mjs */ \"./src/modules/Body.mjs\");\n\n\n/**\n * A polygon used to detect collisions\n * @class\n */\nclass Polygon extends _Body_mjs__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\n\t/**\n\t * @constructor\n\t * @param {Number} [x = 0] The starting X coordinate\n\t * @param {Number} [y = 0] The starting Y coordinate\n\t * @param {Array} [points = []] An array of coordinate pairs making up the polygon - [[x1, y1], [x2, y2], ...]\n\t * @param {Number} [angle = 0] The starting rotation in radians\n\t * @param {Number} [scale_x = 1] The starting scale along the X axis\n\t * @param {Number} [scale_y = 1] The starting scale long the Y axis\n\t * @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions\n\t */\n\tconstructor(x = 0, y = 0, points = [], angle = 0, scale_x = 1, scale_y = 1, padding = 0) {\n\t\tsuper(x, y, padding);\n\n\t\t/**\n\t\t * @desc The angle of the body in radians\n\t\t * @type {Number}\n\t\t */\n\t\tthis.angle = angle;\n\n\t\t/**\n\t\t * @desc The scale of the body along the X axis\n\t\t * @type {Number}\n\t\t */\n\t\tthis.scale_x = scale_x;\n\n\t\t/**\n\t\t * @desc The scale of the body along the Y axis\n\t\t * @type {Number}\n\t\t */\n\t\tthis.scale_y = scale_y;\n\n\n\t\t/** @private */\n\t\tthis._polygon = true;\n\n\t\t/** @private */\n\t\tthis._x = x;\n\n\t\t/** @private */\n\t\tthis._y = y;\n\n\t\t/** @private */\n\t\tthis._angle = angle;\n\n\t\t/** @private */\n\t\tthis._scale_x = scale_x;\n\n\t\t/** @private */\n\t\tthis._scale_y = scale_y;\n\n\t\t/** @private */\n\t\tthis._min_x = 0;\n\n\t\t/** @private */\n\t\tthis._min_y = 0;\n\n\t\t/** @private */\n\t\tthis._max_x = 0;\n\n\t\t/** @private */\n\t\tthis._max_y = 0;\n\n\t\t/** @private */\n\t\tthis._points = null;\n\n\t\t/** @private */\n\t\tthis._coords = null;\n\n\t\t/** @private */\n\t\tthis._edges = null;\n\n\t\t/** @private */\n\t\tthis._normals = null;\n\n\t\t/** @private */\n\t\tthis._dirty_coords = true;\n\n\t\t/** @private */\n\t\tthis._dirty_normals = true;\n\n\t\tPolygon.prototype.setPoints.call(this, points);\n\t}\n\n\t/**\n\t * Draws the polygon to a CanvasRenderingContext2D's current path\n\t * @param {CanvasRenderingContext2D} context The context to add the shape to\n\t */\n\tdraw(context) {\n\t\tif(\n\t\t\tthis._dirty_coords ||\n\t\t\tthis.x !== this._x ||\n\t\t\tthis.y !== this._y ||\n\t\t\tthis.angle !== this._angle ||\n\t\t\tthis.scale_x !== this._scale_x ||\n\t\t\tthis.scale_y !== this._scale_y\n\t\t) {\n\t\t\tthis._calculateCoords();\n\t\t}\n\n\t\tconst coords = this._coords;\n\n\t\tif(coords.length === 2) {\n\t\t\tcontext.moveTo(coords[0], coords[1]);\n\t\t\tcontext.arc(coords[0], coords[1], 1, 0, Math.PI * 2);\n\t\t}\n\t\telse {\n\t\t\tcontext.moveTo(coords[0], coords[1]);\n\n\t\t\tfor(let i = 2; i < coords.length; i += 2) {\n\t\t\t\tcontext.lineTo(coords[i], coords[i + 1]);\n\t\t\t}\n\n\t\t\tif(coords.length > 4) {\n\t\t\t\tcontext.lineTo(coords[0], coords[1]);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Sets the points making up the polygon. It's important to use this function when changing the polygon's shape to ensure internal data is also updated.\n\t * @param {Array} new_points An array of coordinate pairs making up the polygon - [[x1, y1], [x2, y2], ...]\n\t */\n\tsetPoints(new_points) {\n\t\tconst count = new_points.length;\n\n\t\tthis._points = new Float64Array(count * 2);\n\t\tthis._coords = new Float64Array(count * 2);\n\t\tthis._edges = new Float64Array(count * 2);\n\t\tthis._normals = new Float64Array(count * 2);\n\n\t\tconst points = this._points;\n\n\t\tfor(let i = 0, ix = 0, iy = 1; i < count; ++i, ix += 2, iy += 2) {\n\t\t\tconst new_point = new_points[i];\n\n\t\t\tpoints[ix] = new_point[0];\n\t\t\tpoints[iy] = new_point[1];\n\t\t}\n\n\t\tthis._dirty_coords = true;\n\t}\n\n\t/**\n\t * Calculates and caches the polygon's world coordinates based on its points, angle, and scale\n\t */\n\t_calculateCoords() {\n\t\tconst x = this.x;\n\t\tconst y = this.y;\n\t\tconst angle = this.angle;\n\t\tconst scale_x = this.scale_x;\n\t\tconst scale_y = this.scale_y;\n\t\tconst points = this._points;\n\t\tconst coords = this._coords;\n\t\tconst count = points.length;\n\n\t\tlet min_x;\n\t\tlet max_x;\n\t\tlet min_y;\n\t\tlet max_y;\n\n\t\tfor(let ix = 0, iy = 1; ix < count; ix += 2, iy += 2) {\n\t\t\tlet coord_x = points[ix] * scale_x;\n\t\t\tlet coord_y = points[iy] * scale_y;\n\n\t\t\tif(angle) {\n\t\t\t\tconst cos = Math.cos(angle);\n\t\t\t\tconst sin = Math.sin(angle);\n\t\t\t\tconst tmp_x = coord_x;\n\t\t\t\tconst tmp_y = coord_y;\n\n\t\t\t\tcoord_x = tmp_x * cos - tmp_y * sin;\n\t\t\t\tcoord_y = tmp_x * sin + tmp_y * cos;\n\t\t\t}\n\n\t\t\tcoord_x += x;\n\t\t\tcoord_y += y;\n\n\t\t\tcoords[ix] = coord_x;\n\t\t\tcoords[iy] = coord_y;\n\n\t\t\tif(ix === 0) {\n\t\t\t\tmin_x = max_x = coord_x;\n\t\t\t\tmin_y = max_y = coord_y;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif(coord_x < min_x) {\n\t\t\t\t\tmin_x = coord_x;\n\t\t\t\t}\n\t\t\t\telse if(coord_x > max_x) {\n\t\t\t\t\tmax_x = coord_x;\n\t\t\t\t}\n\n\t\t\t\tif(coord_y < min_y) {\n\t\t\t\t\tmin_y = coord_y;\n\t\t\t\t}\n\t\t\t\telse if(coord_y > max_y) {\n\t\t\t\t\tmax_y = coord_y;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis._x = x;\n\t\tthis._y = y;\n\t\tthis._angle = angle;\n\t\tthis._scale_x = scale_x;\n\t\tthis._scale_y = scale_y;\n\t\tthis._min_x = min_x;\n\t\tthis._min_y = min_y;\n\t\tthis._max_x = max_x;\n\t\tthis._max_y = max_y;\n\t\tthis._dirty_coords = false;\n\t\tthis._dirty_normals = true;\n\t}\n\n\t/**\n\t * Calculates the normals and edges of the polygon's sides\n\t */\n\t_calculateNormals() {\n\t\tconst coords = this._coords;\n\t\tconst edges = this._edges;\n\t\tconst normals = this._normals;\n\t\tconst count = coords.length;\n\n\t\tfor(let ix = 0, iy = 1; ix < count; ix += 2, iy += 2) {\n\t\t\tconst next = ix + 2 < count ? ix + 2 : 0;\n\t\t\tconst x = coords[next] - coords[ix];\n\t\t\tconst y = coords[next + 1] - coords[iy];\n\t\t\tconst length = x || y ? Math.sqrt(x * x + y * y) : 0;\n\n\t\t\tedges[ix] = x;\n\t\t\tedges[iy] = y;\n\t\t\tnormals[ix] = length ? y / length : 0;\n\t\t\tnormals[iy] = length ? -x / length : 0;\n\t\t}\n\n\t\tthis._dirty_normals = false;\n\t}\n};\n\n\n//# sourceURL=webpack:///./src/modules/Polygon.mjs?"); /***/ }), @@ -178,7 +178,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) * /***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Result; });\n/**\r\n * An object used to collect the detailed results of a collision test\r\n *\r\n * > **Note:** It is highly recommended you recycle the same Result object if possible in order to avoid wasting memory\r\n * @class\r\n */\r\nclass Result {\r\n\t/**\r\n\t * @constructor\r\n\t */\r\n\tconstructor() {\r\n\t\t/**\r\n\t\t * @desc True if a collision was detected\r\n\t\t * @type {Boolean}\r\n\t\t */\r\n\t\tthis.collision = false;\r\n\r\n\t\t/**\r\n\t\t * @desc The source body tested\r\n\t\t * @type {Circle|Polygon|Point}\r\n\t\t */\r\n\t\tthis.a = null;\r\n\r\n\t\t/**\r\n\t\t * @desc The target body tested against\r\n\t\t * @type {Circle|Polygon|Point}\r\n\t\t */\r\n\t\tthis.b = null;\r\n\r\n\t\t/**\r\n\t\t * @desc True if A is completely contained within B\r\n\t\t * @type {Boolean}\r\n\t\t */\r\n\t\tthis.a_in_b = false;\r\n\r\n\t\t/**\r\n\t\t * @desc True if B is completely contained within A\r\n\t\t * @type {Boolean}\r\n\t\t */\r\n\t\tthis.b_in_a = false;\r\n\r\n\t\t/**\r\n\t\t * @desc The magnitude of the shortest axis of overlap\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.overlap = 0;\r\n\r\n\t\t/**\r\n\t\t * @desc The X direction of the shortest axis of overlap\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.overlap_x = 0;\r\n\r\n\t\t/**\r\n\t\t * @desc The Y direction of the shortest axis of overlap\r\n\t\t * @type {Number}\r\n\t\t */\r\n\t\tthis.overlap_y = 0;\r\n\t}\r\n};\r\n\n\n//# sourceURL=webpack:///./src/modules/Result.mjs?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return Result; });\n/**\n * An object used to collect the detailed results of a collision test\n *\n * > **Note:** It is highly recommended you recycle the same Result object if possible in order to avoid wasting memory\n * @class\n */\nclass Result {\n\t/**\n\t * @constructor\n\t */\n\tconstructor() {\n\t\t/**\n\t\t * @desc True if a collision was detected\n\t\t * @type {Boolean}\n\t\t */\n\t\tthis.collision = false;\n\n\t\t/**\n\t\t * @desc The source body tested\n\t\t * @type {Circle|Polygon|Point}\n\t\t */\n\t\tthis.a = null;\n\n\t\t/**\n\t\t * @desc The target body tested against\n\t\t * @type {Circle|Polygon|Point}\n\t\t */\n\t\tthis.b = null;\n\n\t\t/**\n\t\t * @desc True if A is completely contained within B\n\t\t * @type {Boolean}\n\t\t */\n\t\tthis.a_in_b = false;\n\n\t\t/**\n\t\t * @desc True if B is completely contained within A\n\t\t * @type {Boolean}\n\t\t */\n\t\tthis.b_in_a = false;\n\n\t\t/**\n\t\t * @desc The magnitude of the shortest axis of overlap\n\t\t * @type {Number}\n\t\t */\n\t\tthis.overlap = 0;\n\n\t\t/**\n\t\t * @desc The X direction of the shortest axis of overlap\n\t\t * @type {Number}\n\t\t */\n\t\tthis.overlap_x = 0;\n\n\t\t/**\n\t\t * @desc The Y direction of the shortest axis of overlap\n\t\t * @type {Number}\n\t\t */\n\t\tthis.overlap_y = 0;\n\t}\n};\n\n\n//# sourceURL=webpack:///./src/modules/Result.mjs?"); /***/ }), @@ -190,7 +190,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) * /***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { "use strict"; -eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return SAT; });\n/**\r\n * Determines if two bodies are colliding using the Separating Axis Theorem\r\n * @private\r\n * @param {Circle|Polygon|Point} a The source body to test\r\n * @param {Circle|Polygon|Point} b The target body to test against\r\n * @param {Result} [result = null] A Result object on which to store information about the collision\r\n * @param {Boolean} [aabb = true] Set to false to skip the AABB test (useful if you use your own collision heuristic)\r\n * @returns {Boolean}\r\n */\r\nfunction SAT(a, b, result = null, aabb = true) {\r\n\tconst a_polygon = a._polygon;\r\n\tconst b_polygon = b._polygon;\r\n\r\n\tlet collision = false;\r\n\r\n\tif(result) {\r\n\t\tresult.a = a;\r\n\t\tresult.b = b;\r\n\t\tresult.a_in_b = true;\r\n\t\tresult.b_in_a = true;\r\n\t\tresult.overlap = null;\r\n\t\tresult.overlap_x = 0;\r\n\t\tresult.overlap_y = 0;\r\n\t}\r\n\r\n\tif(a_polygon) {\r\n\t\tif(\r\n\t\t\ta._dirty_coords ||\r\n\t\t\ta.x !== a._x ||\r\n\t\t\ta.y !== a._y ||\r\n\t\t\ta.angle !== a._angle ||\r\n\t\t\ta.scale_x !== a._scale_x ||\r\n\t\t\ta.scale_y !== a._scale_y\r\n\t\t) {\r\n\t\t\ta._calculateCoords();\r\n\t\t}\r\n\t}\r\n\r\n\tif(b_polygon) {\r\n\t\tif(\r\n\t\t\tb._dirty_coords ||\r\n\t\t\tb.x !== b._x ||\r\n\t\t\tb.y !== b._y ||\r\n\t\t\tb.angle !== b._angle ||\r\n\t\t\tb.scale_x !== b._scale_x ||\r\n\t\t\tb.scale_y !== b._scale_y\r\n\t\t) {\r\n\t\t\tb._calculateCoords();\r\n\t\t}\r\n\t}\r\n\r\n\tif(!aabb || aabbAABB(a, b)) {\r\n\t\tif(a_polygon && a._dirty_normals) {\r\n\t\t\ta._calculateNormals();\r\n\t\t}\r\n\r\n\t\tif(b_polygon && b._dirty_normals) {\r\n\t\t\tb._calculateNormals();\r\n\t\t}\r\n\r\n\t\tcollision = (\r\n\t\t\ta_polygon && b_polygon ? polygonPolygon(a, b, result) :\r\n\t\t\ta_polygon ? polygonCircle(a, b, result, false) :\r\n\t\t\tb_polygon ? polygonCircle(b, a, result, true) :\r\n\t\t\tcircleCircle(a, b, result)\r\n\t\t);\r\n\t}\r\n\r\n\tif(result) {\r\n\t\tresult.collision = collision;\r\n\t}\r\n\r\n\treturn collision;\r\n};\r\n\r\n/**\r\n * Determines if two bodies' axis aligned bounding boxes are colliding\r\n * @param {Circle|Polygon|Point} a The source body to test\r\n * @param {Circle|Polygon|Point} b The target body to test against\r\n */\r\nfunction aabbAABB(a, b) {\r\n\tconst a_polygon = a._polygon;\r\n\tconst a_x = a_polygon ? 0 : a.x;\r\n\tconst a_y = a_polygon ? 0 : a.y;\r\n\tconst a_radius = a_polygon ? 0 : a.radius * a.scale;\r\n\tconst a_min_x = a_polygon ? a._min_x : a_x - a_radius;\r\n\tconst a_min_y = a_polygon ? a._min_y : a_y - a_radius;\r\n\tconst a_max_x = a_polygon ? a._max_x : a_x + a_radius;\r\n\tconst a_max_y = a_polygon ? a._max_y : a_y + a_radius;\r\n\r\n\tconst b_polygon = b._polygon;\r\n\tconst b_x = b_polygon ? 0 : b.x;\r\n\tconst b_y = b_polygon ? 0 : b.y;\r\n\tconst b_radius = b_polygon ? 0 : b.radius * b.scale;\r\n\tconst b_min_x = b_polygon ? b._min_x : b_x - b_radius;\r\n\tconst b_min_y = b_polygon ? b._min_y : b_y - b_radius;\r\n\tconst b_max_x = b_polygon ? b._max_x : b_x + b_radius;\r\n\tconst b_max_y = b_polygon ? b._max_y : b_y + b_radius;\r\n\r\n\treturn a_min_x < b_max_x && a_min_y < b_max_y && a_max_x > b_min_x && a_max_y > b_min_y;\r\n}\r\n\r\n/**\r\n * Determines if two polygons are colliding\r\n * @param {Polygon} a The source polygon to test\r\n * @param {Polygon} b The target polygon to test against\r\n * @param {Result} [result = null] A Result object on which to store information about the collision\r\n * @returns {Boolean}\r\n */\r\nfunction polygonPolygon(a, b, result = null) {\r\n\tconst a_count = a._coords.length;\r\n\tconst b_count = b._coords.length;\r\n\r\n\t// Handle points specially\r\n\tif(a_count === 2 && b_count === 2) {\r\n\t\tconst a_coords = a._coords;\r\n\t\tconst b_coords = b._coords;\r\n\r\n\t\tif(result) {\r\n\t\t\tresult.overlap = 0;\r\n\t\t}\r\n\r\n\t\treturn a_coords[0] === b_coords[0] && a_coords[1] === b_coords[1];\r\n\t}\r\n\r\n\tconst a_coords = a._coords;\r\n\tconst b_coords = b._coords;\r\n\tconst a_normals = a._normals;\r\n\tconst b_normals = b._normals;\r\n\r\n\tif(a_count > 2) {\r\n\t\tfor(let ix = 0, iy = 1; ix < a_count; ix += 2, iy += 2) {\r\n\t\t\tif(separatingAxis(a_coords, b_coords, a_normals[ix], a_normals[iy], result)) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tif(b_count > 2) {\r\n\t\tfor(let ix = 0, iy = 1; ix < b_count; ix += 2, iy += 2) {\r\n\t\t\tif(separatingAxis(a_coords, b_coords, b_normals[ix], b_normals[iy], result)) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn true;\r\n}\r\n\r\n/**\r\n * Determines if a polygon and a circle are colliding\r\n * @param {Polygon} a The source polygon to test\r\n * @param {Circle} b The target circle to test against\r\n * @param {Result} [result = null] A Result object on which to store information about the collision\r\n * @param {Boolean} [reverse = false] Set to true to reverse a and b in the result parameter when testing circle->polygon instead of polygon->circle\r\n * @returns {Boolean}\r\n */\r\nfunction polygonCircle(a, b, result = null, reverse = false) {\r\n\tconst a_coords = a._coords;\r\n\tconst a_edges = a._edges;\r\n\tconst a_normals = a._normals;\r\n\tconst b_x = b.x;\r\n\tconst b_y = b.y;\r\n\tconst b_radius = b.radius * b.scale;\r\n\tconst b_radius2 = b_radius * 2;\r\n\tconst radius_squared = b_radius * b_radius;\r\n\tconst count = a_coords.length;\r\n\r\n\tlet a_in_b = true;\r\n\tlet b_in_a = true;\r\n\tlet overlap = null;\r\n\tlet overlap_x = 0;\r\n\tlet overlap_y = 0;\r\n\r\n\t// Handle points specially\r\n\tif(count === 2) {\r\n\t\tconst coord_x = b_x - a_coords[0];\r\n\t\tconst coord_y = b_y - a_coords[1];\r\n\t\tconst length_squared = coord_x * coord_x + coord_y * coord_y;\r\n\r\n\t\tif(length_squared > radius_squared) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tif(result) {\r\n\t\t\tconst length = Math.sqrt(length_squared);\r\n\r\n\t\t\toverlap = b_radius - length;\r\n\t\t\toverlap_x = coord_x / length;\r\n\t\t\toverlap_y = coord_y / length;\r\n\t\t\tb_in_a = false;\r\n\t\t}\r\n\t}\r\n\telse {\r\n\t\tfor(let ix = 0, iy = 1; ix < count; ix += 2, iy += 2) {\r\n\t\t\tconst coord_x = b_x - a_coords[ix];\r\n\t\t\tconst coord_y = b_y - a_coords[iy];\r\n\t\t\tconst edge_x = a_edges[ix];\r\n\t\t\tconst edge_y = a_edges[iy];\r\n\t\t\tconst dot = coord_x * edge_x + coord_y * edge_y;\r\n\t\t\tconst region = dot < 0 ? -1 : dot > edge_x * edge_x + edge_y * edge_y ? 1 : 0;\r\n\r\n\t\t\tlet tmp_overlapping = false;\r\n\t\t\tlet tmp_overlap = 0;\r\n\t\t\tlet tmp_overlap_x = 0;\r\n\t\t\tlet tmp_overlap_y = 0;\r\n\r\n\t\t\tif(result && a_in_b && coord_x * coord_x + coord_y * coord_y > radius_squared) {\r\n\t\t\t\ta_in_b = false;\r\n\t\t\t}\r\n\r\n\t\t\tif(region) {\r\n\t\t\t\tconst left = region === -1;\r\n\t\t\t\tconst other_x = left ? (ix === 0 ? count - 2 : ix - 2) : (ix === count - 2 ? 0 : ix + 2);\r\n\t\t\t\tconst other_y = other_x + 1;\r\n\t\t\t\tconst coord2_x = b_x - a_coords[other_x];\r\n\t\t\t\tconst coord2_y = b_y - a_coords[other_y];\r\n\t\t\t\tconst edge2_x = a_edges[other_x];\r\n\t\t\t\tconst edge2_y = a_edges[other_y];\r\n\t\t\t\tconst dot2 = coord2_x * edge2_x + coord2_y * edge2_y;\r\n\t\t\t\tconst region2 = dot2 < 0 ? -1 : dot2 > edge2_x * edge2_x + edge2_y * edge2_y ? 1 : 0;\r\n\r\n\t\t\t\tif(region2 === -region) {\r\n\t\t\t\t\tconst target_x = left ? coord_x : coord2_x;\r\n\t\t\t\t\tconst target_y = left ? coord_y : coord2_y;\r\n\t\t\t\t\tconst length_squared = target_x * target_x + target_y * target_y;\r\n\r\n\t\t\t\t\tif(length_squared > radius_squared) {\r\n\t\t\t\t\t\treturn false;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif(result) {\r\n\t\t\t\t\t\tconst length = Math.sqrt(length_squared);\r\n\r\n\t\t\t\t\t\ttmp_overlapping = true;\r\n\t\t\t\t\t\ttmp_overlap = b_radius - length;\r\n\t\t\t\t\t\ttmp_overlap_x = target_x / length;\r\n\t\t\t\t\t\ttmp_overlap_y = target_y / length;\r\n\t\t\t\t\t\tb_in_a = false;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tconst normal_x = a_normals[ix];\r\n\t\t\t\tconst normal_y = a_normals[iy];\r\n\t\t\t\tconst length = coord_x * normal_x + coord_y * normal_y;\r\n\t\t\t\tconst absolute_length = length < 0 ? -length : length;\r\n\r\n\t\t\t\tif(length > 0 && absolute_length > b_radius) {\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif(result) {\r\n\t\t\t\t\ttmp_overlapping = true;\r\n\t\t\t\t\ttmp_overlap = b_radius - length;\r\n\t\t\t\t\ttmp_overlap_x = normal_x;\r\n\t\t\t\t\ttmp_overlap_y = normal_y;\r\n\r\n\t\t\t\t\tif(b_in_a && length >= 0 || tmp_overlap < b_radius2) {\r\n\t\t\t\t\t\tb_in_a = false;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif(tmp_overlapping && (overlap === null || overlap > tmp_overlap)) {\r\n\t\t\t\toverlap = tmp_overlap;\r\n\t\t\t\toverlap_x = tmp_overlap_x;\r\n\t\t\t\toverlap_y = tmp_overlap_y;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tif(result) {\r\n\t\tresult.a_in_b = reverse ? b_in_a : a_in_b;\r\n\t\tresult.b_in_a = reverse ? a_in_b : b_in_a;\r\n\t\tresult.overlap = overlap;\r\n\t\tresult.overlap_x = reverse ? -overlap_x : overlap_x;\r\n\t\tresult.overlap_y = reverse ? -overlap_y : overlap_y;\r\n\t}\r\n\r\n\treturn true;\r\n}\r\n\r\n/**\r\n * Determines if two circles are colliding\r\n * @param {Circle} a The source circle to test\r\n * @param {Circle} b The target circle to test against\r\n * @param {Result} [result = null] A Result object on which to store information about the collision\r\n * @returns {Boolean}\r\n */\r\nfunction circleCircle(a, b, result = null) {\r\n\tconst a_radius = a.radius * a.scale;\r\n\tconst b_radius = b.radius * b.scale;\r\n\tconst difference_x = b.x - a.x;\r\n\tconst difference_y = b.y - a.y;\r\n\tconst radius_sum = a_radius + b_radius;\r\n\tconst length_squared = difference_x * difference_x + difference_y * difference_y;\r\n\r\n\tif(length_squared > radius_sum * radius_sum) {\r\n\t\treturn false;\r\n\t}\r\n\r\n\tif(result) {\r\n\t\tconst length = Math.sqrt(length_squared);\r\n\r\n\t\tresult.a_in_b = a_radius <= b_radius && length <= b_radius - a_radius;\r\n\t\tresult.b_in_a = b_radius <= a_radius && length <= a_radius - b_radius;\r\n\t\tresult.overlap = radius_sum - length;\r\n\t\tresult.overlap_x = difference_x / length;\r\n\t\tresult.overlap_y = difference_y / length;\r\n\t}\r\n\r\n\treturn true;\r\n}\r\n\r\n/**\r\n * Determines if two polygons are separated by an axis\r\n * @param {Array} a_coords The coordinates of the polygon to test\r\n * @param {Array} b_coords The coordinates of the polygon to test against\r\n * @param {Number} x The X direction of the axis\r\n * @param {Number} y The Y direction of the axis\r\n * @param {Result} [result = null] A Result object on which to store information about the collision\r\n * @returns {Boolean}\r\n */\r\nfunction separatingAxis(a_coords, b_coords, x, y, result = null) {\r\n\tconst a_count = a_coords.length;\r\n\tconst b_count = b_coords.length;\r\n\r\n\tif(!a_count || !b_count) {\r\n\t\treturn true;\r\n\t}\r\n\r\n\tlet a_start = null;\r\n\tlet a_end = null;\r\n\tlet b_start = null;\r\n\tlet b_end = null;\r\n\r\n\tfor(let ix = 0, iy = 1; ix < a_count; ix += 2, iy += 2) {\r\n\t\tconst dot = a_coords[ix] * x + a_coords[iy] * y;\r\n\r\n\t\tif(a_start === null || a_start > dot) {\r\n\t\t\ta_start = dot;\r\n\t\t}\r\n\r\n\t\tif(a_end === null || a_end < dot) {\r\n\t\t\ta_end = dot;\r\n\t\t}\r\n\t}\r\n\r\n\tfor(let ix = 0, iy = 1; ix < b_count; ix += 2, iy += 2) {\r\n\t\tconst dot = b_coords[ix] * x + b_coords[iy] * y;\r\n\r\n\t\tif(b_start === null || b_start > dot) {\r\n\t\t\tb_start = dot;\r\n\t\t}\r\n\r\n\t\tif(b_end === null || b_end < dot) {\r\n\t\t\tb_end = dot;\r\n\t\t}\r\n\t}\r\n\r\n\tif(a_start > b_end || a_end < b_start) {\r\n\t\treturn true;\r\n\t}\r\n\r\n\tif(result) {\r\n\t\tlet overlap = 0;\r\n\r\n\t\tif(a_start < b_start) {\r\n\t\t\tresult.a_in_b = false;\r\n\r\n\t\t\tif(a_end < b_end) {\r\n\t\t\t\toverlap = a_end - b_start;\r\n\t\t\t\tresult.b_in_a = false;\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tconst option1 = a_end - b_start;\r\n\t\t\t\tconst option2 = b_end - a_start;\r\n\r\n\t\t\t\toverlap = option1 < option2 ? option1 : -option2;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse {\r\n\t\t\tresult.b_in_a = false;\r\n\r\n\t\t\tif(a_end > b_end) {\r\n\t\t\t\toverlap = a_start - b_end;\r\n\t\t\t\tresult.a_in_b = false;\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tconst option1 = a_end - b_start;\r\n\t\t\t\tconst option2 = b_end - a_start;\r\n\r\n\t\t\t\toverlap = option1 < option2 ? option1 : -option2;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tconst current_overlap = result.overlap;\r\n\t\tconst absolute_overlap = overlap < 0 ? -overlap : overlap;\r\n\r\n\t\tif(current_overlap === null || current_overlap > absolute_overlap) {\r\n\t\t\tconst sign = overlap < 0 ? -1 : 1;\r\n\r\n\t\t\tresult.overlap = absolute_overlap;\r\n\t\t\tresult.overlap_x = x * sign;\r\n\t\t\tresult.overlap_y = y * sign;\r\n\t\t}\r\n\t}\r\n\r\n\treturn false;\r\n}\r\n\n\n//# sourceURL=webpack:///./src/modules/SAT.mjs?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return SAT; });\n/**\n * Determines if two bodies are colliding using the Separating Axis Theorem\n * @private\n * @param {Circle|Polygon|Point} a The source body to test\n * @param {Circle|Polygon|Point} b The target body to test against\n * @param {Result} [result = null] A Result object on which to store information about the collision\n * @param {Boolean} [aabb = true] Set to false to skip the AABB test (useful if you use your own collision heuristic)\n * @returns {Boolean}\n */\nfunction SAT(a, b, result = null, aabb = true) {\n\tconst a_polygon = a._polygon;\n\tconst b_polygon = b._polygon;\n\n\tlet collision = false;\n\n\tif(result) {\n\t\tresult.a = a;\n\t\tresult.b = b;\n\t\tresult.a_in_b = true;\n\t\tresult.b_in_a = true;\n\t\tresult.overlap = null;\n\t\tresult.overlap_x = 0;\n\t\tresult.overlap_y = 0;\n\t}\n\n\tif(a_polygon) {\n\t\tif(\n\t\t\ta._dirty_coords ||\n\t\t\ta.x !== a._x ||\n\t\t\ta.y !== a._y ||\n\t\t\ta.angle !== a._angle ||\n\t\t\ta.scale_x !== a._scale_x ||\n\t\t\ta.scale_y !== a._scale_y\n\t\t) {\n\t\t\ta._calculateCoords();\n\t\t}\n\t}\n\n\tif(b_polygon) {\n\t\tif(\n\t\t\tb._dirty_coords ||\n\t\t\tb.x !== b._x ||\n\t\t\tb.y !== b._y ||\n\t\t\tb.angle !== b._angle ||\n\t\t\tb.scale_x !== b._scale_x ||\n\t\t\tb.scale_y !== b._scale_y\n\t\t) {\n\t\t\tb._calculateCoords();\n\t\t}\n\t}\n\n\tif(!aabb || aabbAABB(a, b)) {\n\t\tif(a_polygon && a._dirty_normals) {\n\t\t\ta._calculateNormals();\n\t\t}\n\n\t\tif(b_polygon && b._dirty_normals) {\n\t\t\tb._calculateNormals();\n\t\t}\n\n\t\tcollision = (\n\t\t\ta_polygon && b_polygon ? polygonPolygon(a, b, result) :\n\t\t\ta_polygon ? polygonCircle(a, b, result, false) :\n\t\t\tb_polygon ? polygonCircle(b, a, result, true) :\n\t\t\tcircleCircle(a, b, result)\n\t\t);\n\t}\n\n\tif(result) {\n\t\tresult.collision = collision;\n\t}\n\n\treturn collision;\n};\n\n/**\n * Determines if two bodies' axis aligned bounding boxes are colliding\n * @param {Circle|Polygon|Point} a The source body to test\n * @param {Circle|Polygon|Point} b The target body to test against\n */\nfunction aabbAABB(a, b) {\n\tconst a_polygon = a._polygon;\n\tconst a_x = a_polygon ? 0 : a.x;\n\tconst a_y = a_polygon ? 0 : a.y;\n\tconst a_radius = a_polygon ? 0 : a.radius * a.scale;\n\tconst a_min_x = a_polygon ? a._min_x : a_x - a_radius;\n\tconst a_min_y = a_polygon ? a._min_y : a_y - a_radius;\n\tconst a_max_x = a_polygon ? a._max_x : a_x + a_radius;\n\tconst a_max_y = a_polygon ? a._max_y : a_y + a_radius;\n\n\tconst b_polygon = b._polygon;\n\tconst b_x = b_polygon ? 0 : b.x;\n\tconst b_y = b_polygon ? 0 : b.y;\n\tconst b_radius = b_polygon ? 0 : b.radius * b.scale;\n\tconst b_min_x = b_polygon ? b._min_x : b_x - b_radius;\n\tconst b_min_y = b_polygon ? b._min_y : b_y - b_radius;\n\tconst b_max_x = b_polygon ? b._max_x : b_x + b_radius;\n\tconst b_max_y = b_polygon ? b._max_y : b_y + b_radius;\n\n\treturn a_min_x < b_max_x && a_min_y < b_max_y && a_max_x > b_min_x && a_max_y > b_min_y;\n}\n\n/**\n * Determines if two polygons are colliding\n * @param {Polygon} a The source polygon to test\n * @param {Polygon} b The target polygon to test against\n * @param {Result} [result = null] A Result object on which to store information about the collision\n * @returns {Boolean}\n */\nfunction polygonPolygon(a, b, result = null) {\n\tconst a_count = a._coords.length;\n\tconst b_count = b._coords.length;\n\n\t// Handle points specially\n\tif(a_count === 2 && b_count === 2) {\n\t\tconst a_coords = a._coords;\n\t\tconst b_coords = b._coords;\n\n\t\tif(result) {\n\t\t\tresult.overlap = 0;\n\t\t}\n\n\t\treturn a_coords[0] === b_coords[0] && a_coords[1] === b_coords[1];\n\t}\n\n\tconst a_coords = a._coords;\n\tconst b_coords = b._coords;\n\tconst a_normals = a._normals;\n\tconst b_normals = b._normals;\n\n\tif(a_count > 2) {\n\t\tfor(let ix = 0, iy = 1; ix < a_count; ix += 2, iy += 2) {\n\t\t\tif(separatingAxis(a_coords, b_coords, a_normals[ix], a_normals[iy], result)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n\n\tif(b_count > 2) {\n\t\tfor(let ix = 0, iy = 1; ix < b_count; ix += 2, iy += 2) {\n\t\t\tif(separatingAxis(a_coords, b_coords, b_normals[ix], b_normals[iy], result)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn true;\n}\n\n/**\n * Determines if a polygon and a circle are colliding\n * @param {Polygon} a The source polygon to test\n * @param {Circle} b The target circle to test against\n * @param {Result} [result = null] A Result object on which to store information about the collision\n * @param {Boolean} [reverse = false] Set to true to reverse a and b in the result parameter when testing circle->polygon instead of polygon->circle\n * @returns {Boolean}\n */\nfunction polygonCircle(a, b, result = null, reverse = false) {\n\tconst a_coords = a._coords;\n\tconst a_edges = a._edges;\n\tconst a_normals = a._normals;\n\tconst b_x = b.x;\n\tconst b_y = b.y;\n\tconst b_radius = b.radius * b.scale;\n\tconst b_radius2 = b_radius * 2;\n\tconst radius_squared = b_radius * b_radius;\n\tconst count = a_coords.length;\n\n\tlet a_in_b = true;\n\tlet b_in_a = true;\n\tlet overlap = null;\n\tlet overlap_x = 0;\n\tlet overlap_y = 0;\n\n\t// Handle points specially\n\tif(count === 2) {\n\t\tconst coord_x = b_x - a_coords[0];\n\t\tconst coord_y = b_y - a_coords[1];\n\t\tconst length_squared = coord_x * coord_x + coord_y * coord_y;\n\n\t\tif(length_squared > radius_squared) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif(result) {\n\t\t\tconst length = Math.sqrt(length_squared);\n\n\t\t\toverlap = b_radius - length;\n\t\t\toverlap_x = coord_x / length;\n\t\t\toverlap_y = coord_y / length;\n\t\t\tb_in_a = false;\n\t\t}\n\t}\n\telse {\n\t\tfor(let ix = 0, iy = 1; ix < count; ix += 2, iy += 2) {\n\t\t\tconst coord_x = b_x - a_coords[ix];\n\t\t\tconst coord_y = b_y - a_coords[iy];\n\t\t\tconst edge_x = a_edges[ix];\n\t\t\tconst edge_y = a_edges[iy];\n\t\t\tconst dot = coord_x * edge_x + coord_y * edge_y;\n\t\t\tconst region = dot < 0 ? -1 : dot > edge_x * edge_x + edge_y * edge_y ? 1 : 0;\n\n\t\t\tlet tmp_overlapping = false;\n\t\t\tlet tmp_overlap = 0;\n\t\t\tlet tmp_overlap_x = 0;\n\t\t\tlet tmp_overlap_y = 0;\n\n\t\t\tif(result && a_in_b && coord_x * coord_x + coord_y * coord_y > radius_squared) {\n\t\t\t\ta_in_b = false;\n\t\t\t}\n\n\t\t\tif(region) {\n\t\t\t\tconst left = region === -1;\n\t\t\t\tconst other_x = left ? (ix === 0 ? count - 2 : ix - 2) : (ix === count - 2 ? 0 : ix + 2);\n\t\t\t\tconst other_y = other_x + 1;\n\t\t\t\tconst coord2_x = b_x - a_coords[other_x];\n\t\t\t\tconst coord2_y = b_y - a_coords[other_y];\n\t\t\t\tconst edge2_x = a_edges[other_x];\n\t\t\t\tconst edge2_y = a_edges[other_y];\n\t\t\t\tconst dot2 = coord2_x * edge2_x + coord2_y * edge2_y;\n\t\t\t\tconst region2 = dot2 < 0 ? -1 : dot2 > edge2_x * edge2_x + edge2_y * edge2_y ? 1 : 0;\n\n\t\t\t\tif(region2 === -region) {\n\t\t\t\t\tconst target_x = left ? coord_x : coord2_x;\n\t\t\t\t\tconst target_y = left ? coord_y : coord2_y;\n\t\t\t\t\tconst length_squared = target_x * target_x + target_y * target_y;\n\n\t\t\t\t\tif(length_squared > radius_squared) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\n\t\t\t\t\tif(result) {\n\t\t\t\t\t\tconst length = Math.sqrt(length_squared);\n\n\t\t\t\t\t\ttmp_overlapping = true;\n\t\t\t\t\t\ttmp_overlap = b_radius - length;\n\t\t\t\t\t\ttmp_overlap_x = target_x / length;\n\t\t\t\t\t\ttmp_overlap_y = target_y / length;\n\t\t\t\t\t\tb_in_a = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\telse {\n\t\t\t\tconst normal_x = a_normals[ix];\n\t\t\t\tconst normal_y = a_normals[iy];\n\t\t\t\tconst length = coord_x * normal_x + coord_y * normal_y;\n\t\t\t\tconst absolute_length = length < 0 ? -length : length;\n\n\t\t\t\tif(length > 0 && absolute_length > b_radius) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tif(result) {\n\t\t\t\t\ttmp_overlapping = true;\n\t\t\t\t\ttmp_overlap = b_radius - length;\n\t\t\t\t\ttmp_overlap_x = normal_x;\n\t\t\t\t\ttmp_overlap_y = normal_y;\n\n\t\t\t\t\tif(b_in_a && length >= 0 || tmp_overlap < b_radius2) {\n\t\t\t\t\t\tb_in_a = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif(tmp_overlapping && (overlap === null || overlap > tmp_overlap)) {\n\t\t\t\toverlap = tmp_overlap;\n\t\t\t\toverlap_x = tmp_overlap_x;\n\t\t\t\toverlap_y = tmp_overlap_y;\n\t\t\t}\n\t\t}\n\t}\n\n\tif(result) {\n\t\tresult.a_in_b = reverse ? b_in_a : a_in_b;\n\t\tresult.b_in_a = reverse ? a_in_b : b_in_a;\n\t\tresult.overlap = overlap;\n\t\tresult.overlap_x = reverse ? -overlap_x : overlap_x;\n\t\tresult.overlap_y = reverse ? -overlap_y : overlap_y;\n\t}\n\n\treturn true;\n}\n\n/**\n * Determines if two circles are colliding\n * @param {Circle} a The source circle to test\n * @param {Circle} b The target circle to test against\n * @param {Result} [result = null] A Result object on which to store information about the collision\n * @returns {Boolean}\n */\nfunction circleCircle(a, b, result = null) {\n\tconst a_radius = a.radius * a.scale;\n\tconst b_radius = b.radius * b.scale;\n\tconst difference_x = b.x - a.x;\n\tconst difference_y = b.y - a.y;\n\tconst radius_sum = a_radius + b_radius;\n\tconst length_squared = difference_x * difference_x + difference_y * difference_y;\n\n\tif(length_squared > radius_sum * radius_sum) {\n\t\treturn false;\n\t}\n\n\tif(result) {\n\t\tconst length = Math.sqrt(length_squared);\n\n\t\tresult.a_in_b = a_radius <= b_radius && length <= b_radius - a_radius;\n\t\tresult.b_in_a = b_radius <= a_radius && length <= a_radius - b_radius;\n\t\tresult.overlap = radius_sum - length;\n\t\tresult.overlap_x = difference_x / length;\n\t\tresult.overlap_y = difference_y / length;\n\t}\n\n\treturn true;\n}\n\n/**\n * Determines if two polygons are separated by an axis\n * @param {Array} a_coords The coordinates of the polygon to test\n * @param {Array} b_coords The coordinates of the polygon to test against\n * @param {Number} x The X direction of the axis\n * @param {Number} y The Y direction of the axis\n * @param {Result} [result = null] A Result object on which to store information about the collision\n * @returns {Boolean}\n */\nfunction separatingAxis(a_coords, b_coords, x, y, result = null) {\n\tconst a_count = a_coords.length;\n\tconst b_count = b_coords.length;\n\n\tif(!a_count || !b_count) {\n\t\treturn true;\n\t}\n\n\tlet a_start = null;\n\tlet a_end = null;\n\tlet b_start = null;\n\tlet b_end = null;\n\n\tfor(let ix = 0, iy = 1; ix < a_count; ix += 2, iy += 2) {\n\t\tconst dot = a_coords[ix] * x + a_coords[iy] * y;\n\n\t\tif(a_start === null || a_start > dot) {\n\t\t\ta_start = dot;\n\t\t}\n\n\t\tif(a_end === null || a_end < dot) {\n\t\t\ta_end = dot;\n\t\t}\n\t}\n\n\tfor(let ix = 0, iy = 1; ix < b_count; ix += 2, iy += 2) {\n\t\tconst dot = b_coords[ix] * x + b_coords[iy] * y;\n\n\t\tif(b_start === null || b_start > dot) {\n\t\t\tb_start = dot;\n\t\t}\n\n\t\tif(b_end === null || b_end < dot) {\n\t\t\tb_end = dot;\n\t\t}\n\t}\n\n\tif(a_start > b_end || a_end < b_start) {\n\t\treturn true;\n\t}\n\n\tif(result) {\n\t\tlet overlap = 0;\n\n\t\tif(a_start < b_start) {\n\t\t\tresult.a_in_b = false;\n\n\t\t\tif(a_end < b_end) {\n\t\t\t\toverlap = a_end - b_start;\n\t\t\t\tresult.b_in_a = false;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tconst option1 = a_end - b_start;\n\t\t\t\tconst option2 = b_end - a_start;\n\n\t\t\t\toverlap = option1 < option2 ? option1 : -option2;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tresult.b_in_a = false;\n\n\t\t\tif(a_end > b_end) {\n\t\t\t\toverlap = a_start - b_end;\n\t\t\t\tresult.a_in_b = false;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tconst option1 = a_end - b_start;\n\t\t\t\tconst option2 = b_end - a_start;\n\n\t\t\t\toverlap = option1 < option2 ? option1 : -option2;\n\t\t\t}\n\t\t}\n\n\t\tconst current_overlap = result.overlap;\n\t\tconst absolute_overlap = overlap < 0 ? -overlap : overlap;\n\n\t\tif(current_overlap === null || current_overlap > absolute_overlap) {\n\t\t\tconst sign = overlap < 0 ? -1 : 1;\n\n\t\t\tresult.overlap = absolute_overlap;\n\t\t\tresult.overlap_x = x * sign;\n\t\t\tresult.overlap_y = y * sign;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n\n//# sourceURL=webpack:///./src/modules/SAT.mjs?"); /***/ })