1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2025-12-22 15:23:01 +01:00

Don't tween entities that are not visible on screen or when zoomed out

This commit is contained in:
ζeh Matt
2025-05-22 04:13:11 +03:00
parent f71b45b6ef
commit 3770ab0b51
2 changed files with 174 additions and 105 deletions

View File

@@ -10,6 +10,8 @@
#include "../entity/Guest.h" #include "../entity/Guest.h"
#include "../entity/Staff.h" #include "../entity/Staff.h"
#include "../interface/Viewport.h"
#include "../interface/Window.h"
#include "../ride/Vehicle.h" #include "../ride/Vehicle.h"
#include "EntityList.h" #include "EntityList.h"
#include "EntityRegistry.h" #include "EntityRegistry.h"
@@ -17,114 +19,170 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
void EntityTweener::AddEntity(EntityBase* entity) namespace OpenRCT2
{ {
Entities.push_back(entity); static inline ViewportList GetUnzoomedViewports() noexcept
PrePos.emplace_back(entity->GetLocation());
}
void EntityTweener::PopulateEntities()
{
for (auto ent : EntityList<Guest>())
{ {
AddEntity(ent); ViewportList viewports;
WindowVisitEach([&](WindowBase* w) {
if (auto* vp = WindowGetViewport(w); vp != nullptr)
{
if (!vp->isVisible)
{
// Ignore viewports that are not visible.
return;
}
if (vp->zoom > ZoomLevel{ 0 })
{
// Ignore viewports that are zoomed out, interpolation wouldn't have much of an effect
// due to the loss of detail.
return;
}
viewports.push_back(vp);
}
});
return viewports;
} }
for (auto ent : EntityList<Staff>())
{
AddEntity(ent);
}
for (auto ent : EntityList<Vehicle>())
{
AddEntity(ent);
}
}
void EntityTweener::PreTick() static inline bool IsEntityVisible(const ViewportList& vpList, const EntityBase* entity) noexcept
{
Restore();
Reset();
PopulateEntities();
}
void EntityTweener::PostTick()
{
for (auto* ent : Entities)
{ {
if (ent == nullptr) const auto worldLoc = entity->GetLocation();
for (const auto* vp : vpList)
{ {
// Sprite was removed, add a dummy position to keep the index aligned. const auto screenPos = Translate3DTo2DWithZ(vp->rotation, worldLoc);
PostPos.emplace_back(0, 0, 0); if (vp->Contains(screenPos))
{
// Entity is visible in at least one viewport, tween.
return true;
}
} }
else
return false;
}
void EntityTweener::AddEntity(const ViewportList& vpList, EntityBase* entity)
{
if (!IsEntityVisible(vpList, entity))
{ {
PostPos.emplace_back(ent->GetLocation()); return;
}
Entities.push_back(entity);
PrePos.emplace_back(entity->GetLocation());
}
void EntityTweener::PopulateEntities()
{
const auto vpList = GetUnzoomedViewports();
if (vpList.empty())
{
// No viewports that fit the criteria, bail.
return;
}
for (auto ent : EntityList<Guest>())
{
AddEntity(vpList, ent);
}
for (auto ent : EntityList<Staff>())
{
AddEntity(vpList, ent);
}
for (auto ent : EntityList<Vehicle>())
{
AddEntity(vpList, ent);
} }
} }
}
static bool CanTweenEntity(EntityBase* ent) void EntityTweener::PreTick()
{
if (ent->Is<Guest>() || ent->Is<Staff>() || ent->Is<Vehicle>())
return true;
return false;
}
void EntityTweener::RemoveEntity(EntityBase* entity)
{
if (!CanTweenEntity(entity))
{ {
// Only peeps and vehicles are tweened, bail if type is incorrect. Restore();
return; Reset();
PopulateEntities();
} }
auto it = std::find(Entities.begin(), Entities.end(), entity); void EntityTweener::PostTick()
if (it != Entities.end())
*it = nullptr;
}
void EntityTweener::Tween(float alpha)
{
const float inv = (1.0f - alpha);
for (size_t i = 0; i < Entities.size(); ++i)
{ {
auto* ent = Entities[i]; for (auto* ent : Entities)
if (ent == nullptr) {
continue; if (ent == nullptr)
{
auto& posA = PrePos[i]; // Sprite was removed, add a dummy position to keep the index aligned.
auto& posB = PostPos[i]; PostPos.emplace_back(0, 0, 0);
}
if (posA == posB) else
continue; {
PostPos.emplace_back(ent->GetLocation());
ent->MoveTo({ static_cast<int32_t>(std::round(posB.x * alpha + posA.x * inv)), }
static_cast<int32_t>(std::round(posB.y * alpha + posA.y * inv)), }
static_cast<int32_t>(std::round(posB.z * alpha + posA.z * inv)) });
} }
}
void EntityTweener::Restore() static bool CanTweenEntity(EntityBase* ent)
{
for (size_t i = 0; i < Entities.size(); ++i)
{ {
auto* ent = Entities[i]; if (ent->Is<Guest>() || ent->Is<Staff>() || ent->Is<Vehicle>())
if (ent == nullptr || PrePos[i] == PostPos[i]) return true;
continue; return false;
ent->MoveTo(PostPos[i]);
} }
}
void EntityTweener::Reset() void EntityTweener::RemoveEntity(EntityBase* entity)
{ {
Entities.clear(); if (!CanTweenEntity(entity))
PrePos.clear(); {
PostPos.clear(); // Only peeps and vehicles are tweened, bail if type is incorrect.
} return;
}
static EntityTweener tweener; auto it = std::find(Entities.begin(), Entities.end(), entity);
if (it != Entities.end())
*it = nullptr;
}
EntityTweener& EntityTweener::Get() void EntityTweener::Tween(float alpha)
{ {
return tweener; const float inv = (1.0f - alpha);
} for (size_t i = 0; i < Entities.size(); ++i)
{
auto* ent = Entities[i];
if (ent == nullptr)
continue;
auto& posA = PrePos[i];
auto& posB = PostPos[i];
if (posA == posB)
continue;
ent->MoveTo({ static_cast<int32_t>(std::round(posB.x * alpha + posA.x * inv)),
static_cast<int32_t>(std::round(posB.y * alpha + posA.y * inv)),
static_cast<int32_t>(std::round(posB.z * alpha + posA.z * inv)) });
}
}
void EntityTweener::Restore()
{
for (size_t i = 0; i < Entities.size(); ++i)
{
auto* ent = Entities[i];
if (ent == nullptr || PrePos[i] == PostPos[i])
continue;
ent->MoveTo(PostPos[i]);
}
}
void EntityTweener::Reset()
{
Entities.clear();
PrePos.clear();
PostPos.clear();
}
static EntityTweener tweener;
EntityTweener& EntityTweener::Get()
{
return tweener;
}
} // namespace OpenRCT2

View File

@@ -9,27 +9,38 @@
#pragma once #pragma once
#include "../interface/Window.h"
#include "EntityBase.h" #include "EntityBase.h"
#include <sfl/static_vector.hpp>
#include <vector> #include <vector>
class EntityTweener namespace OpenRCT2
{ {
std::vector<EntityBase*> Entities; struct Viewport;
std::vector<CoordsXYZ> PrePos;
std::vector<CoordsXYZ> PostPos;
private: // TODO: Move this to somewhere else, currently filters also by zoom.
void PopulateEntities(); using ViewportList = sfl::static_vector<Viewport*, kWindowLimitMax>;
void AddEntity(EntityBase* entity);
public: class EntityTweener
static EntityTweener& Get(); {
std::vector<EntityBase*> Entities;
std::vector<CoordsXYZ> PrePos;
std::vector<CoordsXYZ> PostPos;
void PreTick(); private:
void PostTick(); void PopulateEntities();
void RemoveEntity(EntityBase* entity); void AddEntity(const ViewportList& vp, EntityBase* entity);
void Tween(float alpha);
void Restore(); public:
void Reset(); static EntityTweener& Get();
};
void PreTick();
void PostTick();
void RemoveEntity(EntityBase* entity);
void Tween(float alpha);
void Restore();
void Reset();
};
} // namespace OpenRCT2