mirror of
https://github.com/genxium/DelayNoMore
synced 2025-10-16 12:09:03 +00:00
Compare commits
6 Commits
v1.0.7-bac
...
v1.0.9
Author | SHA1 | Date | |
---|---|---|---|
|
21806a3754 | ||
|
e213fdfb04 | ||
|
b9827f8430 | ||
|
91d16b1cc4 | ||
|
b19868920a | ||
|
be5200663c |
@@ -47,11 +47,10 @@ type Player struct {
|
||||
TutorialStage int `db:"tutorial_stage"`
|
||||
|
||||
// other in-battle info fields
|
||||
LastReceivedInputFrameId int32
|
||||
LastUdpReceivedInputFrameId int32
|
||||
LastSentInputFrameId int32
|
||||
AckingFrameId int32
|
||||
AckingInputFrameId int32
|
||||
LastConsecutiveRecvInputFrameId int32
|
||||
LastSentInputFrameId int32
|
||||
AckingFrameId int32
|
||||
AckingInputFrameId int32
|
||||
|
||||
UdpAddr *PeerUdpAddr
|
||||
BattleUdpTunnelAddr *net.UDPAddr // This addr is used by backend only, not visible to frontend
|
||||
|
@@ -136,7 +136,7 @@ type Room struct {
|
||||
EffectivePlayerCount int32
|
||||
DismissalWaitGroup sync.WaitGroup
|
||||
InputsBuffer *resolv.RingBuffer // Indices are STRICTLY consecutive
|
||||
InputsBufferLock sync.Mutex // Guards [InputsBuffer, LatestPlayerUpsyncedInputFrameId, LastAllConfirmedInputFrameId, LastAllConfirmedInputList, LastAllConfirmedInputFrameIdWithChange, LastIndividuallyConfirmedInputList, player.LastReceivedInputFrameId, player.LastUdpReceivedInputFrameId]
|
||||
InputsBufferLock sync.Mutex // Guards [InputsBuffer, LatestPlayerUpsyncedInputFrameId, LastAllConfirmedInputFrameId, LastAllConfirmedInputList, LastAllConfirmedInputFrameIdWithChange, LastIndividuallyConfirmedInputFrameId, LastIndividuallyConfirmedInputList, player.LastConsecutiveRecvInputFrameId]
|
||||
RenderFrameBuffer *resolv.RingBuffer // Indices are STRICTLY consecutive
|
||||
LatestPlayerUpsyncedInputFrameId int32
|
||||
LastAllConfirmedInputFrameId int32
|
||||
@@ -156,8 +156,10 @@ type Room struct {
|
||||
TmxPointsMap StrToVec2DListMap
|
||||
TmxPolygonsMap StrToPolygon2DListMap
|
||||
|
||||
rdfIdToActuallyUsedInput map[int32]*pb.InputFrameDownsync
|
||||
LastIndividuallyConfirmedInputList []uint64
|
||||
rdfIdToActuallyUsedInput map[int32]*pb.InputFrameDownsync
|
||||
allowUpdateInputFrameInPlaceUponDynamics bool
|
||||
LastIndividuallyConfirmedInputFrameId []int32
|
||||
LastIndividuallyConfirmedInputList []uint64
|
||||
|
||||
BattleUdpTunnelLock sync.Mutex
|
||||
BattleUdpTunnelAddr *pb.PeerUdpAddr
|
||||
@@ -194,8 +196,7 @@ func (pR *Room) AddPlayerIfPossible(pPlayerFromDbInit *Player, speciesId int, se
|
||||
pPlayerFromDbInit.AckingFrameId = -1
|
||||
pPlayerFromDbInit.AckingInputFrameId = -1
|
||||
pPlayerFromDbInit.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED
|
||||
pPlayerFromDbInit.LastReceivedInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED
|
||||
pPlayerFromDbInit.LastUdpReceivedInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED
|
||||
pPlayerFromDbInit.LastConsecutiveRecvInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED
|
||||
pPlayerFromDbInit.BattleState = PlayerBattleStateIns.ADDED_PENDING_BATTLE_COLLIDER_ACK
|
||||
|
||||
pPlayerFromDbInit.ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded
|
||||
@@ -237,7 +238,7 @@ func (pR *Room) ReAddPlayerIfPossible(pTmpPlayerInstance *Player, session *webso
|
||||
pEffectiveInRoomPlayerInstance.AckingFrameId = -1
|
||||
pEffectiveInRoomPlayerInstance.AckingInputFrameId = -1
|
||||
pEffectiveInRoomPlayerInstance.LastSentInputFrameId = MAGIC_LAST_SENT_INPUT_FRAME_ID_READDED
|
||||
// [WARNING] DON'T reset "player.LastReceivedInputFrameId" & "player.LastUdpReceivedInputFrameId" upon reconnection!
|
||||
// [WARNING] DON'T reset "player.LastConsecutiveRecvInputFrameId" & "pR.LastIndividuallyConfirmedInputFrameId[...]" upon reconnection!
|
||||
pEffectiveInRoomPlayerInstance.BattleState = PlayerBattleStateIns.READDED_PENDING_BATTLE_COLLIDER_ACK
|
||||
|
||||
pEffectiveInRoomPlayerInstance.ColliderRadius = DEFAULT_PLAYER_RADIUS // Hardcoded
|
||||
@@ -804,6 +805,11 @@ func (pR *Room) OnDismissed() {
|
||||
pR.RenderFrameBuffer = resolv.NewRingBuffer(pR.RenderCacheSize)
|
||||
pR.InputsBuffer = resolv.NewRingBuffer((pR.RenderCacheSize >> 1) + 1)
|
||||
pR.rdfIdToActuallyUsedInput = make(map[int32]*pb.InputFrameDownsync)
|
||||
pR.allowUpdateInputFrameInPlaceUponDynamics = true
|
||||
pR.LastIndividuallyConfirmedInputFrameId = make([]int32, pR.Capacity)
|
||||
for i := 0; i < pR.Capacity; i++ {
|
||||
pR.LastIndividuallyConfirmedInputFrameId[i] = MAGIC_LAST_SENT_INPUT_FRAME_ID_NORMAL_ADDED
|
||||
}
|
||||
pR.LastIndividuallyConfirmedInputList = make([]uint64, pR.Capacity)
|
||||
|
||||
pR.LatestPlayerUpsyncedInputFrameId = -1
|
||||
@@ -817,7 +823,7 @@ func (pR *Room) OnDismissed() {
|
||||
|
||||
pR.collisionHolder = resolv.NewCollision()
|
||||
pR.effPushbacks = make([]*battle.Vec2D, pR.Capacity)
|
||||
for i := 0; i < len(pR.effPushbacks); i++ {
|
||||
for i := 0; i < pR.Capacity; i++ {
|
||||
pR.effPushbacks[i] = &battle.Vec2D{X: 0, Y: 0}
|
||||
}
|
||||
pR.hardPushbackNormsArr = make([][]*battle.Vec2D, pR.Capacity)
|
||||
@@ -1190,9 +1196,9 @@ func (pR *Room) markConfirmationIfApplicable(inputFrameUpsyncBatch []*pb.InputFr
|
||||
Logger.Debug(fmt.Sprintf("Omitting obsolete inputFrameUpsync#1: roomId=%v, playerId=%v, clientInputFrameId=%v, InputsBuffer=%v", pR.Id, playerId, clientInputFrameId, pR.InputsBufferString(false)))
|
||||
continue
|
||||
}
|
||||
if clientInputFrameId < player.LastReceivedInputFrameId {
|
||||
// [WARNING] It's important for correctness that we use "player.LastReceivedInputFrameId" instead of "player.LastUdpReceivedInputFrameId" here!
|
||||
Logger.Debug(fmt.Sprintf("Omitting obsolete inputFrameUpsync#2: roomId=%v, playerId=%v, clientInputFrameId=%v, playerLastReceivedInputFrameId=%v, InputsBuffer=%v", pR.Id, playerId, clientInputFrameId, player.LastReceivedInputFrameId, pR.InputsBufferString(false)))
|
||||
if clientInputFrameId < player.LastConsecutiveRecvInputFrameId {
|
||||
// [WARNING] It's important for correctness that we use "player.LastConsecutiveRecvInputFrameId" instead of "pR.LastIndividuallyConfirmedInputFrameId[player.JoinIndex-1]" here!
|
||||
Logger.Debug(fmt.Sprintf("Omitting obsolete inputFrameUpsync#2: roomId=%v, playerId=%v, clientInputFrameId=%v, playerLastConsecutiveRecvInputFrameId=%v, InputsBuffer=%v", pR.Id, playerId, clientInputFrameId, player.LastConsecutiveRecvInputFrameId, pR.InputsBufferString(false)))
|
||||
continue
|
||||
}
|
||||
if clientInputFrameId > pR.InputsBuffer.EdFrameId {
|
||||
@@ -1208,19 +1214,19 @@ func (pR *Room) markConfirmationIfApplicable(inputFrameUpsyncBatch []*pb.InputFr
|
||||
/*
|
||||
[WARNING] We have to distinguish whether or not the incoming batch is from UDP here, otherwise "pR.LatestPlayerUpsyncedInputFrameId - pR.LastAllConfirmedInputFrameId" might become unexpectedly large in case of "UDP packet loss + slow ws session"!
|
||||
|
||||
Moreover, only ws session upsyncs should advance "player.LastReceivedInputFrameId" & "pR.LatestPlayerUpsyncedInputFrameId".
|
||||
Moreover, only ws session upsyncs should advance "player.LastConsecutiveRecvInputFrameId" & "pR.LatestPlayerUpsyncedInputFrameId".
|
||||
|
||||
Kindly note that the updates of "player.LastReceivedInputFrameId" could be discrete before and after reconnection.
|
||||
Kindly note that the updates of "player.LastConsecutiveRecvInputFrameId" could be discrete before and after reconnection.
|
||||
*/
|
||||
player.LastReceivedInputFrameId = clientInputFrameId
|
||||
player.LastConsecutiveRecvInputFrameId = clientInputFrameId
|
||||
if clientInputFrameId > pR.LatestPlayerUpsyncedInputFrameId {
|
||||
pR.LatestPlayerUpsyncedInputFrameId = clientInputFrameId
|
||||
}
|
||||
}
|
||||
|
||||
if clientInputFrameId > player.LastUdpReceivedInputFrameId {
|
||||
// No need to update "player.LastUdpReceivedInputFrameId" only when "true == fromUDP", we should keep "player.LastUdpReceivedInputFrameId >= player.LastReceivedInputFrameId" at any moment.
|
||||
player.LastUdpReceivedInputFrameId = clientInputFrameId
|
||||
if clientInputFrameId > pR.LastIndividuallyConfirmedInputFrameId[player.JoinIndex-1] {
|
||||
// No need to update "pR.LastIndividuallyConfirmedInputFrameId[player.JoinIndex-1]" only when "true == fromUDP", we should keep "pR.LastIndividuallyConfirmedInputFrameId[player.JoinIndex-1] >= player.LastConsecutiveRecvInputFrameId" at any moment.
|
||||
pR.LastIndividuallyConfirmedInputFrameId[player.JoinIndex-1] = clientInputFrameId
|
||||
// It's safe (in terms of getting an eventually correct "RenderFrameBuffer") to put the following update of "pR.LastIndividuallyConfirmedInputList" which is ONLY used for prediction in "InputsBuffer" out of "false == fromUDP" block.
|
||||
pR.LastIndividuallyConfirmedInputList[player.JoinIndex-1] = inputFrameUpsync.Encoded
|
||||
}
|
||||
@@ -1299,6 +1305,9 @@ func (pR *Room) forceConfirmationIfApplicable(prevRenderFrameId int32) uint64 {
|
||||
panic(fmt.Sprintf("inputFrameId=%v doesn't exist for roomId=%v! InputsBuffer=%v", j, pR.Id, pR.InputsBufferString(false)))
|
||||
}
|
||||
inputFrameDownsync := tmp.(*battle.InputFrameDownsync)
|
||||
if pR.allowUpdateInputFrameInPlaceUponDynamics {
|
||||
battle.UpdateInputFrameInPlaceUponDynamics(j, pR.Capacity, inputFrameDownsync.ConfirmedList, inputFrameDownsync.InputList, pR.LastIndividuallyConfirmedInputFrameId, pR.LastIndividuallyConfirmedInputList, int32(MAGIC_JOIN_INDEX_INVALID))
|
||||
}
|
||||
unconfirmedMask |= (allConfirmedMask ^ inputFrameDownsync.ConfirmedList)
|
||||
inputFrameDownsync.ConfirmedList = allConfirmedMask
|
||||
pR.onInputFrameDownsyncAllConfirmed(inputFrameDownsync, -1)
|
||||
@@ -1379,7 +1388,7 @@ func (pR *Room) applyInputFrameDownsyncDynamics(fromRenderFrameId int32, toRende
|
||||
}
|
||||
}
|
||||
|
||||
battle.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(pR.InputsBuffer, currRenderFrame.Id, pR.Space, pR.CollisionSysMap, pR.SpaceOffsetX, pR.SpaceOffsetY, pR.CharacterConfigsArr, pR.RenderFrameBuffer, pR.collisionHolder, pR.effPushbacks, pR.hardPushbackNormsArr, pR.jumpedOrNotList, pR.dynamicRectangleColliders)
|
||||
battle.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(pR.InputsBuffer, currRenderFrame.Id, pR.Space, pR.CollisionSysMap, pR.SpaceOffsetX, pR.SpaceOffsetY, pR.CharacterConfigsArr, pR.RenderFrameBuffer, pR.collisionHolder, pR.effPushbacks, pR.hardPushbackNormsArr, pR.jumpedOrNotList, pR.dynamicRectangleColliders, pR.LastIndividuallyConfirmedInputFrameId, pR.LastIndividuallyConfirmedInputList, false, MAGIC_JOIN_INDEX_INVALID) // "allowUpdateInputFrameInPlaceUponDynamics" is instead used when "forceConfirmationIfApplicable"
|
||||
pR.CurDynamicsRenderFrameId++
|
||||
}
|
||||
}
|
||||
|
@@ -400,7 +400,7 @@
|
||||
"ctor": "Float64Array",
|
||||
"array": [
|
||||
0,
|
||||
0,
|
||||
32,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@@ -536,7 +536,7 @@
|
||||
"array": [
|
||||
0,
|
||||
0,
|
||||
216.05530045313827,
|
||||
210.43877906529718,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@@ -2129,7 +2129,7 @@
|
||||
"_left": 0,
|
||||
"_right": 0,
|
||||
"_top": 0,
|
||||
"_bottom": 640,
|
||||
"_bottom": 672,
|
||||
"_verticalCenter": 0,
|
||||
"_horizontalCenter": 0,
|
||||
"_isAbsLeft": true,
|
||||
|
@@ -382,7 +382,7 @@
|
||||
"ctor": "Float64Array",
|
||||
"array": [
|
||||
0,
|
||||
0,
|
||||
32,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
@@ -518,7 +518,7 @@
|
||||
"array": [
|
||||
0,
|
||||
0,
|
||||
216.50635094610968,
|
||||
210.43877906529718,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
|
@@ -512,8 +512,7 @@ cc.Class({
|
||||
window.clearBoundRoomIdInBothVolatileAndPersistentStorage();
|
||||
window.initPersistentSessionClient(self.initAfterWSConnected, null /* Deliberately NOT passing in any `expectedRoomId`. -- YFLu */ );
|
||||
};
|
||||
resultPanelScriptIns.onCloseDelegate = () => {
|
||||
};
|
||||
resultPanelScriptIns.onCloseDelegate = () => {};
|
||||
|
||||
self.gameRuleNode = cc.instantiate(self.gameRulePrefab);
|
||||
self.gameRuleNode.width = self.canvasNode.width;
|
||||
@@ -1428,7 +1427,8 @@ othersForcedDownsyncRenderFrame=${JSON.stringify(othersForcedDownsyncRenderFrame
|
||||
};
|
||||
self.rdfIdToActuallyUsedInput.set(i, inputFrameDownsyncClone);
|
||||
}
|
||||
const renderRes = gopkgs.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(self.recentInputCache, i, collisionSys, collisionSysMap, self.spaceOffsetX, self.spaceOffsetY, self.chConfigsOrderedByJoinIndex, self.recentRenderCache, self.collisionHolder, self.effPushbacks, self.hardPushbackNormsArr, self.jumpedOrNotList, self.dynamicRectangleColliders);
|
||||
const allowUpdateInputFrameInPlaceUponDynamics = (!isChasing);
|
||||
const renderRes = gopkgs.ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(self.recentInputCache, i, collisionSys, collisionSysMap, self.spaceOffsetX, self.spaceOffsetY, self.chConfigsOrderedByJoinIndex, self.recentRenderCache, self.collisionHolder, self.effPushbacks, self.hardPushbackNormsArr, self.jumpedOrNotList, self.dynamicRectangleColliders, self.lastIndividuallyConfirmedInputFrameId, self.lastIndividuallyConfirmedInputList, allowUpdateInputFrameInPlaceUponDynamics, self.selfPlayerInfo.joinIndex);
|
||||
const nextRdf = gopkgs.GetRoomDownsyncFrame(self.recentRenderCache, i + 1);
|
||||
|
||||
if (true == isChasing) {
|
||||
|
File diff suppressed because one or more lines are too long
@@ -76,7 +76,7 @@
|
||||
"shelter_z_reducer",
|
||||
"shelter"
|
||||
],
|
||||
"last-module-event-record-time": 1676513919950,
|
||||
"last-module-event-record-time": 1677337364473,
|
||||
"simulator-orientation": false,
|
||||
"simulator-resolution": {
|
||||
"height": 640,
|
||||
|
@@ -22,7 +22,7 @@ const (
|
||||
GRAVITY_X = int32(0)
|
||||
GRAVITY_Y = -int32(float64(0.5) * WORLD_TO_VIRTUAL_GRID_RATIO) // makes all "playerCollider.Y" a multiple of 0.5 in all cases
|
||||
|
||||
INPUT_DELAY_FRAMES = int32(6) // in the count of render frames
|
||||
INPUT_DELAY_FRAMES = int32(4) // in the count of render frames
|
||||
|
||||
/*
|
||||
[WARNING]
|
||||
@@ -490,6 +490,23 @@ func calcHardPushbacksNorms(joinIndex int32, currPlayerDownsync, thatPlayerInNex
|
||||
return retCnt
|
||||
}
|
||||
|
||||
func UpdateInputFrameInPlaceUponDynamics(inputFrameId int32, roomCapacity int, confirmedList uint64, inputList []uint64, lastIndividuallyConfirmedInputFrameId []int32, lastIndividuallyConfirmedInputList []uint64, toExcludeJoinIndexUpdateInputFrameInPlaceUponDynamics int32) {
|
||||
for i := 0; i < roomCapacity; i++ {
|
||||
if int32(i+1) == toExcludeJoinIndexUpdateInputFrameInPlaceUponDynamics {
|
||||
// On frontend, a "self input" is only confirmed by websocket downsync, which is quite late and might get the "self input" incorrectly overwritten if not excluded here
|
||||
continue
|
||||
}
|
||||
if 0 < (confirmedList & (1 << uint32(i))) {
|
||||
// This in-place update on the "inputsBuffer" is only correct when "delayed input for this player is not yet confirmed"
|
||||
continue
|
||||
}
|
||||
if lastIndividuallyConfirmedInputFrameId[i] >= inputFrameId {
|
||||
continue
|
||||
}
|
||||
inputList[i] = (lastIndividuallyConfirmedInputList[i] & uint64(15))
|
||||
}
|
||||
}
|
||||
|
||||
func deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync, currRenderFrame *RoomDownsyncFrame, chConfig *CharacterConfig, inputsBuffer *resolv.RingBuffer) (int, bool, int32, int32) {
|
||||
// returns (patternId, jumpedOrNot, effectiveDx, effectiveDy)
|
||||
delayedInputFrameId := ConvertToDelayedInputFrameId(currRenderFrame.Id)
|
||||
@@ -503,10 +520,13 @@ func deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync,
|
||||
return PATTERN_ID_UNABLE_TO_OP, false, 0, 0
|
||||
}
|
||||
|
||||
delayedInputList := inputsBuffer.GetByFrameId(delayedInputFrameId).(*InputFrameDownsync).InputList
|
||||
delayedInputFrameDownsync := inputsBuffer.GetByFrameId(delayedInputFrameId).(*InputFrameDownsync)
|
||||
delayedInputList := delayedInputFrameDownsync.InputList
|
||||
|
||||
var delayedInputListForPrevRdf []uint64 = nil
|
||||
if 0 < delayedInputFrameIdForPrevRdf {
|
||||
delayedInputListForPrevRdf = inputsBuffer.GetByFrameId(delayedInputFrameIdForPrevRdf).(*InputFrameDownsync).InputList
|
||||
delayedInputFrameDownsyncForPrevRdf := inputsBuffer.GetByFrameId(delayedInputFrameIdForPrevRdf).(*InputFrameDownsync)
|
||||
delayedInputListForPrevRdf = delayedInputFrameDownsyncForPrevRdf.InputList
|
||||
}
|
||||
|
||||
jumpedOrNot := false
|
||||
@@ -564,7 +584,7 @@ func deriveOpPattern(currPlayerDownsync, thatPlayerInNextFrame *PlayerDownsync,
|
||||
|
||||
The function "ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame" is creating new heap-memory blocks at 60fps, e.g. nextRenderFramePlayers & nextRenderFrameMeleeBullets & nextRenderFrameFireballBullets & effPushbacks & hardPushbackNorms & jumpedOrNotList & dynamicRectangleColliders("player" & "bullet"), which would induce "possibly performance impacting garbage collections" when many rooms are running simultaneously.
|
||||
*/
|
||||
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.RingBuffer, currRenderFrameId int32, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, collisionSpaceOffsetX, collisionSpaceOffsetY float64, chConfigsOrderedByJoinIndex []*CharacterConfig, renderFrameBuffer *resolv.RingBuffer, collision *resolv.Collision, effPushbacks []*Vec2D, hardPushbackNormsArr [][]*Vec2D, jumpedOrNotList []bool, dynamicRectangleColliders []*resolv.Object) bool {
|
||||
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.RingBuffer, currRenderFrameId int32, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, collisionSpaceOffsetX, collisionSpaceOffsetY float64, chConfigsOrderedByJoinIndex []*CharacterConfig, renderFrameBuffer *resolv.RingBuffer, collision *resolv.Collision, effPushbacks []*Vec2D, hardPushbackNormsArr [][]*Vec2D, jumpedOrNotList []bool, dynamicRectangleColliders []*resolv.Object, lastIndividuallyConfirmedInputFrameId []int32, lastIndividuallyConfirmedInputList []uint64, allowUpdateInputFrameInPlaceUponDynamics bool, toExcludeJoinIndexUpdateInputFrameInPlaceUponDynamics int32) bool {
|
||||
currRenderFrame := renderFrameBuffer.GetByFrameId(currRenderFrameId).(*RoomDownsyncFrame)
|
||||
nextRenderFrameId := currRenderFrameId + 1
|
||||
roomCapacity := len(currRenderFrame.PlayersArr)
|
||||
@@ -610,6 +630,17 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.Rin
|
||||
|
||||
bulletLocalId := currRenderFrame.BulletLocalIdCounter
|
||||
// 1. Process player inputs
|
||||
delayedInputFrameId := ConvertToDelayedInputFrameId(currRenderFrame.Id)
|
||||
|
||||
if 0 < delayedInputFrameId {
|
||||
delayedInputFrameDownsync := inputsBuffer.GetByFrameId(delayedInputFrameId).(*InputFrameDownsync)
|
||||
delayedInputList := delayedInputFrameDownsync.InputList
|
||||
roomCapacity := len(delayedInputList)
|
||||
if allowUpdateInputFrameInPlaceUponDynamics {
|
||||
UpdateInputFrameInPlaceUponDynamics(delayedInputFrameId, roomCapacity, delayedInputFrameDownsync.ConfirmedList, delayedInputList, lastIndividuallyConfirmedInputFrameId, lastIndividuallyConfirmedInputList, toExcludeJoinIndexUpdateInputFrameInPlaceUponDynamics)
|
||||
}
|
||||
}
|
||||
|
||||
for i, currPlayerDownsync := range currRenderFrame.PlayersArr {
|
||||
chConfig := chConfigsOrderedByJoinIndex[i]
|
||||
thatPlayerInNextFrame := nextRenderFramePlayers[i]
|
||||
@@ -700,7 +731,7 @@ func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer *resolv.Rin
|
||||
} else {
|
||||
// Updates CharacterState and thus the animation to make user see graphical feedback asap.
|
||||
thatPlayerInNextFrame.CharacterState = ATK_CHARACTER_STATE_WALKING
|
||||
thatPlayerInNextFrame.FramesToRecover = ((chConfig.InertiaFramesToRecover >> 1) + (chConfig.InertiaFramesToRecover >> 2))
|
||||
thatPlayerInNextFrame.FramesToRecover = (chConfig.InertiaFramesToRecover >> 1)
|
||||
}
|
||||
} else {
|
||||
thatPlayerInNextFrame.CapturedByInertia = false
|
||||
|
@@ -106,9 +106,9 @@ func GetCharacterConfigsOrderedByJoinIndex(speciesIdList []int) []*js.Object {
|
||||
return ret
|
||||
}
|
||||
|
||||
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(inputsBuffer *resolv.RingBuffer, currRenderFrameId int32, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, collisionSpaceOffsetX, collisionSpaceOffsetY float64, chConfigsOrderedByJoinIndex []*CharacterConfig, renderFrameBuffer *resolv.RingBuffer, collision *resolv.Collision, effPushbacks []*Vec2D, hardPushbackNormsArr [][]*Vec2D, jumpedOrNotList []bool, dynamicRectangleColliders []*resolv.Object) bool {
|
||||
func ApplyInputFrameDownsyncDynamicsOnSingleRenderFrameJs(inputsBuffer *resolv.RingBuffer, currRenderFrameId int32, collisionSys *resolv.Space, collisionSysMap map[int32]*resolv.Object, collisionSpaceOffsetX, collisionSpaceOffsetY float64, chConfigsOrderedByJoinIndex []*CharacterConfig, renderFrameBuffer *resolv.RingBuffer, collision *resolv.Collision, effPushbacks []*Vec2D, hardPushbackNormsArr [][]*Vec2D, jumpedOrNotList []bool, dynamicRectangleColliders []*resolv.Object, lastIndividuallyConfirmedInputFrameId []int32, lastIndividuallyConfirmedInputList []uint64, allowUpdateInputFrameInPlaceUponDynamics bool, toExcludeJoinIndexUpdateInputFrameInPlaceUponDynamics int32) bool {
|
||||
// We need access to all fields of RoomDownsyncFrame for displaying in frontend
|
||||
return ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer, currRenderFrameId, collisionSys, collisionSysMap, collisionSpaceOffsetX, collisionSpaceOffsetY, chConfigsOrderedByJoinIndex, renderFrameBuffer, collision, effPushbacks, hardPushbackNormsArr, jumpedOrNotList, dynamicRectangleColliders)
|
||||
return ApplyInputFrameDownsyncDynamicsOnSingleRenderFrame(inputsBuffer, currRenderFrameId, collisionSys, collisionSysMap, collisionSpaceOffsetX, collisionSpaceOffsetY, chConfigsOrderedByJoinIndex, renderFrameBuffer, collision, effPushbacks, hardPushbackNormsArr, jumpedOrNotList, dynamicRectangleColliders, lastIndividuallyConfirmedInputFrameId, lastIndividuallyConfirmedInputList, allowUpdateInputFrameInPlaceUponDynamics, toExcludeJoinIndexUpdateInputFrameInPlaceUponDynamics)
|
||||
}
|
||||
|
||||
func GetRoomDownsyncFrame(renderFrameBuffer *resolv.RingBuffer, frameId int32) *js.Object {
|
||||
|
@@ -1,164 +0,0 @@
|
||||
// This file contains code from the gonum repository:
|
||||
// https://github.com/gonum/gonum/blob/master/internal/asm/f64/scalunitaryto_amd64.s
|
||||
// it is distributed under the 3-Clause BSD license:
|
||||
//
|
||||
// Copyright ©2013 The Gonum Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// * Neither the name of the Gonum project nor the names of its authors and
|
||||
// contributors may be used to endorse or promote products derived from this
|
||||
// software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Some of the loop unrolling code is copied from:
|
||||
// http://golang.org/src/math/big/arith_amd64.s
|
||||
// which is distributed under these terms:
|
||||
//
|
||||
// Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// +build !noasm
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
#define X_PTR SI
|
||||
#define Y_PTR DX
|
||||
#define DST_PTR DI
|
||||
#define IDX AX
|
||||
#define LEN CX
|
||||
#define TAIL BX
|
||||
#define ALPHA X0
|
||||
#define ALPHA_2 X1
|
||||
|
||||
// func axpyUnitaryTo(dst []float64, alpha float64, x, y []float64)
|
||||
TEXT ·axpyUnitaryTo(SB), NOSPLIT, $0
|
||||
MOVQ dst_base+0(FP), DST_PTR // DST_PTR := &dst
|
||||
MOVQ x_base+32(FP), X_PTR // X_PTR := &x
|
||||
MOVQ y_base+56(FP), Y_PTR // Y_PTR := &y
|
||||
MOVQ x_len+40(FP), LEN // LEN = min( len(x), len(y), len(dst) )
|
||||
CMPQ y_len+64(FP), LEN
|
||||
CMOVQLE y_len+64(FP), LEN
|
||||
CMPQ dst_len+8(FP), LEN
|
||||
CMOVQLE dst_len+8(FP), LEN
|
||||
|
||||
CMPQ LEN, $0
|
||||
JE end // if LEN == 0 { return }
|
||||
|
||||
XORQ IDX, IDX // IDX = 0
|
||||
MOVSD alpha+24(FP), ALPHA
|
||||
SHUFPD $0, ALPHA, ALPHA // ALPHA := { alpha, alpha }
|
||||
MOVQ Y_PTR, TAIL // Check memory alignment
|
||||
ANDQ $15, TAIL // TAIL = &y % 16
|
||||
JZ no_trim // if TAIL == 0 { goto no_trim }
|
||||
|
||||
// Align on 16-byte boundary
|
||||
MOVSD (X_PTR), X2 // X2 := x[0]
|
||||
MULSD ALPHA, X2 // X2 *= a
|
||||
ADDSD (Y_PTR), X2 // X2 += y[0]
|
||||
MOVSD X2, (DST_PTR) // y[0] = X2
|
||||
INCQ IDX // i++
|
||||
DECQ LEN // LEN--
|
||||
JZ end // if LEN == 0 { return }
|
||||
|
||||
no_trim:
|
||||
MOVQ LEN, TAIL
|
||||
ANDQ $7, TAIL // TAIL := n % 8
|
||||
SHRQ $3, LEN // LEN = floor( n / 8 )
|
||||
JZ tail_start // if LEN == 0 { goto tail_start }
|
||||
|
||||
MOVUPS ALPHA, ALPHA_2 // ALPHA_2 := ALPHA for pipelining
|
||||
|
||||
loop: // do {
|
||||
// y[i] += alpha * x[i] unrolled 8x.
|
||||
MOVUPS (X_PTR)(IDX*8), X2 // X_i = x[i]
|
||||
MOVUPS 16(X_PTR)(IDX*8), X3
|
||||
MOVUPS 32(X_PTR)(IDX*8), X4
|
||||
MOVUPS 48(X_PTR)(IDX*8), X5
|
||||
|
||||
MULPD ALPHA, X2 // X_i *= alpha
|
||||
MULPD ALPHA_2, X3
|
||||
MULPD ALPHA, X4
|
||||
MULPD ALPHA_2, X5
|
||||
|
||||
ADDPD (Y_PTR)(IDX*8), X2 // X_i += y[i]
|
||||
ADDPD 16(Y_PTR)(IDX*8), X3
|
||||
ADDPD 32(Y_PTR)(IDX*8), X4
|
||||
ADDPD 48(Y_PTR)(IDX*8), X5
|
||||
|
||||
MOVUPS X2, (DST_PTR)(IDX*8) // y[i] = X_i
|
||||
MOVUPS X3, 16(DST_PTR)(IDX*8)
|
||||
MOVUPS X4, 32(DST_PTR)(IDX*8)
|
||||
MOVUPS X5, 48(DST_PTR)(IDX*8)
|
||||
|
||||
ADDQ $8, IDX // i += 8
|
||||
DECQ LEN
|
||||
JNZ loop // } while --LEN > 0
|
||||
CMPQ TAIL, $0 // if TAIL == 0 { return }
|
||||
JE end
|
||||
|
||||
tail_start: // Reset loop registers
|
||||
MOVQ TAIL, LEN // Loop counter: LEN = TAIL
|
||||
SHRQ $1, LEN // LEN = floor( TAIL / 2 )
|
||||
JZ tail_one // if LEN == 0 { goto tail }
|
||||
|
||||
tail_two: // do {
|
||||
MOVUPS (X_PTR)(IDX*8), X2 // X2 = x[i]
|
||||
MULPD ALPHA, X2 // X2 *= alpha
|
||||
ADDPD (Y_PTR)(IDX*8), X2 // X2 += y[i]
|
||||
MOVUPS X2, (DST_PTR)(IDX*8) // y[i] = X2
|
||||
ADDQ $2, IDX // i += 2
|
||||
DECQ LEN
|
||||
JNZ tail_two // } while --LEN > 0
|
||||
|
||||
ANDQ $1, TAIL
|
||||
JZ end // if TAIL == 0 { goto end }
|
||||
|
||||
tail_one:
|
||||
MOVSD (X_PTR)(IDX*8), X2 // X2 = x[i]
|
||||
MULSD ALPHA, X2 // X2 *= a
|
||||
ADDSD (Y_PTR)(IDX*8), X2 // X2 += y[i]
|
||||
MOVSD X2, (DST_PTR)(IDX*8) // y[i] = X2
|
||||
|
||||
end:
|
||||
RET
|
@@ -1,137 +0,0 @@
|
||||
// This file contains code from the gonum repository:
|
||||
// https://github.com/gonum/gonum/blob/master/internal/asm/f64/axpyunitaryto_amd64.s
|
||||
// it is distributed under the 3-Clause BSD license:
|
||||
//
|
||||
// Copyright ©2013 The Gonum Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// * Neither the name of the Gonum project nor the names of its authors and
|
||||
// contributors may be used to endorse or promote products derived from this
|
||||
// software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Some of the loop unrolling code is copied from:
|
||||
// http://golang.org/src/math/big/arith_amd64.s
|
||||
// which is distributed under these terms:
|
||||
//
|
||||
// Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// +build !noasm
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
#define MOVDDUP_ALPHA LONG $0x44120FF2; WORD $0x2024 // @ MOVDDUP 32(SP), X0 /*XMM0, 32[RSP]*/
|
||||
|
||||
#define X_PTR SI
|
||||
#define DST_PTR DI
|
||||
#define IDX AX
|
||||
#define LEN CX
|
||||
#define TAIL BX
|
||||
#define ALPHA X0
|
||||
#define ALPHA_2 X1
|
||||
|
||||
// func scalUnitaryTo(dst []float64, alpha float64, x []float64)
|
||||
// This function assumes len(dst) >= len(x).
|
||||
TEXT ·scalUnitaryTo(SB), NOSPLIT, $0
|
||||
MOVQ x_base+32(FP), X_PTR // X_PTR = &x
|
||||
MOVQ dst_base+0(FP), DST_PTR // DST_PTR = &dst
|
||||
MOVDDUP_ALPHA // ALPHA = { alpha, alpha }
|
||||
MOVQ x_len+40(FP), LEN // LEN = len(x)
|
||||
CMPQ LEN, $0
|
||||
JE end // if LEN == 0 { return }
|
||||
|
||||
XORQ IDX, IDX // IDX = 0
|
||||
MOVQ LEN, TAIL
|
||||
ANDQ $7, TAIL // TAIL = LEN % 8
|
||||
SHRQ $3, LEN // LEN = floor( LEN / 8 )
|
||||
JZ tail_start // if LEN == 0 { goto tail_start }
|
||||
|
||||
MOVUPS ALPHA, ALPHA_2 // ALPHA_2 = ALPHA for pipelining
|
||||
|
||||
loop: // do { // dst[i] = alpha * x[i] unrolled 8x.
|
||||
MOVUPS (X_PTR)(IDX*8), X2 // X_i = x[i]
|
||||
MOVUPS 16(X_PTR)(IDX*8), X3
|
||||
MOVUPS 32(X_PTR)(IDX*8), X4
|
||||
MOVUPS 48(X_PTR)(IDX*8), X5
|
||||
|
||||
MULPD ALPHA, X2 // X_i *= ALPHA
|
||||
MULPD ALPHA_2, X3
|
||||
MULPD ALPHA, X4
|
||||
MULPD ALPHA_2, X5
|
||||
|
||||
MOVUPS X2, (DST_PTR)(IDX*8) // dst[i] = X_i
|
||||
MOVUPS X3, 16(DST_PTR)(IDX*8)
|
||||
MOVUPS X4, 32(DST_PTR)(IDX*8)
|
||||
MOVUPS X5, 48(DST_PTR)(IDX*8)
|
||||
|
||||
ADDQ $8, IDX // i += 8
|
||||
DECQ LEN
|
||||
JNZ loop // while --LEN > 0
|
||||
CMPQ TAIL, $0
|
||||
JE end // if TAIL == 0 { return }
|
||||
|
||||
tail_start: // Reset loop counters
|
||||
MOVQ TAIL, LEN // Loop counter: LEN = TAIL
|
||||
SHRQ $1, LEN // LEN = floor( TAIL / 2 )
|
||||
JZ tail_one // if LEN == 0 { goto tail_one }
|
||||
|
||||
tail_two: // do {
|
||||
MOVUPS (X_PTR)(IDX*8), X2 // X_i = x[i]
|
||||
MULPD ALPHA, X2 // X_i *= ALPHA
|
||||
MOVUPS X2, (DST_PTR)(IDX*8) // dst[i] = X_i
|
||||
ADDQ $2, IDX // i += 2
|
||||
DECQ LEN
|
||||
JNZ tail_two // while --LEN > 0
|
||||
|
||||
ANDQ $1, TAIL
|
||||
JZ end // if TAIL == 0 { return }
|
||||
|
||||
tail_one:
|
||||
MOVSD (X_PTR)(IDX*8), X2 // X_i = x[i]
|
||||
MULSD ALPHA, X2 // X_i *= ALPHA
|
||||
MOVSD X2, (DST_PTR)(IDX*8) // dst[i] = X_i
|
||||
|
||||
end:
|
||||
RET
|
@@ -1,9 +0,0 @@
|
||||
//go:build !noasm
|
||||
// +build !noasm
|
||||
|
||||
package resolv
|
||||
|
||||
// functions from the gonum package that optimizes arithmetic
|
||||
// operations on lists of float64 values
|
||||
func axpyUnitaryTo(dst []float64, alpha float64, x, y []float64)
|
||||
func scalUnitaryTo(dst []float64, alpha float64, x []float64)
|
@@ -33,20 +33,12 @@ func NewLine(x, y, x2, y2 float64) *Line {
|
||||
}
|
||||
}
|
||||
|
||||
func (line *Line) Project(axis Vector) Vector {
|
||||
return line.Vector().Scale(axis.Dot(line.Start.Sub(line.End)))
|
||||
}
|
||||
|
||||
func (line *Line) Normal() Vector {
|
||||
dy := line.End[1] - line.Start[1]
|
||||
dx := line.End[0] - line.Start[0]
|
||||
return Vector{dy, -dx}.Unit()
|
||||
}
|
||||
|
||||
func (line *Line) Vector() Vector {
|
||||
return line.End.Clone().Sub(line.Start).Unit()
|
||||
}
|
||||
|
||||
// IntersectionPointsLine returns the intersection point of a Line with another Line as a Vector. If no intersection is found, it will return nil.
|
||||
func (line *Line) IntersectionPointsLine(other *Line) Vector {
|
||||
|
||||
@@ -79,51 +71,6 @@ func (line *Line) IntersectionPointsLine(other *Line) Vector {
|
||||
|
||||
}
|
||||
|
||||
// IntersectionPointsCircle returns a slice of Vectors, each indicating the intersection point. If no intersection is found, it will return an empty slice.
|
||||
func (line *Line) IntersectionPointsCircle(circle *Circle) []Vector {
|
||||
|
||||
points := []Vector{}
|
||||
|
||||
cp := Vector{circle.X, circle.Y}
|
||||
lStart := line.Start.Sub(cp)
|
||||
lEnd := line.End.Sub(cp)
|
||||
diff := lEnd.Sub(lStart)
|
||||
|
||||
a := diff[0]*diff[0] + diff[1]*diff[1]
|
||||
b := 2 * ((diff[0] * lStart[0]) + (diff[1] * lStart[1]))
|
||||
c := (lStart[0] * lStart[0]) + (lStart[1] * lStart[1]) - (circle.Radius * circle.Radius)
|
||||
|
||||
det := b*b - (4 * a * c)
|
||||
|
||||
if det < 0 {
|
||||
// Do nothing, no intersections
|
||||
} else if det == 0 {
|
||||
|
||||
t := -b / (2 * a)
|
||||
|
||||
if t >= 0 && t <= 1 {
|
||||
points = append(points, Vector{line.Start[0] + t*diff[0], line.Start[1] + t*diff[1]})
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
t := (-b + math.Sqrt(det)) / (2 * a)
|
||||
|
||||
// We have to ensure t is between 0 and 1; otherwise, the collision points are on the circle as though the lines were infinite in length.
|
||||
if t >= 0 && t <= 1 {
|
||||
points = append(points, Vector{line.Start[0] + t*diff[0], line.Start[1] + t*diff[1]})
|
||||
}
|
||||
t = (-b - math.Sqrt(det)) / (2 * a)
|
||||
if t >= 0 && t <= 1 {
|
||||
points = append(points, Vector{line.Start[0] + t*diff[0], line.Start[1] + t*diff[1]})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return points
|
||||
|
||||
}
|
||||
|
||||
type ConvexPolygon struct {
|
||||
Points *RingBuffer
|
||||
X, Y float64
|
||||
@@ -294,24 +241,6 @@ func (cp *ConvexPolygon) MoveVec(vec Vector) {
|
||||
cp.Y += vec.Y()
|
||||
}
|
||||
|
||||
// Center returns the transformed Center of the ConvexPolygon.
|
||||
func (cp *ConvexPolygon) Center() Vector {
|
||||
|
||||
pos := Vector{0, 0}
|
||||
|
||||
vertices := cp.Transformed()
|
||||
for _, v := range vertices {
|
||||
pos.Add(v)
|
||||
}
|
||||
|
||||
denom := float64(len(vertices))
|
||||
pos[0] /= denom
|
||||
pos[1] /= denom
|
||||
|
||||
return pos
|
||||
|
||||
}
|
||||
|
||||
// Project projects (i.e. flattens) the ConvexPolygon onto the provided axis.
|
||||
func (cp *ConvexPolygon) Project(axis Vector) Projection {
|
||||
axis = axis.Unit()
|
||||
@@ -453,13 +382,7 @@ func (cp *ConvexPolygon) Intersection(dx, dy float64, other Shape) *ContactSet {
|
||||
cp.X += dx
|
||||
cp.Y += dy
|
||||
|
||||
if circle, isCircle := other.(*Circle); isCircle {
|
||||
|
||||
for _, line := range cp.Lines() {
|
||||
contactSet.Points = append(contactSet.Points, line.IntersectionPointsCircle(circle)...)
|
||||
}
|
||||
|
||||
} else if poly, isPoly := other.(*ConvexPolygon); isPoly {
|
||||
if poly, isPoly := other.(*ConvexPolygon); isPoly {
|
||||
|
||||
for _, line := range cp.Lines() {
|
||||
|
||||
@@ -476,29 +399,11 @@ func (cp *ConvexPolygon) Intersection(dx, dy float64, other Shape) *ContactSet {
|
||||
}
|
||||
|
||||
if len(contactSet.Points) > 0 {
|
||||
|
||||
for _, point := range contactSet.Points {
|
||||
contactSet.Center = contactSet.Center.Add(point)
|
||||
}
|
||||
|
||||
contactSet.Center[0] /= float64(len(contactSet.Points))
|
||||
contactSet.Center[1] /= float64(len(contactSet.Points))
|
||||
|
||||
if mtv := cp.calculateMTV(contactSet, other); mtv != nil {
|
||||
contactSet.MTV = mtv
|
||||
}
|
||||
|
||||
// Do nothing
|
||||
} else {
|
||||
contactSet = nil
|
||||
}
|
||||
|
||||
// If dx or dy aren't 0, then the MTV will be greater to compensate; this adjusts the vector back.
|
||||
if contactSet != nil && (dx != 0 || dy != 0) {
|
||||
deltaMagnitude := Vector{dx, dy}.Magnitude()
|
||||
ogMagnitude := contactSet.MTV.Magnitude()
|
||||
contactSet.MTV = contactSet.MTV.Unit().Scale(ogMagnitude - deltaMagnitude)
|
||||
}
|
||||
|
||||
cp.X = ogX
|
||||
cp.Y = ogY
|
||||
|
||||
@@ -506,76 +411,6 @@ func (cp *ConvexPolygon) Intersection(dx, dy float64, other Shape) *ContactSet {
|
||||
|
||||
}
|
||||
|
||||
// calculateMTV returns the MTV, if possible, and a bool indicating whether it was possible or not.
|
||||
func (cp *ConvexPolygon) calculateMTV(contactSet *ContactSet, otherShape Shape) Vector {
|
||||
|
||||
delta := Vector{0, 0}
|
||||
|
||||
smallest := Vector{math.MaxFloat64, 0}
|
||||
|
||||
switch other := otherShape.(type) {
|
||||
|
||||
case *ConvexPolygon:
|
||||
|
||||
for _, axis := range cp.SATAxes() {
|
||||
if !cp.Project(axis).Overlapping(other.Project(axis)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
overlap := cp.Project(axis).Overlap(other.Project(axis))
|
||||
|
||||
if smallest.Magnitude() > overlap {
|
||||
smallest = axis.Scale(overlap)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for _, axis := range other.SATAxes() {
|
||||
|
||||
if !cp.Project(axis).Overlapping(other.Project(axis)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
overlap := cp.Project(axis).Overlap(other.Project(axis))
|
||||
|
||||
if smallest.Magnitude() > overlap {
|
||||
smallest = axis.Scale(overlap)
|
||||
}
|
||||
|
||||
}
|
||||
// Removed support of "Circle" to remove dependency of "sort" module
|
||||
}
|
||||
|
||||
delta[0] = smallest[0]
|
||||
delta[1] = smallest[1]
|
||||
|
||||
return delta
|
||||
}
|
||||
|
||||
// ContainedBy returns if the ConvexPolygon is wholly contained by the other shape provided.
|
||||
func (cp *ConvexPolygon) ContainedBy(otherShape Shape) bool {
|
||||
|
||||
switch other := otherShape.(type) {
|
||||
|
||||
case *ConvexPolygon:
|
||||
|
||||
for _, axis := range cp.SATAxes() {
|
||||
if !cp.Project(axis).IsInside(other.Project(axis)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, axis := range other.SATAxes() {
|
||||
if !cp.Project(axis).IsInside(other.Project(axis)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// NewRectangle returns a rectangular ConvexPolygon with the vertices in clockwise order. In actuality, an AABBRectangle should be its own
|
||||
// "thing" with its own optimized Intersection code check.
|
||||
func NewRectangle(x, y, w, h float64) *ConvexPolygon {
|
||||
@@ -587,143 +422,6 @@ func NewRectangle(x, y, w, h float64) *ConvexPolygon {
|
||||
)
|
||||
}
|
||||
|
||||
type Circle struct {
|
||||
X, Y, Radius float64
|
||||
}
|
||||
|
||||
// NewCircle returns a new Circle, with its center at the X and Y position given, and with the defined radius.
|
||||
func NewCircle(x, y, radius float64) *Circle {
|
||||
circle := &Circle{
|
||||
X: x,
|
||||
Y: y,
|
||||
Radius: radius,
|
||||
}
|
||||
return circle
|
||||
}
|
||||
|
||||
func (circle *Circle) Clone() Shape {
|
||||
return NewCircle(circle.X, circle.Y, circle.Radius)
|
||||
}
|
||||
|
||||
// Bounds returns the top-left and bottom-right corners of the Circle.
|
||||
func (circle *Circle) Bounds() (Vector, Vector) {
|
||||
return Vector{circle.X - circle.Radius, circle.Y - circle.Radius}, Vector{circle.X + circle.Radius, circle.Y + circle.Radius}
|
||||
}
|
||||
|
||||
// Intersection tests to see if a Circle intersects with the other given Shape. dx and dy are delta movement variables indicating
|
||||
// movement to be applied before the intersection check (thereby allowing you to see if a Shape would collide with another if it
|
||||
// were in a different relative location). If an Intersection is found, a ContactSet will be returned, giving information regarding
|
||||
// the intersection.
|
||||
func (circle *Circle) Intersection(dx, dy float64, other Shape) *ContactSet {
|
||||
|
||||
var contactSet *ContactSet
|
||||
|
||||
ox := circle.X
|
||||
oy := circle.Y
|
||||
|
||||
circle.X += dx
|
||||
circle.Y += dy
|
||||
|
||||
// here
|
||||
|
||||
switch shape := other.(type) {
|
||||
case *ConvexPolygon:
|
||||
// Maybe this would work?
|
||||
contactSet = shape.Intersection(-dx, -dy, circle)
|
||||
if contactSet != nil {
|
||||
contactSet.MTV = contactSet.MTV.Scale(-1)
|
||||
}
|
||||
case *Circle:
|
||||
|
||||
contactSet = NewContactSet()
|
||||
|
||||
contactSet.Points = circle.IntersectionPointsCircle(shape)
|
||||
|
||||
if len(contactSet.Points) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
contactSet.MTV = Vector{circle.X - shape.X, circle.Y - shape.Y}
|
||||
dist := contactSet.MTV.Magnitude()
|
||||
contactSet.MTV = contactSet.MTV.Unit().Scale(circle.Radius + shape.Radius - dist)
|
||||
|
||||
for _, point := range contactSet.Points {
|
||||
contactSet.Center = contactSet.Center.Add(point)
|
||||
}
|
||||
|
||||
contactSet.Center[0] /= float64(len(contactSet.Points))
|
||||
contactSet.Center[1] /= float64(len(contactSet.Points))
|
||||
|
||||
// if contactSet != nil {
|
||||
// contactSet.MTV[0] -= dx
|
||||
// contactSet.MTV[1] -= dy
|
||||
// }
|
||||
|
||||
// contactSet.MTV = Vector{circle.X - shape.X, circle.Y - shape.Y}
|
||||
}
|
||||
|
||||
circle.X = ox
|
||||
circle.Y = oy
|
||||
|
||||
return contactSet
|
||||
}
|
||||
|
||||
// Move translates the Circle by the designated X and Y values.
|
||||
func (circle *Circle) Move(x, y float64) {
|
||||
circle.X += x
|
||||
circle.Y += y
|
||||
}
|
||||
|
||||
// MoveVec translates the Circle by the designated Vector.
|
||||
func (circle *Circle) MoveVec(vec Vector) {
|
||||
circle.X += vec.X()
|
||||
circle.Y += vec.Y()
|
||||
}
|
||||
|
||||
// SetPosition sets the center position of the Circle using the X and Y values given.
|
||||
func (circle *Circle) SetPosition(x, y float64) {
|
||||
circle.X = x
|
||||
circle.Y = y
|
||||
}
|
||||
|
||||
// SetPosition sets the center position of the Circle using the Vector given.
|
||||
func (circle *Circle) SetPositionVec(vec Vector) {
|
||||
circle.X = vec.X()
|
||||
circle.Y = vec.Y()
|
||||
}
|
||||
|
||||
// Position() returns the X and Y position of the Circle.
|
||||
func (circle *Circle) Position() (float64, float64) {
|
||||
return circle.X, circle.Y
|
||||
}
|
||||
|
||||
// PointInside returns if the given Vector is inside of the circle.
|
||||
func (circle *Circle) PointInside(point Vector) bool {
|
||||
return point.Sub(Vector{circle.X, circle.Y}).Magnitude() <= circle.Radius
|
||||
}
|
||||
|
||||
// IntersectionPointsCircle returns the intersection points of the two circles provided.
|
||||
func (circle *Circle) IntersectionPointsCircle(other *Circle) []Vector {
|
||||
|
||||
d := math.Sqrt(math.Pow(other.X-circle.X, 2) + math.Pow(other.Y-circle.Y, 2))
|
||||
|
||||
if d > circle.Radius+other.Radius || d < math.Abs(circle.Radius-other.Radius) || d == 0 && circle.Radius == other.Radius {
|
||||
return nil
|
||||
}
|
||||
|
||||
a := (math.Pow(circle.Radius, 2) - math.Pow(other.Radius, 2) + math.Pow(d, 2)) / (2 * d)
|
||||
h := math.Sqrt(math.Pow(circle.Radius, 2) - math.Pow(a, 2))
|
||||
|
||||
x2 := circle.X + a*(other.X-circle.X)/d
|
||||
y2 := circle.Y + a*(other.Y-circle.Y)/d
|
||||
|
||||
return []Vector{
|
||||
{x2 + h*(other.Y-circle.Y)/d, y2 - h*(other.X-circle.X)/d},
|
||||
{x2 - h*(other.Y-circle.Y)/d, y2 + h*(other.X-circle.X)/d},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type Projection struct {
|
||||
Min, Max float64
|
||||
}
|
||||
|
@@ -31,6 +31,7 @@ func (v Vector) Clone() Vector {
|
||||
return clone
|
||||
}
|
||||
|
||||
/*
|
||||
// Add a vector with a vector or a set of vectors
|
||||
func Add(v1 Vector, vs ...Vector) Vector {
|
||||
return v1.Clone().Add(vs...)
|
||||
@@ -81,6 +82,7 @@ func (v Vector) Scale(size float64) Vector {
|
||||
scalUnitaryTo(v, size, v)
|
||||
return v
|
||||
}
|
||||
*/
|
||||
|
||||
// Equal compares that two vectors are equal to each other
|
||||
func Equal(v1, v2 Vector) bool {
|
||||
|
Reference in New Issue
Block a user