diff --git a/CHANGELOG.md b/CHANGELOG.md index e5162d49d2..ca36013dd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -348,6 +348,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Removed `RealToSimCap` and `OneSimUpdatePerFrame`. Instead we try to always target 60fps, even if it slows the simulation down a little. +- Removed `Settings.ini` property `SimplifiedCollisionDetection = 0/1`. With the physics detection overhaul in pre-5, this became unnecessary. + *** diff --git a/Source/Activities/GameActivity.cpp b/Source/Activities/GameActivity.cpp index cded08a7e2..4db05a3070 100644 --- a/Source/Activities/GameActivity.cpp +++ b/Source/Activities/GameActivity.cpp @@ -1229,8 +1229,6 @@ void GameActivity::Update() { else if (m_ViewState[player] == ViewState::ActorSelect) { // Continuously display message g_FrameMan.SetScreenText("Select a body to switch control to...", ScreenOfPlayer(player)); - // Get cursor input - m_PlayerController[player].RelativeCursorMovement(m_ActorCursor[player]); // Find the actor closest to the cursor, if any within the radius Vector markedDistance; @@ -1284,10 +1282,6 @@ void GameActivity::Update() { } } - // Set the view to the cursor pos - g_SceneMan.ForceBounds(m_ActorCursor[player]); - g_CameraMan.SetScrollTarget(m_ActorCursor[player], 0.1, ScreenOfPlayer(player)); - if (m_pLastMarkedActor[player]) { if (!g_MovableMan.ValidMO(m_pLastMarkedActor[player])) { m_pLastMarkedActor[player] = nullptr; @@ -1701,9 +1695,6 @@ void GameActivity::Update() { m_pBuyGUI[player]->Update(); } - // Trap the mouse if we're in gameplay and not in menus - g_UInputMan.TrapMousePos(!m_pBuyGUI[player]->IsEnabled() && !m_InventoryMenuGUI[player]->IsEnabledAndNotCarousel() && !m_LuaLockActor[player], player); - // Start LZ picking mode if a purchase was made if (m_pBuyGUI[player]->PurchaseMade()) { m_LZCursorWidth[player] = std::min(m_pBuyGUI[player]->GetDeliveryWidth(), g_FrameMan.GetPlayerScreenWidth() - 24); @@ -1889,8 +1880,23 @@ void GameActivity::DrawGUI(BITMAP* pTargetBitmap, const Vector& targetPos, int w // Iterate through all players, drawing each currently used LZ cursor. for (int player = Players::PlayerOne; player < Players::MaxPlayerCount; ++player) { - if (!(m_IsActive[player] && m_IsHuman[player])) + if (!(m_IsActive[player] && m_IsHuman[player])) { continue; + } + + // TODO_MULTITHREAD properly formalize this. Maybe an UpdateRender/UpdateRealTime function on things? + m_PlayerController[player].Update(); + // Trap the mouse if we're in gameplay and not in menus + g_UInputMan.TrapMousePos(!m_pBuyGUI[player]->IsEnabled() && !m_InventoryMenuGUI[player]->IsEnabledAndNotCarousel() && !m_LuaLockActor[player], player); + if (m_ViewState[player] == ViewState::ActorSelect) { + // Get cursor input + m_PlayerController[player].RelativeCursorMovement(m_ActorCursor[player]); + + // Set the view to the cursor pos + g_SceneMan.ForceBounds(m_ActorCursor[player]); + g_CameraMan.SetScrollTarget(m_ActorCursor[player], 0.1, ScreenOfPlayer(player)); + } + // TODO_MULTITHREAD if (m_ViewState[player] == ViewState::LandingZoneSelect) { int halfWidth = std::max(m_LZCursorWidth[player] / 2, 36); @@ -1989,6 +1995,8 @@ void GameActivity::DrawGUI(BITMAP* pTargetBitmap, const Vector& targetPos, int w float rightStackY = leftStackY; // Draw the objective points this player should care about + // TODO_MULTITHREAD +#ifndef MULTITHREAD_SIM_AND_RENDER for (std::list::iterator itr = m_Objectives.begin(); itr != m_Objectives.end(); ++itr) { // Only draw objectives of the same team as the current player if (itr->m_Team == team) { @@ -2051,6 +2059,7 @@ void GameActivity::DrawGUI(BITMAP* pTargetBitmap, const Vector& targetPos, int w } } } +#endif // Team Icon up in the top left corner const Icon* pIcon = GetTeamIcon(m_Team[PoS]); diff --git a/Source/Entities/ACrab.cpp b/Source/Entities/ACrab.cpp index d3163efae1..2e6bcde2dc 100644 --- a/Source/Entities/ACrab.cpp +++ b/Source/Entities/ACrab.cpp @@ -1423,29 +1423,6 @@ void ACrab::DrawHUD(BITMAP* pTargetBitmap, const Vector& targetPos, int whichScr if (!m_HUDVisible) { return; } - /* - // TODO: REMOVE< THIS IS TEMP - // Draw the AI paths - list::iterator last = m_MovePath.begin(); - Vector waypoint, lastPoint, lineVec; - for (list::iterator lItr = m_MovePath.begin(); lItr != m_MovePath.end(); ++lItr) - { - lastPoint = (*last) - targetPos; - waypoint = lastPoint + g_SceneMan.ShortestDistance(lastPoint, (*lItr) - targetPos); - line(pTargetBitmap, lastPoint.m_X, lastPoint.m_Y, waypoint.m_X, waypoint.m_Y, g_RedColor); - last = lItr; - } - waypoint = m_MoveTarget - targetPos; - circlefill(pTargetBitmap, waypoint.m_X, waypoint.m_Y, 3, g_RedColor); - lastPoint = m_PrevPathTarget - targetPos; - circlefill(pTargetBitmap, lastPoint.m_X, lastPoint.m_Y, 2, g_YellowGlowColor); - lastPoint = m_DigTunnelEndPos - targetPos; - circlefill(pTargetBitmap, lastPoint.m_X, lastPoint.m_Y, 2, g_YellowGlowColor); - // Raidus - // waypoint = m_Pos - targetPos; - // circle(pTargetBitmap, waypoint.m_X, waypoint.m_Y, m_MoveProximityLimit, g_RedColor); - // TODO: REMOVE THIS IS TEMP - */ // Player AI drawing @@ -1554,63 +1531,6 @@ void ACrab::DrawHUD(BITMAP* pTargetBitmap, const Vector& targetPos, int whichScr } m_HUDStack -= 9; } - - // Print aim angle and rot angle stoff - /*{ - std::snprintf(str, sizeof(str), "Aim %.2f Rot %.2f Lim %.2f", m_AimAngle, GetRotAngle(), m_AimRange + GetRotAngle()); - pSmallFont->DrawAligned(&allegroBitmap, drawPos.m_X - 0, drawPos.m_Y + m_HUDStack + 3, str, GUIFont::Centre); - - m_HUDStack += -10; - }*/ - - /* - // AI Mode select GUI HUD - if (m_Controller.IsState(AI_MODE_SET)) - { - int iconOff = m_apAIIcons[0]->w + 2; - int iconColor = m_Team == Activity::TeamOne ? AIICON_RED : AIICON_GREEN; - Vector iconPos = GetCPUPos() - targetPos; - - if (m_AIMode == AIMODE_SENTRY) - { - std::snprintf(str, sizeof(str), "%s", "Sentry"); - pSmallFont->DrawAligned(&allegroBitmap, iconPos.m_X, iconPos.m_Y - 18, str, GUIFont::Centre); - } - else if (m_AIMode == AIMODE_PATROL) - { - std::snprintf(str, sizeof(str), "%s", "Patrol"); - pSmallFont->DrawAligned(&allegroBitmap, iconPos.m_X - 9, iconPos.m_Y - 5, str, GUIFont::Right); - } - else if (m_AIMode == AIMODE_BRAINHUNT) - { - std::snprintf(str, sizeof(str), "%s", "Brainhunt"); - pSmallFont->DrawAligned(&allegroBitmap, iconPos.m_X + 9, iconPos.m_Y - 5, str, GUIFont::Left); - } - else if (m_AIMode == AIMODE_GOLDDIG) - { - std::snprintf(str, sizeof(str), "%s", "Gold Dig"); - pSmallFont->DrawAligned(&allegroBitmap, iconPos.m_X, iconPos.m_Y + 8, str, GUIFont::Centre); - } - - // Draw the mode alternatives if they are not the current one - if (m_AIMode != AIMODE_SENTRY) - { - draw_sprite(pTargetBitmap, m_apAIIcons[AIMODE_SENTRY], iconPos.m_X - 6, iconPos.m_Y - 6 - iconOff); - } - if (m_AIMode != AIMODE_PATROL) - { - draw_sprite(pTargetBitmap, m_apAIIcons[AIMODE_PATROL], iconPos.m_X - 6 - iconOff, iconPos.m_Y - 6); - } - if (m_AIMode != AIMODE_BRAINHUNT) - { - draw_sprite(pTargetBitmap, m_apAIIcons[AIMODE_BRAINHUNT], iconPos.m_X - 6 + iconOff, iconPos.m_Y - 6); - } - if (m_AIMode != AIMODE_GOLDDIG) - { - draw_sprite(pTargetBitmap, m_apAIIcons[AIMODE_GOLDDIG], iconPos.m_X - 6, iconPos.m_Y - 6 + iconOff); - } - } - */ } } diff --git a/Source/Entities/ACraft.cpp b/Source/Entities/ACraft.cpp index 2dba8af53c..f2450b1094 100644 --- a/Source/Entities/ACraft.cpp +++ b/Source/Entities/ACraft.cpp @@ -752,8 +752,9 @@ void ACraft::DrawHUD(BITMAP* pTargetBitmap, const Vector& targetPos, int whichSc m_HUDStack = -m_CharHeight / 2; // Only do HUD if on a team - if (m_Team < 0) + if (m_Team < 0) { return; + } // Only draw if the team viewing this is on the same team OR has seen the space where this is located. int viewingTeam = g_ActivityMan.GetActivity()->GetTeamOfPlayer(g_ActivityMan.GetActivity()->PlayerOfScreen(whichScreen)); @@ -771,93 +772,39 @@ void ACraft::DrawHUD(BITMAP* pTargetBitmap, const Vector& targetPos, int whichSc GUIFont* pSmallFont = g_FrameMan.GetSmallFont(); // Draw hud guides for the Exits, depending on whether the doors are open - if (m_HatchState == OPEN) // || m_HatchState == OPENING) - { + if (m_HatchState == OPEN) { // Doors open and inventory not empty yet, so show arrows pointing out of the exits since things are still coming out if (!IsInventoryEmpty()) { // -------- // | \ \ - // -+- | | + // -+- | | // | / / // -------- // Make the dotted lines crawl out of the exit, indicating that things are still coming out - if (--m_ExitLinePhase < 0) + if (--m_ExitLinePhase < 0) { m_ExitLinePhase = EXITLINESPACING - 1; + } } // Inventory empty and doors open, so show arrows pointing into the exits IF the delay to allow for things to eject away all the way has passed else if (m_ExitTimer.IsPastSimMS(EXITSUCKDELAYMS)) { // Make the dotted lines crawl back into the exit, inviting people to jump in - if (++m_ExitLinePhase >= EXITLINESPACING) + if (++m_ExitLinePhase >= EXITLINESPACING) { m_ExitLinePhase = 0; + } } - Vector exitRadius; - Vector exitCorner; - Vector arrowVec; // Draw the actual dotted lines for (std::list::iterator exit = m_Exits.begin(); exit != m_Exits.end(); ++exit) { - if (exit->CheckIfClear(m_Pos, m_Rotation, 18)) { - exitRadius = RotateOffset(exit->GetVelocity().GetPerpendicular().SetMagnitude(exit->GetRadius())); - exitCorner = m_Pos - targetPos + RotateOffset(exit->GetOffset()) + exitRadius; - arrowVec = RotateOffset(exit->GetVelocity().SetMagnitude(exit->GetRange())); - g_FrameMan.DrawLine(pTargetBitmap, exitCorner, exitCorner + arrowVec, 120, 120, EXITLINESPACING, m_ExitLinePhase); - exitCorner -= exitRadius * 2; - g_FrameMan.DrawLine(pTargetBitmap, exitCorner, exitCorner + arrowVec, 120, 120, EXITLINESPACING, m_ExitLinePhase); + if (!exit->CheckIfClear(m_Pos, m_Rotation, 18)) { + continue; } - } - } - // Only show extra HUD if this guy is controlled by a player - if (m_Controller.IsPlayerControlled() && pSmallFont && pSymbolFont) { - AllegroBitmap pBitmapInt(pTargetBitmap); - /* - // AI Mode select GUI HUD - if (m_Controller && m_Controller.IsState(PIE_MENU_ACTIVE)) - { - char str[64]; - int iconOff = m_apAIIcons[0]->w + 2; - int iconColor = m_Team == Activity::TeamOne ? AIICON_RED : AIICON_GREEN; - Vector iconPos = GetCPUPos() - targetPos; - - if (m_AIMode == AIMODE_RETURN) - { - std::snprintf(str, sizeof(str), "%s", "Return"); - pSmallFont->DrawAligned(&pBitmapInt, iconPos.m_X, iconPos.m_Y - 18, str, GUIFont::Centre); - } - else if (m_AIMode == AIMODE_DELIVER) - { - std::snprintf(str, sizeof(str), "%s", "Deliver"); - pSmallFont->DrawAligned(&pBitmapInt, iconPos.m_X - 9, iconPos.m_Y - 5, str, GUIFont::Right); - } - else if (m_AIMode == AIMODE_SCUTTLE) - { - std::snprintf(str, sizeof(str), "%s", "Scuttle"); - pSmallFont->DrawAligned(&pBitmapInt, iconPos.m_X + 9, iconPos.m_Y - 5, str, GUIFont::Left); - } - else if (m_AIMode == AIMODE_STAY) - { - std::snprintf(str, sizeof(str), "%s", "Stay"); - pSmallFont->DrawAligned(&pBitmapInt, iconPos.m_X, iconPos.m_Y + 8, str, GUIFont::Centre); - } - - // Draw the mode alternatives if they are not the current one - if (m_AIMode != AIMODE_RETURN) - { - draw_sprite(pTargetBitmap, m_apAIIcons[AIMODE_RETURN], iconPos.m_X - 6, iconPos.m_Y - 6 - iconOff); - } - if (m_AIMode != AIMODE_DELIVER) - { - draw_sprite(pTargetBitmap, m_apAIIcons[AIMODE_DELIVER], iconPos.m_X - 6 - iconOff, iconPos.m_Y - 6); - } - if (m_AIMode != AIMODE_SCUTTLE) - { - draw_sprite(pTargetBitmap, m_apAIIcons[AIMODE_SCUTTLE], iconPos.m_X - 6 + iconOff, iconPos.m_Y - 6); - } - if (m_AIMode != AIMODE_STAY) - { - draw_sprite(pTargetBitmap, m_apAIIcons[AIMODE_STAY], iconPos.m_X - 6, iconPos.m_Y - 6 + iconOff); - } - } - */ + Vector exitRadius = RotateOffset(exit->GetVelocity().GetPerpendicular().SetMagnitude(exit->GetRadius())); + Vector exitCorner = m_Pos - targetPos + RotateOffset(exit->GetOffset()) + exitRadius; + Vector arrowVec = RotateOffset(exit->GetVelocity().SetMagnitude(exit->GetRange())); + g_FrameMan.DrawLine(pTargetBitmap, exitCorner, exitCorner + arrowVec, 120, 120, EXITLINESPACING, m_ExitLinePhase); + exitCorner -= exitRadius * 2; + g_FrameMan.DrawLine(pTargetBitmap, exitCorner, exitCorner + arrowVec, 120, 120, EXITLINESPACING, m_ExitLinePhase); + } } } diff --git a/Source/Entities/ADoor.cpp b/Source/Entities/ADoor.cpp index 510c762736..bb6dc4cd33 100644 --- a/Source/Entities/ADoor.cpp +++ b/Source/Entities/ADoor.cpp @@ -452,13 +452,13 @@ void ADoor::Update() { // Lose health when door is lost, spinning out of control until grinds to halt if (!m_Door && m_Status != DYING && m_Status != DEAD) { m_SpriteAnimMode = ALWAYSLOOP; - m_SpriteAnimDuration = static_cast(LERP(0, m_MaxHealth, 10.0F, static_cast(m_InitialSpriteAnimDuration), m_Health)); + m_SpriteAnimDuration = static_cast(Lerp(0, m_MaxHealth, 10.0F, static_cast(m_InitialSpriteAnimDuration), m_Health)); if (m_DoorMoveSound) { if (!m_DoorMoveSound->IsBeingPlayed()) { m_DoorMoveSound->Play(m_Pos); } - m_DoorMoveSound->SetPitch(LERP(10.0F, static_cast(m_InitialSpriteAnimDuration), 2.0F, 1.0F, static_cast(m_SpriteAnimDuration))); + m_DoorMoveSound->SetPitch(Lerp(10.0F, static_cast(m_InitialSpriteAnimDuration), 2.0F, 1.0F, static_cast(m_SpriteAnimDuration))); } m_Health -= 0.4F; @@ -551,11 +551,8 @@ void ADoor::UpdateDoorAttachableActions() { m_DoorState = CLOSED; } } else { - Vector updatedOffset(LERP(0, m_DoorMoveTime, startOffset.m_X, endOffset.m_X, m_DoorMoveTimer.GetElapsedSimTimeMS()), LERP(0, m_DoorMoveTime, startOffset.m_Y, endOffset.m_Y, m_DoorMoveTimer.GetElapsedSimTimeMS())); - - // TODO: Make this work across rotation 0. Probably the best solution would be to setup an angle LERP that properly handles the 2PI border and +- angles. - // TODO_MULTITHREAD: multithread branch has lerped rotation, so once that's done! - float updatedAngle = LERP(0, m_DoorMoveTime, startAngle, endAngle, m_DoorMoveTimer.GetElapsedSimTimeMS()); + Vector updatedOffset(Lerp(0, m_DoorMoveTime, startOffset.m_X, endOffset.m_X, m_DoorMoveTimer.GetElapsedSimTimeMS()), Lerp(0, m_DoorMoveTime, startOffset.m_Y, endOffset.m_Y, m_DoorMoveTimer.GetElapsedSimTimeMS())); + float updatedAngle = Lerp(0, m_DoorMoveTime, Matrix(startAngle), Matrix(endAngle), m_DoorMoveTimer.GetElapsedSimTimeMS()).GetRadAngle(); m_Door->SetParentOffset(updatedOffset); m_Door->SetRotAngle(m_Rotation.GetRadAngle() + (updatedAngle * GetFlipFactor())); @@ -574,6 +571,7 @@ void ADoor::DrawHUD(BITMAP* targetBitmap, const Vector& targetPos, int whichScre if (!m_HUDVisible) { return; } + // Only draw if the team viewing this is on the same team OR has seen the space where this is located. int viewingTeam = g_ActivityMan.GetActivity()->GetTeamOfPlayer(g_ActivityMan.GetActivity()->PlayerOfScreen(whichScreen)); if (viewingTeam != m_Team && viewingTeam != Activity::NoTeam && (!g_SettingsMan.ShowEnemyHUD() || g_SceneMan.IsUnseen(m_Pos.GetFloorIntX(), m_Pos.GetFloorIntY(), viewingTeam))) { diff --git a/Source/Entities/AEmitter.cpp b/Source/Entities/AEmitter.cpp index 514b918793..eade83f980 100644 --- a/Source/Entities/AEmitter.cpp +++ b/Source/Entities/AEmitter.cpp @@ -314,8 +314,8 @@ int AEmitter::GetTotalBurstSize() const { } float AEmitter::GetScaledThrottle(float throttle, float multiplier) const { - float throttleFactor = LERP(-1.0f, 1.0f, m_NegativeThrottleMultiplier, m_PositiveThrottleMultiplier, throttle); - return LERP(m_NegativeThrottleMultiplier, m_PositiveThrottleMultiplier, -1.0f, 1.0f, throttleFactor * multiplier); + float throttleFactor = Lerp(-1.0f, 1.0f, m_NegativeThrottleMultiplier, m_PositiveThrottleMultiplier, throttle); + return Lerp(m_NegativeThrottleMultiplier, m_PositiveThrottleMultiplier, -1.0f, 1.0f, throttleFactor * multiplier); } void AEmitter::SetFlash(Attachable* newFlash) { diff --git a/Source/Entities/AEmitter.h b/Source/Entities/AEmitter.h index d6cc20ff42..9c98af386f 100644 --- a/Source/Entities/AEmitter.h +++ b/Source/Entities/AEmitter.h @@ -140,11 +140,11 @@ namespace RTE { /// Gets the adjusted throttle multiplier that is factored into the emission rate of this AEmitter. /// @return The throttle strength as a multiplier. - float GetThrottleFactor() const { return LERP(-1.0f, 1.0f, m_NegativeThrottleMultiplier, m_PositiveThrottleMultiplier, m_Throttle); } + float GetThrottleFactor() const { return Lerp(-1.0f, 1.0f, m_NegativeThrottleMultiplier, m_PositiveThrottleMultiplier, m_Throttle); } /// Gets the throttle value that will achieve a given throttle factor that is factored into the emission rate of this AEmitter. /// @return The throttle value that will achieve the given throttle factor. - float GetThrottleForThrottleFactor(float throttleFactor) const { return LERP(m_NegativeThrottleMultiplier, m_PositiveThrottleMultiplier, -1.0f, 1.0f, throttleFactor); } + float GetThrottleForThrottleFactor(float throttleFactor) const { return Lerp(m_NegativeThrottleMultiplier, m_PositiveThrottleMultiplier, -1.0f, 1.0f, throttleFactor); } /// Returns a scaled throttle value that represents a linear increase of force. /// Because of (bad) reasons, throttle is in the range -1.0F to 1.0F, where -1.0F is "minimum force" and 1.0F is "maximum force". diff --git a/Source/Entities/AHuman.cpp b/Source/Entities/AHuman.cpp index 7cf726d2be..54dd073857 100644 --- a/Source/Entities/AHuman.cpp +++ b/Source/Entities/AHuman.cpp @@ -764,11 +764,7 @@ bool AHuman::EquipDeviceInGroup(std::string group, bool doEquip) { // Note - This is a fix to deal with an edge case bug when this method is called by a global script. // Because the global script runs before everything has finished traveling, the removed item needs to undraw itself from the MO layer, otherwise it can result in ghost collisions and crashes. if (previouslyHeldItem->GetsHitByMOs()) { -#ifdef DRAW_MOID_LAYER - previouslyHeldItem->Draw(g_SceneMan.GetMOIDBitmap(), Vector(), g_DrawNoMOID, true); -#else previouslyHeldItem->SetTraveling(true); -#endif } AddToInventoryBack(previouslyHeldItem); } @@ -1457,12 +1453,12 @@ void AHuman::UpdateCrouching() { desiredWalkPathYOffset = m_CrouchAmountOverride * m_MaxWalkPathCrouchShift; } - float finalWalkPathYOffset = std::clamp(LERP(0.0F, 1.0F, -m_WalkPathOffset.m_Y, desiredWalkPathYOffset, 0.3F), 0.0F, m_MaxWalkPathCrouchShift); + float finalWalkPathYOffset = std::clamp(Lerp(0.0F, 1.0F, -m_WalkPathOffset.m_Y, desiredWalkPathYOffset, 0.3F), 0.0F, m_MaxWalkPathCrouchShift); m_WalkPathOffset.m_Y = -finalWalkPathYOffset; // If crouching, move at reduced speed const float crouchSpeedMultiplier = 0.5F; - float travelSpeedMultiplier = LERP(0.0F, m_MaxWalkPathCrouchShift, 1.0F, crouchSpeedMultiplier, -m_WalkPathOffset.m_Y); + float travelSpeedMultiplier = Lerp(0.0F, m_MaxWalkPathCrouchShift, 1.0F, crouchSpeedMultiplier, -m_WalkPathOffset.m_Y); m_Paths[FGROUND][WALK].SetTravelSpeedMultiplier(travelSpeedMultiplier); m_Paths[BGROUND][WALK].SetTravelSpeedMultiplier(travelSpeedMultiplier); @@ -2466,7 +2462,7 @@ void AHuman::Update() { // Lean forwards when crouching float crouchAngleAdjust = m_HFlipped ? m_MaxCrouchRotation : -m_MaxCrouchRotation; - rotTarget += LERP(0.0F, m_MaxWalkPathCrouchShift, 0.0F, crouchAngleAdjust, m_WalkPathOffset.m_Y * -1.0F); + rotTarget += Lerp(0.0F, m_MaxWalkPathCrouchShift, 0.0F, crouchAngleAdjust, m_WalkPathOffset.m_Y * -1.0F); float rotDiff = rot - rotTarget; m_AngularVel = m_AngularVel * (0.98F - 0.06F * (m_Health / m_MaxHealth)) - (rotDiff * 0.5F); @@ -2586,8 +2582,9 @@ void AHuman::DrawHUD(BITMAP* pTargetBitmap, const Vector& targetPos, int whichSc m_HUDStack = -m_CharHeight / 2; // Only do HUD if on a team - if (m_Team < 0) + if (m_Team < 0) { return; + } // Only draw if the team viewing this is on the same team OR has seen the space where this is located. int viewingTeam = g_ActivityMan.GetActivity()->GetTeamOfPlayer(g_ActivityMan.GetActivity()->PlayerOfScreen(whichScreen)); diff --git a/Source/Entities/Actor.cpp b/Source/Entities/Actor.cpp index 41906932f8..35b51a760d 100644 --- a/Source/Entities/Actor.cpp +++ b/Source/Entities/Actor.cpp @@ -941,16 +941,6 @@ void Actor::GibThis(const Vector& impactImpulse, MovableObject* movableObjectToI bool Actor::CollideAtPoint(HitData& hd) { return MOSRotating::CollideAtPoint(hd); - - // if (hd.ResImpulse[HITEE].MagnitudeIsGreaterThan(GetMaterial().strength)) { - // m_pParent-> - // } - /* Obsolete - // Set item as being reached if it collides with us - if (hd.Body[HITOR]->IsHeldDevice()) - m_pItemInReach = dynamic_cast(hd.Body[HITOR]); - */ - // if (Status != ACTIVE) } bool Actor::ParticlePenetration(HitData& hd) { @@ -1275,24 +1265,6 @@ void Actor::Update() { g_FrameMan.FlashScreen(g_ActivityMan.GetActivity()->ScreenOfPlayer(brainOfPlayer), g_WhiteColor, 500); } } - - // Do NOT mess witht he HUD stack in update... it should only be altered in DrawHUD, or it will jitter when multiple sim updates happen - // m_HUDStack = -m_CharHeight / 2; - - /* - // *** TEMP Hack for testing animation - int bajs = m_aSprite->GetVelX(); - bajs %= 5; - m_aSprite->SetVelX(++bajs); - - if (bajs == 1) - { - int frame = m_aSprite->GetFrame(); - if (++frame >= 7) - frame = 1; - m_aSprite->SetFrame(frame); - } - */ } void Actor::FullUpdate() { @@ -1302,12 +1274,13 @@ void Actor::FullUpdate() { } void Actor::DrawHUD(BITMAP* pTargetBitmap, const Vector& targetPos, int whichScreen, bool playerControlled) { - // This should indeed be a local var and not alter a member one in a draw func! Can cause nasty jittering etc if multiple sim updates are done without a drawing in between etc + // This should probably be a local var and not alter a member one in a draw func. Would let us make these functions const m_HUDStack = -m_CharHeight / 2; // Only do HUD if on a team - if (m_Team < 0) + if (m_Team < 0) { return; + } // Only draw if the team viewing this is on the same team OR has seen the space where this is located. int viewingTeam = g_ActivityMan.GetActivity()->GetTeamOfPlayer(g_ActivityMan.GetActivity()->PlayerOfScreen(whichScreen)); @@ -1336,6 +1309,7 @@ void Actor::DrawHUD(BITMAP* pTargetBitmap, const Vector& targetPos, int whichScr cpuPos.m_X += sceneWidth; } } + // Spans horizontal scene seam int sceneHeight = g_SceneMan.GetSceneHeight(); if (g_SceneMan.SceneWrapsY() && pTargetBitmap->h < sceneHeight) { @@ -1361,13 +1335,13 @@ void Actor::DrawHUD(BITMAP* pTargetBitmap, const Vector& targetPos, int whichScr // Draw the selection arrow, if controlled and under the arrow's time limit if (m_Controller.IsPlayerControlled() && m_NewControlTmr.GetElapsedSimTimeMS() < ARROWTIME) { - // Draw the appropriate selection arrow color based on player team draw_sprite(pTargetBitmap, m_apSelectArrow[m_Team], cpuPos.m_X, EaseOut(drawPos.m_Y + m_HUDStack - 60, drawPos.m_Y + m_HUDStack - 20, m_NewControlTmr.GetElapsedSimTimeMS() / (float)ARROWTIME)); } // Draw the alarm exclamation mark if we are alarmed! - if (m_AlarmTimer.SimTimeLimitProgress() < 0.25) + if (m_AlarmTimer.SimTimeLimitProgress() < 0.25) { draw_sprite(pTargetBitmap, m_apAlarmExclamation[m_AgeTimer.AlternateSim(100)], cpuPos.m_X - 3, EaseOut(drawPos.m_Y + m_HUDStack - 10, drawPos.m_Y + m_HUDStack - 25, m_AlarmTimer.SimTimeLimitProgress() / 0.25f)); + } if (pSmallFont && pSymbolFont) { AllegroBitmap bitmapInt(pTargetBitmap); @@ -1377,29 +1351,33 @@ void Actor::DrawHUD(BITMAP* pTargetBitmap, const Vector& targetPos, int whichScr if (m_Health > 0) { if (IsPlayerControlled() && g_FrameMan.IsInMultiplayerMode()) { m_pControllerIcon = 0; - if (m_Team == 0) + + if (m_Team == 0) { m_pControllerIcon = g_UInputMan.GetDeviceIcon(DEVICE_GAMEPAD_1); - else if (m_Team == 1) + } else if (m_Team == 1) { m_pControllerIcon = g_UInputMan.GetDeviceIcon(DEVICE_GAMEPAD_2); - else if (m_Team == 2) + } else if (m_Team == 2) { m_pControllerIcon = g_UInputMan.GetDeviceIcon(DEVICE_GAMEPAD_3); - else if (m_Team == 3) + } else if (m_Team == 3) { m_pControllerIcon = g_UInputMan.GetDeviceIcon(DEVICE_GAMEPAD_4); + } + if (m_pControllerIcon) { std::vector apControllerBitmaps = m_pControllerIcon->GetBitmaps8(); - masked_blit(apControllerBitmaps[0], pTargetBitmap, 0, 0, drawPos.m_X - apControllerBitmaps[0]->w - 2 + 10, drawPos.m_Y + m_HUDStack - (apControllerBitmaps[0]->h / 2) + 8, apControllerBitmaps[0]->w, apControllerBitmaps[0]->h); } } // Get the Icon bitmaps of this Actor's team, if any std::vector apIconBitmaps; - if (m_pTeamIcon) + if (m_pTeamIcon) { apIconBitmaps = m_pTeamIcon->GetBitmaps8(); + } // Team Icon could not be found, or of no team, so use the static noteam Icon instead - if (apIconBitmaps.empty()) + if (apIconBitmaps.empty()) { apIconBitmaps = m_apNoTeamIcon; + } // Now draw the Icon if we can if (!apIconBitmaps.empty() && m_pTeamIcon && m_pTeamIcon->GetFrameCount() > 0) { @@ -1408,31 +1386,13 @@ void Actor::DrawHUD(BITMAP* pTargetBitmap, const Vector& targetPos, int whichScr f = MIN(f, m_pTeamIcon ? m_pTeamIcon->GetFrameCount() - 1 : 1); masked_blit(apIconBitmaps.at(f), pTargetBitmap, 0, 0, drawPos.m_X - apIconBitmaps.at(f)->w - 2, drawPos.m_Y + m_HUDStack - (apIconBitmaps.at(f)->h / 2) + 8, apIconBitmaps.at(f)->w, apIconBitmaps.at(f)->h); } - } - // Draw death icon - else { + } else { + // Draw death icon str[0] = -39; str[1] = 0; pSymbolFont->DrawAligned(&bitmapInt, drawPos.m_X - 10, drawPos.m_Y + m_HUDStack, str, GUIFont::Left); } - /* Obsolete red/gren heart Team icon - // Health - if (m_HeartBeat.GetElapsedSimTimeMS() > (m_Health > 90 ? 850 : (m_Health > 25 ? 350 : 100)) || m_Health <= 0) - { - str[0] = m_Health > 0 ? (m_Team == 0 ? -64 : -61) : -39; - str[1] = 0; - pSymbolFont->DrawAligned(&bitmapInt, drawPos.m_X - 10, drawPos.m_Y + m_HUDStack, str, GUIFont::Left); - if (m_HeartBeat.GetElapsedSimTimeMS() > (m_Health > 90 ? 950 : (m_Health > 25 ? 500 : 175))) - m_HeartBeat.Reset(); - } - else - { - str[0] = m_Team == 0 ? -63 : -60; - str[1] = 0; - pSymbolFont->DrawAligned(&bitmapInt, drawPos.m_X - 11, drawPos.m_Y + m_HUDStack, str, GUIFont::Left); - } - */ std::snprintf(str, sizeof(str), "%.0f", std::ceil(m_Health)); pSymbolFont->DrawAligned(&bitmapInt, drawPos.m_X - 0, drawPos.m_Y + m_HUDStack, str, GUIFont::Left); @@ -1456,20 +1416,13 @@ void Actor::DrawHUD(BITMAP* pTargetBitmap, const Vector& targetPos, int whichScr } } } - /* Obsolete - // Draw the contol pointer, if controlled and under the icon's time limit - if (m_Controller.IsPlayetControlled() && m_NewControlTmr.GetElapsedSimTimeMS() < 1500) - { - std::snprintf(str, sizeof(str), "%c", -38); - pSymbolFont->DrawAligned(&bitmapInt, cpuPos.m_X - 0, drawPos.m_Y + m_HUDStack, str, GUIFont::Left); - } - */ } } // Don't proceed to draw all the secret stuff below if this screen is for a player on the other team! - if (g_ActivityMan.GetActivity() && g_ActivityMan.GetActivity()->GetTeamOfPlayer(whichScreen) != m_Team) + if (g_ActivityMan.GetActivity() && g_ActivityMan.GetActivity()->GetTeamOfPlayer(whichScreen) != m_Team) { return; + } // AI waypoints or points of interest if (m_DrawWaypoints && m_PlayerControllable && (m_AIMode == AIMODE_GOTO || m_AIMode == AIMODE_SQUAD)) { @@ -1482,10 +1435,6 @@ void Actor::DrawHUD(BITMAP* pTargetBitmap, const Vector& targetPos, int whichScr // Draw the line between the end of the movepath and the first waypoint after that, if any if (!m_Waypoints.empty()) { - // Draw the first destination/waypoint point - // waypoint = m_MoveTarget - targetPos; - // circlefill(pTargetBitmap, waypoint.m_X, waypoint.m_Y, 2, g_YellowGlowColor); - // Draw the additional waypoint points beyond the first one vLast = m_Waypoints.rbegin(); vItr = m_Waypoints.rbegin(); @@ -1497,15 +1446,17 @@ void Actor::DrawHUD(BITMAP* pTargetBitmap, const Vector& targetPos, int whichScr // Draw the points waypoint = (*vItr).first - targetPos; circlefill(pTargetBitmap, waypoint.m_X, waypoint.m_Y, 2, g_YellowGlowColor); + // Add pixel glow area around it, in scene coordinates g_PostProcessMan.RegisterGlowArea((*vItr).first, 5); } // Draw line from the last movetarget on the current path to the first waypoint in queue after that - if (!m_MovePath.empty()) + if (!m_MovePath.empty()) { g_FrameMan.DrawLine(pTargetBitmap, m_MovePath.back() - targetPos, m_Waypoints.front().first - targetPos, g_YellowGlowColor, 0, AILINEDOTSPACING, 0, true); - else + } else { g_FrameMan.DrawLine(pTargetBitmap, m_MoveTarget - targetPos, m_Waypoints.front().first - targetPos, g_YellowGlowColor, 0, AILINEDOTSPACING, 0, true); + } } // Draw the current movepath, but backwards so the dot spacing can be even and they don't crawl as the guy approaches @@ -1520,19 +1471,23 @@ void Actor::DrawHUD(BITMAP* pTargetBitmap, const Vector& targetPos, int whichScr // Draw the line between the current position and to the start of the movepath, backwards so the dotted lines doesn't crawl skipPhase = g_FrameMan.DrawLine(pTargetBitmap, m_MovePath.front() - targetPos, m_Pos - targetPos, g_YellowGlowColor, 0, AILINEDOTSPACING, skipPhase, true); + // Draw the first destination/waypoint point waypoint = m_MovePath.back() - targetPos; circlefill(pTargetBitmap, waypoint.m_X, waypoint.m_Y, 2, g_YellowGlowColor); + // Add pixel glow area around it, in scene coordinates g_PostProcessMan.RegisterGlowArea(m_MovePath.back(), 5); - } - // If no points left on movepath, then draw straight line to the movetarget - else { + } else { + // No points left on movepath, so draw straight line to the movetarget + // Draw it backwards so the dotted lines doesn't crawl skipPhase = g_FrameMan.DrawLine(pTargetBitmap, m_MoveTarget - targetPos, m_Pos - targetPos, g_YellowGlowColor, 0, AILINEDOTSPACING, skipPhase, true); + // Draw the first destination/waypoint point waypoint = m_MoveTarget - targetPos; circlefill(pTargetBitmap, waypoint.m_X, waypoint.m_Y, 2, g_YellowGlowColor); + // Add pixel glow area around it, in scene coordinates g_PostProcessMan.RegisterGlowArea(m_MoveTarget, 5); } @@ -1564,36 +1519,41 @@ void Actor::DrawHUD(BITMAP* pTargetBitmap, const Vector& targetPos, int whichScr // Get the previous available actor in the list (not controlled by another player) std::list::reverse_iterator prevItr = selfRItr; do { - if (++prevItr == pRoster->rend()) + if (++prevItr == pRoster->rend()) { prevItr = pRoster->rbegin(); - if ((*prevItr) == (*selfItr)) + } + + if ((*prevItr) == (*selfItr)) { break; - } while (!(*prevItr)->IsPlayerControllable() || (*prevItr)->GetController()->IsPlayerControlled() || - g_ActivityMan.GetActivity()->IsOtherPlayerBrain((*prevItr), m_Controller.GetPlayer())); + } + } while (!(*prevItr)->IsPlayerControllable() || (*prevItr)->GetController()->IsPlayerControlled() || g_ActivityMan.GetActivity()->IsOtherPlayerBrain((*prevItr), m_Controller.GetPlayer())); // Get the next actor in the list (not controlled by another player) std::list::iterator nextItr = selfItr; do { - if (++nextItr == pRoster->end()) + if (++nextItr == pRoster->end()) { nextItr = pRoster->begin(); - if ((*nextItr) == (*selfItr)) + } + + if ((*nextItr) == (*selfItr)) { break; - } while (!(*nextItr)->IsPlayerControllable() || (*nextItr)->GetController()->IsPlayerControlled() || - g_ActivityMan.GetActivity()->IsOtherPlayerBrain((*prevItr), m_Controller.GetPlayer())); + } + } while (!(*nextItr)->IsPlayerControllable() || (*nextItr)->GetController()->IsPlayerControlled() || g_ActivityMan.GetActivity()->IsOtherPlayerBrain((*prevItr), m_Controller.GetPlayer())); Vector iconPos = cpuPos; + // Only continue if there are available adjacent Actors if ((*prevItr) != (*selfItr) && (*nextItr) != (*selfItr)) { pPrevAdj = *prevItr; pNextAdj = *nextItr; - // Only draw both lines if they're not pointing to the same thing if (pPrevAdj != pNextAdj) { + // Only draw both lines if they're not pointing to the same thing g_FrameMan.DrawLine(pTargetBitmap, cpuPos, pPrevAdj->GetCPUPos() - targetPos, prevColor, prevColor, prevSpacing, 0, true); g_FrameMan.DrawLine(pTargetBitmap, cpuPos, pNextAdj->GetCPUPos() - targetPos, nextColor, nextColor, nextSpacing, 0, true); - } - // If only one other available Actor, only draw one yellow line to it - else + } else { + // If only one other available Actor, only draw one yellow line to it g_FrameMan.DrawLine(pTargetBitmap, cpuPos, pNextAdj->GetCPUPos() - targetPos, 122, 122, 3, 0, true); + } // Prev selected icon iconPos = pPrevAdj->GetCPUPos() - targetPos; @@ -1607,21 +1567,6 @@ void Actor::DrawHUD(BITMAP* pTargetBitmap, const Vector& targetPos, int whichScr // Self selected icon iconPos = cpuPos; draw_sprite(pTargetBitmap, GetAIModeIcon(), iconPos.m_X - 6, iconPos.m_Y - 6); - /* Too many lines, confusing! - // Again get the next and previous actors in the list - if (++prevItr == pRoster->rend()) - prevItr = pRoster->rbegin(); - if (++nextItr == pRoster->end()) - nextItr = pRoster->begin(); - g_FrameMan.DrawLine(pTargetBitmap, pPrevAdj->GetCPUPos() - targetPos, (*prevItr)->GetCPUPos() - targetPos, prevColor, prevColor, 12, 0, true); - g_FrameMan.DrawLine(pTargetBitmap, pNextAdj->GetCPUPos() - targetPos, (*nextItr)->GetCPUPos() - targetPos, nextColor, nextColor, 12, 0, true); - // Prev selected icon - iconPos = (*prevItr)->GetCPUPos(); - draw_sprite(pTargetBitmap, (*prevItr)->GetAIModeIcon(), iconPos.m_X - 6, iconPos.m_Y - 6); - // Next selected icon - iconPos = (*nextItr)->GetCPUPos(); - draw_sprite(pTargetBitmap, (*nextItr)->GetAIModeIcon(), iconPos.m_X - 6, iconPos.m_Y - 6); - */ } } } diff --git a/Source/Entities/Arm.cpp b/Source/Entities/Arm.cpp index 55e00710ce..604c6f4962 100644 --- a/Source/Entities/Arm.cpp +++ b/Source/Entities/Arm.cpp @@ -1,9 +1,11 @@ #include "Arm.h" + #include "HDFirearm.h" #include "ThrownDevice.h" #include "AHuman.h" #include "PresetMan.h" #include "SceneMan.h" +#include "ThreadMan.h" using namespace RTE; @@ -359,18 +361,42 @@ void Arm::DrawHand(BITMAP* targetBitmap, const Vector& targetPos, DrawMode mode) Vector handPos(m_JointPos + m_HandCurrentOffset + (m_Recoiled ? m_RecoilOffset : Vector()) - targetPos); handPos -= Vector(static_cast(m_HandSpriteBitmap->w / 2), static_cast(m_HandSpriteBitmap->h / 2)); - if (!m_HFlipped) { - if (mode == DrawMode::g_DrawWhite) { - draw_character_ex(targetBitmap, m_HandSpriteBitmap, handPos.GetFloorIntX(), handPos.GetFloorIntY(), g_WhiteColor, -1); - } else { - draw_sprite(targetBitmap, m_HandSpriteBitmap, handPos.GetFloorIntX(), handPos.GetFloorIntY()); + // TODO + // Wrapping?! + + // MULTITHREAD_TODO + // Proper interpolation + Vector prevPos = handPos; + + BITMAP* handSpriteBitmap = m_HandSpriteBitmap; + + auto renderFunc = [=](float interpolationAmount) { + BITMAP* pTargetBitmap = targetBitmap; + Vector renderPos = g_SceneMan.Lerp(0.0F, 1.0F, prevPos, handPos, interpolationAmount); + if (targetBitmap == nullptr) { + pTargetBitmap = g_ThreadMan.GetRenderTarget(); + renderPos -= g_ThreadMan.GetRenderOffset(); } - } else { - // TODO this draw_character_ex won't draw flipped. It should draw onto a temp bitmap and then draw that flipped. Maybe it can reuse a temp bitmap from MOSR, maybe not? - if (mode == DrawMode::g_DrawWhite) { - draw_character_ex(targetBitmap, m_HandSpriteBitmap, handPos.GetFloorIntX(), handPos.GetFloorIntY(), g_WhiteColor, -1); + + if (!m_HFlipped) { + if (mode == DrawMode::g_DrawWhite) { + draw_character_ex(pTargetBitmap, handSpriteBitmap, renderPos.GetFloorIntX(), renderPos.GetFloorIntY(), g_WhiteColor, -1); + } else { + draw_sprite(pTargetBitmap, handSpriteBitmap, renderPos.GetFloorIntX(), renderPos.GetFloorIntY()); + } } else { - draw_sprite_h_flip(targetBitmap, m_HandSpriteBitmap, handPos.GetFloorIntX(), handPos.GetFloorIntY()); + // TODO this draw_character_ex won't draw flipped. It should draw onto a temp bitmap and then draw that flipped. Maybe it can reuse a temp bitmap from MOSR, maybe not? + if (mode == DrawMode::g_DrawWhite) { + draw_character_ex(pTargetBitmap, handSpriteBitmap, renderPos.GetFloorIntX(), renderPos.GetFloorIntY(), g_WhiteColor, -1); + } else { + draw_sprite_h_flip(pTargetBitmap, handSpriteBitmap, renderPos.GetFloorIntX(), renderPos.GetFloorIntY()); + } } + }; + + if (targetBitmap == nullptr) { + g_ThreadMan.GetSimRenderQueue().push_back(renderFunc); + } else { + renderFunc(1.0F); } } diff --git a/Source/Entities/AtomGroup.cpp b/Source/Entities/AtomGroup.cpp index 72af546418..70a28379c8 100644 --- a/Source/Entities/AtomGroup.cpp +++ b/Source/Entities/AtomGroup.cpp @@ -415,11 +415,6 @@ float AtomGroup::Travel(Vector& position, Vector& velocity, Matrix& rotation, fl } } } -#ifdef DEBUG_BUILD - // TODO: Remove this once AtomGroup drawing in Material layer draw mode is implemented. - // Draw the positions of the Atoms at the start of each segment, for visual debugging. - // putpixel(g_SceneMan.GetMOColorBitmap(), atom->GetCurrentPos().GetFloorIntX(), atom->GetCurrentPos().GetFloorIntY(), 122); -#endif } linSegTraj = velocity * timeLeft * c_PPM; @@ -497,14 +492,6 @@ float AtomGroup::Travel(Vector& position, Vector& velocity, Matrix& rotation, fl atomsHitMOsCount++; } } -#ifdef DEBUG_BUILD - // TODO: Remove this once AtomGroup drawing in Material layer draw mode is implemented. - Vector tPos = atom->GetCurrentPos(); - Vector tNorm = m_OwnerMOSR->RotateOffset(atom->GetNormal()) * 7; - line(g_SceneMan.GetMOColorBitmap(), tPos.GetFloorIntX(), tPos.GetFloorIntY(), tPos.GetFloorIntX() + tNorm.GetFloorIntX(), tPos.GetFloorIntY() + tNorm.GetFloorIntY(), 244); - // Draw the positions of the hit points on screen for easy debugging. - // putpixel(g_SceneMan.GetMOColorBitmap(), tPos.GetFloorIntX(), tPos.GetFloorIntY(), 5); -#endif } } @@ -944,12 +931,6 @@ Vector AtomGroup::PushTravel(Vector& position, const Vector& velocity, float pus } else if (atomsHitMOsCount == 0 && g_SceneMan.GetTerrMatter(intPos[X] + flippedOffset.GetFloorIntX(), intPos[Y] + flippedOffset.GetFloorIntY())) { hitTerrAtoms.push_back({atom, flippedOffset}); } - -#ifdef DEBUG_BUILD - // TODO: Remove this once AtomGroup drawing in Material layer draw mode is implemented. - // Draw the positions of the hit points on screen for easy debugging. - // putpixel(g_SceneMan.GetMOColorBitmap(), std::floor(position.GetFloorIntX() + flippedOffset.GetFloorIntX()), std::floor(position.GetFloorIntY() + flippedOffset.GetFloorIntY()), 122); -#endif } // If no collisions, continue on to the next step. @@ -1470,9 +1451,6 @@ bool AtomGroup::ResolveMOSIntersection(Vector& position) { if (tempMO->GetsHitByMOs()) { // Make that MO draw itself again in the MOID layer so we can find its true edges intersectedMO = tempMO; -#ifdef DRAW_MOID_LAYER - intersectedMO->Draw(g_SceneMan.GetMOIDBitmap(), Vector(), g_DrawMOID, true); -#endif break; } } diff --git a/Source/Entities/Attachable.cpp b/Source/Entities/Attachable.cpp index d3340001be..7c9241048a 100644 --- a/Source/Entities/Attachable.cpp +++ b/Source/Entities/Attachable.cpp @@ -408,6 +408,7 @@ void Attachable::PreUpdate() { if (InheritsHFlipped() != 0) { m_HFlipped = m_InheritsHFlipped == 1 ? m_Parent->IsHFlipped() : !m_Parent->IsHFlipped(); } + if (InheritsRotAngle()) { SetRotAngle(m_Parent->GetRotAngle() + m_InheritedRotAngleOffset * m_Parent->GetFlipFactor()); m_AngularVel = 0.0F; @@ -541,11 +542,15 @@ void Attachable::SetParent(MOSRotating* newParent) { } } -void Attachable::UpdatePositionAndJointPositionBasedOnOffsets() { +void Attachable::UpdatePositionAndJointPositionBasedOnOffsets(bool newAdded) { if (m_Parent) { m_JointPos = m_Parent->GetPos() + m_Parent->RotateOffset(GetParentOffset()); m_PrevPos = m_Pos; m_Pos = m_JointPos - RotateOffset(m_JointOffset); + if (newAdded) { + // Avoid render interp from 0, 0 to our new position + m_PrevPos = m_Pos; + } } else { m_JointPos = m_Pos + RotateOffset(m_JointOffset); } diff --git a/Source/Entities/Attachable.h b/Source/Entities/Attachable.h index 2861309092..f6cd62a0b4 100644 --- a/Source/Entities/Attachable.h +++ b/Source/Entities/Attachable.h @@ -454,7 +454,8 @@ namespace RTE { private: /// Updates the position of this Attachable based on its parent offset and joint offset. Used during update and when something sets these offsets through setters. - void UpdatePositionAndJointPositionBasedOnOffsets(); + /// @param newAdded Whether this attachable was just added to it's parent. + void UpdatePositionAndJointPositionBasedOnOffsets(bool newAdded = false); /// Turns on/off this Attachable's terrain collisions while it is attached by adding/removing its Atoms to/from its root parent's AtomGroup. /// @param addAtoms Whether to add this Attachable's Atoms to the root parent's AtomGroup or remove them. diff --git a/Source/Entities/HDFirearm.cpp b/Source/Entities/HDFirearm.cpp index 96aa21eb9e..08a5fa3636 100644 --- a/Source/Entities/HDFirearm.cpp +++ b/Source/Entities/HDFirearm.cpp @@ -959,16 +959,16 @@ void HDFirearm::Update() { int animDuration = m_SpriteAnimDuration; // Spin up - can only spin up if mag is inserted if (m_Activated && !m_Reloading && m_ActivationTimer.GetElapsedSimTimeMS() < m_ActivationDelay) { - animDuration = (int)LERP(0, m_ActivationDelay, (float)(m_SpriteAnimDuration * 10), (float)m_SpriteAnimDuration, m_ActivationTimer.GetElapsedSimTimeMS()); + animDuration = (int)Lerp(0, m_ActivationDelay, (float)(m_SpriteAnimDuration * 10), (float)m_SpriteAnimDuration, m_ActivationTimer.GetElapsedSimTimeMS()); if (m_ActiveSound) { - m_ActiveSound->SetPitch(LERP(0, m_ActivationDelay, 0, 1.0, m_ActivationTimer.GetElapsedSimTimeMS())); + m_ActiveSound->SetPitch(Lerp(0, m_ActivationDelay, 0, 1.0, m_ActivationTimer.GetElapsedSimTimeMS())); } } // Spin down if ((!m_Activated || m_Reloading) && m_LastFireTmr.GetElapsedSimTimeMS() < m_DeactivationDelay) { - animDuration = (int)LERP(0, m_DeactivationDelay, (float)m_SpriteAnimDuration, (float)(m_SpriteAnimDuration * 10), m_LastFireTmr.GetElapsedSimTimeMS()); + animDuration = (int)Lerp(0, m_DeactivationDelay, (float)m_SpriteAnimDuration, (float)(m_SpriteAnimDuration * 10), m_LastFireTmr.GetElapsedSimTimeMS()); if (m_ActiveSound) { - m_ActiveSound->SetPitch(LERP(0, m_DeactivationDelay, 1.0, 0, m_LastFireTmr.GetElapsedSimTimeMS())); + m_ActiveSound->SetPitch(Lerp(0, m_DeactivationDelay, 1.0, 0, m_LastFireTmr.GetElapsedSimTimeMS())); } } @@ -1024,14 +1024,14 @@ void HDFirearm::Draw(BITMAP* pTargetBitmap, const Vector& targetPos, DrawMode mo } void HDFirearm::DrawHUD(BITMAP* pTargetBitmap, const Vector& targetPos, int whichScreen, bool playerControlled) { - if (!m_HUDVisible) + if (!m_HUDVisible) { return; + } // Only draw if the team viewing this is on the same team OR has seen the space where this is located int viewingTeam = g_ActivityMan.GetActivity()->GetTeamOfPlayer(g_ActivityMan.GetActivity()->PlayerOfScreen(whichScreen)); - if (viewingTeam != m_Team && viewingTeam != Activity::NoTeam) { - if (g_SceneMan.IsUnseen(m_Pos.m_X, m_Pos.m_Y, viewingTeam)) - return; + if (viewingTeam != m_Team && viewingTeam != Activity::NoTeam && g_SceneMan.IsUnseen(m_Pos.m_X, m_Pos.m_Y, viewingTeam)) { + return; } HeldDevice::DrawHUD(pTargetBitmap, targetPos, whichScreen); @@ -1048,11 +1048,11 @@ void HDFirearm::DrawHUD(BITMAP* pTargetBitmap, const Vector& targetPos, int whic } else { pointCount = 2; } + int pointSpacing = 10 - pointCount; sharpLength -= static_cast(pointSpacing * pointCount) * 0.5F; Vector muzzleOffset(std::max(m_MuzzleOff.m_X, m_SpriteRadius), m_MuzzleOff.m_Y); - // acquire_bitmap(pTargetBitmap); for (int i = 0; i < pointCount; ++i) { Vector aimPoint(sharpLength + static_cast(pointSpacing * i), 0); aimPoint = RotateOffset(aimPoint + muzzleOffset) + m_Pos; @@ -1062,5 +1062,4 @@ void HDFirearm::DrawHUD(BITMAP* pTargetBitmap, const Vector& targetPos, int whic g_SceneMan.WrapPosition(aimPoint); putpixel(pTargetBitmap, aimPoint.GetFloorIntX(), aimPoint.GetFloorIntY(), g_YellowGlowColor); } - // release_bitmap(pTargetBitmap); } diff --git a/Source/Entities/LimbPath.cpp b/Source/Entities/LimbPath.cpp index 4e3c0f4c1a..3d3c4b6504 100644 --- a/Source/Entities/LimbPath.cpp +++ b/Source/Entities/LimbPath.cpp @@ -489,6 +489,8 @@ float LimbPath::GetMiddleX() const { void LimbPath::Draw(BITMAP* pTargetBitmap, const Vector& targetPos, unsigned char color) const { + // TODO_MULTITHREAD. Fix this + Vector prevPoint = m_Start; Vector nextPoint = prevPoint; for (std::deque::const_iterator itr = m_Segments.begin(); itr != m_Segments.end(); ++itr) { @@ -500,7 +502,6 @@ void LimbPath::Draw(BITMAP* pTargetBitmap, Vector min(std::min(prevWorldPosition.m_X, nextWorldPosition.m_X), std::min(prevWorldPosition.m_Y, nextWorldPosition.m_Y)); Vector max(std::max(prevWorldPosition.m_X, nextWorldPosition.m_X), std::max(prevWorldPosition.m_Y, nextWorldPosition.m_Y)); - g_SceneMan.RegisterDrawing(pTargetBitmap, g_NoMOID, min.m_X, max.m_Y, max.m_X, min.m_Y); prevPoint += *itr; } diff --git a/Source/Entities/MOPixel.cpp b/Source/Entities/MOPixel.cpp index 6911d8beff..649395cd14 100644 --- a/Source/Entities/MOPixel.cpp +++ b/Source/Entities/MOPixel.cpp @@ -2,6 +2,7 @@ #include "Atom.h" #include "PostProcessMan.h" +#include "ThreadMan.h" #include "FrameMan.h" using namespace RTE; @@ -224,47 +225,49 @@ void MOPixel::Update() { } void MOPixel::Draw(BITMAP* targetBitmap, const Vector& targetPos, DrawMode mode, bool onlyPhysical) const { - // Don't draw color if this isn't a drawing frame - if (!g_TimerMan.DrawnSimUpdate() && mode == g_DrawColor) { + int drawColor = -1; + + if (mode == g_DrawMOID) { + g_SceneMan.RegisterMOIDDrawing(m_MOID, m_Pos, 1); return; } - int drawColor = -1; - switch (mode) { case g_DrawMaterial: drawColor = m_Atom->GetMaterial()->GetSettleMaterial(); break; - case g_DrawMOID: - drawColor = m_MOID; - break; - case g_DrawNoMOID: - drawColor = g_NoMOID; - break; default: drawColor = m_Color.GetIndex(); break; } - bool shouldDraw = true; + if (drawColor != g_MaskColor) { + Vector prevSpritePos = m_PrevPos - targetPos; + Vector spritePos = m_Pos - targetPos; -#ifndef DRAW_MOID_LAYER - shouldDraw = mode != DrawMode::g_DrawMOID; -#endif + auto renderFunc = [=](float interpolationAmount) { + BITMAP* pTargetBitmap = targetBitmap; + Vector renderPos = g_SceneMan.Lerp(0.0F, 1.0F, prevSpritePos, spritePos, interpolationAmount); + if (targetBitmap == nullptr) { + pTargetBitmap = g_ThreadMan.GetRenderTarget(); + renderPos -= g_ThreadMan.GetRenderOffset(); + } - Vector pixelPos = m_Pos - targetPos; + putpixel(pTargetBitmap, renderPos.GetFloorIntX(), renderPos.GetFloorIntY(), drawColor); + }; - if (shouldDraw) { - putpixel(targetBitmap, pixelPos.GetFloorIntX(), pixelPos.GetFloorIntY(), drawColor); + if (targetBitmap == nullptr) { + g_ThreadMan.GetSimRenderQueue().push_back(renderFunc); + } else { + renderFunc(1.0F); + } } - - g_SceneMan.RegisterDrawing(targetBitmap, mode == g_DrawNoMOID ? g_NoMOID : m_MOID, pixelPos, 1.0F); } void MOPixel::SetPostScreenEffectToDraw() const { if (m_AgeTimer.GetElapsedSimTimeMS() >= m_EffectStartTime && (m_EffectStopTime == 0 || !m_AgeTimer.IsPastSimMS(m_EffectStopTime))) { if (m_EffectAlwaysShows || !g_SceneMan.ObscuredPoint(m_Pos.GetFloorIntX(), m_Pos.GetFloorIntY())) { - g_PostProcessMan.RegisterPostEffect(m_Pos, m_pScreenEffect, m_ScreenEffectHash, LERP(m_EffectStartTime, m_EffectStopTime, m_EffectStartStrength, m_EffectStopStrength, m_AgeTimer.GetElapsedSimTimeMS()), m_EffectRotAngle); + g_PostProcessMan.RegisterPostEffect(m_Pos, m_pScreenEffect, m_ScreenEffectHash, Lerp(m_EffectStartTime, m_EffectStopTime, m_EffectStartStrength, m_EffectStopStrength, m_AgeTimer.GetElapsedSimTimeMS()), m_EffectRotAngle); } } } diff --git a/Source/Entities/MOSParticle.cpp b/Source/Entities/MOSParticle.cpp index fd08dc8f5d..17211f77b0 100644 --- a/Source/Entities/MOSParticle.cpp +++ b/Source/Entities/MOSParticle.cpp @@ -2,6 +2,7 @@ #include "Atom.h" #include "PostProcessMan.h" +#include "ThreadMan.h" #include @@ -157,78 +158,118 @@ void MOSParticle::Draw(BITMAP* targetBitmap, const Vector& targetPos, DrawMode m RTEAssert(!m_aSprite.empty(), "No sprite bitmaps loaded to draw " + GetPresetName()); RTEAssert(m_Frame >= 0 && m_Frame < m_FrameCount, "Frame is out of bounds for " + GetPresetName()); - if (mode == g_DrawMOID && m_MOID == g_NoMOID) { - return; + BITMAP* currentFrame = m_aSprite[m_Frame]; + if (!currentFrame) { + RTEAbort("Sprite frame pointer is null when drawing MOSprite!"); } + Vector prevSpritePos(m_PrevPos + m_SpriteOffset - targetPos); Vector spritePos(m_Pos + m_SpriteOffset - targetPos); - // TODO I think this is an array with 4 elements to account for Y wrapping. Y wrapping is not really handled in this game, so this can probably be knocked down to 2 elements. Also, I'm sure this code can be simplified. - std::array drawPositions = {spritePos}; - int drawPasses = 1; - if (g_SceneMan.SceneWrapsX()) { - if (targetPos.IsZero() && m_WrapDoubleDraw) { - if (spritePos.GetFloorIntX() < m_aSprite[m_Frame]->w) { - drawPositions.at(drawPasses) = spritePos; - drawPositions.at(drawPasses).m_X += static_cast(targetBitmap->w); - drawPasses++; - } else if (spritePos.GetFloorIntX() > targetBitmap->w - m_aSprite[m_Frame]->w) { - drawPositions.at(drawPasses) = spritePos; - drawPositions.at(drawPasses).m_X -= static_cast(targetBitmap->w); - drawPasses++; - } - } else if (m_WrapDoubleDraw) { - if (targetPos.m_X < 0) { - drawPositions.at(drawPasses) = drawPositions[0]; - drawPositions.at(drawPasses).m_X -= static_cast(g_SceneMan.GetSceneWidth()); - drawPasses++; + if (mode == g_DrawMOID) { + g_SceneMan.RegisterMOIDDrawing(m_MOID, spritePos.GetX(), spritePos.GetY(), spritePos.GetX() + currentFrame->w, spritePos.GetY() + currentFrame->h); + return; + } + + bool wrapDoubleDraw = m_WrapDoubleDraw; + char settleMaterial = mode != g_DrawMaterial ? 0 : m_SettleMaterialDisabled ? GetMaterial()->GetIndex() + : GetMaterial()->GetSettleMaterial(); + + auto renderFunc = [=](float interpolationAmount) { + BITMAP* pTargetBitmap = targetBitmap; + Vector renderPos = g_SceneMan.Lerp(0.0F, 1.0F, prevSpritePos, spritePos, interpolationAmount); + if (targetBitmap == nullptr) { + pTargetBitmap = g_ThreadMan.GetRenderTarget(); + renderPos -= g_ThreadMan.GetRenderOffset(); + } + + // Take care of wrapping situations + std::array drawPositions = {renderPos}; + int drawPasses = 1; + if (g_SceneMan.SceneWrapsX()) { + if (renderPos.IsZero() && wrapDoubleDraw) { + if (spritePos.GetFloorIntX() < currentFrame->w) { + drawPositions[drawPasses] = spritePos; + drawPositions[drawPasses].m_X += static_cast(pTargetBitmap->w); + drawPasses++; + } else if (spritePos.GetFloorIntX() > pTargetBitmap->w - currentFrame->w) { + drawPositions[drawPasses] = spritePos; + drawPositions[drawPasses].m_X -= static_cast(pTargetBitmap->w); + drawPasses++; + } + } else if (wrapDoubleDraw) { + if (renderPos.m_X < 0) { + drawPositions[drawPasses] = drawPositions[0]; + drawPositions[drawPasses].m_X += static_cast(g_SceneMan.GetSceneWidth()); + drawPasses++; + } + if (renderPos.GetFloorIntX() + pTargetBitmap->w > g_SceneMan.GetSceneWidth()) { + drawPositions[drawPasses] = drawPositions[0]; + drawPositions[drawPasses].m_X -= static_cast(g_SceneMan.GetSceneWidth()); + drawPasses++; + } } - if (targetPos.GetFloorIntX() + targetBitmap->w > g_SceneMan.GetSceneWidth()) { - drawPositions.at(drawPasses) = drawPositions[0]; - drawPositions.at(drawPasses).m_X += static_cast(g_SceneMan.GetSceneWidth()); - drawPasses++; + } + if (g_SceneMan.SceneWrapsY()) { + if (renderPos.IsZero() && wrapDoubleDraw) { + if (spritePos.GetFloorIntY() < currentFrame->h) { + drawPositions[drawPasses] = spritePos; + drawPositions[drawPasses].m_Y += static_cast(pTargetBitmap->h); + drawPasses++; + } else if (spritePos.GetFloorIntY() > pTargetBitmap->h - currentFrame->h) { + drawPositions[drawPasses] = spritePos; + drawPositions[drawPasses].m_Y -= static_cast(pTargetBitmap->h); + drawPasses++; + } + } else if (wrapDoubleDraw) { + if (renderPos.m_Y < 0) { + drawPositions[drawPasses] = drawPositions[0]; + drawPositions[drawPasses].m_Y += static_cast(g_SceneMan.GetSceneHeight()); + drawPasses++; + } + if (renderPos.GetFloorIntY() + pTargetBitmap->h > g_SceneMan.GetSceneHeight()) { + drawPositions[drawPasses] = drawPositions[0]; + drawPositions[drawPasses].m_Y -= static_cast(g_SceneMan.GetSceneHeight()); + drawPasses++; + } } } - } - for (int i = 0; i < drawPasses; ++i) { - int spriteX = drawPositions.at(i).GetFloorIntX(); - int spriteY = drawPositions.at(i).GetFloorIntY(); - switch (mode) { - case g_DrawMaterial: - draw_character_ex(targetBitmap, m_aSprite[m_Frame], spriteX, spriteY, m_SettleMaterialDisabled ? GetMaterial()->GetIndex() : GetMaterial()->GetSettleMaterial(), -1); - break; - case g_DrawWhite: - draw_character_ex(targetBitmap, m_aSprite[m_Frame], spriteX, spriteY, g_WhiteColor, -1); - break; - case g_DrawMOID: -#ifdef DRAW_MOID_LAYER - draw_character_ex(targetBitmap, m_aSprite[m_Frame], spriteX, spriteY, m_MOID, -1); -#endif - break; - case g_DrawNoMOID: - draw_character_ex(targetBitmap, m_aSprite[m_Frame], spriteX, spriteY, g_NoMOID, -1); - break; - case g_DrawTrans: - draw_trans_sprite(targetBitmap, m_aSprite[m_Frame], spriteX, spriteY); - break; - case g_DrawAlpha: - set_alpha_blender(); - draw_trans_sprite(targetBitmap, m_aSprite[m_Frame], spriteX, spriteY); - break; - default: - draw_sprite(targetBitmap, m_aSprite[m_Frame], spriteX, spriteY); - break; + for (int i = 0; i < drawPasses; ++i) { + int spriteX = drawPositions.at(i).GetFloorIntX(); + int spriteY = drawPositions.at(i).GetFloorIntY(); + switch (mode) { + case g_DrawMaterial: + draw_character_ex(pTargetBitmap, currentFrame, spriteX, spriteY, settleMaterial, -1); + break; + case g_DrawWhite: + draw_character_ex(pTargetBitmap, currentFrame, spriteX, spriteY, g_WhiteColor, -1); + break; + case g_DrawTrans: + draw_trans_sprite(pTargetBitmap, currentFrame, spriteX, spriteY); + break; + case g_DrawAlpha: + set_alpha_blender(); + draw_trans_sprite(pTargetBitmap, currentFrame, spriteX, spriteY); + break; + default: + draw_sprite(pTargetBitmap, currentFrame, spriteX, spriteY); + break; + } } + }; - g_SceneMan.RegisterDrawing(targetBitmap, mode == g_DrawNoMOID ? g_NoMOID : m_MOID, spriteX, spriteY, spriteX + m_aSprite[m_Frame]->w, spriteY + m_aSprite[m_Frame]->h); + if (targetBitmap == nullptr) { + g_ThreadMan.GetSimRenderQueue().push_back(renderFunc); + } else { + renderFunc(1.0F); } } void MOSParticle::SetPostScreenEffectToDraw() const { if (m_AgeTimer.GetElapsedSimTimeMS() >= m_EffectStartTime && (m_EffectStopTime == 0 || !m_AgeTimer.IsPastSimMS(m_EffectStopTime))) { if (m_EffectAlwaysShows || !g_SceneMan.ObscuredPoint(m_Pos.GetFloorIntX(), m_Pos.GetFloorIntY())) { - g_PostProcessMan.RegisterPostEffect(m_Pos, m_pScreenEffect, m_ScreenEffectHash, LERP(m_EffectStartTime, m_EffectStopTime, m_EffectStartStrength, m_EffectStopStrength, m_AgeTimer.GetElapsedSimTimeMS()), m_EffectRotAngle); + g_PostProcessMan.RegisterPostEffect(m_Pos, m_pScreenEffect, m_ScreenEffectHash, Lerp(m_EffectStartTime, m_EffectStopTime, m_EffectStartStrength, m_EffectStopStrength, m_AgeTimer.GetElapsedSimTimeMS()), m_EffectRotAngle); } } } diff --git a/Source/Entities/MOSRotating.cpp b/Source/Entities/MOSRotating.cpp index 39a3a3f0ab..726bb41b86 100644 --- a/Source/Entities/MOSRotating.cpp +++ b/Source/Entities/MOSRotating.cpp @@ -12,6 +12,7 @@ #include "HDFirearm.h" #include "SoundContainer.h" #include "PostProcessMan.h" +#include "ThreadMan.h" #include "RTEError.h" @@ -19,19 +20,15 @@ using namespace RTE; ConcreteClassInfo(MOSRotating, MOSprite, 500); -BITMAP* MOSRotating::m_spTempBitmap16 = 0; -BITMAP* MOSRotating::m_spTempBitmap32 = 0; -BITMAP* MOSRotating::m_spTempBitmap64 = 0; -BITMAP* MOSRotating::m_spTempBitmap128 = 0; -BITMAP* MOSRotating::m_spTempBitmap256 = 0; -BITMAP* MOSRotating::m_spTempBitmap512 = 0; +// Temp drawing bitmaps shared between all MOSRotatings +thread_local bool s_BitmapsInitialised = false; -BITMAP* MOSRotating::m_spTempBitmapS16 = 0; -BITMAP* MOSRotating::m_spTempBitmapS32 = 0; -BITMAP* MOSRotating::m_spTempBitmapS64 = 0; -BITMAP* MOSRotating::m_spTempBitmapS128 = 0; -BITMAP* MOSRotating::m_spTempBitmapS256 = 0; -BITMAP* MOSRotating::m_spTempBitmapS512 = 0; +thread_local BITMAP* s_pTempBitmap16 = nullptr; +thread_local BITMAP* s_pTempBitmap32 = nullptr; +thread_local BITMAP* s_pTempBitmap64 = nullptr; +thread_local BITMAP* s_pTempBitmap128 = nullptr; +thread_local BITMAP* s_pTempBitmap256 = nullptr; +thread_local BITMAP* s_pTempBitmap512 = nullptr; MOSRotating::MOSRotating() { Clear(); @@ -71,10 +68,8 @@ void MOSRotating::Clear() { m_GibAtEndOfLifetime = false; m_GibSound = nullptr; m_EffectOnGib = true; - m_pFlipBitmap = 0; - m_pFlipBitmapS = 0; - m_pTempBitmap = 0; - m_pTempBitmapS = 0; + m_pTempBitmap = nullptr; + m_pFlipBitmap = nullptr; m_LoudnessOnGib = 1; m_DamageMultiplier = 0; m_NoSetDamageMultiplier = true; @@ -107,62 +102,32 @@ int MOSRotating::Create() { if (!m_pFlipBitmap && m_aSprite[0]) { m_pFlipBitmap = create_bitmap_ex(8, m_aSprite[0]->w, m_aSprite[0]->h); } - if (!m_pFlipBitmapS && m_aSprite[0]) { - m_pFlipBitmapS = create_bitmap_ex(c_MOIDLayerBitDepth, m_aSprite[0]->w, m_aSprite[0]->h); - } - - /* Not anymore; points to shared static bitmaps - if (!m_pTempBitmap && m_aSprite[0]) - m_pTempBitmap = create_bitmap_ex(8, m_aSprite[0]->w, m_aSprite[0]->h); - */ // Can't create these earlier in the static declaration because allegro_init needs to be called before create_bitmap - if (!m_spTempBitmap16) - m_spTempBitmap16 = create_bitmap_ex(8, 16, 16); - if (!m_spTempBitmap32) - m_spTempBitmap32 = create_bitmap_ex(8, 32, 32); - if (!m_spTempBitmap64) - m_spTempBitmap64 = create_bitmap_ex(8, 64, 64); - if (!m_spTempBitmap128) - m_spTempBitmap128 = create_bitmap_ex(8, 128, 128); - if (!m_spTempBitmap256) - m_spTempBitmap256 = create_bitmap_ex(8, 256, 256); - if (!m_spTempBitmap512) - m_spTempBitmap512 = create_bitmap_ex(8, 512, 512); + if (!s_BitmapsInitialised) { + s_BitmapsInitialised = true; - // Can't create these earlier in the static declaration because allegro_init needs to be called before create_bitmap - if (!m_spTempBitmapS16) - m_spTempBitmapS16 = create_bitmap_ex(c_MOIDLayerBitDepth, 16, 16); - if (!m_spTempBitmapS32) - m_spTempBitmapS32 = create_bitmap_ex(c_MOIDLayerBitDepth, 32, 32); - if (!m_spTempBitmapS64) - m_spTempBitmapS64 = create_bitmap_ex(c_MOIDLayerBitDepth, 64, 64); - if (!m_spTempBitmapS128) - m_spTempBitmapS128 = create_bitmap_ex(c_MOIDLayerBitDepth, 128, 128); - if (!m_spTempBitmapS256) - m_spTempBitmapS256 = create_bitmap_ex(c_MOIDLayerBitDepth, 256, 256); - if (!m_spTempBitmapS512) - m_spTempBitmapS512 = create_bitmap_ex(c_MOIDLayerBitDepth, 512, 512); + s_pTempBitmap16 = create_bitmap_ex(8, 16, 16); + s_pTempBitmap32 = create_bitmap_ex(8, 32, 32); + s_pTempBitmap64 = create_bitmap_ex(8, 64, 64); + s_pTempBitmap128 = create_bitmap_ex(8, 128, 128); + s_pTempBitmap256 = create_bitmap_ex(8, 256, 256); + s_pTempBitmap512 = create_bitmap_ex(8, 512, 512); + } // Choose an appropriate size for this' diameter if (m_SpriteDiameter >= 256) { - m_pTempBitmap = m_spTempBitmap512; - m_pTempBitmapS = m_spTempBitmapS512; + m_pTempBitmap = s_pTempBitmap512; } else if (m_SpriteDiameter >= 128) { - m_pTempBitmap = m_spTempBitmap256; - m_pTempBitmapS = m_spTempBitmapS256; + m_pTempBitmap = s_pTempBitmap256; } else if (m_SpriteDiameter >= 64) { - m_pTempBitmap = m_spTempBitmap128; - m_pTempBitmapS = m_spTempBitmapS128; + m_pTempBitmap = s_pTempBitmap128; } else if (m_SpriteDiameter >= 32) { - m_pTempBitmap = m_spTempBitmap64; - m_pTempBitmapS = m_spTempBitmapS64; + m_pTempBitmap = s_pTempBitmap64; } else if (m_SpriteDiameter >= 16) { - m_pTempBitmap = m_spTempBitmap32; - m_pTempBitmapS = m_spTempBitmapS32; + m_pTempBitmap = s_pTempBitmap32; } else { - m_pTempBitmap = m_spTempBitmap16; - m_pTempBitmapS = m_spTempBitmapS16; + m_pTempBitmap = s_pTempBitmap16; } return 0; @@ -179,9 +144,6 @@ int MOSRotating::Create(ContentFile spriteFile, if (!m_pFlipBitmap && m_aSprite[0]) { m_pFlipBitmap = create_bitmap_ex(8, m_aSprite[0]->w, m_aSprite[0]->h); } - if (!m_pFlipBitmapS && m_aSprite[0]) { - m_pFlipBitmapS = create_bitmap_ex(c_MOIDLayerBitDepth, m_aSprite[0]->w, m_aSprite[0]->h); - } return 0; } @@ -248,14 +210,10 @@ int MOSRotating::Create(const MOSRotating& reference) { m_NoSetDamageMultiplier = reference.m_NoSetDamageMultiplier; m_pTempBitmap = reference.m_pTempBitmap; - m_pTempBitmapS = reference.m_pTempBitmapS; if (!m_pFlipBitmap && m_aSprite[0]) { m_pFlipBitmap = create_bitmap_ex(8, m_aSprite[0]->w, m_aSprite[0]->h); } - if (!m_pFlipBitmapS && m_aSprite[0]) { - m_pFlipBitmapS = create_bitmap_ex(c_MOIDLayerBitDepth, m_aSprite[0]->w, m_aSprite[0]->h); - } return 0; } @@ -461,6 +419,7 @@ void MOSRotating::AddWound(AEmitter* woundToAdd, const Vector& parentOffsetToSet if (woundToAdd->HasNoSetDamageMultiplier()) { woundToAdd->SetDamageMultiplier(1.0F); } + woundToAdd->UpdatePositionAndJointPositionBasedOnOffsets(true); m_AttachableAndWoundMass += woundToAdd->GetMass(); m_Wounds.push_back(woundToAdd); } @@ -555,10 +514,6 @@ void MOSRotating::Destroy(bool notInherited) { } destroy_bitmap(m_pFlipBitmap); - destroy_bitmap(m_pFlipBitmapS); - - // Not anymore; point to shared static bitmaps - // destroy_bitmap(m_pTempBitmap); delete m_GibSound; @@ -693,8 +648,9 @@ bool MOSRotating::OnSink(HitData& hd) { bool MOSRotating::ParticlePenetration(HitData& hd) { // Only particles can penetrate. - if (!(dynamic_cast(hd.Body[HITOR]) || dynamic_cast(hd.Body[HITOR]))) + if (!(dynamic_cast(hd.Body[HITOR]) || dynamic_cast(hd.Body[HITOR]))) { return false; + } float impulseForce = hd.ResImpulse[HITEE].GetMagnitude(); Material const* myMat = GetMaterial(); @@ -710,9 +666,6 @@ bool MOSRotating::ParticlePenetration(HitData& hd) { int error, dom, sub, domSteps, subSteps; bool inside = false, exited = false, subStepped = false; - // Lock all bitmaps involved outside the loop. - acquire_bitmap(m_aSprite[m_Frame]); - bounds[X] = m_aSprite[m_Frame]->w; bounds[Y] = m_aSprite[m_Frame]->h; @@ -797,8 +750,6 @@ bool MOSRotating::ParticlePenetration(HitData& hd) { } error += delta2[sub]; } - // Unlock all bitmaps involved outside the loop. - release_bitmap(m_aSprite[m_Frame]); if (m_pEntryWound) { // Add entry wound AEmitter to actor where the particle penetrated. @@ -838,12 +789,8 @@ bool MOSRotating::ParticlePenetration(HitData& hd) { // absorbed all the energy, and the hittee gets deleted from the scene. hd.ResImpulse[HITEE] -= hd.ResImpulse[HITEE] * (impulseForce / hd.ResImpulse[HITEE].GetMagnitude()); hd.ResImpulse[HITOR] = -(hd.ResImpulse[HITEE]); - } - // Particle got lodged inside this MOSRotating, so stop it and delete it from scene. - else { - // Set the exiting particle's position to where it looks lodged - // hd.Body[HITOR]->SetPos(m_Pos - m_SpriteOffset + entryPos); - // hd.Body[HITOR]->SetVel(Vector()); + } else { + // Particle got lodged inside this MOSRotating, so stop it and delete it from scene. hd.Body[HITOR]->SetToDelete(true); hd.Terminate[HITOR] = true; } @@ -1130,8 +1077,6 @@ void MOSRotating::RestDetection() { m_ToSettle = false; } } - m_PrevRotation = m_Rotation; - m_PrevAngVel = m_AngularVel; } bool MOSRotating::IsAtRest() { @@ -1173,11 +1118,13 @@ void MOSRotating::EraseFromTerrain() { if (m_HFlipped) { // Create intermediate flipping bitmap if there isn't one yet // Don't size the intermediate bitmaps to teh m_Scale, because the scaling happens after they are done - if (!m_pFlipBitmap) + if (!m_pFlipBitmap) { m_pFlipBitmap = create_bitmap_ex(8, m_aSprite[m_Frame]->w, m_aSprite[m_Frame]->h); + } + clear_to_color(m_pFlipBitmap, g_MaskColor); - // Draw eitehr the source color bitmap or the intermediate material bitmap onto the intermediate flipping bitmap + // Draw either the source color bitmap or the intermediate material bitmap onto the intermediate flipping bitmap draw_sprite_h_flip(m_pFlipBitmap, m_aSprite[m_Frame], 0, 0); pivot.m_X = m_pFlipBitmap->w + m_SpriteOffset.m_X; @@ -1204,12 +1151,13 @@ bool MOSRotating::DeepCheck(bool makeMOPs, int skipMOP, int maxMOPs) { if (m_HFlipped) { // Create intermediate flipping bitmap if there isn't one yet - // Don't size the intermediate bitmaps to teh m_Scale, because the scaling happens after they are done - if (!m_pFlipBitmap) + // Don't size the intermediate bitmaps to the m_Scale, because the scaling happens after they are done + if (!m_pFlipBitmap) { m_pFlipBitmap = create_bitmap_ex(8, m_aSprite[m_Frame]->w, m_aSprite[m_Frame]->h); + } clear_to_color(m_pFlipBitmap, g_MaskColor); - // Draw eitehr the source color bitmap or the intermediate material bitmap onto the intermediate flipping bitmap + // Draw either the source color bitmap or the intermediate material bitmap onto the intermediate flipping bitmap draw_sprite_h_flip(m_pFlipBitmap, m_aSprite[m_Frame], 0, 0); pivot.m_X = m_pFlipBitmap->w + m_SpriteOffset.m_X; @@ -1247,13 +1195,6 @@ bool MOSRotating::DeepCheck(bool makeMOPs, int skipMOP, int maxMOPs) { void MOSRotating::PreTravel() { MOSprite::PreTravel(); - -#ifdef DRAW_MOID_LAYER - // If this is going slow enough, check for and redraw the MOID representations of any other MOSRotatings that may be overlapping this - if (m_GetsHitByMOs && m_HitsMOs && m_Vel.GetX() < 2.0F && m_Vel.GetY() < 2.0F) { - g_MovableMan.RedrawOverlappingMOIDs(this); - } -#endif } void MOSRotating::Travel() { @@ -1404,23 +1345,6 @@ void MOSRotating::PostUpdate() { MovableObject::PostUpdate(); } -bool MOSRotating::DrawMOIDIfOverlapping(MovableObject* pOverlapMO) { - if (pOverlapMO == this || !m_GetsHitByMOs || !pOverlapMO->GetsHitByMOs()) { - return false; - } - - if (m_IgnoresTeamHits && pOverlapMO->IgnoresTeamHits() && m_Team == pOverlapMO->GetTeam()) { - return false; - } - - if (g_SceneMan.ShortestDistance(m_Pos, pOverlapMO->GetPos(), g_SceneMan.SceneWrapsX()).MagnitudeIsLessThan(GetRadius() + pOverlapMO->GetRadius())) { - Draw(g_SceneMan.GetMOIDBitmap(), Vector(), g_DrawMOID, true); - return true; - } - - return false; -} - // TODO This should just be defined in MOSR instead of having an empty definition in MO. MOSR would need to override UpdateMOID accordingly, but this would clean things up a little. void MOSRotating::UpdateChildMOIDs(std::vector& MOIDIndex, MOID rootMOID, bool makeNewMOID) { MOSprite::UpdateChildMOIDs(MOIDIndex, m_RootMOID, makeNewMOID); @@ -1456,6 +1380,7 @@ void MOSRotating::AddAttachable(Attachable* attachable, const Vector& parentOffs } attachable->SetParentOffset(parentOffsetToSet); attachable->SetParent(this); + attachable->UpdatePositionAndJointPositionBasedOnOffsets(true); m_AttachableAndWoundMass += attachable->GetMass(); HandlePotentialRadiusAffectingAttachable(attachable); m_Attachables.push_back(attachable); @@ -1577,15 +1502,10 @@ void MOSRotating::SetWhichMOToNotHit(MovableObject* moToNotHit, float forHowLong } } -void MOSRotating::Draw(BITMAP* pTargetBitmap, const Vector& targetPos, DrawMode mode, bool onlyPhysical) const { +void MOSRotating::Draw(BITMAP* targetBitmap, const Vector& targetPos, DrawMode mode, bool onlyPhysical) const { RTEAssert(!m_aSprite.empty(), "No sprite bitmaps loaded to draw!"); RTEAssert(m_Frame >= 0 && m_Frame < m_FrameCount, "Frame is out of bounds!"); - // Only draw MOID if this has a valid MOID assigned to it - if (mode == g_DrawMOID && m_MOID == g_NoMOID) { - return; - } - if (mode == g_DrawColor && !m_FlashWhiteTimer.IsPastRealTimeLimit()) { mode = g_DrawWhite; } @@ -1595,7 +1515,7 @@ void MOSRotating::Draw(BITMAP* pTargetBitmap, const Vector& targetPos, DrawMode if (mode == g_DrawColor || (!onlyPhysical && mode == g_DrawMaterial)) { for (const AEmitter* woundToDraw: m_Wounds) { if (!woundToDraw->IsDrawnAfterParent()) { - woundToDraw->Draw(pTargetBitmap, targetPos, mode, onlyPhysical); + woundToDraw->Draw(targetBitmap, targetPos, mode, onlyPhysical); } } } @@ -1603,173 +1523,222 @@ void MOSRotating::Draw(BITMAP* pTargetBitmap, const Vector& targetPos, DrawMode // Draw all the attached attachables for (const Attachable* attachableToDraw: m_Attachables) { if (!attachableToDraw->IsDrawnAfterParent() && attachableToDraw->IsDrawnNormallyByParent()) { - attachableToDraw->Draw(pTargetBitmap, targetPos, mode, onlyPhysical); + attachableToDraw->Draw(targetBitmap, targetPos, mode, onlyPhysical); } } - BITMAP* pTempBitmap = m_pTempBitmap; - BITMAP* pFlipBitmap = m_pFlipBitmap; - int keyColor = g_MaskColor; - - // Switch to non 8-bit drawing mode if we're drawing onto MO layer - if (mode == g_DrawMOID || mode == g_DrawNoMOID) { - pTempBitmap = m_pTempBitmapS; - pFlipBitmap = m_pFlipBitmapS; - keyColor = g_MOIDMaskColor; - } - - Vector spritePos(m_Pos.GetRounded() - targetPos); + Vector prevSpritePos(m_PrevPos - targetPos); + Vector spritePos(m_Pos - targetPos); if (m_Recoiled) { spritePos += m_RecoilOffset; } - // If we're drawing a material silhouette, then create an intermediate material bitmap as well -#ifdef DRAW_MOID_LAYER - bool intermediateBitmapUsed = mode != g_DrawColor && mode != g_DrawTrans; -#else - bool intermediateBitmapUsed = mode != g_DrawColor && mode != g_DrawTrans && mode != g_DrawMOID; - RTEAssert(mode != g_DrawNoMOID, "DrawNoMOID drawing mode used with no MOID layer!"); -#endif - if (intermediateBitmapUsed) { - clear_to_color(pTempBitmap, keyColor); - - // TODO: Fix that MaterialAir and KeyColor don't work at all because they're drawing 0 to a field of 0's - // Draw the requested material silhouette on the material bitmap - if (mode == g_DrawMaterial) { - draw_character_ex(pTempBitmap, m_aSprite[m_Frame], 0, 0, m_SettleMaterialDisabled ? GetMaterial()->GetIndex() : GetMaterial()->GetSettleMaterial(), -1); - } else if (mode == g_DrawWhite) { - draw_character_ex(pTempBitmap, m_aSprite[m_Frame], 0, 0, g_WhiteColor, -1); - } else if (mode == g_DrawMOID) { - draw_character_ex(pTempBitmap, m_aSprite[m_Frame], 0, 0, m_MOID, -1); - } else if (mode == g_DrawNoMOID) { - draw_character_ex(pTempBitmap, m_aSprite[m_Frame], 0, 0, g_NoMOID, -1); - } else if (mode == g_DrawDoor) { - draw_character_ex(pTempBitmap, m_aSprite[m_Frame], 0, 0, g_MaterialDoor, -1); - } else { - RTEAbort("Unknown draw mode selected in MOSRotating::Draw()!"); - } - } - -#ifdef DRAW_MOID_LAYER - bool needsWrap = true; -#else - bool needsWrap = mode != g_DrawMOID; -#endif - - // Take care of wrapping situations - Vector aDrawPos[4]; - int passes = 1; - - aDrawPos[0] = spritePos; - - if (needsWrap && g_SceneMan.SceneWrapsX()) { - // See if need to double draw this across the scene seam if we're being drawn onto a scenewide bitmap - if (targetPos.IsZero() && m_WrapDoubleDraw) { - if (spritePos.m_X < m_SpriteDiameter) { - aDrawPos[passes] = spritePos; - aDrawPos[passes].m_X += pTargetBitmap->w; - passes++; - } else if (spritePos.m_X > pTargetBitmap->w - m_SpriteDiameter) { - aDrawPos[passes] = spritePos; - aDrawPos[passes].m_X -= pTargetBitmap->w; - passes++; - } - } else if (m_WrapDoubleDraw) { - // Only screenwide target bitmap, so double draw within the screen if the screen is straddling a scene seam - if (targetPos.m_X < 0) { - aDrawPos[passes] = aDrawPos[0]; - aDrawPos[passes].m_X -= g_SceneMan.GetSceneWidth(); - passes++; - } - if (targetPos.m_X + pTargetBitmap->w > g_SceneMan.GetSceneWidth()) { - aDrawPos[passes] = aDrawPos[0]; - aDrawPos[passes].m_X += g_SceneMan.GetSceneWidth(); - passes++; - } - } - } + if (mode == g_DrawMOID) { + g_SceneMan.RegisterMOIDDrawing(m_MOID, + spritePos.GetX() - m_SpriteRadius + m_SpriteOffset.m_X, spritePos.GetY() - m_SpriteRadius + m_SpriteOffset.m_Y, + spritePos.GetX() + m_SpriteRadius - m_SpriteOffset.m_X, spritePos.GetY() + m_SpriteRadius - m_SpriteOffset.m_Y); + } else { + BITMAP* pTempBitmap = m_pTempBitmap; + BITMAP* pFlipBitmap = targetBitmap ? m_pFlipBitmap : nullptr; + int keyColor = g_MaskColor; - if (m_HFlipped && pFlipBitmap) { -#ifdef DRAW_MOID_LAYER - bool drawIntermediate = true; -#else - bool drawIntermediate = mode != g_DrawMOID; -#endif + BITMAP* currentFrame = m_aSprite[m_Frame]; - if (drawIntermediate) { - // Don't size the intermediate bitmaps to the m_Scale, because the scaling happens after they are done - clear_to_color(pFlipBitmap, keyColor); + // If we're drawing a material silhouette, then create an intermediate material bitmap as well + bool intermediateBitmapUsed = mode != g_DrawColor && mode != g_DrawTrans && mode != g_DrawMOID; + if (intermediateBitmapUsed) { + clear_to_color(pTempBitmap, keyColor); - // Draw either the source color bitmap or the intermediate material bitmap onto the intermediate flipping bitmap - if (mode == g_DrawColor || mode == g_DrawTrans) { - draw_sprite_h_flip(pFlipBitmap, m_aSprite[m_Frame], 0, 0); + // TODO: Fix that MaterialAir and KeyColor don't work at all because they're drawing 0 to a field of 0's + // Draw the requested material silhouette on the material bitmap + if (mode == g_DrawMaterial) { + draw_character_ex(pTempBitmap, currentFrame, 0, 0, m_SettleMaterialDisabled ? GetMaterial()->GetIndex() : GetMaterial()->GetSettleMaterial(), -1); + } else if (mode == g_DrawWhite) { + draw_character_ex(pTempBitmap, currentFrame, 0, 0, g_WhiteColor, -1); + } else if (mode == g_DrawDoor) { + draw_character_ex(pTempBitmap, currentFrame, 0, 0, g_MaterialDoor, -1); } else { - // If using the temp bitmap (which is always larger than the sprite) make sure the flipped image ends up in the upper right corner as if it was just as small as the sprite bitmap - draw_sprite_h_flip(pFlipBitmap, pTempBitmap, -(pTempBitmap->w - m_aSprite[m_Frame]->w), 0); + RTEAbort("Unknown draw mode selected in MOSRotating::Draw()!"); } } - if (mode == g_DrawTrans) { - clear_to_color(pTempBitmap, keyColor); + bool hFlipped = m_HFlipped; + bool wrapDoubleDraw = m_WrapDoubleDraw; + Vector spriteOffset = m_SpriteOffset; + Matrix prevRotation = m_PrevRotation; + Matrix currRotation = m_Rotation; + float scale = m_Scale; + + auto renderFunc = [=](float interpolationAmount) { + BITMAP* pTargetBitmap = targetBitmap; + Vector renderPos = g_SceneMan.Lerp(0.0F, 1.0F, prevSpritePos, spritePos, interpolationAmount); + Matrix rotation(Lerp(0.0F, 1.0F, prevRotation, currRotation, interpolationAmount)); + if (targetBitmap == nullptr) { + pTargetBitmap = g_ThreadMan.GetRenderTarget(); + renderPos -= g_ThreadMan.GetRenderOffset(); + } - // Draw the rotated thing onto the intermediate bitmap so its COM position aligns with the middle of the temp bitmap. - // The temp bitmap should be able to hold the full size since it is larger than the max diameter. - // Take into account the h-flipped pivot point - pivot_scaled_sprite(pTempBitmap, pFlipBitmap, pTempBitmap->w / 2, pTempBitmap->h / 2, pFlipBitmap->w + m_SpriteOffset.m_X, -(m_SpriteOffset.m_Y), ftofix(m_Rotation.GetAllegroAngle()), ftofix(m_Scale)); - - // Draw the now rotated object's temporary bitmap onto the final drawing bitmap with transperency - // Do the passes loop in here so the intermediate drawing doesn't get done multiple times - for (int i = 0; i < passes; ++i) { - int spriteX = aDrawPos[i].GetFloorIntX() - (pTempBitmap->w / 2); - int spriteY = aDrawPos[i].GetFloorIntY() - (pTempBitmap->h / 2); - g_SceneMan.RegisterDrawing(pTargetBitmap, g_NoMOID, spriteX, spriteY, spriteX + pTempBitmap->w, spriteY + pTempBitmap->h); - draw_trans_sprite(pTargetBitmap, pTempBitmap, spriteX, spriteY); + // Take care of wrapping situations + std::array drawPositions = {renderPos}; + int drawPasses = 1; + if (g_SceneMan.SceneWrapsX()) { + if (renderPos.IsZero() && wrapDoubleDraw) { + if (spritePos.GetFloorIntX() < currentFrame->w) { + drawPositions[drawPasses] = spritePos; + drawPositions[drawPasses].m_X += static_cast(pTargetBitmap->w); + drawPasses++; + } else if (spritePos.GetFloorIntX() > pTargetBitmap->w - currentFrame->w) { + drawPositions[drawPasses] = spritePos; + drawPositions[drawPasses].m_X -= static_cast(pTargetBitmap->w); + drawPasses++; + } + } else if (wrapDoubleDraw) { + if (renderPos.m_X < 0) { + drawPositions[drawPasses] = drawPositions[0]; + drawPositions[drawPasses].m_X += static_cast(g_SceneMan.GetSceneWidth()); + drawPasses++; + } + if (renderPos.GetFloorIntX() + pTargetBitmap->w > g_SceneMan.GetSceneWidth()) { + drawPositions[drawPasses] = drawPositions[0]; + drawPositions[drawPasses].m_X -= static_cast(g_SceneMan.GetSceneWidth()); + drawPasses++; + } + } } - } else { - // Do the passes loop in here so the flipping operation doesn't get done multiple times - for (int i = 0; i < passes; ++i) { - int spriteX = aDrawPos[i].GetFloorIntX(); - int spriteY = aDrawPos[i].GetFloorIntY(); - g_SceneMan.RegisterDrawing(pTargetBitmap, mode == g_DrawNoMOID ? g_NoMOID : m_MOID, spriteX + m_SpriteOffset.m_X - (m_SpriteRadius * m_Scale), spriteY + m_SpriteOffset.m_Y - (m_SpriteRadius * m_Scale), spriteX - m_SpriteOffset.m_X + (m_SpriteRadius * m_Scale), spriteY - m_SpriteOffset.m_Y + (m_SpriteRadius * m_Scale)); -#ifndef DRAW_MOID_LAYER - if (mode == g_DrawMOID) { - continue; + if (g_SceneMan.SceneWrapsY()) { + if (renderPos.IsZero() && wrapDoubleDraw) { + if (spritePos.GetFloorIntY() < currentFrame->h) { + drawPositions[drawPasses] = spritePos; + drawPositions[drawPasses].m_Y += static_cast(pTargetBitmap->h); + drawPasses++; + } else if (spritePos.GetFloorIntY() > pTargetBitmap->h - currentFrame->h) { + drawPositions[drawPasses] = spritePos; + drawPositions[drawPasses].m_Y -= static_cast(pTargetBitmap->h); + drawPasses++; + } + } else if (wrapDoubleDraw) { + if (renderPos.m_Y < 0) { + drawPositions[drawPasses] = drawPositions[0]; + drawPositions[drawPasses].m_Y += static_cast(g_SceneMan.GetSceneHeight()); + drawPasses++; + } + if (renderPos.GetFloorIntY() + pTargetBitmap->h > g_SceneMan.GetSceneHeight()) { + drawPositions[drawPasses] = drawPositions[0]; + drawPositions[drawPasses].m_Y -= static_cast(g_SceneMan.GetSceneHeight()); + drawPasses++; + } } -#endif - // Take into account the h-flipped pivot point - pivot_scaled_sprite(pTargetBitmap, pFlipBitmap, spriteX, spriteY, pFlipBitmap->w + m_SpriteOffset.GetFloorIntX(), -(m_SpriteOffset.GetFloorIntY()), ftofix(m_Rotation.GetAllegroAngle()), ftofix(m_Scale)); } - } - } else { - if (mode == g_DrawTrans) { - clear_to_color(pTempBitmap, keyColor); - // Draw the rotated thing onto the intermediate bitmap so its COM position aligns with the middle of the temp bitmap. - // The temp bitmap should be able to hold the full size since it is larger than the max diameter. - // Take into account the h-flipped pivot point - pivot_scaled_sprite(pTempBitmap, m_aSprite[m_Frame], pTempBitmap->w / 2, pTempBitmap->h / 2, -m_SpriteOffset.GetFloorIntX(), -m_SpriteOffset.GetFloorIntY(), ftofix(m_Rotation.GetAllegroAngle()), ftofix(m_Scale)); - - // Draw the now rotated object's temporary bitmap onto the final drawing bitmap with transperency - // Do the passes loop in here so the intermediate drawing doesn't get done multiple times - for (int i = 0; i < passes; ++i) { - int spriteX = aDrawPos[i].GetFloorIntX() - (pTempBitmap->w / 2); - int spriteY = aDrawPos[i].GetFloorIntY() - (pTempBitmap->h / 2); - g_SceneMan.RegisterDrawing(pTargetBitmap, g_NoMOID, spriteX, spriteY, spriteX + pTempBitmap->w, spriteY + pTempBitmap->h); - draw_trans_sprite(pTargetBitmap, pTempBitmap, spriteX, spriteY); - } - } else { - for (int i = 0; i < passes; ++i) { - int spriteX = aDrawPos[i].GetFloorIntX(); - int spriteY = aDrawPos[i].GetFloorIntY(); - g_SceneMan.RegisterDrawing(pTargetBitmap, mode == g_DrawNoMOID ? g_NoMOID : m_MOID, spriteX + m_SpriteOffset.m_X - (m_SpriteRadius * m_Scale), spriteY + m_SpriteOffset.m_Y - (m_SpriteRadius * m_Scale), spriteX - m_SpriteOffset.m_X + (m_SpriteRadius * m_Scale), spriteY - m_SpriteOffset.m_Y + (m_SpriteRadius * m_Scale)); -#ifndef DRAW_MOID_LAYER - if (mode == g_DrawMOID) { - continue; + if (hFlipped) { + bool tempBitmap = false; + BITMAP* usedFlipBitmap = pFlipBitmap; + if (!usedFlipBitmap) { + usedFlipBitmap = create_bitmap_ex(8, currentFrame->w, currentFrame->h); + tempBitmap = true; + } + + // Don't size the intermediate bitmaps to the scale, because the scaling happens after they are done + clear_to_color(usedFlipBitmap, keyColor); + // Draw either the source color bitmap or the intermediate material bitmap onto the intermediate flipping bitmap + if (mode == g_DrawColor || mode == g_DrawTrans) { + draw_sprite_h_flip(usedFlipBitmap, currentFrame, 0, 0); + // If using the temp bitmap (which is always larger than the sprite) make sure the flipped image ends up in the upper right corner as if it was just as small as the sprite bitmap + } else { + draw_sprite_h_flip(usedFlipBitmap, pTempBitmap, -(pTempBitmap->w - currentFrame->w), 0); + } + + // Transparent mode + if (mode == g_DrawTrans) { + clear_to_color(pTempBitmap, keyColor); + // Draw the rotated thing onto the intermediate bitmap so its COM position aligns with the middle of the temp bitmap. + // The temp bitmap should be able to hold the full size since it is larger than the max diameter. + // Take into account the h-flipped pivot point + pivot_scaled_sprite(pTempBitmap, + usedFlipBitmap, + pTempBitmap->w / 2, + pTempBitmap->h / 2, + usedFlipBitmap->w + spriteOffset.m_X, + -(spriteOffset.m_Y), + ftofix(rotation.GetAllegroAngle()), + ftofix(scale)); + + // Draw the now rotated object's temporary bitmap onto the final drawing bitmap with transperency + // Do the passes loop in here so the intermediate drawing doesn't get done multiple times + for (int i = 0; i < drawPasses; ++i) { + int spriteX = drawPositions[i].GetFloorIntX() - (pTempBitmap->w / 2); + int spriteY = drawPositions[i].GetFloorIntY() - (pTempBitmap->h / 2); + + draw_trans_sprite(pTargetBitmap, pTempBitmap, spriteX, spriteY); + } + } else { + // Do the passes loop in here so the flipping operation doesn't get done multiple times + for (int i = 0; i < drawPasses; ++i) { + int spriteX = drawPositions[i].GetFloorIntX(); + int spriteY = drawPositions[i].GetFloorIntY(); + + // Take into account the h-flipped pivot point + pivot_scaled_sprite(pTargetBitmap, + usedFlipBitmap, + spriteX, + spriteY, + usedFlipBitmap->w + spriteOffset.m_X, + -(spriteOffset.m_Y), + ftofix(rotation.GetAllegroAngle()), + ftofix(scale)); + } + } + + if (tempBitmap) { + destroy_bitmap(usedFlipBitmap); + } + } else { + // Transparent mode + if (mode == g_DrawTrans) { + clear_to_color(pTempBitmap, keyColor); + // Draw the rotated thing onto the intermediate bitmap so its COM position aligns with the middle of the temp bitmap. + // The temp bitmap should be able to hold the full size since it is larger than the max diameter. + // Take into account the h-flipped pivot point + pivot_scaled_sprite(pTempBitmap, + currentFrame, + pTempBitmap->w / 2, + pTempBitmap->h / 2, + -(spriteOffset.m_X), + -(spriteOffset.m_Y), + ftofix(rotation.GetAllegroAngle()), + ftofix(scale)); + + // Draw the now rotated object's temporary bitmap onto the final drawing bitmap with transperency + // Do the passes loop in here so the intermediate drawing doesn't get done multiple times + for (int i = 0; i < drawPasses; ++i) { + int spriteX = drawPositions[i].GetFloorIntX() - (pTempBitmap->w / 2); + int spriteY = drawPositions[i].GetFloorIntY() - (pTempBitmap->h / 2); + + draw_trans_sprite(pTargetBitmap, pTempBitmap, spriteX, spriteY); + } + } else { + for (int i = 0; i < drawPasses; ++i) { + int spriteX = drawPositions[i].GetFloorIntX(); + int spriteY = drawPositions[i].GetFloorIntY(); + + pivot_scaled_sprite(pTargetBitmap, + mode == g_DrawColor ? currentFrame : pTempBitmap, + spriteX, + spriteY, + -(spriteOffset.m_X), + -(spriteOffset.m_Y), + ftofix(rotation.GetAllegroAngle()), + ftofix(scale)); + } } -#endif - pivot_scaled_sprite(pTargetBitmap, mode == g_DrawColor ? m_aSprite[m_Frame] : pTempBitmap, spriteX, spriteY, -m_SpriteOffset.GetFloorIntX(), -m_SpriteOffset.GetFloorIntY(), ftofix(m_Rotation.GetAllegroAngle()), ftofix(m_Scale)); } + }; + + if (targetBitmap == nullptr) { + g_ThreadMan.GetSimRenderQueue().push_back(renderFunc); + } else { + renderFunc(1.0F); } } @@ -1778,7 +1747,7 @@ void MOSRotating::Draw(BITMAP* pTargetBitmap, const Vector& targetPos, DrawMode if (mode == g_DrawColor || (!onlyPhysical && mode == g_DrawMaterial)) { for (const AEmitter* woundToDraw: m_Wounds) { if (woundToDraw->IsDrawnAfterParent()) { - woundToDraw->Draw(pTargetBitmap, targetPos, mode, onlyPhysical); + woundToDraw->Draw(targetBitmap, targetPos, mode, onlyPhysical); } } } @@ -1786,13 +1755,13 @@ void MOSRotating::Draw(BITMAP* pTargetBitmap, const Vector& targetPos, DrawMode // Draw all the attached attachables for (const Attachable* attachableToDraw: m_Attachables) { if (attachableToDraw->IsDrawnAfterParent() && attachableToDraw->IsDrawnNormallyByParent()) { - attachableToDraw->Draw(pTargetBitmap, targetPos, mode, onlyPhysical); + attachableToDraw->Draw(targetBitmap, targetPos, mode, onlyPhysical); } } if (mode == g_DrawColor && !onlyPhysical && m_pAtomGroup && g_SettingsMan.DrawAtomGroupVisualizations() && GetRootParent() == this) { - m_pAtomGroup->Draw(pTargetBitmap, targetPos, false, 122); - // m_pDeepGroup->Draw(pTargetBitmap, targetPos, false, 13); + m_pAtomGroup->Draw(targetBitmap, targetPos, false, 122); + // m_pDeepGroup->Draw(targetBitmap, targetPos, false, 13); } } @@ -1824,13 +1793,13 @@ void MOSRotating::CorrectAttachableAndWoundPositionsAndRotations() const { for (Attachable* attachable: m_Attachables) { attachable->PreUpdate(); attachable->m_PreUpdateHasRunThisFrame = false; - attachable->UpdatePositionAndJointPositionBasedOnOffsets(); + attachable->UpdatePositionAndJointPositionBasedOnOffsets(true); attachable->CorrectAttachableAndWoundPositionsAndRotations(); } for (Attachable* wound: m_Wounds) { wound->PreUpdate(); wound->m_PreUpdateHasRunThisFrame = false; - wound->UpdatePositionAndJointPositionBasedOnOffsets(); + wound->UpdatePositionAndJointPositionBasedOnOffsets(true); wound->CorrectAttachableAndWoundPositionsAndRotations(); } } @@ -1859,3 +1828,14 @@ bool MOSRotating::TransferForcesFromAttachable(Attachable* attachable) { } return intact; } + +void MOSRotating::NewFrame() { + MOSprite::NewFrame(); + + for (Attachable* attachable: m_Attachables) { + attachable->NewFrame(); + } + for (Attachable* wound: m_Wounds) { + wound->NewFrame(); + } +} diff --git a/Source/Entities/MOSRotating.h b/Source/Entities/MOSRotating.h index 9528ea0469..fbb67d37bd 100644 --- a/Source/Entities/MOSRotating.h +++ b/Source/Entities/MOSRotating.h @@ -334,12 +334,6 @@ namespace RTE { void Update() override; void PostUpdate() override; - /// Draws the MOID representation of this to the SceneMan's MOID layer if - /// this is found to potentially overlap another MovableObject. - /// @param pOverlapMO The MovableObject to check this for overlap against. - /// @return Whether it was drawn or not. - bool DrawMOIDIfOverlapping(MovableObject* pOverlapMO) override; - /// Draws this MOSRotating's current graphical representation to a /// BITMAP of choice. /// @param pTargetBitmap A pointer to a BITMAP to draw on. @@ -492,6 +486,9 @@ namespace RTE { /// Ensures all attachables and wounds are positioned and rotated correctly. Must be run when this MOSRotating is added to MovableMan to avoid issues with Attachables spawning in at (0, 0). virtual void CorrectAttachableAndWoundPositionsAndRotations() const; + /// Notify that a new frame has started, allowing us to update information like our previous state. + void NewFrame() override; + /// Method to be run when the game is saved via ActivityMan::SaveCurrentGame. Not currently used in metagame or editor saving. void OnSave() override; @@ -581,26 +578,8 @@ namespace RTE { // Intermediary drawing bitmap used to flip rotating bitmaps. Owned! BITMAP* m_pFlipBitmap; - BITMAP* m_pFlipBitmapS; // Intermediary drawing bitmap used to draw sihouettes and other effects. Not owned; points to the shared static bitmaps BITMAP* m_pTempBitmap; - // Temp drawing bitmaps shared between all MOSRotatings - static BITMAP* m_spTempBitmap16; - static BITMAP* m_spTempBitmap32; - static BITMAP* m_spTempBitmap64; - static BITMAP* m_spTempBitmap128; - static BITMAP* m_spTempBitmap256; - static BITMAP* m_spTempBitmap512; - - // Intermediary drawing bitmap used to draw MO silhouettes. Not owned; points to the shared static bitmaps - BITMAP* m_pTempBitmapS; - // Temp drawing bitmaps shared between all MOSRotatings - static BITMAP* m_spTempBitmapS16; - static BITMAP* m_spTempBitmapS32; - static BITMAP* m_spTempBitmapS64; - static BITMAP* m_spTempBitmapS128; - static BITMAP* m_spTempBitmapS256; - static BITMAP* m_spTempBitmapS512; /// Private member variable and method declarations private: diff --git a/Source/Entities/MOSprite.cpp b/Source/Entities/MOSprite.cpp index 4d8cf36cbc..4411891a05 100644 --- a/Source/Entities/MOSprite.cpp +++ b/Source/Entities/MOSprite.cpp @@ -3,6 +3,7 @@ #include "AEmitter.h" #include "PresetMan.h" #include "SceneMan.h" +#include "ThreadMan.h" using namespace RTE; @@ -112,6 +113,7 @@ int MOSprite::Create(const MOSprite& reference) { m_SpriteDiameter = reference.m_SpriteDiameter; m_Rotation = reference.m_Rotation; + m_PrevRotation = reference.m_PrevRotation; m_AngularVel = reference.m_AngularVel; m_SettleMaterialDisabled = reference.m_SettleMaterialDisabled; m_pEntryWound = reference.m_pEntryWound; @@ -341,7 +343,7 @@ Vector MOSprite::UnRotateOffset(const Vector& offset) const { void MOSprite::Update() { MovableObject::Update(); - // First, check that the sprite has enough frames to even have an animation and override the setting if not + // Check that the sprite has enough frames to even have an animation and override the setting if not if (m_FrameCount > 1) { // If animation mode is set to something other than ALWAYSLOOP but only has 2 frames, override it because it's pointless if ((m_SpriteAnimMode == ALWAYSRANDOM || m_SpriteAnimMode == ALWAYSPINGPONG) && m_FrameCount == 2) { @@ -364,20 +366,19 @@ void MOSprite::Update() { } // Animate the sprite, if applicable - unsigned int frameTime = m_SpriteAnimDuration / m_FrameCount; - unsigned int prevFrame = m_Frame; - - if (m_SpriteAnimTimer.GetElapsedSimTimeMS() > frameTime) { + double frameTime = m_SpriteAnimDuration / m_FrameCount; + while (m_SpriteAnimTimer.GetElapsedSimTimeMS() > frameTime) { + unsigned int prevFrame = m_Frame; + double newTime = frameTime > 0 ? m_SpriteAnimTimer.GetElapsedSimTimeMS() - frameTime : 0; + m_SpriteAnimTimer.SetElapsedSimTimeMS(newTime); switch (m_SpriteAnimMode) { case ALWAYSLOOP: m_Frame = ((m_Frame + 1) % m_FrameCount); - m_SpriteAnimTimer.Reset(); break; case ALWAYSRANDOM: while (m_Frame == prevFrame) { m_Frame = RandomNum(0, m_FrameCount - 1); } - m_SpriteAnimTimer.Reset(); break; case ALWAYSPINGPONG: if (m_Frame == m_FrameCount - 1) { @@ -386,7 +387,6 @@ void MOSprite::Update() { m_SpriteAnimIsReversingFrames = false; } m_SpriteAnimIsReversingFrames ? m_Frame-- : m_Frame++; - m_SpriteAnimTimer.Reset(); break; default: break; @@ -394,89 +394,132 @@ void MOSprite::Update() { } } -void MOSprite::Draw(BITMAP* pTargetBitmap, +void MOSprite::Draw(BITMAP* targetBitmap, const Vector& targetPos, DrawMode mode, bool onlyPhysical) const { - if (!m_aSprite[m_Frame]) + BITMAP* currentFrame = m_aSprite[m_Frame]; + if (!currentFrame) { RTEAbort("Sprite frame pointer is null when drawing MOSprite!"); + } // Apply offsets and positions. Vector spriteOffset; - if (m_HFlipped) - spriteOffset.SetXY(-(m_aSprite[m_Frame]->w + m_SpriteOffset.m_X), m_SpriteOffset.m_Y); - else + if (m_HFlipped) { + spriteOffset.SetXY(-(currentFrame->w + m_SpriteOffset.m_X), m_SpriteOffset.m_Y); + } else { spriteOffset = m_SpriteOffset; + } + Vector prevSpritePos(m_PrevPos + spriteOffset - targetPos); Vector spritePos(m_Pos + spriteOffset - targetPos); - // Take care of wrapping situations - Vector aDrawPos[4]; - aDrawPos[0] = spritePos; - int passes = 1; - - // Only bother with wrap drawing if the scene actually wraps around - if (g_SceneMan.SceneWrapsX()) { - // See if need to double draw this across the scene seam if we're being drawn onto a scenewide bitmap - if (targetPos.IsZero() && m_WrapDoubleDraw) { - if (spritePos.m_X < m_aSprite[m_Frame]->w) { - aDrawPos[passes] = spritePos; - aDrawPos[passes].m_X += pTargetBitmap->w; - passes++; - } else if (spritePos.m_X > pTargetBitmap->w - m_aSprite[m_Frame]->w) { - aDrawPos[passes] = spritePos; - aDrawPos[passes].m_X -= pTargetBitmap->w; - passes++; - } + if (mode == g_DrawMOID) { + g_SceneMan.RegisterMOIDDrawing(m_MOID, spritePos.GetX(), spritePos.GetY(), spritePos.GetX() + currentFrame->w, spritePos.GetY() + currentFrame->h); + return; + } + + bool hFlipped = m_HFlipped; + bool wrapDoubleDraw = m_WrapDoubleDraw; + + auto renderFunc = [=](float interpolationAmount) { + BITMAP* pTargetBitmap = targetBitmap; + Vector renderPos = g_SceneMan.Lerp(0.0F, 1.0F, prevSpritePos, spritePos, interpolationAmount); + if (targetBitmap == nullptr) { + pTargetBitmap = g_ThreadMan.GetRenderTarget(); + renderPos -= g_ThreadMan.GetRenderOffset(); } - // Only screenwide target bitmap, so double draw within the screen if the screen is straddling a scene seam - else if (m_WrapDoubleDraw) { - if (targetPos.m_X < 0) { - aDrawPos[passes] = aDrawPos[0]; - aDrawPos[passes].m_X -= g_SceneMan.GetSceneWidth(); - passes++; + + // Take care of wrapping situations + std::array drawPositions = {renderPos}; + int drawPasses = 1; + if (g_SceneMan.SceneWrapsX()) { + if (renderPos.IsZero() && wrapDoubleDraw) { + if (spritePos.GetFloorIntX() < currentFrame->w) { + drawPositions[drawPasses] = spritePos; + drawPositions[drawPasses].m_X += static_cast(pTargetBitmap->w); + drawPasses++; + } else if (spritePos.GetFloorIntX() > pTargetBitmap->w - currentFrame->w) { + drawPositions[drawPasses] = spritePos; + drawPositions[drawPasses].m_X -= static_cast(pTargetBitmap->w); + drawPasses++; + } + } else if (wrapDoubleDraw) { + if (renderPos.m_X < 0) { + drawPositions[drawPasses] = drawPositions[0]; + drawPositions[drawPasses].m_X += static_cast(g_SceneMan.GetSceneWidth()); + drawPasses++; + } + if (renderPos.GetFloorIntX() + pTargetBitmap->w > g_SceneMan.GetSceneWidth()) { + drawPositions[drawPasses] = drawPositions[0]; + drawPositions[drawPasses].m_X -= static_cast(g_SceneMan.GetSceneWidth()); + drawPasses++; + } } - if (targetPos.m_X + pTargetBitmap->w > g_SceneMan.GetSceneWidth()) { - aDrawPos[passes] = aDrawPos[0]; - aDrawPos[passes].m_X += g_SceneMan.GetSceneWidth(); - passes++; + } + if (g_SceneMan.SceneWrapsY()) { + if (renderPos.IsZero() && wrapDoubleDraw) { + if (spritePos.GetFloorIntY() < currentFrame->h) { + drawPositions[drawPasses] = spritePos; + drawPositions[drawPasses].m_Y += static_cast(pTargetBitmap->h); + drawPasses++; + } else if (spritePos.GetFloorIntY() > pTargetBitmap->h - currentFrame->h) { + drawPositions[drawPasses] = spritePos; + drawPositions[drawPasses].m_Y -= static_cast(pTargetBitmap->h); + drawPasses++; + } + } else if (wrapDoubleDraw) { + if (renderPos.m_Y < 0) { + drawPositions[drawPasses] = drawPositions[0]; + drawPositions[drawPasses].m_Y += static_cast(g_SceneMan.GetSceneHeight()); + drawPasses++; + } + if (renderPos.GetFloorIntY() + pTargetBitmap->h > g_SceneMan.GetSceneHeight()) { + drawPositions[drawPasses] = drawPositions[0]; + drawPositions[drawPasses].m_Y -= static_cast(g_SceneMan.GetSceneHeight()); + drawPasses++; + } } } - } - for (int i = 0; i < passes; ++i) { - int spriteX = aDrawPos[i].GetFloorIntX(); - int spriteY = aDrawPos[i].GetFloorIntY(); - switch (mode) { - case g_DrawMaterial: - RTEAbort("Ordered to draw an MOSprite in its material, which is not possible!"); - break; - case g_DrawWhite: - draw_character_ex(pTargetBitmap, m_aSprite[m_Frame], spriteX, spriteY, g_WhiteColor, -1); - break; - case g_DrawMOID: -#ifdef DRAW_MOID_LAYER - draw_character_ex(pTargetBitmap, m_aSprite[m_Frame], spriteX, spriteY, m_MOID, -1); -#endif - break; - case g_DrawNoMOID: - draw_character_ex(pTargetBitmap, m_aSprite[m_Frame], spriteX, spriteY, g_NoMOID, -1); - break; - case g_DrawTrans: - draw_trans_sprite(pTargetBitmap, m_aSprite[m_Frame], spriteX, spriteY); - break; - case g_DrawAlpha: - set_alpha_blender(); - draw_trans_sprite(pTargetBitmap, m_aSprite[m_Frame], spriteX, spriteY); - break; - default: - if (!m_HFlipped) { - draw_sprite(pTargetBitmap, m_aSprite[m_Frame], spriteX, spriteY); - } else { - draw_sprite_h_flip(pTargetBitmap, m_aSprite[m_Frame], spriteX, spriteY); - } + for (int i = 0; i < drawPasses; ++i) { + int spriteX = drawPositions[i].GetFloorIntX(); + int spriteY = drawPositions[i].GetFloorIntY(); + switch (mode) { + case g_DrawMaterial: + RTEAbort("Ordered to draw an MOSprite in its material, which is not possible!"); + break; + case g_DrawWhite: + draw_character_ex(pTargetBitmap, currentFrame, spriteX, spriteY, g_WhiteColor, -1); + break; + break; + case g_DrawTrans: + draw_trans_sprite(pTargetBitmap, currentFrame, spriteX, spriteY); + break; + case g_DrawAlpha: + set_alpha_blender(); + draw_trans_sprite(pTargetBitmap, currentFrame, spriteX, spriteY); + break; + default: + if (!hFlipped) { + draw_sprite(pTargetBitmap, currentFrame, spriteX, spriteY); + } else { + draw_sprite_h_flip(pTargetBitmap, currentFrame, spriteX, spriteY); + } + } } + }; - g_SceneMan.RegisterDrawing(pTargetBitmap, mode == g_DrawNoMOID ? g_NoMOID : m_MOID, spriteX, spriteY, spriteX + m_aSprite[m_Frame]->w, spriteY + m_aSprite[m_Frame]->h); + if (targetBitmap == nullptr) { + g_ThreadMan.GetSimRenderQueue().push_back(renderFunc); + } else { + renderFunc(1.0F); } } + +void MOSprite::NewFrame() { + MovableObject::NewFrame(); + + m_PrevRotation = m_Rotation; + m_PrevAngVel = m_AngularVel; +} diff --git a/Source/Entities/MOSprite.h b/Source/Entities/MOSprite.h index 4f35d49927..f83911d4f0 100644 --- a/Source/Entities/MOSprite.h +++ b/Source/Entities/MOSprite.h @@ -279,6 +279,9 @@ namespace RTE { /// @return 1 for not flipped, -1 for flipped. float GetFlipFactor() const { return m_HFlipped ? -1.0F : 1.0F; } + /// Notify that a new frame has started, allowing us to update information like our previous state. + void NewFrame() override; + /// Protected member variable and method declarations protected: // Member variables diff --git a/Source/Entities/MovableObject.cpp b/Source/Entities/MovableObject.cpp index 56c9480d23..f62059f5f9 100644 --- a/Source/Entities/MovableObject.cpp +++ b/Source/Entities/MovableObject.cpp @@ -64,7 +64,6 @@ void MovableObject::Clear() { m_IgnoresActorHits = false; m_MissionCritical = false; m_CanBeSquished = true; - m_IsUpdated = false; m_WrapDoubleDraw = true; m_DidWrap = false; m_MOID = g_NoMOID; @@ -167,7 +166,9 @@ int MovableObject::Create(const float mass, bool getHitByMOs) { m_Mass = mass; m_Pos = position; + m_PrevPos = position; m_Vel = velocity; + m_PrevVel = velocity; m_AgeTimer.Reset(); m_RestTimer.Reset(); m_Lifetime = lifetime; @@ -192,7 +193,9 @@ int MovableObject::Create(const MovableObject& reference) { m_MOType = reference.m_MOType; m_Mass = reference.m_Mass; m_Pos = reference.m_Pos; + m_PrevPos = reference.m_PrevPos; m_Vel = reference.m_Vel; + m_PrevVel = reference.m_PrevVel; m_Scale = reference.m_Scale; m_GlobalAccScalar = reference.m_GlobalAccScalar; m_AirResistance = reference.m_AirResistance; @@ -827,17 +830,8 @@ void MovableObject::PreTravel() { // Temporarily remove the representation of this from the scene MO sampler if (m_GetsHitByMOs) { m_IsTraveling = true; -#ifdef DRAW_MOID_LAYER - if (!g_SettingsMan.SimplifiedCollisionDetection()) { - Draw(g_SceneMan.GetMOIDBitmap(), Vector(), DrawMode::g_DrawNoMOID, true); - } -#endif } - // Save previous position and velocities before moving - m_PrevPos = m_Pos; - m_PrevVel = m_Vel; - m_MOIDHit = g_NoMOID; m_TerrainMatHit = g_MaterialAir; m_ParticleUniqueIDHit = 0; @@ -855,15 +849,9 @@ void MovableObject::PostTravel() { if (m_GetsHitByMOs) { if (!GetParent()) { m_IsTraveling = false; -#ifdef DRAW_MOID_LAYER - if (!g_SettingsMan.SimplifiedCollisionDetection()) { - Draw(g_SceneMan.GetMOIDBitmap(), Vector(), DrawMode::g_DrawMOID, true); - } -#endif } m_AlreadyHitBy.clear(); } - m_IsUpdated = true; // Check for age expiration if (m_Lifetime && m_AgeTimer.GetElapsedSimTimeMS() > m_Lifetime) { @@ -895,14 +883,6 @@ void MovableObject::Update() { } } -void MovableObject::Draw(BITMAP* targetBitmap, const Vector& targetPos, DrawMode mode, bool onlyPhysical) const { - if (mode == g_DrawMOID && m_MOID == g_NoMOID) { - return; - } - - g_SceneMan.RegisterDrawing(targetBitmap, mode == g_DrawNoMOID ? g_NoMOID : m_MOID, m_Pos - targetPos, 1.0F); -} - int MovableObject::UpdateScripts() { m_SimUpdatesSinceLastScriptedUpdate++; @@ -1121,3 +1101,15 @@ bool MovableObject::DrawToTerrain(SLTerrain* terrain) { } return true; } + +void MovableObject::SetPos(const Vector& newPos, bool teleport) { + m_Pos = newPos; + if (teleport) { + m_PrevPos = newPos; + } +} + +void MovableObject::NewFrame() { + m_PrevPos = m_Pos; + m_PrevVel = m_Vel; +} diff --git a/Source/Entities/MovableObject.h b/Source/Entities/MovableObject.h index 7c1d3d400d..44ef88e695 100644 --- a/Source/Entities/MovableObject.h +++ b/Source/Entities/MovableObject.h @@ -632,13 +632,8 @@ namespace RTE { /// Indicates whether this MovableObject has been at rest with no movement for longer than its RestThreshold. virtual bool IsAtRest(); - /// Indicates wheter this MovableObject has been updated yet during this - /// frame. - /// @return Wheter or not the MovableObject has been updated yet during this frame. - bool IsUpdated() const { return m_IsUpdated; } - - /// Tell this MovableObject that a new frame has started. - void NewFrame() { m_IsUpdated = false; } + /// Notify that a new frame has started, allowing us to update information like our previous state. + virtual void NewFrame(); /// Indicates whether this MO is marked for settling at the end of the /// MovableMan update. @@ -806,6 +801,14 @@ namespace RTE { /// @param newSimUpdatesBetweenScriptedUpdates The new number of Sim updates that run between each script update for this MovableObject. void SetSimUpdatesBetweenScriptedUpdates(int newSimUpdatesBetweenScriptedUpdates) { m_SimUpdatesBetweenScriptedUpdates = std::max(1, newSimUpdatesBetweenScriptedUpdates); } + /// Sets the position of the MO. + /// @param newPos New position for this MovableObject. + /// @param teleport Whether we're teleporting or moving smoothly. If we teleport, we don't interpolate between positions when rendering. + void SetPos(const Vector& newPos, bool teleport = true) override; + + // I couldn't get the getter/setter stuff in Lua to work with overloading... so... + void SetPosLuaBinding(const Vector& newPos) { SetPos(newPos, true); } + /// Does stuff that needs to be done before Travel(). Always call before /// calling Travel. virtual void PreTravel(); @@ -825,8 +828,6 @@ namespace RTE { /// transferred forces of MOs attached to this. void Update() override; - void Draw(BITMAP* pTargetBitmap, const Vector& targetPos = Vector(), DrawMode mode = g_DrawColor, bool onlyPhysical = false) const override; - /// Updates this MovableObject's Lua scripts. /// @return An error return value signaling success or any particular failure. Anything below 0 is an error signal. virtual int UpdateScripts(); @@ -915,12 +916,6 @@ namespace RTE { /// be done every frame. void UpdateMOID(std::vector& MOIDIndex, MOID rootMOID = g_NoMOID, bool makeNewMOID = true); - /// Draws the MOID representation of this to the SceneMan's MOID layer if - /// this is found to potentially overlap another MovableObject. - /// @param pOverlapMO The MovableObject to check this for overlap against. - /// @return Whether it was drawn or not. - virtual bool DrawMOIDIfOverlapping(MovableObject* pOverlapMO) { return false; } - /// Draws this' current graphical HUD overlay representation to a /// BITMAP of choice. /// @param pTargetBitmap A pointer to a BITMAP to draw on. @@ -1153,8 +1148,6 @@ namespace RTE { bool m_MissionCritical; // Whether this can be destroyed by being squished into the terrain bool m_CanBeSquished; - // Whether or not this MovableObject has been updated yet this frame. - bool m_IsUpdated; // Whether wrap drawing double across wrapping seams is enabled or not bool m_WrapDoubleDraw; // Whether the position of this object wrapped around the world this frame, or not. diff --git a/Source/Entities/PEmitter.h b/Source/Entities/PEmitter.h index 30d8ab5bae..e01e1185c5 100644 --- a/Source/Entities/PEmitter.h +++ b/Source/Entities/PEmitter.h @@ -141,11 +141,11 @@ namespace RTE { /// Gets the adjusted throttle multiplier that is factored into the emission rate of this PEmitter. /// @return The throttle strength as a multiplier. - float GetThrottleFactor() const { return LERP(-1.0f, 1.0f, m_NegativeThrottleMultiplier, m_PositiveThrottleMultiplier, m_Throttle); } + float GetThrottleFactor() const { return Lerp(-1.0f, 1.0f, m_NegativeThrottleMultiplier, m_PositiveThrottleMultiplier, m_Throttle); } /// Gets the throttle value that will achieve a given throttle factor that is factored into the emission rate of this AEmitter. /// @return The throttle value that will achieve the given throttle factor. - float GetThrottleForThrottleFactor(float throttleFactor) const { return LERP(m_NegativeThrottleMultiplier, m_PositiveThrottleMultiplier, -1.0f, 1.0f, throttleFactor); } + float GetThrottleForThrottleFactor(float throttleFactor) const { return Lerp(m_NegativeThrottleMultiplier, m_PositiveThrottleMultiplier, -1.0f, 1.0f, throttleFactor); } /* /// Sets the rate at which this PEmitter emits its particles. diff --git a/Source/Entities/PieMenu.cpp b/Source/Entities/PieMenu.cpp index 0d5f3f14ab..c079ff9c66 100644 --- a/Source/Entities/PieMenu.cpp +++ b/Source/Entities/PieMenu.cpp @@ -674,14 +674,14 @@ void PieMenu::UpdateWobbling() { void PieMenu::UpdateEnablingAndDisablingProgress() { m_BGBitmapNeedsRedrawing = true; if (m_EnabledState == EnabledState::Enabling) { - m_CurrentInnerRadius = static_cast(LERP(0.0F, static_cast(c_EnablingDelay), 0.0F, static_cast(m_FullInnerRadius), static_cast(m_EnableDisableAnimationTimer.GetElapsedRealTimeMS()))); + m_CurrentInnerRadius = static_cast(Lerp(0.0F, static_cast(c_EnablingDelay), 0.0F, static_cast(m_FullInnerRadius), static_cast(m_EnableDisableAnimationTimer.GetElapsedRealTimeMS()))); if (IsSubPieMenu() || m_EnableDisableAnimationTimer.IsPastRealMS(c_EnablingDelay)) { m_EnabledState = EnabledState::Enabled; m_CurrentInnerRadius = m_FullInnerRadius; m_SubPieMenuHoverOpenTimer.Reset(); } } else if (m_EnabledState == EnabledState::Disabling) { - m_CurrentInnerRadius = static_cast(LERP(0.0F, static_cast(c_EnablingDelay), static_cast(m_FullInnerRadius), 0.0F, static_cast(m_EnableDisableAnimationTimer.GetElapsedRealTimeMS()))); + m_CurrentInnerRadius = static_cast(Lerp(0.0F, static_cast(c_EnablingDelay), static_cast(m_FullInnerRadius), 0.0F, static_cast(m_EnableDisableAnimationTimer.GetElapsedRealTimeMS()))); if (IsSubPieMenu() || m_EnableDisableAnimationTimer.IsPastRealMS(c_EnablingDelay)) { m_EnabledState = EnabledState::Disabled; m_CurrentInnerRadius = 0; diff --git a/Source/Entities/Scene.cpp b/Source/Entities/Scene.cpp index 5410ac3f84..b516ca06c1 100644 --- a/Source/Entities/Scene.cpp +++ b/Source/Entities/Scene.cpp @@ -2411,7 +2411,7 @@ void Scene::Unlock() { } } -void Scene::Update() { +void Scene::UpdateSim() { ZoneScoped; m_PathfindingUpdated = false; @@ -2449,7 +2449,7 @@ void Scene::Update() { } // Occasionally update pathfinding. There's a tradeoff between how often updates occur vs how big the multithreaded batched node lists to update are. - if (m_PartialPathUpdateTimer.IsPastRealMS(100)) { + if (m_PartialPathUpdateTimer.IsPastSimMS(100)) { UpdatePathFinding(); } } diff --git a/Source/Entities/Scene.h b/Source/Entities/Scene.h index cd1fad45e9..0f1cba55a3 100644 --- a/Source/Entities/Scene.h +++ b/Source/Entities/Scene.h @@ -698,7 +698,7 @@ namespace RTE { /// Updates the state of this Scene. Supposed to be done every frame /// before drawing. - void Update(); + void UpdateSim(); /// Whether this scene is a temprorary metagame scene and should /// not be used anywhere except in metagame. diff --git a/Source/Entities/SceneObject.h b/Source/Entities/SceneObject.h index 10b9457270..365f16ad66 100644 --- a/Source/Entities/SceneObject.h +++ b/Source/Entities/SceneObject.h @@ -149,7 +149,10 @@ namespace RTE { /// Sets the absolute position of this SceneObject in the scene. /// @param newPos A Vector describing the current absolute position in pixels. - void SetPos(const Vector& newPos) { m_Pos = newPos; } + virtual void SetPos(const Vector& newPos, bool teleport = true) { m_Pos = newPos; } + + // I couldn't get the getter/setter stuff in Lua to work with overloading... so... + void SetPosLuaBinding(const Vector& newPos) { SetPos(newPos, true); } /// Returns whether this is being drawn flipped horizontally (around the /// vertical axis), or not. diff --git a/Source/Entities/TerrainDebris.cpp b/Source/Entities/TerrainDebris.cpp index 379b157dba..cefb985a4d 100644 --- a/Source/Entities/TerrainDebris.cpp +++ b/Source/Entities/TerrainDebris.cpp @@ -222,10 +222,6 @@ void TerrainDebris::DrawToTerrain(SLTerrain* terrain, BITMAP* bitmapToDraw, cons void TerrainDebris::ScatterOnTerrain(SLTerrain* terrain) { RTEAssert(!m_Bitmaps.empty() && m_BitmapCount > 0, "No bitmaps loaded for terrain debris during TerrainDebris::ScatterOnTerrain!"); - // Reference. Do not remove. - // acquire_bitmap(terrain->GetFGColorBitmap()); - // acquire_bitmap(terrain->GetMaterialBitmap()); - int possiblePieceToPlaceCount = static_cast((static_cast(terrain->GetMaterialBitmap()->w) * c_MPP) * m_Density); for (int piece = 0; piece < possiblePieceToPlaceCount; ++piece) { int pieceBitmapIndex = RandomNum(0, m_BitmapCount - 1); @@ -235,7 +231,4 @@ void TerrainDebris::ScatterOnTerrain(SLTerrain* terrain) { DrawToTerrain(terrain, m_Bitmaps[pieceBitmapIndex], possiblePiecePosition.GetCorner()); } } - // Reference. Do not remove. - // release_bitmap(terrain->GetMaterialBitmap()); - // release_bitmap(terrain->GetFGColorBitmap()); } diff --git a/Source/Entities/TerrainFrosting.cpp b/Source/Entities/TerrainFrosting.cpp index 12824b689b..6a8785d663 100644 --- a/Source/Entities/TerrainFrosting.cpp +++ b/Source/Entities/TerrainFrosting.cpp @@ -46,11 +46,6 @@ void TerrainFrosting::FrostTerrain(SLTerrain* terrain) const { bool applyingFrosting = false; int appliedThickness = 0; - // Reference. Do not remove. - // acquire_bitmap(matBitmap); - // acquire_bitmap(fgColorBitmap); - // if (frostingTexture) { acquire_bitmap(frostingTexture); } - for (int xPos = 0; xPos < matBitmap->w; ++xPos) { int thicknessGoal = RandomNum(m_MinThickness, m_MaxThickness); @@ -73,8 +68,4 @@ void TerrainFrosting::FrostTerrain(SLTerrain* terrain) const { } } } - // Reference. Do not remove. - // if (frostingTexture) { release_bitmap(frostingTexture); } - // release_bitmap(fgColorBitmap); - // release_bitmap(matBitmap); } diff --git a/Source/Lua/LuaBindingsEntities.cpp b/Source/Lua/LuaBindingsEntities.cpp index cc4db715e8..5e66977b0c 100644 --- a/Source/Lua/LuaBindingsEntities.cpp +++ b/Source/Lua/LuaBindingsEntities.cpp @@ -878,7 +878,7 @@ LuaBindingRegisterFunctionDefinitionForType(EntityLuaBindings, MovableObject) { .property("Material", &MovableObject::GetMaterial) .property("Mass", &MovableObject::GetMass, &MovableObject::SetMass) - .property("Pos", &MovableObject::GetPos, &MovableObject::SetPos) + .property("Pos", &MovableObject::GetPos, &MovableObject::SetPosLuaBinding) .property("Vel", &MovableObject::GetVel, &MovableObject::SetVel) .property("PrevPos", &MovableObject::GetPrevPos) .property("PrevVel", &MovableObject::GetPrevVel) @@ -1208,8 +1208,7 @@ LuaBindingRegisterFunctionDefinitionForType(EntityLuaBindings, SceneLayer) { LuaBindingRegisterFunctionDefinitionForType(EntityLuaBindings, SceneObject) { return AbstractTypeLuaClassDefinition(SceneObject, Entity) - - .property("Pos", &SceneObject::GetPos, &SceneObject::SetPos) + .property("Pos", &SceneObject::GetPos, &SceneObject::SetPosLuaBinding) .property("HFlipped", &SceneObject::IsHFlipped, &SceneObject::SetHFlipped) .property("RotAngle", &SceneObject::GetRotAngle, &SceneObject::SetRotAngle) .property("Team", &SceneObject::GetTeam, &SceneObject::SetTeam) diff --git a/Source/Lua/LuaBindingsManagers.cpp b/Source/Lua/LuaBindingsManagers.cpp index 942c5c8020..90790c6b79 100644 --- a/Source/Lua/LuaBindingsManagers.cpp +++ b/Source/Lua/LuaBindingsManagers.cpp @@ -320,10 +320,10 @@ LuaBindingRegisterFunctionDefinitionForType(ManagerLuaBindings, SceneMan) { .def("FindAltitude", (float(SceneMan::*)(const Vector&, int, int, bool)) & SceneMan::FindAltitude) .def("MovePointToGround", &SceneMan::MovePointToGround) .def("IsWithinBounds", &SceneMan::IsWithinBounds) - .def("ForceBounds", (bool(SceneMan::*)(int&, int&)) & SceneMan::ForceBounds) - .def("ForceBounds", (bool(SceneMan::*)(Vector&)) & SceneMan::ForceBounds) //, out_value(_2)) - .def("WrapPosition", (bool(SceneMan::*)(int&, int&)) & SceneMan::WrapPosition) - .def("WrapPosition", (bool(SceneMan::*)(Vector&)) & SceneMan::WrapPosition) //, out_value(_2)) + .def("ForceBounds", (bool(SceneMan::*)(int&, int&) const) & SceneMan::ForceBounds) + .def("ForceBounds", (bool(SceneMan::*)(Vector&) const) & SceneMan::ForceBounds) //, out_value(_2)) + .def("WrapPosition", (bool(SceneMan::*)(int&, int&) const) & SceneMan::WrapPosition) + .def("WrapPosition", (bool(SceneMan::*)(Vector&) const) & SceneMan::WrapPosition) //, out_value(_2)) .def("SnapPosition", &SceneMan::SnapPosition) .def("ShortestDistance", &SceneMan::ShortestDistance) .def("WrapBox", &LuaAdaptersSceneMan::WrapBoxes, luabind::return_stl_iterator) @@ -382,8 +382,7 @@ LuaBindingRegisterFunctionDefinitionForType(ManagerLuaBindings, TimerMan) { .property("TicksPerSecond", &LuaAdaptersTimerMan::GetTicksPerSecond) - .def("TimeForSimUpdate", &TimerMan::TimeForSimUpdate) - .def("DrawnSimUpdate", &TimerMan::DrawnSimUpdate); + .def("TimeForSimUpdate", &TimerMan::TimeForSimUpdate); } LuaBindingRegisterFunctionDefinitionForType(ManagerLuaBindings, UInputMan) { diff --git a/Source/Main.cpp b/Source/Main.cpp index 9b58f1d9ed..ce670d455d 100644 --- a/Source/Main.cpp +++ b/Source/Main.cpp @@ -39,6 +39,7 @@ #include "UInputMan.h" #include "PerformanceMan.h" #include "FrameMan.h" +#include "ThreadMan.h" #include "MetaMan.h" #include "WindowMan.h" #include "NetworkServer.h" @@ -138,6 +139,7 @@ void DestroyManagers() { ContentFile::FreeAllLoaded(); g_ConsoleMan.Destroy(); g_WindowMan.Destroy(); + g_ThreadMan.Destroy(); #ifdef DEBUG_BUILD Entity::ClassInfo::DumpPoolMemoryInfo(Writer("MemCleanupInfo.txt")); @@ -242,6 +244,23 @@ void RunMenuLoop() { g_UInputMan.DisableKeys(false); g_UInputMan.TrapMousePos(false); + g_TimerMan.PauseSim(true); + + // TODO_MULTITHREAD + // The next philosophical question is how to handle the quadrillion-and-one edge cases where things update in different places, that all have dependencies. + // GUI for example, that needs to interact with sim but also draw to screen. HUD is another example. Various editors, pie menus, all that jazz + // These are single-threaded fundamentally. I thought about doing this properly (like with the MO drawing), and that's possible... + // but it's really tough because there's a billion edge cases and it's tough to solve all of them. + // As such, the current plan I have is that we'll still do some limited drawing on the sim thread to handle these situations. + // It's definitely not ideal, but following in line with the pareto principle we've already achieved the bulk of the advantage with what we've properly split. + // (that is to say, the main game world drawing) + // So, that being said... next steps: + //  Store a Allegro bitmap (per screen) on the RenderableGameState. + // Sim thread can safely draw to that without a worry in the world, and it'll be swapped over to render to simply blit after our other redrawing is done. + // Need to think about how to properly allow the player to pan the camera without stuff "sticking" at a low update simrate... + // I could just pan around the bitmap, but then the edges would be cut off. Alternatively we draw to a scene-wide bitmap... but that's expensive to clear. + // Moving things over to a more formal render/sim split will be an ongoing task that we'll do over time. + while (!System::IsSetToQuit()) { g_WindowMan.ClearRenderer(); PollSDLEvents(); @@ -250,7 +269,6 @@ void RunMenuLoop() { g_UInputMan.Update(); g_TimerMan.Update(); - g_TimerMan.UpdateSim(); g_AudioMan.Update(); if (g_WindowMan.ResolutionChanged()) { @@ -262,9 +280,11 @@ void RunMenuLoop() { } if (g_MenuMan.Update()) { + g_TimerMan.PauseSim(false); break; } g_ConsoleMan.Update(); + g_ThreadMan.RunSimulationThreadFunctions(); g_MenuMan.Draw(); g_ConsoleMan.Draw(g_FrameMan.GetBackBuffer32()); @@ -292,89 +312,105 @@ void RunGameLoop() { } } - long long updateStartTime = 0; - long long updateTotalTime = 0; - long long updateEndAndDrawStartTime = 0; - long long drawStartTime = 0; - long long drawTotalTime = 0; + bool serverUpdated = false; - while (!System::IsSetToQuit()) { - bool serverUpdated = false; - updateStartTime = g_TimerMan.GetAbsoluteTime(); + auto simFunction = [&serverUpdated]() { + g_LuaMan.Update(); + g_ThreadMan.RunSimulationThreadFunctions(); - PollSDLEvents(); - g_WindowMan.Update(); - g_WindowMan.ClearRenderer(); - - g_TimerMan.Update(); + if (g_ActivityMan.ActivitySetToRestart() && !g_ActivityMan.RestartActivity()) { + return; + } // Simulation update, as many times as the fixed update step allows in the span since last frame draw. - while (g_TimerMan.TimeForSimUpdate()) { - ZoneScopedN("Simulation Update"); + if (!g_TimerMan.TimeForSimUpdate()) { + return; + } - serverUpdated = false; + ZoneScopedN("Simulation Update"); + long long updateStartTime = g_TimerMan.GetAbsoluteTime(); + serverUpdated = false; - g_PerformanceMan.NewPerformanceSample(); - g_PerformanceMan.UpdateMSPSU(); - g_TimerMan.UpdateSim(); + g_TimerMan.UpdateSim(); + g_UInputMan.Update(); - g_PerformanceMan.StartPerformanceMeasurement(PerformanceMan::SimTotal); + g_PerformanceMan.StartPerformanceMeasurement(PerformanceMan::SimTotal); - g_LuaMan.Update(); + // TODO_MULTITHREAD +#ifndef MULTITHREAD_SIM_AND_RENDER + // It is vital that server is updated after input manager but before activity because input manager will clear received pressed and released events on next update. + if (g_NetworkServer.IsServerModeEnabled()) { + g_NetworkServer.Update(true); + serverUpdated = true; + } +#endif - g_UInputMan.Update(); - g_ConsoleMan.Update(); + g_FrameMan.Update(); + g_ActivityMan.Update(); - // It is vital that server is updated after input manager but before activity because input manager will clear received pressed and released events on next update. - if (g_NetworkServer.IsServerModeEnabled()) { - g_NetworkServer.Update(true); - serverUpdated = true; - } + if (g_SceneMan.GetScene()) { + g_SceneMan.GetScene()->UpdateSim(); + } - g_FrameMan.Update(); + g_LuaMan.ClearScriptTimings(); + g_MovableMan.Update(); + g_PerformanceMan.UpdateSortedScriptTimings(g_LuaMan.GetScriptTimings()); - g_ActivityMan.Update(); + g_AudioMan.Update(); - if (g_SceneMan.GetScene()) { - g_SceneMan.GetScene()->Update(); - } + g_ActivityMan.LateUpdateGlobalScripts(); - g_LuaMan.ClearScriptTimings(); - g_MovableMan.Update(); - g_PerformanceMan.UpdateSortedScriptTimings(g_LuaMan.GetScriptTimings()); + // This is to support hot reloading entities in SceneEditorGUI. It's a bit hacky to put it in Main like this, but PresetMan has no update in which to clear the value, and I didn't want to set up a listener for the job. + // It's in this spot to allow it to be set by UInputMan update and ConsoleMan update, and read from ActivityMan update. + g_PresetMan.ClearReloadEntityPresetCalledThisUpdate(); - g_AudioMan.Update(); + g_PerformanceMan.StopPerformanceMeasurement(PerformanceMan::SimTotal); - g_ActivityMan.LateUpdateGlobalScripts(); + g_ThreadMan.TransferSimStateToRenderer(); - // This is to support hot reloading entities in SceneEditorGUI. It's a bit hacky to put it in Main like this, but PresetMan has no update in which to clear the value, and I didn't want to set up a listener for the job. - // It's in this spot to allow it to be set by UInputMan update and ConsoleMan update, and read from ActivityMan update. - g_PresetMan.ClearReloadEntityPresetCalledThisUpdate(); + long long updateEndTime = g_TimerMan.GetAbsoluteTime(); + g_PerformanceMan.NewPerformanceSample(); + g_PerformanceMan.UpdateMSPU(updateEndTime - updateStartTime); + }; - g_PerformanceMan.StopPerformanceMeasurement(PerformanceMan::SimTotal); + while (!System::IsSetToQuit()) { + long long frameStartTime = g_TimerMan.GetAbsoluteTime(); - if (!g_ActivityMan.IsInActivity()) { - g_TimerMan.PauseSim(true); + if (!g_ActivityMan.IsInActivity()) { + g_TimerMan.PauseSim(true); - if (!g_ActivityMan.ActivitySetToRestart()) { - g_MenuMan.HandleTransitionIntoMenuLoop(); - RunMenuLoop(); - } - } - if (g_ActivityMan.ActivitySetToRestart()) { - g_LoadingScreen.DrawLoadingSplash(); - g_WindowMan.UploadFrame(); - if (!g_ActivityMan.RestartActivity()) { - break; - } + if (!g_ActivityMan.ActivitySetToRestart()) { + g_MenuMan.HandleTransitionIntoMenuLoop(); + RunMenuLoop(); } - if (g_ActivityMan.ActivitySetToResume()) { - g_ActivityMan.ResumeActivity(); - g_PerformanceMan.ResetSimUpdateTimer(); - updateStartTime = g_TimerMan.GetAbsoluteTime(); + } + if (g_ActivityMan.ActivitySetToRestart()) { + g_LoadingScreen.DrawLoadingSplash(); + g_WindowMan.UploadFrame(); + if (!g_ActivityMan.RestartActivity()) { + break; } } + if (g_ActivityMan.ActivitySetToResume()) { + g_ActivityMan.ResumeActivity(); + + // TODO_MULTITHEAD is this okay? + g_PerformanceMan.ResetSimUpdateTimer(); + } + + g_TimerMan.Update(); + + PollSDLEvents(); + g_WindowMan.Update(); + g_WindowMan.ClearRenderer(); + + while (g_TimerMan.TimeForSimUpdate()) { + simFunction(); + } + + // TODO_MULTITHREAD +#ifndef MULTITHREAD_SIM_AND_RENDER if (g_NetworkServer.IsServerModeEnabled()) { // Pause sim while we're waiting for scene transmission or scene will start changing before clients receive them and those changes will be lost. g_TimerMan.PauseSim(!(g_NetworkServer.ReadyForSimulation() && g_ActivityMan.IsInActivity())); @@ -392,16 +428,21 @@ void RunGameLoop() { } } } - updateEndAndDrawStartTime = g_TimerMan.GetAbsoluteTime(); - updateTotalTime = updateEndAndDrawStartTime - updateStartTime; - drawStartTime = updateEndAndDrawStartTime; +#endif + g_ConsoleMan.Update(); + g_ThreadMan.Update(); + + long long drawStartTime = g_TimerMan.GetAbsoluteTime(); g_FrameMan.Draw(); g_WindowMan.DrawPostProcessBuffer(); + long long drawEndTime = g_TimerMan.GetAbsoluteTime(); + g_PerformanceMan.UpdateMSPD(drawEndTime - drawStartTime); + g_WindowMan.UploadFrame(); - drawTotalTime = g_TimerMan.GetAbsoluteTime() - drawStartTime; - g_PerformanceMan.UpdateMSPF(updateTotalTime, drawTotalTime); + long long frameEndTime = g_TimerMan.GetAbsoluteTime(); + g_PerformanceMan.UpdateMSPF(frameEndTime - frameStartTime); } } diff --git a/Source/Managers/ActivityMan.cpp b/Source/Managers/ActivityMan.cpp index c2d4b8baef..b7f20c3c82 100644 --- a/Source/Managers/ActivityMan.cpp +++ b/Source/Managers/ActivityMan.cpp @@ -322,8 +322,6 @@ int ActivityMan::StartActivity(Activity* activity) { m_LastMusicPos = 0; g_AudioMan.PauseIngameSounds(false); - g_PerformanceMan.ResetPerformanceTimings(); - return error; } @@ -370,7 +368,11 @@ void ActivityMan::PauseActivity(bool pause, bool skipPauseMenu) { } } - m_Activity->SetPaused(pause); + g_ThreadMan.QueueInSimulationThread([&]() { + m_Activity->SetPaused(pause); + }); + + g_TimerMan.PauseSim(pause); m_InActivity = !pause; m_ResumingActivityFromPauseMenu = false; m_SkipPauseMenuWhenPausingActivity = skipPauseMenu; @@ -386,7 +388,6 @@ void ActivityMan::ResumeActivity() { PauseActivity(false); g_TimerMan.PauseSim(false); - g_PerformanceMan.ResetPerformanceTimings(); } } @@ -410,7 +411,7 @@ bool ActivityMan::RestartActivity() { } else { activityStarted = StartActivity(m_DefaultActivityType, m_DefaultActivityName); } - g_TimerMan.PauseSim(false); + if (activityStarted >= 0) { m_InActivity = true; return true; diff --git a/Source/Managers/AudioMan.cpp b/Source/Managers/AudioMan.cpp index 839aa3a9d2..0489f1a0ad 100644 --- a/Source/Managers/AudioMan.cpp +++ b/Source/Managers/AudioMan.cpp @@ -866,7 +866,7 @@ void AudioMan::Update3DEffectsForSFXChannels() { if (sqrDistanceToPlayer < (m_MinimumDistanceForPanning * m_MinimumDistanceForPanning) || soundContainer->GetCustomPanValue() != 0.0f) { soundChannel->set3DLevel(0); } else if (sqrDistanceToPlayer < (doubleMinimumDistanceForPanning * doubleMinimumDistanceForPanning)) { - soundChannel->set3DLevel(LERP(0, 1, 0, m_SoundPanningEffectStrength * soundContainer->GetPanningStrengthMultiplier(), channel3dLevel)); + soundChannel->set3DLevel(Lerp(0, 1, 0, m_SoundPanningEffectStrength * soundContainer->GetPanningStrengthMultiplier(), channel3dLevel)); } else { soundChannel->set3DLevel(m_SoundPanningEffectStrength * soundContainer->GetPanningStrengthMultiplier()); } diff --git a/Source/Managers/CameraMan.cpp b/Source/Managers/CameraMan.cpp index 4e28c2c47f..9e5181a24c 100644 --- a/Source/Managers/CameraMan.cpp +++ b/Source/Managers/CameraMan.cpp @@ -5,6 +5,7 @@ #include "FrameMan.h" #include "Scene.h" #include "SceneMan.h" +#include "ThreadMan.h" #include "SLTerrain.h" #include "NetworkClient.h" @@ -49,7 +50,7 @@ void CameraMan::SetOffset(const Vector& offset, int screenId) { Vector CameraMan::GetUnwrappedOffset(int screenId) const { const Screen& screen = m_Screens[screenId]; - const SLTerrain* terrain = g_SceneMan.GetScene()->GetTerrain(); + const SLTerrain* terrain = g_ThreadMan.GetDrawableGameState().m_Terrain; return Vector(screen.Offset.GetX() + static_cast(terrain->GetBitmap()->w * screen.SeamCrossCount[Axes::X]), screen.Offset.GetY() + static_cast(terrain->GetBitmap()->h * screen.SeamCrossCount[Axes::Y])); } @@ -118,12 +119,14 @@ float CameraMan::TargetDistanceScalar(const Vector& point) const { } void CameraMan::CheckOffset(int screenId) { - RTEAssert(g_SceneMan.GetScene(), "Trying to check offset before there is a scene or terrain!"); + Screen& screen = m_Screens[screenId]; - const SLTerrain* terrain = g_SceneMan.GetScene()->GetTerrain(); - RTEAssert(terrain, "Trying to get terrain matter before there is a scene or terrain!"); + const SLTerrain* terrain = g_ThreadMan.GetDrawableGameState().m_Terrain; + if (!terrain) { + return; + } - Screen& screen = m_Screens[screenId]; + RTEAssert(terrain, "Trying to get terrain matter before there is a scene or terrain!"); if (!terrain->WrapsX() && screen.Offset.GetX() < 0) { screen.Offset.SetX(0.0F); @@ -197,27 +200,29 @@ void CameraMan::AddScreenShake(float magnitude, const Vector& position) { void CameraMan::Update(int screenId) { Screen& screen = m_Screens[screenId]; - const SLTerrain* terrain = g_SceneMan.GetScene()->GetTerrain(); - if (g_TimerMan.DrawnSimUpdate()) { - // Adjust for wrapping if the scroll target jumped a seam this frame, as reported by whatever screen set it (the scroll target) this frame. This is to avoid big, scene-wide jumps in scrolling when traversing the seam. - if (screen.TargetXWrapped) { - if (terrain->WrapsX()) { - int wrappingScrollDirection = (screen.ScrollTarget.GetFloorIntX() < (terrain->GetBitmap()->w / 2)) ? 1 : -1; - screen.Offset.SetX(screen.Offset.GetX() - (static_cast(terrain->GetBitmap()->w * wrappingScrollDirection))); - screen.SeamCrossCount[Axes::X] += wrappingScrollDirection; - } - screen.TargetXWrapped = false; + // Adjust for wrapping if the scroll target jumped a seam this frame, as reported by whatever screen set it (the scroll target) this frame. This is to avoid big, scene-wide jumps in scrolling when traversing the seam. + const SLTerrain* terrain = g_ThreadMan.GetDrawableGameState().m_Terrain; + if (!terrain) { + return; + } + + if (screen.TargetXWrapped) { + if (terrain->WrapsX()) { + int wrappingScrollDirection = (screen.ScrollTarget.GetFloorIntX() < (terrain->GetBitmap()->w / 2)) ? 1 : -1; + screen.Offset.SetX(screen.Offset.GetX() - (static_cast(terrain->GetBitmap()->w * wrappingScrollDirection))); + screen.SeamCrossCount[Axes::X] += wrappingScrollDirection; } + screen.TargetXWrapped = false; + } - if (screen.TargetYWrapped) { - if (terrain->WrapsY()) { - int wrappingScrollDirection = (screen.ScrollTarget.GetFloorIntY() < (terrain->GetBitmap()->h / 2)) ? 1 : -1; - screen.Offset.SetY(screen.Offset.GetY() - (static_cast(terrain->GetBitmap()->h * wrappingScrollDirection))); - screen.SeamCrossCount[Axes::Y] += wrappingScrollDirection; - } - screen.TargetYWrapped = false; + if (screen.TargetYWrapped) { + if (terrain->WrapsY()) { + int wrappingScrollDirection = (screen.ScrollTarget.GetFloorIntY() < (terrain->GetBitmap()->h / 2)) ? 1 : -1; + screen.Offset.SetY(screen.Offset.GetY() - (static_cast(terrain->GetBitmap()->h * wrappingScrollDirection))); + screen.SeamCrossCount[Axes::Y] += wrappingScrollDirection; } + screen.TargetYWrapped = false; } Vector oldOffset(screen.Offset); diff --git a/Source/Managers/FrameMan.cpp b/Source/Managers/FrameMan.cpp index 0ef3948918..1b656d9d75 100644 --- a/Source/Managers/FrameMan.cpp +++ b/Source/Managers/FrameMan.cpp @@ -9,6 +9,8 @@ #include "CameraMan.h" #include "ConsoleMan.h" #include "SettingsMan.h" +#include "MovableMan.h" +#include "ThreadMan.h" #include "UInputMan.h" #include "SLTerrain.h" @@ -239,6 +241,13 @@ void FrameMan::Update() { for (int playerScreen = 0; playerScreen < screenCount; ++playerScreen) { g_CameraMan.Update(playerScreen); } + + // Queue our MO renders + Vector targetPos{}; + g_MovableMan.Draw(nullptr, targetPos); + + // TODO_MULTITHREAD + // g_MovableMan.DrawHUD(nullptr, targetPos, playerScreen); } void FrameMan::ResetSplitScreens(bool hSplit, bool vSplit) { @@ -637,8 +646,6 @@ int FrameMan::SharedDrawLine(BITMAP* bitmap, const Vector& start, const Vector& int dotHeight = drawDot ? dot->h : 0; int dotWidth = drawDot ? dot->w : 0; - // acquire_bitmap(bitmap); - // Just make the alt the same color as the main one if no one was specified if (altColor == 0) { altColor = color; @@ -712,8 +719,6 @@ int FrameMan::SharedDrawLine(BITMAP* bitmap, const Vector& start, const Vector& } } - // release_bitmap(bitmap); - // Return the end phase state of the skipping return skipped; } @@ -792,7 +797,7 @@ void FrameMan::Draw() { std::list screenRelativeEffects; std::list screenRelativeGlowBoxes; - const Activity* pActivity = g_ActivityMan.GetActivity(); + const Activity* pActivity = g_ThreadMan.GetDrawableGameState().m_Activity; for (int playerScreen = 0; playerScreen < screenCount; ++playerScreen) { screenRelativeEffects.clear(); @@ -851,8 +856,11 @@ void FrameMan::Draw() { // Get only the scene-relative post effects that affect this player's screen if (pActivity) { + // TODO_MULTITHREAD +#ifndef MULTITHREAD_SIM_AND_RENDER g_PostProcessMan.GetPostScreenEffectsWrapped(targetPos, drawScreen->w, drawScreen->h, screenRelativeEffects, pActivity->GetTeamOfPlayer(pActivity->PlayerOfScreen(playerScreen))); g_PostProcessMan.GetGlowAreasWrapped(targetPos, drawScreen->w, drawScreen->h, screenRelativeGlowBoxes); +#endif if (IsInMultiplayerMode()) { g_PostProcessMan.SetNetworkPostEffectsList(playerScreen, screenRelativeEffects); @@ -917,17 +925,13 @@ void FrameMan::Draw() { } if (g_ActivityMan.IsInActivity()) { + // TODO_MULTITHREAD: add post processing effects to RenderableGameState g_PostProcessMan.PostProcess(); } // Draw the performance stats and console on top of everything. g_PerformanceMan.Draw(m_BackBuffer32.get()); g_ConsoleMan.Draw(m_BackBuffer32.get()); - -#ifdef DEBUG_BUILD - // Draw scene seam - vline(m_BackBuffer8.get(), 0, 0, g_SceneMan.GetSceneHeight(), 5); -#endif } void FrameMan::DrawScreenText(int playerScreen, AllegroBitmap playerGUIBitmap) { @@ -959,16 +963,11 @@ void FrameMan::DrawScreenText(int playerScreen, AllegroBitmap playerGUIBitmap) { textPosY += 12; } - // Draw info text when in MOID or material layer draw mode + // Draw info text when in material layer draw mode switch (g_SceneMan.GetLayerDrawMode()) { case g_LayerTerrainMatter: - GetSmallFont()->DrawAligned(&playerGUIBitmap, GetPlayerScreenWidth() / 2, GetPlayerScreenHeight() - 12, "Viewing terrain material layer\nHit Ctrl+M to cycle modes", GUIFont::Centre, GUIFont::Bottom); + GetSmallFont()->DrawAligned(&playerGUIBitmap, GetPlayerScreenWidth() / 2, GetPlayerScreenHeight() - 12, "Viewing terrain material layer\nHit Ctrl+M to toggle", GUIFont::Centre, GUIFont::Bottom); break; -#ifdef DRAW_MOID_LAYER - case g_LayerMOID: - GetSmallFont()->DrawAligned(&playerGUIBitmap, GetPlayerScreenWidth() / 2, GetPlayerScreenHeight() - 12, "Viewing MovableObject ID layer\nHit Ctrl+M to cycle modes", GUIFont::Centre, GUIFont::Bottom); - break; -#endif default: break; } @@ -1026,7 +1025,7 @@ void FrameMan::DrawWorldDump(bool drawForScenePreview) const { Vector targetPos(0, 0); // Draw objects - draw_sprite(m_WorldDumpBuffer.get(), g_SceneMan.GetMOColorBitmap(), 0, 0); + g_MovableMan.Draw(m_WorldDumpBuffer.get()); // Draw post-effects g_PostProcessMan.GetPostScreenEffectsWrapped(targetPos, worldBitmapWidth, worldBitmapHeight, postEffectsList, -1); diff --git a/Source/Managers/FrameMan.h b/Source/Managers/FrameMan.h index b6da8605ca..ac763b391d 100644 --- a/Source/Managers/FrameMan.h +++ b/Source/Managers/FrameMan.h @@ -70,7 +70,7 @@ namespace RTE { BITMAP* GetOverlayBitmap32() const { return m_OverlayBitmap32.get(); } #pragma endregion -#pragma region Split - Screen Handling +#pragma region Split-Screen Handling /// Gets whether the screen is split horizontally across the screen, ie as two splitscreens one above the other. /// @return Whether or not screen has a horizontal split. bool GetHSplit() const { return m_HSplit; } diff --git a/Source/Managers/LuaMan.cpp b/Source/Managers/LuaMan.cpp index da19f55554..354bf6d9e6 100644 --- a/Source/Managers/LuaMan.cpp +++ b/Source/Managers/LuaMan.cpp @@ -93,7 +93,9 @@ void LuaStateWrapper::Initialize() { .def("FileEOF", &LuaStateWrapper::FileEOF), luabind::def("DeleteEntity", &LuaAdaptersUtility::DeleteEntity, luabind::adopt(_1)), // NOT a member function, so adopting _1 instead of the _2 for the first param, since there's no "this" pointer!! - luabind::def("LERP", &LERP), + luabind::def("Lerp", (float (*)(float, float, float, float, float)) & Lerp), + luabind::def("Lerp", (Vector(*)(float, float, Vector, Vector, float)) & Lerp), + luabind::def("Lerp", (Matrix(*)(float, float, Matrix, Matrix, float)) & Lerp), luabind::def("EaseIn", &EaseIn), luabind::def("EaseOut", &EaseOut), luabind::def("EaseInOut", &EaseInOut), diff --git a/Source/Managers/LuaMan.h b/Source/Managers/LuaMan.h index 6b0997678f..d51715a2ba 100644 --- a/Source/Managers/LuaMan.h +++ b/Source/Managers/LuaMan.h @@ -325,7 +325,7 @@ namespace RTE { const std::unordered_map GetScriptTimings() const; #pragma endregion -#pragma region File I / O Handling +#pragma region File I/O Handling /// Returns a vector of all the directories in path, which is relative to the working directory. /// @param path Directory path relative to the working directory. /// @return A vector of the directories in path. diff --git a/Source/Managers/MovableMan.cpp b/Source/Managers/MovableMan.cpp index 0c629275f4..2956747f21 100644 --- a/Source/Managers/MovableMan.cpp +++ b/Source/Managers/MovableMan.cpp @@ -3,6 +3,7 @@ #include "PrimitiveMan.h" #include "PostProcessMan.h" #include "PerformanceMan.h" +#include "ThreadMan.h" #include "PresetMan.h" #include "AEmitter.h" #include "AHuman.h" @@ -1146,20 +1147,6 @@ void MovableMan::RegisterAlarmEvent(const AlarmEvent& newEvent) { m_AddedAlarmEvents.push_back(newEvent); } -void MovableMan::RedrawOverlappingMOIDs(MovableObject* pOverlapsThis) { - for (std::deque::iterator aIt = m_Actors.begin(); aIt != m_Actors.end(); ++aIt) { - (*aIt)->DrawMOIDIfOverlapping(pOverlapsThis); - } - - for (std::deque::iterator iIt = m_Items.begin(); iIt != m_Items.end(); ++iIt) { - (*iIt)->DrawMOIDIfOverlapping(pOverlapsThis); - } - - for (std::deque::iterator parIt = m_Particles.begin(); parIt != m_Particles.end(); ++parIt) { - (*parIt)->DrawMOIDIfOverlapping(pOverlapsThis); - } -} - void callLuaFunctionOnMORecursive(MovableObject* mo, const std::string& functionName, const std::vector& functionEntityArguments, const std::vector& functionLiteralArguments, const std::vector& functionObjectArguments) { if (MOSRotating* mosr = dynamic_cast(mo)) { for (auto attachablrItr = mosr->GetAttachableList().begin(); attachablrItr != mosr->GetAttachableList().end();) { @@ -1268,18 +1255,7 @@ void MovableMan::Update() { m_SimUpdateFrameNumber++; - // ---TEMP --- - // These are here for multithreaded AI, but will be unnecessary when multithreaded-sim-and-render is in! - // Clear the MO color layer only if this is a drawn update - if (g_TimerMan.DrawnSimUpdate()) { - g_SceneMan.ClearMOColorLayer(); - } - - // If this is the first sim update since a drawn one, then clear the post effects - if (g_TimerMan.SimUpdatesSinceDrawn() == 0) { - g_PostProcessMan.ClearScenePostEffects(); - } - // ---TEMP--- + g_PostProcessMan.ClearScenePostEffects(); // Reset the draw HUD roster line settings m_SortTeamRoster[Activity::TeamOne] = false; @@ -1641,15 +1617,9 @@ void MovableMan::Update() { //////////////////////////////////////////////////////////////////////// // Draw the MO matter and IDs to their layers for next frame m_DrawMOIDsTask = g_ThreadMan.GetPriorityThreadPool().submit([this]() { - UpdateDrawMOIDs(g_SceneMan.GetMOIDBitmap()); + UpdateDrawMOIDs(); }); - //////////////////////////////////////////////////////////////////// - // Draw the MO colors ONLY if this is a drawn update! - - if (g_TimerMan.DrawnSimUpdate()) - Draw(g_SceneMan.GetMOColorBitmap()); - // Sort team rosters if necessary { if (m_SortTeamRoster[Activity::TeamOne]) @@ -1676,13 +1646,11 @@ void MovableMan::Travel() { g_PerformanceMan.StartPerformanceMeasurement(PerformanceMan::ActorsTravel); for (auto aIt = m_Actors.begin(); aIt != m_Actors.end(); ++aIt) { - if (!((*aIt)->IsUpdated())) { - (*aIt)->ApplyForces(); - (*aIt)->PreTravel(); - (*aIt)->Travel(); - (*aIt)->PostTravel(); - } (*aIt)->NewFrame(); + (*aIt)->ApplyForces(); + (*aIt)->PreTravel(); + (*aIt)->Travel(); + (*aIt)->PostTravel(); } g_PerformanceMan.StopPerformanceMeasurement(PerformanceMan::ActorsTravel); } @@ -1692,13 +1660,11 @@ void MovableMan::Travel() { ZoneScopedN("Items Travel"); for (auto iIt = m_Items.begin(); iIt != m_Items.end(); ++iIt) { - if (!((*iIt)->IsUpdated())) { - (*iIt)->ApplyForces(); - (*iIt)->PreTravel(); - (*iIt)->Travel(); - (*iIt)->PostTravel(); - } (*iIt)->NewFrame(); + (*iIt)->ApplyForces(); + (*iIt)->PreTravel(); + (*iIt)->Travel(); + (*iIt)->PostTravel(); } } @@ -1708,12 +1674,10 @@ void MovableMan::Travel() { g_PerformanceMan.StartPerformanceMeasurement(PerformanceMan::ParticlesTravel); for (auto parIt = m_Particles.begin(); parIt != m_Particles.end(); ++parIt) { - if (!((*parIt)->IsUpdated())) { - (*parIt)->ApplyForces(); - (*parIt)->PreTravel(); - (*parIt)->Travel(); - (*parIt)->PostTravel(); - } + (*parIt)->ApplyForces(); + (*parIt)->PreTravel(); + (*parIt)->Travel(); + (*parIt)->PostTravel(); (*parIt)->NewFrame(); } g_PerformanceMan.StopPerformanceMeasurement(PerformanceMan::ParticlesTravel); @@ -1805,7 +1769,7 @@ void MovableMan::VerifyMOIDIndex() { } } -void MovableMan::UpdateDrawMOIDs(BITMAP* pTargetBitmap) { +void MovableMan::UpdateDrawMOIDs() { ZoneScoped; /////////////////////////////////////////////////// @@ -1827,7 +1791,7 @@ void MovableMan::UpdateDrawMOIDs(BITMAP* pTargetBitmap) { m_ContiguousActorIDs[actor] = actorID++; if (!actor->IsSetToDelete()) { actor->UpdateMOID(m_MOIDIndex); - actor->Draw(pTargetBitmap, Vector(), g_DrawMOID, true); + actor->Draw(nullptr, Vector(), g_DrawMOID, true); currentMOID = m_MOIDIndex.size(); } else { actor->SetAsNoID(); @@ -1837,7 +1801,7 @@ void MovableMan::UpdateDrawMOIDs(BITMAP* pTargetBitmap) { for (MovableObject* item: m_Items) { if (!item->IsSetToDelete()) { item->UpdateMOID(m_MOIDIndex); - item->Draw(pTargetBitmap, Vector(), g_DrawMOID, true); + item->Draw(nullptr, Vector(), g_DrawMOID, true); currentMOID = m_MOIDIndex.size(); } else { item->SetAsNoID(); @@ -1847,7 +1811,7 @@ void MovableMan::UpdateDrawMOIDs(BITMAP* pTargetBitmap) { for (MovableObject* particle: m_Particles) { if (!particle->IsSetToDelete()) { particle->UpdateMOID(m_MOIDIndex); - particle->Draw(pTargetBitmap, Vector(), g_DrawMOID, true); + particle->Draw(nullptr, Vector(), g_DrawMOID, true); currentMOID = m_MOIDIndex.size(); } else { particle->SetAsNoID(); diff --git a/Source/Managers/MovableMan.h b/Source/Managers/MovableMan.h index 43bef364bf..e33a5f0525 100644 --- a/Source/Managers/MovableMan.h +++ b/Source/Managers/MovableMan.h @@ -446,12 +446,6 @@ namespace RTE { /// @return Whether enabled or not. bool IsMOSubtractionEnabled() { return m_MOSubtractionEnabled; } - /// Forces all objects potnetially overlapping a specific MO to re-draw - /// this MOID representations onto the MOID bitmap. - /// @param pOverlapsThis A pointer to the MO to check for overlaps against. Ownerhip is NOT - /// transferred. - void RedrawOverlappingMOIDs(MovableObject* pOverlapsThis); - /// Updates the state of this MovableMan. Supposed to be done every frame. void Update(); @@ -461,10 +455,9 @@ namespace RTE { /// @param targetPos The absolute position of the target bitmap's upper left corner in the scene. void DrawMatter(BITMAP* pTargetBitmap, Vector& targetPos); - /// Updates the MOIDs of all current MOs and draws their ID's to a BITMAP - /// of choice. If there are more than 255 MO's to draw, some will not be. - /// @param pTargetBitmap A pointer to a BITMAP to draw on. - void UpdateDrawMOIDs(BITMAP* pTargetBitmap); + /// Updates the MOIDs of all current MOs and draws their ID's to the grid + /// @param A pointer to a BITMAP to draw on. + void UpdateDrawMOIDs(); /// Draws this MovableMan's current graphical representation to a /// BITMAP of choice. diff --git a/Source/Managers/PerformanceMan.cpp b/Source/Managers/PerformanceMan.cpp index 34b2a46f79..810a4d3b1a 100644 --- a/Source/Managers/PerformanceMan.cpp +++ b/Source/Managers/PerformanceMan.cpp @@ -26,8 +26,6 @@ void PerformanceMan::Clear() { m_AdvancedPerfStats = true; m_Sample = 0; m_SimUpdateTimer = nullptr; - m_MSPSUs.clear(); - m_MSPSUAverage = 0; m_MSPFs.clear(); m_MSPFAverage = 0; m_MSPUs.clear(); @@ -89,22 +87,33 @@ uint64_t PerformanceMan::GetPerformanceCounterAverage(PerformanceCounters counte return totalPerformanceMeasurement / c_Average; } -void PerformanceMan::CalculateTimeAverage(std::deque& timeMeasurements, float& avgResult, float newTimeMeasurement) const { +void PerformanceMan::CalculateTimeAverage(std::deque& timeMeasurements, std::atomic& avgResult, float newTimeMeasurement) const { + static std::mutex mut; + std::lock_guard lock(mut); + timeMeasurements.emplace_back(newTimeMeasurement); while (timeMeasurements.size() > c_MSPAverageSampleSize) { timeMeasurements.pop_front(); } - avgResult = 0; + float averageTime = 0; for (const float& timeMeasurement: timeMeasurements) { - avgResult += timeMeasurement; + averageTime += timeMeasurement; } - avgResult /= static_cast(timeMeasurements.size()); + averageTime /= static_cast(timeMeasurements.size()); + + avgResult = averageTime; } -void PerformanceMan::UpdateMSPF(long long measuredUpdateTime, long long measuredDrawTime) { +void PerformanceMan::UpdateMSPU(long long measuredUpdateTime) { CalculateTimeAverage(m_MSPUs, m_MSPUAverage, static_cast(measuredUpdateTime / 1000)); +} + +void PerformanceMan::UpdateMSPD(long long measuredDrawTime) { CalculateTimeAverage(m_MSPDs, m_MSPDAverage, static_cast(measuredDrawTime / 1000)); - CalculateTimeAverage(m_MSPFs, m_MSPFAverage, static_cast((measuredUpdateTime + measuredDrawTime) / 1000)); +} + +void PerformanceMan::UpdateMSPF(long long measuredFrameTime) { + CalculateTimeAverage(m_MSPFs, m_MSPFAverage, static_cast(measuredFrameTime / 1000)); } void PerformanceMan::Draw(BITMAP* bitmapToDrawTo) { @@ -115,11 +124,11 @@ void PerformanceMan::Draw(BITMAP* bitmapToDrawTo) { char str[128]; float fps = 1.0F / (m_MSPFAverage / 1000.0F); - float ups = 1.0F / (m_MSPSUAverage / 1000.0F); + float ups = 1.0F / std::max(m_MSPUAverage / 1000.0F, g_TimerMan.GetDeltaTimeSecs() / g_TimerMan.GetTimeScale()); std::snprintf(str, sizeof(str), "FPS: %.0f | UPS: %.0f", fps, ups); guiFont->DrawAligned(&drawBitmap, c_StatsOffsetX, c_StatsHeight, str, GUIFont::Left); - std::snprintf(str, sizeof(str), "Frame: %.1fms | Update: %.1fms | Draw: %.1fms", m_MSPFAverage, m_MSPUAverage, m_MSPDAverage); + std::snprintf(str, sizeof(str), "Draw: %.1fms | Update: %.1fms", m_MSPDAverage.load(), m_MSPUAverage.load()); guiFont->DrawAligned(&drawBitmap, c_StatsOffsetX, c_StatsHeight + 10, str, GUIFont::Left); std::snprintf(str, sizeof(str), "Time Scale: x%.2f ([1]-, [2]+, [Ctrl+1]Rst) | Sim Speed: x%.2f", g_TimerMan.GetTimeScale(), g_TimerMan.GetSimSpeed()); @@ -141,10 +150,13 @@ void PerformanceMan::Draw(BITMAP* bitmapToDrawTo) { std::snprintf(str, sizeof(str), "MOIDs: %i", g_MovableMan.GetMOIDCount()); guiFont->DrawAligned(&drawBitmap, c_StatsOffsetX, c_StatsHeight + 70, str, GUIFont::Left); + // TODO_MULTITHREAD +#ifndef MULTITHREAD_SIM_AND_RENDER if (int totalPlayingChannelCount = 0, realPlayingChannelCount = 0; g_AudioMan.GetPlayingChannelCount(&totalPlayingChannelCount, &realPlayingChannelCount)) { std::snprintf(str, sizeof(str), "Sound Channels: %d / %d Real | %d / %d Virtual", realPlayingChannelCount, g_AudioMan.GetTotalRealChannelCount(), totalPlayingChannelCount - realPlayingChannelCount, g_AudioMan.GetTotalVirtualChannelCount()); } guiFont->DrawAligned(&drawBitmap, c_StatsOffsetX, c_StatsHeight + 80, str, GUIFont::Left); +#endif if (!m_SortedScriptTimings.empty()) { std::snprintf(str, sizeof(str), "Lua scripts taking the most time to call Update() this frame:"); diff --git a/Source/Managers/PerformanceMan.h b/Source/Managers/PerformanceMan.h index fac21aa757..fa9abfac06 100644 --- a/Source/Managers/PerformanceMan.h +++ b/Source/Managers/PerformanceMan.h @@ -73,13 +73,13 @@ namespace RTE { /// @param showGraphs Whether to show the performance graphs or not. void ShowAdvancedPerformanceStats(bool showGraphs = true) { m_AdvancedPerfStats = showGraphs; } - /// Gets the average of the MSPU reading buffer, calculated each update. - /// @return The average value of the MSPU reading buffer. - float GetMSPSUAverage() const { return m_MSPSUAverage; } - /// Gets the average of the MSPF reading buffer, calculated each frame. /// @return The average value of the MSPF reading buffer. float GetMSPFAverage() const { return m_MSPFAverage; } + + /// Gets the average of the MSPU reading buffer, calculated each frame. + /// @return The average value of the MSPF reading buffer. + float GetMSPUAverage() const { return m_MSPUAverage; } #pragma endregion #pragma region Performance Counter Handling @@ -100,27 +100,21 @@ namespace RTE { #pragma endregion #pragma region Concrete Methods - /// Clears current performance timings. - void ResetPerformanceTimings() { - m_MSPSUs.clear(); - m_MSPFs.clear(); - m_MSPUs.clear(); - m_MSPDs.clear(); - } - /// Resets the sim update timer. void ResetSimUpdateTimer() const { m_SimUpdateTimer->Reset(); } - /// Updates the frame time measurements and recalculates the averages. Supposed to be done every game loop iteration. + /// Updates the draw time measurements and recalculates the averages. Supposed to be done every draw iteration. /// @param measuredUpdateTime The total sim update time measured in the game loop iteration. /// @param measuredDrawTime The total draw time measured in the game loop iteration. - void UpdateMSPF(long long measuredUpdateTime, long long measuredDrawTime); + void UpdateMSPD(long long measuredDrawTime); + + /// Updates the sim time measurements and recalculates the averages. Supposed to be done every simulation update. + /// @param measuredUpdateTime The total sim update time measured in the game loop iteration. + void UpdateMSPU(long long measuredUpdateTime); - /// Updates the individual sim update time measurements and recalculates the average. Supposed to be done every sim update. - void UpdateMSPSU() { - CalculateTimeAverage(m_MSPSUs, m_MSPSUAverage, static_cast(m_SimUpdateTimer->GetElapsedRealTimeMS())); - m_SimUpdateTimer->Reset(); - } + /// Updates the full frametime measurements and recalculates the averages. Supposed to be done every simulation update. + /// @param measuredUpdateTime The total frame time measured in the game loop iteration. + void UpdateMSPF(long long measuredFrameTime); /// Draws the performance stats to the screen. /// @param bitmapToDrawTo The BITMAP to draw the performance stats to. @@ -149,19 +143,17 @@ namespace RTE { bool m_ShowPerfStats; //!< Whether to show performance stats on screen or not. bool m_AdvancedPerfStats; //!< Whether to show performance graphs on screen or not. - int m_Sample; //!< Sample counter. + std::atomic m_Sample; //!< Sample counter. std::unique_ptr m_SimUpdateTimer; //!< Timer for measuring milliseconds per sim update for performance stats readings. - std::deque m_MSPSUs; //!< History log of single update time measurements in milliseconds, for averaging the results. In milliseconds. - std::deque m_MSPFs; //!< History log total frame time measurements in milliseconds, for averaging the results. - std::deque m_MSPUs; //!< History log of frame update time measurements in milliseconds, for averaging the results. In milliseconds. - std::deque m_MSPDs; //!< History log of frame draw time measurements in milliseconds, for averaging the results. + std::deque m_MSPFs; //!< History log total render-thread frame time measurements in milliseconds, for averaging the results. + std::deque m_MSPUs; //!< History log of update time measurements in milliseconds, for averaging the results. In milliseconds. + std::deque m_MSPDs; //!< History log of draw time measurements in milliseconds, for averaging the results. - float m_MSPSUAverage; //!< The average of the MSPSU reading buffer, calculated each sim update. - float m_MSPFAverage; //!< The average of the MSPF reading buffer, calculated each game loop iteration. - float m_MSPUAverage; //!< The average of the MSPU reading buffer, calculated each game loop iteration. - float m_MSPDAverage; //!< The average of the MSPD reading buffer, calculated each game loop iteration. + std::atomic m_MSPFAverage; //!< The average of the MSPF reading buffer, calculated each game loop iteration. + std::atomic m_MSPUAverage; //!< The average of the MSPU reading buffer, calculated each game loop iteration. + std::atomic m_MSPDAverage; //!< The average of the MSPD reading buffer, calculated each game loop iteration. int m_CurrentPing; //!< Current ping value to display on screen. @@ -190,7 +182,7 @@ namespace RTE { /// @param timeMeasurements The deque of time measurements to store the new measurement in and to recalculate the average with. /// @param avgResult The variable the recalculated average should be stored in. /// @param newTimeMeasurement The new time measurement to store. - void CalculateTimeAverage(std::deque& timeMeasurements, float& avgResult, float newTimeMeasurement) const; + void CalculateTimeAverage(std::deque& timeMeasurements, std::atomic& avgResult, float newTimeMeasurement) const; /// Draws the performance graphs to the screen. This will be called by Draw() if advanced performance stats are enabled. void DrawPeformanceGraphs(AllegroBitmap& bitmapToDrawTo); diff --git a/Source/Managers/PostProcessMan.cpp b/Source/Managers/PostProcessMan.cpp index 6771bfbee7..6eb9c91e48 100644 --- a/Source/Managers/PostProcessMan.cpp +++ b/Source/Managers/PostProcessMan.cpp @@ -195,12 +195,14 @@ void PostProcessMan::AdjustEffectsPosToPlayerScreen(int playerScreen, BITMAP* ta } void PostProcessMan::RegisterPostEffect(const Vector& effectPos, BITMAP* effect, size_t hash, int strength, float angle) { + // TODO_MULTITHREAD +#ifndef MULTITHREAD_SIM_AND_RENDER // These effects get applied when there's a drawn frame that followed one or more sim updates. // They are not only registered on drawn sim updates; flashes and stuff could be missed otherwise if they occur on undrawn sim updates. - - if (effect && g_TimerMan.SimUpdatesSinceDrawn() >= 0) { + if (effect) { m_PostSceneEffects.push_back(PostEffect(effectPos, effect, hash, strength, angle)); } +#endif } bool PostProcessMan::GetPostScreenEffectsWrapped(const Vector& boxPos, int boxWidth, int boxHeight, std::list& effectsList, int team) { @@ -250,7 +252,7 @@ BITMAP* PostProcessMan::GetTempEffectBitmap(BITMAP* bitmap) const { void PostProcessMan::RegisterGlowDotEffect(const Vector& effectPos, DotGlowColor color, int strength) { // These effects only apply only once per drawn sim update, and only on the first frame drawn after one or more sim updates - if (color != NoDot && g_TimerMan.DrawnSimUpdate() && g_TimerMan.SimUpdatesSinceDrawn() >= 0) { + if (color != NoDot) { RegisterPostEffect(effectPos, GetDotGlowEffect(color), GetDotGlowEffectHash(color), strength); } } @@ -409,11 +411,14 @@ void PostProcessMan::PostProcess() { m_PostProcessShader->Use(); + // TODO_MULTITHREAD: add post processing effects to RenderableGameState +#ifndef MULTITHREAD_SIM_AND_RENDER DrawDotGlowEffects(); DrawPostScreenEffects(); // Clear the effects list for this frame - m_PostScreenEffects.clear(); + ClearScreenPostEffects(); +#endif } void PostProcessMan::DrawDotGlowEffects() { diff --git a/Source/Managers/PostProcessMan.h b/Source/Managers/PostProcessMan.h index cabc93bfd9..5b7d7c31f6 100644 --- a/Source/Managers/PostProcessMan.h +++ b/Source/Managers/PostProcessMan.h @@ -112,11 +112,7 @@ namespace RTE { /// Registers a specific IntRect to be post-processed and have special pixel colors lit up by glow effects in it. /// @param glowArea The IntRect to have special color pixels glow in, in scene coordinates. - void RegisterGlowArea(const IntRect& glowArea) { - if (g_TimerMan.DrawnSimUpdate() && g_TimerMan.SimUpdatesSinceDrawn() >= 0) { - m_GlowAreas.push_back(glowArea); - } - } + void RegisterGlowArea(const IntRect& glowArea) { m_GlowAreas.push_back(glowArea); } /// Creates an IntRect and registers it to be post-processed and have special pixel colors lit up by glow effects in it. /// @param center The center of the IntRect. diff --git a/Source/Managers/SceneMan.cpp b/Source/Managers/SceneMan.cpp index ccd2d2a422..a2bedb68ff 100644 --- a/Source/Managers/SceneMan.cpp +++ b/Source/Managers/SceneMan.cpp @@ -4,6 +4,7 @@ #include "SceneMan.h" #include "PresetMan.h" #include "FrameMan.h" +#include "ThreadMan.h" #include "ActivityMan.h" #include "UInputMan.h" #include "CameraMan.h" @@ -52,8 +53,7 @@ void SceneMan::Clear() { m_PlaceObjects = true; m_PlaceUnits = true; m_pCurrentScene = nullptr; - m_pMOColorLayer = nullptr; - m_pMOIDLayer = nullptr; + m_MOIDsGrid = SpatialPartitionGrid(); m_pDebugLayer = nullptr; m_LayerDrawMode = g_LayerNormal; @@ -156,21 +156,8 @@ int SceneMan::LoadScene(Scene* pNewScene, bool placeObjects, bool placeUnits) { // m_pCurrentScene->GetTerrain()->CleanAir(); - // Re-create the MoveableObject:s color SceneLayer - delete m_pMOColorLayer; - BITMAP* pBitmap = create_bitmap_ex(8, GetSceneWidth(), GetSceneHeight()); - clear_to_color(pBitmap, g_MaskColor); - m_pMOColorLayer = new SceneLayerTracked(); - m_pMOColorLayer->Create(pBitmap, true, Vector(), m_pCurrentScene->WrapsX(), m_pCurrentScene->WrapsY(), Vector(1.0, 1.0)); - pBitmap = 0; - - // Re-create the MoveableObject:s ID SceneLayer - delete m_pMOIDLayer; - pBitmap = create_bitmap_ex(c_MOIDLayerBitDepth, GetSceneWidth(), GetSceneHeight()); - clear_to_color(pBitmap, g_NoMOID); - m_pMOIDLayer = new SceneLayerTracked(); - m_pMOIDLayer->Create(pBitmap, false, Vector(), m_pCurrentScene->WrapsX(), m_pCurrentScene->WrapsY(), Vector(1.0, 1.0)); - pBitmap = 0; + // Re-initialize our threadman so it sets up our renderable game state properly + g_ThreadMan.Initialize(); const int cellSize = 20; m_MOIDsGrid = SpatialPartitionGrid(GetSceneWidth(), GetSceneHeight(), cellSize); @@ -178,15 +165,15 @@ int SceneMan::LoadScene(Scene* pNewScene, bool placeObjects, bool placeUnits) { // Create the Debug SceneLayer if (m_DrawRayCastVisualizations || m_DrawPixelCheckVisualizations) { delete m_pDebugLayer; - pBitmap = create_bitmap_ex(8, GetSceneWidth(), GetSceneHeight()); + BITMAP* pBitmap = create_bitmap_ex(8, GetSceneWidth(), GetSceneHeight()); clear_to_color(pBitmap, g_MaskColor); m_pDebugLayer = new SceneLayer(); m_pDebugLayer->Create(pBitmap, true, Vector(), m_pCurrentScene->WrapsX(), m_pCurrentScene->WrapsY(), Vector(1.0, 1.0)); pBitmap = nullptr; } - // Finally draw the ID:s of the MO:s to the MOID layers for the first time - g_MovableMan.UpdateDrawMOIDs(m_pMOIDLayer->GetBitmap()); + // Finally draw the IDs of the MOs to the MOID grid for the first time + g_MovableMan.UpdateDrawMOIDs(); g_NetworkServer.LockScene(false); g_NetworkServer.ResetScene(); @@ -306,8 +293,6 @@ void SceneMan::Destroy() { delete m_pCurrentScene; delete m_pDebugLayer; - delete m_pMOIDLayer; - delete m_pMOColorLayer; delete m_pUnseenRevealSound; destroy_bitmap(m_pOrphanSearchBitmap); @@ -381,36 +366,11 @@ SLTerrain* SceneMan::GetTerrain() { return nullptr; } -BITMAP* SceneMan::GetMOColorBitmap() const { - return m_pMOColorLayer->GetBitmap(); -} - BITMAP* SceneMan::GetDebugBitmap() const { RTEAssert(m_pDebugLayer, "Tried to get debug bitmap but debug layer doesn't exist. Note that the debug layer is only created under certain circumstances."); return m_pDebugLayer->GetBitmap(); } -BITMAP* SceneMan::GetMOIDBitmap() const { - return m_pMOIDLayer->GetBitmap(); -} - -// TEMP! -bool SceneMan::MOIDClearCheck() { - BITMAP* pMOIDMap = m_pMOIDLayer->GetBitmap(); - int badMOID = g_NoMOID; - for (int y = 0; y < pMOIDMap->h; ++y) { - for (int x = 0; x < pMOIDMap->w; ++x) { - if ((badMOID = _getpixel(pMOIDMap, x, y)) != g_NoMOID) { - g_FrameMan.SaveBitmapToPNG(pMOIDMap, "MOIDCheck"); - g_FrameMan.SaveBitmapToPNG(m_pMOColorLayer->GetBitmap(), "MOIDCheck"); - RTEAbort("Bad MOID of MO detected: " + g_MovableMan.GetMOFromID(badMOID)->GetPresetName()); - return false; - } - } - } - return true; -} - unsigned char SceneMan::GetTerrMatter(int pixelX, int pixelY) { RTEAssert(m_pCurrentScene, "Trying to get terrain matter before there is a scene or terrain!"); @@ -444,25 +404,8 @@ MOID SceneMan::GetMOIDPixel(int pixelX, int pixelY, int ignoreTeam) { m_pDebugLayer->SetPixel(pixelX, pixelY, 5); } - if (pixelX < 0 || pixelX >= m_pMOIDLayer->GetBitmap()->w || pixelY < 0 || pixelY >= m_pMOIDLayer->GetBitmap()->h) { - return g_NoMOID; - } - -#ifdef DRAW_MOID_LAYER - MOID moid = getpixel(m_pMOIDLayer->GetBitmap(), pixelX, pixelY); -#else const std::vector& moidList = m_MOIDsGrid.GetMOIDsAtPosition(pixelX, pixelY, ignoreTeam, true); MOID moid = g_MovableMan.GetMOIDPixel(pixelX, pixelY, moidList); -#endif - - if (g_SettingsMan.SimplifiedCollisionDetection()) { - if (moid != ColorKeys::g_NoMOID && moid != ColorKeys::g_MOIDMaskColor) { - const MOSprite* mo = dynamic_cast(g_MovableMan.GetMOFromID(moid)); - return (mo && !mo->GetTraveling()) ? moid : ColorKeys::g_NoMOID; - } else { - return ColorKeys::g_NoMOID; - } - } return moid; } @@ -485,8 +428,6 @@ void SceneMan::LockScene() { // RTEAssert(!m_pCurrentScene->IsLocked(), "Hey, locking already locked scene!"); if (m_pCurrentScene && !m_pCurrentScene->IsLocked()) { m_pCurrentScene->Lock(); - m_pMOColorLayer->LockBitmaps(); - m_pMOIDLayer->LockBitmaps(); } } @@ -494,8 +435,6 @@ void SceneMan::UnlockScene() { // RTEAssert(m_pCurrentScene->IsLocked(), "Hey, unlocking already unlocked scene!"); if (m_pCurrentScene && m_pCurrentScene->IsLocked()) { m_pCurrentScene->Unlock(); - m_pMOColorLayer->UnlockBitmaps(); - m_pMOIDLayer->UnlockBitmaps(); } } @@ -504,34 +443,20 @@ bool SceneMan::SceneIsLocked() const { return m_pCurrentScene->IsLocked(); } -void SceneMan::RegisterDrawing(const BITMAP* bitmap, int moid, int left, int top, int right, int bottom) { - if (m_pMOColorLayer && m_pMOColorLayer->GetBitmap() == bitmap) { - m_pMOColorLayer->RegisterDrawing(left, top, right, bottom); - } else if (m_pMOIDLayer && m_pMOIDLayer->GetBitmap() == bitmap) { -#ifdef DRAW_MOID_LAYER - m_pMOIDLayer->RegisterDrawing(left, top, right, bottom); -#else - const MovableObject* mo = g_MovableMan.GetMOFromID(moid); - if (mo) { - IntRect rect(left, top, right, bottom); - m_MOIDsGrid.Add(rect, *mo); - } -#endif +void SceneMan::RegisterMOIDDrawing(int moid, int left, int top, int right, int bottom) { + if (const MovableObject* mo = g_MovableMan.GetMOFromID(moid)) { + m_MOIDsGrid.Add(IntRect(left, top, right, bottom), *mo); } } -void SceneMan::RegisterDrawing(const BITMAP* bitmap, int moid, const Vector& center, float radius) { +void SceneMan::RegisterMOIDDrawing(int moid, const Vector& center, float radius) { if (radius != 0.0F) { - RegisterDrawing(bitmap, moid, static_cast(std::floor(center.m_X - radius)), static_cast(std::floor(center.m_Y - radius)), static_cast(std::floor(center.m_X + radius)), static_cast(std::floor(center.m_Y + radius))); + RegisterMOIDDrawing(moid, center.m_X - radius, center.m_Y - radius, center.m_X + radius, center.m_Y + radius); } } void SceneMan::ClearAllMOIDDrawings() { -#ifdef DRAW_MOID_LAYER - m_pMOIDLayer->ClearBitmap(g_NoMOID); -#else m_MOIDsGrid.Reset(); -#endif } bool SceneMan::WillPenetrate(const int posX, @@ -2046,28 +1971,9 @@ MOID SceneMan::CastMORay(const Vector& start, const Vector& ray, MOID ignoreMOID // Detect MOIDs hitMOID = GetMOIDPixel(intPos[X], intPos[Y], ignoreTeam); if (hitMOID != g_NoMOID && hitMOID != ignoreMOID && g_MovableMan.GetRootMOID(hitMOID) != ignoreMOID) { -#ifdef DRAW_MOID_LAYER // Unnecessary with non-drawn MOIDs - they'll be culled out at the spatial partition level. - // Check if we're supposed to ignore the team of what we hit - if (ignoreTeam != Activity::NoTeam) { - const MovableObject* pHitMO = g_MovableMan.GetMOFromID(hitMOID); - pHitMO = pHitMO ? pHitMO->GetRootParent() : 0; - // Yup, we are supposed to ignore this! - if (pHitMO && pHitMO->IgnoresTeamHits() && pHitMO->GetTeam() == ignoreTeam) { - ; - } else { - // Save last ray pos - s_LastRayHitPos.SetXY(intPos[X], intPos[Y]); - return hitMOID; - } - } - // Legit hit - else -#endif - { - // Save last ray pos - s_LastRayHitPos.SetXY(intPos[X], intPos[Y]); - return hitMOID; - } + // Save last ray pos + s_LastRayHitPos.SetXY(intPos[X], intPos[Y]); + return hitMOID; } // Detect terrain hits @@ -2256,16 +2162,6 @@ float SceneMan::CastObstacleRay(const Vector& start, const Vector& ray, Vector& MovableObject* pHitMO = g_MovableMan.GetMOFromID(checkMOID); if (pHitMO) { checkMOID = pHitMO->GetRootID(); -#ifdef DRAW_MOID_LAYER // Unnecessary with non-drawn MOIDs - they'll be culled out at the spatial partition level. \ - // Check if we're supposed to ignore the team of what we hit - if (ignoreTeam != Activity::NoTeam) { - pHitMO = pHitMO->GetRootParent(); - // We are indeed supposed to ignore this object because of its ignoring of its specific team - if (pHitMO && pHitMO->IgnoresTeamHits() && pHitMO->GetTeam() == ignoreTeam) { - checkMOID = g_NoMOID; - } - } -#endif } } @@ -2370,19 +2266,19 @@ Vector SceneMan::MovePointToGround(const Vector& from, int maxAltitude, int accu return groundPoint; } -bool SceneMan::IsWithinBounds(const int pixelX, const int pixelY, const int margin) { +bool SceneMan::IsWithinBounds(const int pixelX, const int pixelY, const int margin) const { if (m_pCurrentScene) return m_pCurrentScene->GetTerrain()->IsWithinBounds(pixelX, pixelY, margin); return false; } -bool SceneMan::ForceBounds(int& posX, int& posY) { +bool SceneMan::ForceBounds(int& posX, int& posY) const { RTEAssert(m_pCurrentScene, "Trying to access scene before there is one!"); return m_pCurrentScene->GetTerrain()->ForceBounds(posX, posY); } -bool SceneMan::ForceBounds(Vector& pos) { +bool SceneMan::ForceBounds(Vector& pos) const { RTEAssert(m_pCurrentScene, "Trying to access scene before there is one!"); int posX = std::floor(pos.m_X); @@ -2396,12 +2292,12 @@ bool SceneMan::ForceBounds(Vector& pos) { return wrapped; } -bool SceneMan::WrapPosition(int& posX, int& posY) { +bool SceneMan::WrapPosition(int& posX, int& posY) const { RTEAssert(m_pCurrentScene, "Trying to access scene before there is one!"); return m_pCurrentScene->GetTerrain()->WrapPosition(posX, posY); } -bool SceneMan::WrapPosition(Vector& pos) { +bool SceneMan::WrapPosition(Vector& pos) const { RTEAssert(m_pCurrentScene, "Trying to access scene before there is one!"); int posX = std::floor(pos.m_X); @@ -2415,7 +2311,7 @@ bool SceneMan::WrapPosition(Vector& pos) { return wrapped; } -Vector SceneMan::SnapPosition(const Vector& pos, bool snap) { +Vector SceneMan::SnapPosition(const Vector& pos, bool snap) const { Vector snappedPos = pos; if (snap) { @@ -2426,7 +2322,7 @@ Vector SceneMan::SnapPosition(const Vector& pos, bool snap) { return snappedPos; } -Vector SceneMan::ShortestDistance(Vector pos1, Vector pos2, bool checkBounds) { +Vector SceneMan::ShortestDistance(Vector pos1, Vector pos2, bool checkBounds) const { if (!m_pCurrentScene) return Vector(); @@ -2462,7 +2358,7 @@ Vector SceneMan::ShortestDistance(Vector pos1, Vector pos2, bool checkBounds) { return distance; } -float SceneMan::ShortestDistanceX(float val1, float val2, bool checkBounds, int direction) { +float SceneMan::ShortestDistanceX(float val1, float val2, bool checkBounds, int direction) const { if (!m_pCurrentScene) return 0; @@ -2498,7 +2394,7 @@ float SceneMan::ShortestDistanceX(float val1, float val2, bool checkBounds, int return distance; } -float SceneMan::ShortestDistanceY(float val1, float val2, bool checkBounds, int direction) { +float SceneMan::ShortestDistanceY(float val1, float val2, bool checkBounds, int direction) const { if (!m_pCurrentScene) return 0; @@ -2631,6 +2527,17 @@ int SceneMan::WrapBox(const Box& wrapBox, std::list& outputList) { return addedTimes; } +Vector SceneMan::Lerp(float scaleStart, float scaleEnd, Vector startPos, Vector endPos, float progressScalar) const { + if (!m_pCurrentScene) { + return RTE::Lerp(scaleStart, scaleEnd, startPos, endPos, progressScalar); + } + + Vector startToEnd = ShortestDistance(startPos, endPos); + Vector lerped = startPos + (startToEnd * RTE::Lerp(scaleStart, scaleEnd, 0.0F, 1.0F, progressScalar)); + WrapPosition(lerped); + return lerped; +} + bool SceneMan::AddSceneObject(SceneObject* sceneObject) { bool result = false; if (sceneObject) { @@ -2651,20 +2558,19 @@ bool SceneMan::AddSceneObject(SceneObject* sceneObject) { void SceneMan::Update(int screenId) { ZoneScoped; - if (!m_pCurrentScene) { + SLTerrain* terrain = g_ThreadMan.GetDrawableGameState().m_Terrain; + if (!terrain) { return; } m_LastUpdatedScreen = screenId; const Vector& offset = g_CameraMan.GetOffset(screenId); - m_pMOColorLayer->SetOffset(offset); - m_pMOIDLayer->SetOffset(offset); + if (m_pDebugLayer) { m_pDebugLayer->SetOffset(offset); } - SLTerrain* terrain = m_pCurrentScene->GetTerrain(); terrain->SetOffset(offset); terrain->Update(); @@ -2681,6 +2587,8 @@ void SceneMan::Update(int screenId) { unseenLayer->SetOffset(offset); } + // TODO_MULTITHREAD this should be updated in a sim update, not render update + // Is this even necessary, though? if (m_CleanTimer.GetElapsedSimTimeMS() > CLEANAIRINTERVAL) { terrain->CleanAir(); m_CleanTimer.Reset(); @@ -2688,12 +2596,11 @@ void SceneMan::Update(int screenId) { } void SceneMan::Draw(BITMAP* targetBitmap, BITMAP* targetGUIBitmap, const Vector& targetPos, bool skipBackgroundLayers, bool skipTerrain) { - ZoneScoped; - - if (!m_pCurrentScene) { + SLTerrain* terrain = g_ThreadMan.GetDrawableGameState().m_Terrain; + if (!terrain) { return; } - SLTerrain* terrain = m_pCurrentScene->GetTerrain(); + // Set up the target box to draw to on the target bitmap, if it is larger than the scene in either dimension. Box targetBox(Vector(), static_cast(targetBitmap->w), static_cast(targetBitmap->h)); @@ -2711,11 +2618,6 @@ void SceneMan::Draw(BITMAP* targetBitmap, BITMAP* targetGUIBitmap, const Vector& terrain->SetLayerToDraw(SLTerrain::LayerType::MaterialLayer); terrain->Draw(targetBitmap, targetBox); break; -#ifdef DRAW_MOID_LAYER - case LayerDrawMode::g_LayerMOID: - m_pMOIDLayer->Draw(targetBitmap, targetBox); - break; -#endif default: if (!skipBackgroundLayers) { for (std::list::reverse_iterator backgroundLayer = m_pCurrentScene->GetBackLayers().rbegin(); backgroundLayer != m_pCurrentScene->GetBackLayers().rend(); ++backgroundLayer) { @@ -2726,7 +2628,13 @@ void SceneMan::Draw(BITMAP* targetBitmap, BITMAP* targetGUIBitmap, const Vector& terrain->SetLayerToDraw(SLTerrain::LayerType::BackgroundLayer); terrain->Draw(targetBitmap, targetBox); } - m_pMOColorLayer->Draw(targetBitmap, targetBox); + + g_ThreadMan.SetRenderTarget(targetBitmap); + g_ThreadMan.SetRenderOffset(targetPos); + float proportionOfTime = g_TimerMan.GetPredictedProportionOfUpdateCompletion(); + for (auto& renderFunc: g_ThreadMan.GetDrawableGameState().m_RenderQueue) { + renderFunc(proportionOfTime); + } if (!skipTerrain) { terrain->SetLayerToDraw(SLTerrain::LayerType::ForegroundLayer); @@ -2740,11 +2648,14 @@ void SceneMan::Draw(BITMAP* targetBitmap, BITMAP* targetGUIBitmap, const Vector& } bool shouldDrawHUD = !g_FrameMan.IsHudDisabled(m_LastUpdatedScreen); + + // TODO_MULTITHREAD +#ifndef MULTITHREAD_SIM_AND_RENDER if (shouldDrawHUD) { g_MovableMan.DrawHUD(targetGUIBitmap, targetPos, m_LastUpdatedScreen); } - g_PrimitiveMan.DrawPrimitives(m_LastUpdatedScreen, targetGUIBitmap, targetPos); +#endif if (shouldDrawHUD) { g_ActivityMan.GetActivity()->DrawGUI(targetGUIBitmap, targetPos, m_LastUpdatedScreen); @@ -2771,17 +2682,19 @@ void SceneMan::Draw(BITMAP* targetBitmap, BITMAP* targetGUIBitmap, const Vector& if (m_pDebugLayer) { m_pDebugLayer->Draw(targetBitmap, targetBox); + m_pDebugLayer->ClearBitmap(g_MaskColor); } break; } -} -void SceneMan::ClearMOColorLayer() { - m_pMOColorLayer->ClearBitmap(g_MaskColor); - if (m_pDebugLayer) { - m_pDebugLayer->ClearBitmap(g_MaskColor); - } + // #ifdef DEBUG_BUILD + // Draw scene seams + LinePrimitive horizontal(0, Vector(0, 0), Vector(GetSceneWidth(), 0), ColorKeys::g_GreenColor); + LinePrimitive vertical(0, Vector(0, 0), Vector(0, GetSceneHeight()), ColorKeys::g_GreenColor); + horizontal.Draw(targetBitmap, targetPos); + vertical.Draw(targetBitmap, targetPos); + // #endif } void SceneMan::ClearSeenPixels() { diff --git a/Source/Managers/SceneMan.h b/Source/Managers/SceneMan.h index 1a0618f048..d5b17f6b7a 100644 --- a/Source/Managers/SceneMan.h +++ b/Source/Managers/SceneMan.h @@ -39,11 +39,7 @@ namespace RTE { // Different modes to draw the SceneLayers in enum LayerDrawMode { g_LayerNormal = 0, - g_LayerTerrainMatter, - -#ifdef DRAW_MOID_LAYER - g_LayerMOID -#endif + g_LayerTerrainMatter }; #define SCENEGRIDSIZE 24 @@ -203,29 +199,11 @@ namespace RTE { /// @return A pointer to the SLTerrain. Ownership is NOT transferred! SLTerrain* GetTerrain(); - /// Gets the bitmap of the intermediary collection SceneLayer that all - /// MovableObject:s draw themselves onto before it itself gets drawn onto - /// the screen back buffer. - /// @return A BITMAP pointer to the MO bitmap. Ownership is NOT transferred! - BITMAP* GetMOColorBitmap() const; - /// Gets the bitmap of the SceneLayer that debug graphics is drawn onto. /// Will only return valid BITMAP if building with DEBUG_BUILD. /// @return A BITMAP pointer to the debug bitmap. Ownership is NOT transferred! BITMAP* GetDebugBitmap() const; - /// Gets the bitmap of the SceneLayer that all MovableObject:s draw thir - /// current (for the frame only!) MOID's onto. - /// @return A BITMAP pointer to the MO bitmap. Ownership is NOT transferred! - BITMAP* GetMOIDBitmap() const; - - // TEMP! - /// Makes sure the MOID bitmap layer is completely of NoMOID color. - /// If found to be not, dumps MOID layer and the FG actor color layer for - /// debugging. - /// @return Was it clear? - bool MOIDClearCheck(); - /// Gets the current drawing mode of the SceneMan. /// @return The current layer draw mode, see the LayerDrawMode enumeration for the /// different possible mode settings. @@ -290,24 +268,21 @@ namespace RTE { /// @return Whether the entire scene is currently locked or not. bool SceneIsLocked() const; - /// Registers an area to be drawn upon, so it can be tracked and cleared later. - /// @param bitmap The bitmap being drawn upon. - /// @param moid The MOID, if we're drawing MOIDs. + /// Registers a moid being drawn, so it can be added to our spatial partitioning grid. + /// @param moid The MOID. /// @param left The left boundary of the draw area. /// @param top The top boundary of the drawn area. /// @param right The right boundary of the draw area. /// @param bottom The bottom boundary of the draw area. - void RegisterDrawing(const BITMAP* bitmap, int moid, int left, int top, int right, int bottom); + void RegisterMOIDDrawing(int moid, int left, int top, int right, int bottom); - /// Registers an area of to be drawn upon, so it can be tracked and cleared later. - /// @param bitmap The bitmap being drawn upon. - /// @param moid The MOID, if we're drawing MOIDs. + /// Registers a moid being drawn, so it can be added to our spatial partitioning grid. + /// @param moid The MOID. /// @param center The centre position of the drawn area. /// @param radius The radius of the drawn area. - void RegisterDrawing(const BITMAP* bitmap, int moid, const Vector& center, float radius); + void RegisterMOIDDrawing(int moid, const Vector& center, float radius); - /// Clears all registered drawn areas of the MOID layer to the g_NoMOID - /// color and clears the registrations too. Should be done each sim update. + /// Clears all registered as drawn MOIDs, clearing our spatial partitioning grid. void ClearAllMOIDDrawings(); /// Test whether a pixel of the scene would be knocked loose and @@ -784,37 +759,37 @@ namespace RTE { /// @param pixelX Int coordinates. /// @param pixelY A margin /// @return Whether within bounds or not, considering wrapping. - bool IsWithinBounds(const int pixelX, const int pixelY, const int margin = 0); + bool IsWithinBounds(const int pixelX, const int pixelY, const int margin = 0) const; /// Wraps or bounds a position coordinate if it is off bounds of the /// Scene, depending on the wrap settings of this Scene. /// @param posX The X and Y coordinates of the position to wrap, if needed. /// @return Whether wrapping was performed or not. (Does not report on bounding) - bool ForceBounds(int& posX, int& posY); + bool ForceBounds(int& posX, int& posY) const; /// Wraps or bounds a position coordinate if it is off bounds of the /// Scene, depending on the wrap settings of this Scene. /// @param pos The vector coordinates of the position to wrap, if needed. /// @return Whether wrapping was performed or not. (Does not report on bounding) - bool ForceBounds(Vector& pos); + bool ForceBounds(Vector& pos) const; /// Only wraps a position coordinate if it is off bounds of the Scene /// and wrapping in the corresponding axes are turned on. /// @param posX The X and Y coordinates of the position to wrap, if needed. /// @return Whether wrapping was performed or not. - bool WrapPosition(int& posX, int& posY); + bool WrapPosition(int& posX, int& posY) const; /// Only wraps a position coordinate if it is off bounds of the Scene /// and wrapping in the corresponding axes are turned on. /// @param pos The vector coordinates of the position to wrap, if needed. /// @return Whether wrapping was performed or not. - bool WrapPosition(Vector& pos); + bool WrapPosition(Vector& pos) const; /// Returns a position snapped to the current scene grid. /// @param pos The vector coordinates of the position to snap. /// @param snap Whether to actually snap or not. This is useful for cleaner toggle code. (default: true) /// @return The new snapped position. - Vector SnapPosition(const Vector& pos, bool snap = true); + Vector SnapPosition(const Vector& pos, bool snap = true) const; /// Calculates the shortest distance between two points in scene /// coordinates, taking into account all wrapping and out of bounds of the @@ -825,7 +800,7 @@ namespace RTE { /// wrap them if they are. /// @return The resulting vector screen shows the shortest distance, spanning over /// wrapping borders etc. Basically the ideal pos2 - pos1. - Vector ShortestDistance(Vector pos1, Vector pos2, bool checkBounds = false); + Vector ShortestDistance(Vector pos1, Vector pos2, bool checkBounds = false) const; /// Calculates the shortest distance between two x values in scene /// coordinates, taking into account all wrapping and out of bounds of the @@ -839,7 +814,7 @@ namespace RTE { /// will be ignored. /// @return The resulting X value screen shows the shortest distance, spanning over /// wrapping borders etc. Basically the ideal val2 - val1. - float ShortestDistanceX(float val1, float val2, bool checkBounds = false, int direction = 0); + float ShortestDistanceX(float val1, float val2, bool checkBounds = false, int direction = 0) const; /// Calculates the shortest distance between two Y values in scene /// coordinates, taking into account all wrapping and out of bounds of the @@ -853,7 +828,7 @@ namespace RTE { /// will be ignored. /// @return The resulting Y value screen shows the shortest distance, spanning over /// wrapping borders etc. Basically the ideal val2 - val1. - float ShortestDistanceY(float val1, float val2, bool checkBounds = false, int direction = 0); + float ShortestDistanceY(float val1, float val2, bool checkBounds = false, int direction = 0) const; /// Tells whether a point on the scene is obscured by MOID or Terrain /// non-air material. @@ -901,6 +876,9 @@ namespace RTE { /// no wrapping need was detected, up to 4 possible (if straddling both seams) int WrapBox(const Box& wrapBox, std::list& outputList); + /// Lerp between two positions, in a wrapping-safe manner + Vector Lerp(float scaleStart, float scaleEnd, Vector startPos, Vector endPos, float progressScalar) const; + /// Takes any scene object and adds it to the scene in the appropriate way. /// If it's a TerrainObject, then it gets applied to the terrain, if it's /// an MO, it gets added to the correct type group in MovableMan. @@ -922,9 +900,6 @@ namespace RTE { /// is located. void Draw(BITMAP* targetBitmap, BITMAP* targetGUIBitmap, const Vector& targetPos = Vector(), bool skipBackgroundLayers = false, bool skipTerrain = false); - /// Clears the color MO layer. Should be done every frame. - void ClearMOColorLayer(); - /// Clears the list of pixels on the unseen map that have been revealed. void ClearSeenPixels(); @@ -971,8 +946,7 @@ namespace RTE { // Current scene being used Scene* m_pCurrentScene; - // Color MO layer - SceneLayerTracked* m_pMOColorLayer; + // MovableObject ID layer SceneLayerTracked* m_pMOIDLayer; // A spatial partitioning grid of MOIDs, used to optimize collision and distance queries diff --git a/Source/Managers/SettingsMan.cpp b/Source/Managers/SettingsMan.cpp index b167caba26..07078ef629 100644 --- a/Source/Managers/SettingsMan.cpp +++ b/Source/Managers/SettingsMan.cpp @@ -45,7 +45,6 @@ void SettingsMan::Clear() { m_DisableLuaJIT = false; m_RecommendedMOIDCount = 512; - m_SimplifiedCollisionDetection = false; m_SceneBackgroundAutoScaleMode = 1; m_DisableFactionBuyMenuThemes = false; m_DisableFactionBuyMenuThemeCursors = false; @@ -161,7 +160,6 @@ int SettingsMan::ReadProperty(const std::string_view& propName, Reader& reader) MatchProperty("DefaultSceneName", { reader >> g_SceneMan.m_DefaultSceneName; }); MatchProperty("DisableLuaJIT", { reader >> m_DisableLuaJIT; }); MatchProperty("RecommendedMOIDCount", { reader >> m_RecommendedMOIDCount; }); - MatchProperty("SimplifiedCollisionDetection", { reader >> m_SimplifiedCollisionDetection; }); MatchProperty("SceneBackgroundAutoScaleMode", { SetSceneBackgroundAutoScaleMode(std::stoi(reader.ReadPropValue())); }); MatchProperty("DisableFactionBuyMenuThemes", { reader >> m_DisableFactionBuyMenuThemes; }); MatchProperty("DisableFactionBuyMenuThemeCursors", { reader >> m_DisableFactionBuyMenuThemeCursors; }); @@ -306,7 +304,6 @@ int SettingsMan::Save(Writer& writer) const { writer.NewLine(false); writer.NewPropertyWithValue("DisableLuaJIT", m_DisableLuaJIT); writer.NewPropertyWithValue("RecommendedMOIDCount", m_RecommendedMOIDCount); - writer.NewPropertyWithValue("SimplifiedCollisionDetection", m_SimplifiedCollisionDetection); writer.NewPropertyWithValue("SceneBackgroundAutoScaleMode", m_SceneBackgroundAutoScaleMode); writer.NewPropertyWithValue("DisableFactionBuyMenuThemes", m_DisableFactionBuyMenuThemes); writer.NewPropertyWithValue("DisableFactionBuyMenuThemeCursors", m_DisableFactionBuyMenuThemeCursors); diff --git a/Source/Managers/SettingsMan.h b/Source/Managers/SettingsMan.h index 60466cce95..18cce10842 100644 --- a/Source/Managers/SettingsMan.h +++ b/Source/Managers/SettingsMan.h @@ -52,10 +52,6 @@ namespace RTE { /// @return Recommended MOID count. int RecommendedMOIDCount() const { return m_RecommendedMOIDCount; } - /// Gets whether simplified collision detection (reduced MOID layer sampling) is enabled. - /// @return Whether simplified collision detection is enabled or not. - bool SimplifiedCollisionDetection() const { return m_SimplifiedCollisionDetection; } - /// Gets the Scene background layer auto-scaling mode. /// @return The Scene background layer auto-scaling mode. 0 for off, 1 for fit screen dimensions and 2 for always upscaled to x2. int GetSceneBackgroundAutoScaleMode() const { return m_SceneBackgroundAutoScaleMode; } @@ -389,7 +385,6 @@ namespace RTE { bool m_DisableLuaJIT; //!< Whether to disable LuaJIT or not. Disabling will skip loading the JIT library entirely as just setting 'jit.off()' seems to have no visible effect. int m_RecommendedMOIDCount; //!< Recommended max MOID's before removing actors from scenes. - bool m_SimplifiedCollisionDetection; //!< Whether simplified collision detection (reduced MOID layer sampling) is enabled. int m_SceneBackgroundAutoScaleMode; //!< Scene background layer auto-scaling mode. 0 for off, 1 for fit screen dimensions and 2 for always upscaled to x2. bool m_DisableFactionBuyMenuThemes; //!< Whether faction BuyMenu theme support is disabled. bool m_DisableFactionBuyMenuThemeCursors; //!< Whether custom cursor support in faction BuyMenu themes is disabled. diff --git a/Source/Managers/ThreadMan.cpp b/Source/Managers/ThreadMan.cpp index 2e0d60d777..a5fe493a01 100644 --- a/Source/Managers/ThreadMan.cpp +++ b/Source/Managers/ThreadMan.cpp @@ -1,10 +1,28 @@ #include "ThreadMan.h" +#include "Scene.h" +#include "SceneLayer.h" +#include "Vector.h" + +#include "ActivityMan.h" +#include "SceneMan.h" +#include "MovableMan.h" +#include "TimerMan.h" + +#include "tracy/Tracy.hpp" + using namespace RTE; +RenderableGameState::RenderableGameState() { +} + +RenderableGameState::~RenderableGameState() { +} + +const std::string ThreadMan::m_ClassName = "ThreadMan"; + ThreadMan::ThreadMan() { Clear(); - Create(); } ThreadMan::~ThreadMan() { @@ -12,14 +30,83 @@ ThreadMan::~ThreadMan() { } void ThreadMan::Clear() { + m_GameStateModifiable.reset(); + m_GameStateDrawable.reset(); + m_NewSimFrame = false; + m_SimFunctions.clear(); + m_RenderTarget = nullptr; + m_RenderOffset.Reset(); m_PriorityThreadPool.reset(); m_BackgroundThreadPool.reset(std::thread::hardware_concurrency() / 2); } -int ThreadMan::Create() { +int ThreadMan::Initialize() { + Clear(); + m_GameStateModifiable.reset(new RenderableGameState()); + m_GameStateDrawable.reset(new RenderableGameState()); + return 0; } +void ThreadMan::Update() { + if (m_NewSimFrame) { + std::lock_guard lock(m_GameStateCopyMutex); + + std::swap(m_GameStateDrawable, m_GameStateModifiable); + m_NewSimFrame = false; + } +} + +void ThreadMan::TransferSimStateToRenderer() { + ZoneScoped; + + std::lock_guard lock(m_GameStateCopyMutex); + + // Copy game state into our current buffer + // TODO_MULTITHREAD: Remove as much of this as possible... + if (g_ActivityMan.IsInActivity()) { + m_GameStateModifiable->m_Activity = g_ActivityMan.GetActivity(); + m_GameStateModifiable->m_Terrain = g_SceneMan.GetScene()->GetTerrain(); + // m_GameStateModifiable->m_Activity.reset(dynamic_cast(g_ActivityMan.GetActivity()->Clone())); + // m_GameStateModifiable->m_Terrain.reset(dynamic_cast(g_SceneMan.GetScene()->GetTerrain()->Clone())); + } + + std::swap(m_GameStateModifiable->m_RenderQueue, m_SimRenderQueue); + m_SimRenderQueue.clear(); + + // TODO_MULTITHREAD: add post processing effects to RenderableGameState + // Clear the effects list for this frame + // m_PostScreenEffects.clear(); + + // Mark that we have a new sim frame, so we can swap rendered game state at the start of the new render + g_TimerMan.MarkNewSimUpdateComplete(); + m_NewSimFrame = true; +} + +void ThreadMan::QueueInSimulationThread(std::function funcToRun) { + std::lock_guard lock(m_SimFunctionMutex); + + // If we're already queued, don't bother + auto itr = std::find_if(m_SimFunctions.begin(), m_SimFunctions.end(), + [&funcToRun](std::function& func) { + return funcToRun.target() == func.target(); + }); + + if (itr != m_SimFunctions.end()) { + return; + } + + m_SimFunctions.push_back(funcToRun); +} + +void ThreadMan::RunSimulationThreadFunctions() { + std::lock_guard lock(m_SimFunctionMutex); + for (auto& func: m_SimFunctions) { + func(); + } + m_SimFunctions.clear(); +} + void ThreadMan::Destroy() { Clear(); } diff --git a/Source/Managers/ThreadMan.h b/Source/Managers/ThreadMan.h index 88a93aaefa..990321520d 100644 --- a/Source/Managers/ThreadMan.h +++ b/Source/Managers/ThreadMan.h @@ -2,53 +2,103 @@ /// Header file for the ThreadMan class. /// Author(s): -/// Inclusions of header files #include "Singleton.h" + +#include "Activity.h" +#include "SLTerrain.h" + +#include + #define g_ThreadMan ThreadMan::Instance() #include "BS_thread_pool.hpp" namespace RTE { + struct RenderableGameState { + RenderableGameState(); + ~RenderableGameState(); + + SLTerrain* m_Terrain = nullptr; + // std::unique_ptr m_Terrain = nullptr; + Activity* m_Activity = nullptr; + // std::unique_ptr m_Activity = std::make_unique(); + std::vector> m_RenderQueue; + }; - /// The centralized singleton manager of all threads. - class ThreadMan : - public Singleton { - - /// Public member variable, method and friend function declarations + /// The manager for any long-running background threads (i.e the simulation thread), and managing communication between threads. + class ThreadMan : public Singleton { public: /// Constructor method used to instantiate a ThreadMan object in system /// memory. Create() should be called before using the object. ThreadMan(); - /// Makes the TimerMan object ready for use. - void Initialize(){}; - /// Destructor method used to clean up a ThreadMan object before deletion /// from system memory. virtual ~ThreadMan(); - /// Makes the ThreadMan object ready for use. - /// @return An error return value signaling sucess or any particular failure. - /// Anything below 0 is an error signal. - virtual int Create(); - - /// Resets the entire ThreadMan, including its inherited members, to - /// their default settings or values. - virtual void Reset() { Clear(); } + /// Makes the TimerMan object ready for use. + virtual int Initialize(); - /// Destroys and resets (through Clear()) the ThreadMan object. void Destroy(); + /// Updates the state of this ThreadMan. Supposed to be done every render frame. + void Update(); + + /// Notifies us that the next sim update will be drawn, and transfers the + /// required information from the current simulation update to a drawable format. + void TransferSimStateToRenderer(); + + /// Tells us whether or not there's a new sim frame waiting to be drawn. + bool NewSimFrameIsReady() { return m_NewSimFrame; } + + /// Get a game state we can safely read from the render thread. + const RenderableGameState& GetDrawableGameState() const { return *m_GameStateDrawable; } + + std::vector>& GetSimRenderQueue() { return m_SimRenderQueue; } + + /// Queue a function that will be ran in the sim thread + void QueueInSimulationThread(std::function funcToRun); + + /// Run all queued functions for the sim thread + void RunSimulationThreadFunctions(); + + BITMAP* GetRenderTarget() const { return m_RenderTarget; } + void SetRenderTarget(BITMAP* newRenderTarget) { m_RenderTarget = newRenderTarget; } + + Vector GetRenderOffset() const { return m_RenderOffset; } + void SetRenderOffset(Vector newRenderOffset) { m_RenderOffset = newRenderOffset; } + + virtual const std::string& GetClassName() const { return m_ClassName; } + BS::thread_pool& GetPriorityThreadPool() { return m_PriorityThreadPool; } BS::thread_pool& GetBackgroundThreadPool() { return m_BackgroundThreadPool; } - /// Protected member variable and method declarations protected: - /// Private member variable and method declarations + // Member variables + static const std::string m_ClassName; + private: - /// Clears all the member variables of this ThreadMan, effectively - /// resetting the members of this abstraction level only. + // Because of the hacky way we've converted a single-threaded immediate-mode CPU rendered game into a multi-threaded queued renderer + // (and hopefully one day soon, GPU rendered!) + // We need to store a render target and offset that we apply to the draws here. + BITMAP* m_RenderTarget; + Vector m_RenderOffset; + + std::unique_ptr m_GameStateModifiable; //!< Current game state game state that sim can update (owned) + std::unique_ptr m_GameStateDrawable; //!< Stable game state that we are drawing (owned) + std::mutex m_GameStateCopyMutex; //!< Mutex to ensure we can't swap our rendering game state while it's being copied to. + + // We store this here, as well as in the game state copy mutex. + // The sim thread uses this queue, and clears it at the start of each new sim thread + // It then copies it into the RenderableGameState once it's finished with it. + std::vector> m_SimRenderQueue; + + std::atomic m_NewSimFrame; //!< Whether we have a new sim frame ready to draw. + + std::vector> m_SimFunctions; + std::mutex m_SimFunctionMutex; //!< Mutex to ensure that functions are added to the sim thread safely + void Clear(); // Disallow the use of some implicit methods. diff --git a/Source/Managers/TimerMan.cpp b/Source/Managers/TimerMan.cpp index 4aeaa1cb61..ca4925b2fa 100644 --- a/Source/Managers/TimerMan.cpp +++ b/Source/Managers/TimerMan.cpp @@ -3,6 +3,8 @@ #include "Constants.h" #include "PerformanceMan.h" #include "SettingsMan.h" +#include "ThreadMan.h" +#include "ConsoleMan.h" #include #include @@ -19,11 +21,12 @@ void TimerMan::Clear() { m_DeltaTime = 0; m_DeltaTimeS = 0.0F; m_DeltaBuffer.clear(); - m_SimUpdatesSinceDrawn = -1; - m_DrawnSimUpdate = false; m_SimSpeed = 1.0F; m_TimeScale = 1.0F; m_SimPaused = false; + m_LatestUpdateStartTime = 0; + m_LatestUpdateEndTime = 0; + m_UpdateTrueDeltaTimeTicks = 0; } void TimerMan::Initialize() { @@ -48,6 +51,19 @@ float TimerMan::GetAIDeltaTimeSecs() const { return m_DeltaTimeS * static_cast(g_SettingsMan.GetAIUpdateInterval()); } +float TimerMan::GetPredictedProportionOfUpdateCompletion() const { + // We might be rendering the last sim frame even while a new one is waiting for us + // If that's the case, return 1.0F, so we don't "jump" back and start rendering the beginning of the last frame again + if (g_ThreadMan.NewSimFrameIsReady()) { + return 1.0F; + } + + // Our full update time is the max of either how long our update actually took, or how often we update + double fullUpdateTime = std::max(static_cast(m_UpdateTrueDeltaTimeTicks), m_DeltaTime / static_cast(m_TimeScale)); + double timeSinceLastUpdate = m_RealTimeTicks - m_LatestUpdateEndTime; + return std::min(timeSinceLastUpdate / fullUpdateTime, 1.0); +} + void TimerMan::ResetTime() { m_StartTime = std::chrono::steady_clock::now(); @@ -55,27 +71,30 @@ void TimerMan::ResetTime() { m_SimAccumulator = 0; m_SimTimeTicks = 0; m_SimUpdateCount = 0; - m_SimUpdatesSinceDrawn = -1; - m_DrawnSimUpdate = false; m_TimeScale = 1.0F; + m_LatestUpdateStartTime = 0; + m_LatestUpdateEndTime = 0; + m_UpdateTrueDeltaTimeTicks = 0; } void TimerMan::UpdateSim() { + m_LatestUpdateStartTime = std::chrono::duration_cast(std::chrono::steady_clock::now() - m_StartTime).count(); + m_SimSpeed = std::min(GetDeltaTimeMS() / g_PerformanceMan.GetMSPUAverage(), m_TimeScale); + if (TimeForSimUpdate()) { // Transfer ticks from the accumulator to the sim time ticks. m_SimAccumulator -= m_DeltaTime; m_SimTimeTicks += m_DeltaTime; ++m_SimUpdateCount; - ++m_SimUpdatesSinceDrawn; - - // If after deducting the DeltaTime from the accumulator, there is not enough time for another DeltaTime, then flag this as the last sim update before the frame is drawn. - m_DrawnSimUpdate = !TimeForSimUpdate(); - } else { - m_DrawnSimUpdate = true; } } +void TimerMan::MarkNewSimUpdateComplete() { + m_LatestUpdateEndTime = std::chrono::duration_cast(std::chrono::steady_clock::now() - m_StartTime).count(); + m_UpdateTrueDeltaTimeTicks = m_LatestUpdateEndTime - m_LatestUpdateStartTime; +} + void TimerMan::Update() { long long prevTime = m_RealTimeTicks; m_RealTimeTicks = std::chrono::duration_cast(std::chrono::steady_clock::now() - m_StartTime).count(); @@ -85,22 +104,19 @@ void TimerMan::Update() { RTEAssert(timeIncrease > 0, "It seems your CPU is giving bad timing data to the game, this is known to happen on some multi-core processors. This may be fixed by downloading the latest CPU drivers from AMD or Intel."); + m_DrawDeltaTimeS = static_cast(timeIncrease) / 1000.0F; + // If not paused, add the new time difference to the sim accumulator if (!m_SimPaused) { m_SimAccumulator += static_cast(static_cast(timeIncrease) * m_TimeScale); } - float maxPossibleSimSpeed = GetDeltaTimeMS() / std::max(g_PerformanceMan.GetMSPSUAverage(), std::numeric_limits::epsilon()); + float maxPossibleSimSpeed = GetDeltaTimeMS() / std::max(g_PerformanceMan.GetMSPUAverage(), std::numeric_limits::epsilon()); // Make sure we don't get runaway behind schedule - m_SimAccumulator = std::min(m_SimAccumulator, m_DeltaTime + static_cast(m_DeltaTime * maxPossibleSimSpeed)); + m_SimAccumulator = std::min(m_SimAccumulator.load(), m_DeltaTime + static_cast(m_DeltaTime * maxPossibleSimSpeed)); RTEAssert(m_SimAccumulator >= 0, "Negative sim time accumulator?!"); - // Reset the counter since the last drawn update. Set it negative since we're counting full pure sim updates and this will be incremented to 0 on next SimUpdate. - if (m_DrawnSimUpdate) { - m_SimUpdatesSinceDrawn = -1; - } - m_SimSpeed = std::min(maxPossibleSimSpeed, GetTimeScale()); } diff --git a/Source/Managers/TimerMan.h b/Source/Managers/TimerMan.h index b1f272b36f..920fb8fbd8 100644 --- a/Source/Managers/TimerMan.h +++ b/Source/Managers/TimerMan.h @@ -2,6 +2,7 @@ #include "Singleton.h" +#include #include #include @@ -48,15 +49,6 @@ namespace RTE { /// @return Whether there is enough sim time to do a physics update. bool TimeForSimUpdate() const { return m_SimAccumulator >= m_DeltaTime; } - /// Tells whether the current simulation update will be drawn in a frame. Use this to check if it is necessary to draw purely graphical things during the sim update. - /// @return Whether this is the last sim update before a frame with its results will appear. - bool DrawnSimUpdate() const { return m_DrawnSimUpdate; } - - /// Tells how many sim updates have been performed since the last one that ended up being a drawn frame. - /// If negative, it means no sim updates have happened, and a same frame will be drawn again. - /// @return The number of pure sim updates that have happened since the last drawn. - int SimUpdatesSinceDrawn() const { return m_SimUpdatesSinceDrawn; } - /// Gets the simulation speed over real time. /// @return The value of the simulation speed over real time. float GetSimSpeed() const { return m_SimSpeed; } @@ -104,9 +96,17 @@ namespace RTE { m_DeltaTimeS = static_cast(m_DeltaTime) / static_cast(m_TicksPerSecond); } + /// Gets how long the last draw took, in seconds + /// @return How long the last draw took, in seconds. + float GetDrawDeltaTimeSecs() const { return m_DrawDeltaTimeS; } + + /// Gets how long the last draw took, in ms + /// @return How long the last draw took, in ms. + float GetDrawDeltaTimeMS() const { return m_DrawDeltaTimeS * 1000.0F; } + /// Gets the current fixed delta time of the simulation updates, in ms. /// @return The current fixed delta time that the simulation should be updating with, in ms. - float GetDeltaTimeMS() const { return m_DeltaTimeS * 1000; } + float GetDeltaTimeMS() const { return m_DeltaTimeS * 1000.0F; } /// Gets the current fixed delta time of the simulation updates, in seconds. /// @return The current fixed delta time that the simulation should be updating with, in seconds. @@ -126,6 +126,15 @@ namespace RTE { m_DeltaTimeS = newDelta; m_DeltaTime = static_cast(m_DeltaTimeS * static_cast(m_TicksPerSecond)); } + + /// Returns the true update delta time, or how long has actually passed since the last update, in real time. + /// @return The true update dt, in ms. + float GetTrueUpdateDeltaTimeMS() const { return m_UpdateTrueDeltaTimeTicks / 1000.0F; }; + + /// Returns a predicted proportion of how far the current simulation update is through completion, based on the last update time. + /// Return 0.0F if a new update just started, and 1.0F if the time since this update started is equal to or past the last update time. + /// @return A predicted proportion of how complete the current update cycle is. + float GetPredictedProportionOfUpdateCompletion() const; #pragma endregion #pragma region Concrete Methods @@ -135,6 +144,9 @@ namespace RTE { /// Updates the simulation time to represent the current amount of simulation time passed from the start of the simulation up to the last update. void UpdateSim(); + /// Marks that the last simulation update has completed. + void MarkNewSimUpdateComplete(); + /// Updates the real time ticks based on the actual clock time and adds it to the accumulator which the simulation ticks will draw from in whole DeltaTime-sized chunks. void Update(); #pragma endregion @@ -148,22 +160,24 @@ namespace RTE { protected: std::chrono::steady_clock::time_point m_StartTime; //!< The point in real time when the simulation (re)started. long long m_TicksPerSecond; //!< The frequency of ticks each second, ie the resolution of the timer. - long long m_RealTimeTicks; //!< The number of actual microseconds counted so far. + volatile long long m_RealTimeTicks; //!< The number of actual microseconds counted so far. long long m_SimTimeTicks; //!< The number of simulation time ticks counted so far. long long m_SimUpdateCount; //!< The number of whole simulation updates have been made since reset. - long long m_SimAccumulator; //!< Simulation time accumulator keeps track of how much actual time has passed and is chunked into whole DeltaTime:s upon UpdateSim. + std::atomic m_SimAccumulator; //!< Simulation time accumulator keeps track of how much actual time has passed and is chunked into whole DeltaTime:s upon UpdateSim. long long m_DeltaTime; //!< The fixed delta time chunk of the simulation update. float m_DeltaTimeS; //!< The simulation update step size, in seconds. std::deque m_DeltaBuffer; //!< Buffer for measuring the most recent real time differences, used for averaging out the readings. - int m_SimUpdatesSinceDrawn; //!< How many sim updates have been done since the last drawn one. - bool m_DrawnSimUpdate; //!< Tells whether the current simulation update will be drawn in a frame. + float m_DrawDeltaTimeS; //!< How long the last draw took, in seconds. + volatile long long m_UpdateTrueDeltaTimeTicks; //!< How long the last update took, in ticks. + volatile long long m_LatestUpdateStartTime; //!< When our latest update started. + volatile long long m_LatestUpdateEndTime; //!< When our latest update completed. float m_SimSpeed; //!< The simulation speed over real time. float m_TimeScale; //!< The relationship between the real world actual time and the simulation time. A value of 2.0 means simulation runs twice as fast as normal, as perceived by a player. - bool m_SimPaused; //!< Simulation paused; no real time ticks will go to the sim accumulator. + std::atomic m_SimPaused; //!< Simulation paused; no real time ticks will go to the sim accumulator. private: /// Clears all the member variables of this TimerMan, effectively resetting the members of this abstraction level only. diff --git a/Source/Managers/UInputMan.cpp b/Source/Managers/UInputMan.cpp index 4d364e88b1..b45ce32242 100644 --- a/Source/Managers/UInputMan.cpp +++ b/Source/Managers/UInputMan.cpp @@ -1,6 +1,8 @@ #include "UInputMan.h" + #include "SceneMan.h" #include "ActivityMan.h" +#include "ThreadMan.h" #include "MetaMan.h" #include "WindowMan.h" #include "FrameMan.h" @@ -712,6 +714,12 @@ void UInputMan::QueueInputEvent(const SDL_Event& inputEvent) { int UInputMan::Update() { m_LastDeviceWhichControlledGUICursor = InputDevice::DEVICE_KEYB_ONLY; + // TODO_MULTITHREAD + // Handle this properly. + // There should be an intelligent division between inputs and controller states. + // Probably "stack" all UI updates into the controller for the *last* sim frame (so the state doesn't change mid-frame) + // But then that introduces a frame of latency. Alternatively, just let it update midframe. + // Tbh having things update mid-frame hasn't hurt, unless it's deleting stuff or changing lists being looped through by draw std::fill(s_ChangedKeyStates.begin(), s_ChangedKeyStates.end(), false); std::fill(s_ChangedMouseButtonStates.begin(), s_ChangedMouseButtonStates.end(), false); for (Gamepad& gamepad: s_ChangedJoystickStates) { @@ -840,6 +848,7 @@ int UInputMan::Update() { } else { m_NetworkAccumulatedRawMouseMovement[Players::PlayerOne] += m_RawMouseMovement; } + UpdateMouseInput(); UpdateJoystickDigitalAxis(); HandleSpecialInput(); @@ -878,10 +887,10 @@ void UInputMan::HandleSpecialInput() { if (FlagCtrlState() && !FlagAltState()) { // Ctrl+S to save continuous ScreenDumps if (KeyHeld(SDLK_s)) { - g_FrameMan.SaveScreenToPNG("ScreenDump"); + g_ThreadMan.QueueInSimulationThread([]() { g_FrameMan.SaveScreenToPNG("ScreenDump"); }); // Ctrl+W to save a WorldDump } else if (KeyPressed(SDLK_w)) { - g_FrameMan.SaveWorldToPNG("WorldDump"); + g_ThreadMan.QueueInSimulationThread([]() { g_FrameMan.SaveWorldToPNG("WorldDump"); }); // Ctrl+M to cycle draw modes } else if (KeyPressed(SDLK_m)) { g_SceneMan.SetLayerDrawMode((g_SceneMan.GetLayerDrawMode() + 1) % 3); @@ -889,12 +898,14 @@ void UInputMan::HandleSpecialInput() { } else if (KeyPressed(SDLK_p)) { g_PerformanceMan.ShowPerformanceStats(!g_PerformanceMan.IsShowingPerformanceStats()); } else if (KeyPressed(SDLK_F2)) { - g_PresetMan.QuickReloadEntityPreset(); + g_ThreadMan.QueueInSimulationThread([]() { g_PresetMan.QuickReloadEntityPreset(); }); } else if (KeyPressed(SDLK_F9)) { - g_ActivityMan.LoadAndLaunchGame("AutoSave"); + g_ThreadMan.QueueInSimulationThread([]() { g_ActivityMan.LoadAndLaunchGame("AutoSave"); }); } else if (g_PerformanceMan.IsShowingPerformanceStats()) { if (KeyHeld(SDLK_1)) { g_TimerMan.SetTimeScale(1.0F); + } else if (KeyHeld(SDLK_2)) { + g_TimerMan.SetTimeScale(99999.9F); } else if (KeyHeld(SDLK_5)) { g_TimerMan.SetDeltaTimeSecs(c_DefaultDeltaTimeS); } @@ -907,7 +918,7 @@ void UInputMan::HandleSpecialInput() { g_WindowMan.ToggleFullscreen(); // Alt+W to save ScenePreviewDump (miniature WorldDump) } else if (KeyPressed(SDLK_w)) { - g_FrameMan.SaveWorldPreviewToPNG("ScenePreviewDump"); + g_ThreadMan.QueueInSimulationThread([]() { g_FrameMan.SaveWorldPreviewToPNG("ScenePreviewDump"); }); } else if (g_PerformanceMan.IsShowingPerformanceStats()) { if (KeyPressed(SDLK_p)) { g_PerformanceMan.ShowAdvancedPerformanceStats(!g_PerformanceMan.AdvancedPerformanceStatsEnabled()); @@ -917,19 +928,19 @@ void UInputMan::HandleSpecialInput() { if (KeyPressed(SDLK_F1)) { g_ConsoleMan.ShowShortcuts(); } else if (KeyPressed(SDLK_F2)) { - g_PresetMan.ReloadAllScripts(); + g_ThreadMan.QueueInSimulationThread([]() { g_PresetMan.ReloadAllScripts(); }); } else if (KeyPressed(SDLK_F3)) { g_ConsoleMan.SaveAllText("Console.dump.log"); } else if (KeyPressed(SDLK_F4)) { g_ConsoleMan.SaveInputLog("Console.input.log"); } else if (KeyPressed(SDLK_F5)) { if (g_ActivityMan.GetActivity() && g_ActivityMan.GetActivity()->CanBeUserSaved()) { - g_ActivityMan.SaveCurrentGame("QuickSave"); + g_ThreadMan.QueueInSimulationThread([]() { g_ActivityMan.SaveCurrentGame("QuickSave"); }); } else { RTEError::ShowMessageBox("Cannot Save Game - This Activity Does Not Allow QuickSaving!"); } } else if (KeyPressed(SDLK_F9)) { - g_ActivityMan.LoadAndLaunchGame("QuickSave"); + g_ThreadMan.QueueInSimulationThread([]() { g_ActivityMan.LoadAndLaunchGame("QuickSave"); }); } else if (KeyPressed(SDLK_F10)) { g_ConsoleMan.ClearLog(); // F12 to save a single ScreenDump - Note that F12 triggers a breakpoint when the VS debugger is attached, regardless of config - this is by design. Thanks Microsoft. diff --git a/Source/Managers/WindowMan.h b/Source/Managers/WindowMan.h index 4b16441d48..783ef73756 100644 --- a/Source/Managers/WindowMan.h +++ b/Source/Managers/WindowMan.h @@ -273,7 +273,7 @@ namespace RTE { void AttemptToRevertToPreviousResolution(bool revertToDefaults = false); #pragma endregion -#pragma region Multi - Display Handling +#pragma region Multi-Display Handling /// Clears all the multi-display data, resetting the game to a single-window-single-display state. void ClearMultiDisplayData(); diff --git a/Source/Menus/MetagameGUI.cpp b/Source/Menus/MetagameGUI.cpp index 4b11284ef4..3dc636c32d 100644 --- a/Source/Menus/MetagameGUI.cpp +++ b/Source/Menus/MetagameGUI.cpp @@ -69,9 +69,9 @@ void MetagameGUI::SiteTarget::Draw(BITMAP* drawBitmap) const { // Draw the appropriate growing geometric figure around the location, growing if (m_Style == SiteTarget::CROSSHAIRSSHRINK) { - float radius = LERP(0.0, 1.0, 200, 10, m_AnimProgress); - float lineLen = LERP(0.0, 1.0, 60, 10, m_AnimProgress); - float rotation = 0; // LERP(0.0, 1.0, -c_EighthPI, 0.0, m_AnimProgress); + float radius = Lerp(0.0, 1.0, 200, 10, m_AnimProgress); + float lineLen = Lerp(0.0, 1.0, 60, 10, m_AnimProgress); + float rotation = 0; // Lerp(0.0, 1.0, -c_EighthPI, 0.0, m_AnimProgress); Vector inner; Vector outer; @@ -84,9 +84,9 @@ void MetagameGUI::SiteTarget::Draw(BITMAP* drawBitmap) const { DrawGlowLine(drawBitmap, m_CenterPos + inner, m_CenterPos + outer, m_Color); } } else if (m_Style == SiteTarget::CROSSHAIRSGROW) { - float radius = LERP(0.0, 1.0, 10, 200, m_AnimProgress); - float lineLen = LERP(0.0, 1.0, 10, 60, m_AnimProgress); - float rotation = 0; // LERP(0.0, 1.0, -c_EighthPI, 0.0, m_AnimProgress); + float radius = Lerp(0.0, 1.0, 10, 200, m_AnimProgress); + float lineLen = Lerp(0.0, 1.0, 10, 60, m_AnimProgress); + float rotation = 0; // Lerp(0.0, 1.0, -c_EighthPI, 0.0, m_AnimProgress); Vector inner; Vector outer; @@ -99,26 +99,26 @@ void MetagameGUI::SiteTarget::Draw(BITMAP* drawBitmap) const { DrawGlowLine(drawBitmap, m_CenterPos + inner, m_CenterPos + outer, m_Color); } } else if (m_Style == SiteTarget::CIRCLESHRINK) { - float radius = LERP(0.0, 1.0, 24, 6, m_AnimProgress); - int blendAmount = LERP(0.0, 1.0, 0, 255, m_AnimProgress); // + 15 * NormalRand(); + float radius = Lerp(0.0, 1.0, 24, 6, m_AnimProgress); + int blendAmount = Lerp(0.0, 1.0, 0, 255, m_AnimProgress); // + 15 * NormalRand(); set_screen_blender(blendAmount, blendAmount, blendAmount, blendAmount); circle(drawBitmap, m_CenterPos.m_X, m_CenterPos.m_Y, radius, m_Color); } else if (m_Style == SiteTarget::CIRCLEGROW) { - float radius = LERP(0.0, 1.0, 6, 24, m_AnimProgress); - int blendAmount = LERP(0.0, 1.0, 255, 0, m_AnimProgress); // + 15 * NormalRand(); + float radius = Lerp(0.0, 1.0, 6, 24, m_AnimProgress); + int blendAmount = Lerp(0.0, 1.0, 255, 0, m_AnimProgress); // + 15 * NormalRand(); set_screen_blender(blendAmount, blendAmount, blendAmount, blendAmount); circle(drawBitmap, m_CenterPos.m_X, m_CenterPos.m_Y, radius, m_Color); } else if (m_Style == SiteTarget::SQUARESHRINK) { - float radius = LERP(0.0, 1.0, 24, 6, m_AnimProgress); - int blendAmount = LERP(0.0, 1.0, 0, 255, m_AnimProgress); // + 15 * NormalRand(); + float radius = Lerp(0.0, 1.0, 24, 6, m_AnimProgress); + int blendAmount = Lerp(0.0, 1.0, 0, 255, m_AnimProgress); // + 15 * NormalRand(); set_screen_blender(blendAmount, blendAmount, blendAmount, blendAmount); rect(drawBitmap, m_CenterPos.m_X - radius, m_CenterPos.m_Y - radius, m_CenterPos.m_X + radius, m_CenterPos.m_Y + radius, m_Color); } // Default else // if (m_Style == SiteTarget::SQUAREGROW) { - float radius = LERP(0.0, 1.0, 6, 24, m_AnimProgress); - int blendAmount = LERP(0.0, 1.0, 255, 0, m_AnimProgress); // + 15 * NormalRand(); + float radius = Lerp(0.0, 1.0, 6, 24, m_AnimProgress); + int blendAmount = Lerp(0.0, 1.0, 255, 0, m_AnimProgress); // + 15 * NormalRand(); set_screen_blender(blendAmount, blendAmount, blendAmount, blendAmount); rect(drawBitmap, m_CenterPos.m_X - radius, m_CenterPos.m_Y - radius, m_CenterPos.m_X + radius, m_CenterPos.m_Y + radius, m_Color); } diff --git a/Source/Menus/TitleScreen.cpp b/Source/Menus/TitleScreen.cpp index 1e490b75db..3049dc3215 100644 --- a/Source/Menus/TitleScreen.cpp +++ b/Source/Menus/TitleScreen.cpp @@ -158,7 +158,7 @@ void TitleScreen::Update() { m_StationOrbitTimer.Reset(); } m_StationOrbitProgress = std::clamp(static_cast(m_StationOrbitTimer.GetElapsedRealTimeS()) / 60.0F, 0.0F, 0.9999F); - m_StationOrbitRotation = LERP(0, 1.0F, c_PI, -c_PI, m_StationOrbitProgress); + m_StationOrbitRotation = Lerp(0, 1.0F, c_PI, -c_PI, m_StationOrbitProgress); if (!m_FinishedPlayingIntro) { if (m_TitleTransitionState == TitleTransition::PauseMenu) { @@ -172,7 +172,7 @@ void TitleScreen::Update() { if (m_IntroSequenceState >= IntroSequence::DataRealmsLogoFadeIn && m_IntroSequenceState <= IntroSequence::FmodLogoFadeOut) { UpdateIntroLogoSequence(g_UInputMan.AnyStartPress()); } else if (m_IntroSequenceState >= IntroSequence::SlideshowFadeIn && m_IntroSequenceState <= IntroSequence::SlideshowEnd) { - m_ScrollOffset.SetY(LERP(0, 1.0F, m_IntroScrollStartOffsetY, m_GameLogoAppearScrollOffsetY, introScrollProgress)); + m_ScrollOffset.SetY(Lerp(0, 1.0F, m_IntroScrollStartOffsetY, m_GameLogoAppearScrollOffsetY, introScrollProgress)); UpdateIntroSlideshowSequence(g_UInputMan.AnyStartPress()); } else if (m_IntroSequenceState >= IntroSequence::GameLogoAppear && m_IntroSequenceState <= IntroSequence::MainMenuAppear) { if (m_IntroSequenceState < IntroSequence::PreMainMenu) { @@ -209,7 +209,7 @@ void TitleScreen::UpdateIntroLogoSequence(bool skipSection) { SetSectionDurationAndResetSwitch(0.5F); g_GUISound.SplashSound()->Play(); } - m_FadeAmount = static_cast(LERP(0, 1.0F, 255.0F, 0, m_SectionProgress)); + m_FadeAmount = static_cast(Lerp(0, 1.0F, 255.0F, 0, m_SectionProgress)); break; case IntroSequence::DataRealmsLogoDisplay: if (m_SectionSwitch) { @@ -220,13 +220,13 @@ void TitleScreen::UpdateIntroLogoSequence(bool skipSection) { if (m_SectionSwitch) { SetSectionDurationAndResetSwitch(0.25F); } - m_FadeAmount = static_cast(LERP(0, 1.0F, 0, 255.0F, m_SectionProgress)); + m_FadeAmount = static_cast(Lerp(0, 1.0F, 0, 255.0F, m_SectionProgress)); break; case IntroSequence::FmodLogoFadeIn: if (m_SectionSwitch) { SetSectionDurationAndResetSwitch(0.25F); } - m_FadeAmount = static_cast(LERP(0, 1.0F, 255.0F, 0, m_SectionProgress)); + m_FadeAmount = static_cast(Lerp(0, 1.0F, 255.0F, 0, m_SectionProgress)); break; case IntroSequence::FmodLogoDisplay: if (m_SectionSwitch) { @@ -237,7 +237,7 @@ void TitleScreen::UpdateIntroLogoSequence(bool skipSection) { if (m_SectionSwitch) { SetSectionDurationAndResetSwitch(0.5F); } - m_FadeAmount = static_cast(LERP(0, 1.0F, 0, 255.0F, m_SectionProgress)); + m_FadeAmount = static_cast(Lerp(0, 1.0F, 0, 255.0F, m_SectionProgress)); break; default: break; @@ -266,7 +266,7 @@ void TitleScreen::UpdateIntroSlideshowSequence(bool skipSlideshow) { g_AudioMan.PlayMusic("Base.rte/Music/Hubnester/ccintro.ogg", 0); g_AudioMan.SetMusicPosition(0.05F); } - m_FadeAmount = static_cast(LERP(0, 1.0F, 255.0F, 0, m_SectionProgress)); + m_FadeAmount = static_cast(Lerp(0, 1.0F, 255.0F, 0, m_SectionProgress)); break; case IntroSequence::PreSlideshowPause: if (m_SectionSwitch) { @@ -372,7 +372,7 @@ void TitleScreen::UpdateIntroPreMainMenuSequence() { m_StationOrbitTimer.SetElapsedRealTimeS(40); clear_to_color(g_FrameMan.GetOverlayBitmap32(), 0xFFFFFFFF); } - m_FadeAmount = static_cast(LERP(0, 1.0F, 255.0F, 0, m_SectionProgress)); + m_FadeAmount = static_cast(Lerp(0, 1.0F, 255.0F, 0, m_SectionProgress)); break; case IntroSequence::PlanetScroll: if (m_SectionSwitch) { @@ -477,7 +477,7 @@ void TitleScreen::UpdateTitleTransitions() { g_AudioMan.SetMusicMuffledState(false); } g_AudioMan.SetTempMusicVolume(EaseOut(0, 1.0F, m_SectionProgress)); - m_FadeAmount = static_cast(LERP(0, 1.0F, 255.0F, 0, m_SectionProgress)); + m_FadeAmount = static_cast(Lerp(0, 1.0F, 255.0F, 0, m_SectionProgress)); if (m_SectionElapsedTime >= m_SectionDuration) { SetTitleTransitionState((m_TitleTransitionState == TitleTransition::ScenarioFadeIn) ? TitleTransition::ScenarioMenu : TitleTransition::MetaGameMenu); } diff --git a/Source/System/Atom.cpp b/Source/System/Atom.cpp index 78b56f2aad..1b66adf0b6 100644 --- a/Source/System/Atom.cpp +++ b/Source/System/Atom.cpp @@ -2,11 +2,13 @@ #include "SLTerrain.h" #include "MovableMan.h" +#include "ThreadMan.h" #include "MovableObject.h" #include "MOSRotating.h" #include "MOPixel.h" #include "PresetMan.h" #include "Actor.h" +#include "ThreadMan.h" #include "tracy/Tracy.hpp" @@ -81,6 +83,7 @@ void Atom::Clear() { m_HitRadius.Reset(); m_HitImpulse.Reset(); */ + m_LastTrailPoints.clear(); m_TrailColor.Reset(); m_TrailLength = 0; m_TrailLengthVariation = 0.0F; @@ -641,8 +644,6 @@ int Atom::Travel(float travelTime, bool autoTravel, bool scenePreLocked) { bool& didWrap = m_OwnerMO->m_DidWrap; m_LastHit.Reset(); - BITMAP* trailBitmap = 0; - int hitCount = 0; int error = 0; int dom = 0; @@ -677,10 +678,8 @@ int Atom::Travel(float travelTime, bool autoTravel, bool scenePreLocked) { Vector segTraj; Vector hitAccel; - // Static buffer to avoid having to realloc with every atom's travel - // This saves us time because Atom::Travel does a lot of allocations and reallocations if you have a lot of particles. - thread_local std::vector> trailPoints; - trailPoints.clear(); + std::vector> trailPoints; + trailPoints.reserve(6); // This saves us time because Atom::Travel does a lot of allocations and reallocations if you have a lot of particles. didWrap = false; int removeOrphansRadius = m_OwnerMO->m_RemoveOrphanTerrainRadius; @@ -702,7 +701,6 @@ int Atom::Travel(float travelTime, bool autoTravel, bool scenePreLocked) { // Get trail bitmap and put first pixel. if (m_TrailLength) { - trailBitmap = g_SceneMan.GetMOColorBitmap(); trailPoints.push_back({intPos[X], intPos[Y]}); } // Compute and scale the actual on-screen travel trajectory for this segment, based on the velocity, the travel time and the pixels-per-meter constant. @@ -912,11 +910,6 @@ int Atom::Travel(float travelTime, bool autoTravel, bool scenePreLocked) { hitPos[Y] = intPos[Y]; ++hitCount; -#ifdef DEBUG_BUILD - if (m_TrailLength) { - putpixel(trailBitmap, intPos[X], intPos[Y], 199); - } -#endif // Try penetration of the terrain. if (hitMaterial->GetIndex() != g_MaterialOutOfBounds && g_SceneMan.TryPenetrate(intPos[X], intPos[Y], velocity * mass * sharpness, velocity, retardation, 0.65F, m_NumPenetrations, removeOrphansRadius, removeOrphansMaxArea, removeOrphansRate)) { hit[dom] = hit[sub] = sinkHit = true; @@ -948,7 +941,7 @@ int Atom::Travel(float travelTime, bool autoTravel, bool scenePreLocked) { // Weighted random select between stickiness or staininess const float randomChoice = RandomNum(0.0f, m_Material->GetStickiness() + (ownerMOAsPixel ? ownerMOAsPixel->GetStaininess() : 0.0f)); if (randomChoice <= m_Material->GetStickiness()) { - m_OwnerMO->SetPos(Vector(intPos[X], intPos[Y])); + m_OwnerMO->SetPos(Vector(intPos[X], intPos[Y]), false); m_OwnerMO->DrawToTerrain(g_SceneMan.GetTerrain()); m_OwnerMO->SetToDelete(true); m_LastHit.Terminate[HITOR] = hit[dom] = hit[sub] = true; @@ -958,9 +951,9 @@ int Atom::Travel(float travelTime, bool autoTravel, bool scenePreLocked) { stickPos += velocity * (c_PPM * g_TimerMan.GetDeltaTimeSecs()) * RandomNum(); int terrainMaterialID = g_SceneMan.GetTerrain()->GetMaterialPixel(stickPos.GetFloorIntX(), stickPos.GetFloorIntY()); if (terrainMaterialID != g_MaterialAir && terrainMaterialID != g_MaterialDoor) { - m_OwnerMO->SetPos(Vector(stickPos.GetRoundIntX(), stickPos.GetRoundIntY())); + m_OwnerMO->SetPos(Vector(stickPos.GetRoundIntX(), stickPos.GetRoundIntY()), false); } else { - m_OwnerMO->SetPos(Vector(intPos[X], intPos[Y])); + m_OwnerMO->SetPos(Vector(intPos[X], intPos[Y]), false); } m_OwnerMO->DrawToTerrain(g_SceneMan.GetTerrain()); m_OwnerMO->SetToDelete(true); @@ -1045,25 +1038,31 @@ int Atom::Travel(float travelTime, bool autoTravel, bool scenePreLocked) { // RTEAssert(hitCount < 100, "Atom travel resulted in more than 100 segments!!"); // Draw the trail - if (g_TimerMan.DrawnSimUpdate() && m_TrailLength && trailPoints.size() > 0) { - Vector topLeftExtent = Vector(trailPoints[0].first, trailPoints[0].second); - Vector bottomRightExtent = topLeftExtent + Vector(1.0F, 1.0F); - + int trailColorIndex = m_TrailColor.GetIndex(); + if (trailColorIndex != g_MaskColor && m_TrailLength && (trailPoints.size() > 0 || m_LastTrailPoints.size() > 0)) { int length = static_cast(static_cast(m_TrailLength) * RandomNum(1.0F - m_TrailLengthVariation, 1.0F)); - for (int i = trailPoints.size() - std::min(length, static_cast(trailPoints.size())); i < trailPoints.size(); ++i) { - putpixel(trailBitmap, trailPoints[i].first, trailPoints[i].second, m_TrailColor.GetIndex()); - topLeftExtent.m_X = std::min(topLeftExtent.m_X, static_cast(trailPoints[i].first)); - topLeftExtent.m_Y = std::min(topLeftExtent.m_Y, static_cast(trailPoints[i].second)); - bottomRightExtent.m_X = std::max(bottomRightExtent.m_X, static_cast(trailPoints[i].first)); - bottomRightExtent.m_Y = std::max(bottomRightExtent.m_Y, static_cast(trailPoints[i].second)); - } + // TODO_MULTITHREAD - fix to account for wrapping! + // Also, keep drawing trail after we are destroyed for 1 frame, so our trail doesn't immediately disappear? + + auto renderFunc = [lastTrailPoints = std::move(m_LastTrailPoints), trailPoints, trailColorIndex, length](float interpolationAmount) mutable { + BITMAP* pTargetBitmap = g_ThreadMan.GetRenderTarget(); + + int endPoint = lastTrailPoints.size() + (trailPoints.size() * interpolationAmount); + std::vector> allTrailPoints = lastTrailPoints; + allTrailPoints.insert(allTrailPoints.end(), trailPoints.begin(), trailPoints.end()); + for (int i = endPoint - std::min(length, static_cast(endPoint)); i < endPoint; ++i) { + Vector spritePos(allTrailPoints[i].first, allTrailPoints[i].second); + Vector renderPos = spritePos - g_ThreadMan.GetRenderOffset(); + putpixel(pTargetBitmap, renderPos.GetX(), renderPos.GetY(), trailColorIndex); + } + }; - g_SceneMan.RegisterDrawing(trailBitmap, g_NoMOID, topLeftExtent.m_X, topLeftExtent.m_Y, bottomRightExtent.m_X + 1.0F, bottomRightExtent.m_Y + 1.0F); + g_ThreadMan.GetSimRenderQueue().push_back(renderFunc); + m_LastTrailPoints = std::move(trailPoints); } // Unlock all bitmaps involved. - // if (m_TrailLength) { trailBitmap->UnLock(); } if (!scenePreLocked) { g_SceneMan.UnlockScene(); } diff --git a/Source/System/Atom.h b/Source/System/Atom.h index 510b506ec6..ed1e371cde 100644 --- a/Source/System/Atom.h +++ b/Source/System/Atom.h @@ -389,6 +389,7 @@ namespace RTE { Vector m_HitImpulse; //!< The resulting impulse force of the last collision this Atom experienced in Kg * m/s. */ + std::vector> m_LastTrailPoints; //!< Our trail points for the last frame, for render interpolation Color m_TrailColor; //!< Trail color int m_TrailLength; //!< The longest the trail should/can get drawn. If 0, no trail is drawn. float m_TrailLengthVariation; //!< What percentage the trail length of this Atom can vary each frame it's drawn. 0 means no variance, 1 means 100% variance between 0 and its TrailLength. diff --git a/Source/System/Constants.h b/Source/System/Constants.h index 00d295d2e2..cc2efc531c 100644 --- a/Source/System/Constants.h +++ b/Source/System/Constants.h @@ -57,9 +57,6 @@ namespace RTE { enum ColorKeys { g_InvalidColor = -1, g_MaskColor = 0, //!< Mask color for all 8bpp bitmaps (palette index 0 (255,0,255)). This color is fully transparent. - // g_MOIDMaskColor = 0, //!< Mask color for 8bpp MOID layer bitmaps (palette index 0 (255,0,255)). This color is fully transparent. - g_MOIDMaskColor = 0xF81F, //!< Mask color for 16bpp MOID layer bitmaps (255,0,255). This color is fully transparent. - // g_MOIDMaskColor = 0xFF00FF, //!< Mask color for 32bpp MOID layer bitmaps (255,0,255). This color is fully transparent. g_BlackColor = 245, g_WhiteColor = 254, g_RedColor = 13, @@ -345,7 +342,7 @@ namespace RTE { {Directions::Right, 0.0F}}; #pragma endregion -#pragma region Un - Definitions +#pragma region Un-Definitions // Allegro defines these via define in astdint.h and Boost with stdlib go crazy so we need to undefine them manually. #undef int8_t #undef uint8_t diff --git a/Source/System/Entity.h b/Source/System/Entity.h index dcd5cf0698..82e2584cd3 100644 --- a/Source/System/Entity.h +++ b/Source/System/Entity.h @@ -54,7 +54,6 @@ namespace RTE { g_DrawMaterial, g_DrawWhite, g_DrawMOID, - g_DrawNoMOID, g_DrawDoor, g_DrawTrans, g_DrawAlpha diff --git a/Source/System/RTETools.cpp b/Source/System/RTETools.cpp index 0377b729f9..a0b3a9c090 100644 --- a/Source/System/RTETools.cpp +++ b/Source/System/RTETools.cpp @@ -1,6 +1,7 @@ #include "RTETools.h" #include "Vector.h" +#include "Matrix.h" #include "System.h" #include @@ -30,7 +31,7 @@ namespace RTE { g_RandomGenerator.Seed(constSeed); } - float LERP(float scaleStart, float scaleEnd, float startValue, float endValue, float progressScalar) { + float Lerp(float scaleStart, float scaleEnd, float startValue, float endValue, float progressScalar) { if (progressScalar <= scaleStart) { return startValue; } else if (progressScalar >= scaleEnd) { @@ -39,6 +40,31 @@ namespace RTE { return startValue + ((progressScalar - scaleStart) * ((endValue - startValue) / (scaleEnd - scaleStart))); } + Vector Lerp(float scaleStart, float scaleEnd, Vector startPos, Vector endPos, float progressScalar) { + Vector startToEnd = endPos - startPos; + return startPos + (startToEnd * Lerp(scaleStart, scaleEnd, 0.0F, 1.0F, progressScalar)); + } + + Matrix Lerp(float scaleStart, float scaleEnd, Matrix startRot, Matrix endRot, float progressScalar) { + const float fullTurn = c_PI * 2.0F; + float angleDelta = std::fmod(endRot.GetRadAngle() - startRot.GetRadAngle(), fullTurn); + float angleDistance = std::fmod(angleDelta * 2.0F, fullTurn) - angleDelta; + return Matrix(startRot.GetRadAngle() + (angleDistance * Lerp(scaleStart, scaleEnd, 0.0F, 1.0F, progressScalar))); + + float startRad = startRot.GetRadAngle(); + float endRad = endRot.GetRadAngle(); + float diff = startRad - endRad; + if (diff > c_PI) { + std::swap(startRad, endRad); + diff -= c_PI; + } else if (diff < -c_PI) { + std::swap(startRad, endRad); + diff += c_PI; + } + + return Matrix(startRad + (diff * Lerp(scaleStart, scaleEnd, 0.0F, 1.0F, progressScalar))); + } + float EaseIn(float start, float end, float progressScalar) { if (progressScalar <= 0) { return start; diff --git a/Source/System/RTETools.h b/Source/System/RTETools.h index a82d0a5a3d..26b14ce615 100644 --- a/Source/System/RTETools.h +++ b/Source/System/RTETools.h @@ -13,6 +13,7 @@ namespace RTE { class Vector; + class Matrix; #pragma region Random Numbers class RandomGenerator { @@ -115,14 +116,34 @@ namespace RTE { #pragma region Interpolation /// Simple Linear Interpolation, with an added bonus: scaleStart and scaleEnd let you define your scale, where 0 and 1 would be standard scale. - /// This scale is used to normalize your progressScalar value and LERP accordingly. - /// @param scaleStart The start of the scale to LERP along. - /// @param scaleEnd The end of the scale to LERP along. - /// @param startValue The start value of your LERP. - /// @param endValue The end value of your LERP. - /// @param progressScalar How far your LERP has progressed. Automatically normalized through use of scaleStart and scaleEnd. + /// This scale is used to normalize your progressScalar value and Lerp accordingly. + /// @param scaleStart The start of the scale to Lerp along. + /// @param scaleEnd The end of the scale to Lerp along. + /// @param startValue The start value of your Lerp. + /// @param endValue The end value of your Lerp. + /// @param progressScalar How far your Lerp has progressed. Automatically normalized through use of scaleStart and scaleEnd. /// @return Interpolated value. - float LERP(float scaleStart, float scaleEnd, float startValue, float endValue, float progressScalar); + float Lerp(float scaleStart, float scaleEnd, float startValue, float endValue, float progressScalar); + + /// Simple Linear Interpolation, with an added bonus: scaleStart and scaleEnd let you define your scale, where 0 and 1 would be standard scale. + /// This scale is used to normalize your progressScalar value and Lerp accordingly. + /// @param scaleStart The start of the scale to Lerp along. + /// @param scaleEnd The end of the scale to Lerp along. + /// @param startValue The start position of your Lerp. + /// @param endValue The end position of your Lerp. + /// @param progressScalar How far your Lerp has progressed. Automatically normalized through use of scaleStart and scaleEnd. + /// @return Interpolated value. + Vector Lerp(float scaleStart, float scaleEnd, Vector startPos, Vector endPos, float progressScalar); + + /// Simple Linear Interpolation, with an added bonus: scaleStart and scaleEnd let you define your scale, where 0 and 1 would be standard scale. + /// This scale is used to normalize your progressScalar value and Lerp accordingly. + /// @param scaleStart The start of the scale to Lerp along. + /// @param scaleEnd The end of the scale to Lerp along. + /// @param startRot The start rotation of your Lerp. + /// @param endRot The end rotation of your Lerp. + /// @param progressScalar How far your Lerp has progressed. Automatically normalized through use of scaleStart and scaleEnd. + /// @return Interpolated value. + Matrix Lerp(float scaleStart, float scaleEnd, Matrix startRot, Matrix endRot, float progressScalar); /// Nonlinear ease-in interpolation. Starts slow. /// @param start Start value. diff --git a/Source/System/System.cpp b/Source/System/System.cpp index 853e540cd9..daad973db9 100644 --- a/Source/System/System.cpp +++ b/Source/System/System.cpp @@ -31,7 +31,7 @@ using namespace RTE; -bool System::s_Quit = false; +volatile bool System::s_Quit = false; bool System::s_LogToCLI = false; bool System::s_ExternalModuleValidation = false; std::string System::s_ThisExePathAndName = ""; diff --git a/Source/System/System.h b/Source/System/System.h index 4199ce5f05..e24dcb8932 100644 --- a/Source/System/System.h +++ b/Source/System/System.h @@ -84,7 +84,7 @@ namespace RTE { static bool PathExistsCaseSensitive(const std::string& pathToCheck); #pragma endregion -#pragma region Command - Line Interface +#pragma region Command-Line Interface /// Tells whether printing loading progress report and console to command-line is enabled or not. /// @return Whether printing to command-line is enabled or not. static bool IsLoggingToCLI() { return s_LogToCLI; } @@ -129,7 +129,7 @@ namespace RTE { #pragma endregion private: - static bool s_Quit; //!< Whether the user requested program termination through GUI or the window close button. + volatile static bool s_Quit; //!< Whether the user requested program termination through GUI or the window close button. static bool s_LogToCLI; //!< Bool to tell whether to print the loading log and anything specified with PrintToCLI to command-line or not. static bool s_ExternalModuleValidation; //!< Whether to run the program in a special mode where it will immediately quit without any messages after either successful loading of all modules or aborting during loading. For use by an external tool. static std::string s_ThisExePathAndName; //!< String containing the absolute path to this executable. Used for relaunching via abort message.