Changeset View
Changeset View
Standalone View
Standalone View
source/simulation2/components/CCmpUnitRenderer.cpp
Show All 11 Lines | |||||
* GNU General Public License for more details. | * GNU General Public License for more details. | ||||
* | * | ||||
* You should have received a copy of the GNU General Public License | * You should have received a copy of the GNU General Public License | ||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. | * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. | ||||
*/ | */ | ||||
#include "precompiled.h" | #include "precompiled.h" | ||||
#include "CCmpUnitRenderer.h" | |||||
#include "simulation2/system/Component.h" | #include "simulation2/system/Component.h" | ||||
#include "ICmpUnitRenderer.h" | |||||
#include "simulation2/MessageTypes.h" | #include "simulation2/MessageTypes.h" | ||||
#include "ICmpPosition.h" | #include "ICmpPosition.h" | ||||
#include "ICmpRangeManager.h" | |||||
#include "ICmpSelectable.h" | #include "ICmpSelectable.h" | ||||
#include "ICmpVisibility.h" | #include "ICmpVisibility.h" | ||||
#include "ICmpVisual.h" | #include "ICmpVisual.h" | ||||
#include "graphics/ModelAbstract.h" | #include "graphics/ModelAbstract.h" | ||||
#include "graphics/ObjectEntry.h" | #include "graphics/ObjectEntry.h" | ||||
#include "graphics/Overlay.h" | |||||
#include "graphics/Unit.h" | #include "graphics/Unit.h" | ||||
#include "maths/BoundingSphere.h" | |||||
#include "maths/Frustum.h" | #include "maths/Frustum.h" | ||||
#include "maths/Matrix3D.h" | #include "maths/Matrix3D.h" | ||||
#include "ps/GameSetup/Config.h" | #include "ps/GameSetup/Config.h" | ||||
#include "ps/Profile.h" | #include "ps/Profile.h" | ||||
#include "renderer/RenderingOptions.h" | #include "renderer/RenderingOptions.h" | ||||
#include "renderer/Scene.h" | #include "renderer/Scene.h" | ||||
#include "tools/atlas/GameInterface/GameLoop.h" | #include "tools/atlas/GameInterface/GameLoop.h" | ||||
/** | void CCmpUnitRenderer::ClassInit(CComponentManager& componentManager) | ||||
* Efficiently(ish) renders all the units in the world. | |||||
* | |||||
* The class maintains a list of all units that currently exist, and the data | |||||
* needed for frustum-culling them. To minimise the amount of work done per | |||||
* frame (despite a unit's interpolated position changing every frame), the | |||||
* culling data is only updated once per turn: we store the position at the | |||||
* start of the turn, and the position at the end of the turn, and assume the | |||||
* unit might be anywhere between those two points (linearly). | |||||
* | |||||
* (Note this is a slightly invalid assumption: units don't always move linearly, | |||||
* since their interpolated position depends on terrain and water. But over a | |||||
* single turn it's probably going to be a good enough approximation, and will | |||||
* only break for units that both start and end the turn off-screen.) | |||||
* | |||||
* We want to ignore rotation entirely, since it's a complex function of | |||||
* interpolated position and terrain. So we store a bounding sphere, which | |||||
* is rotation-independent, instead of a bounding box. | |||||
*/ | |||||
class CCmpUnitRenderer final : public ICmpUnitRenderer | |||||
{ | |||||
public: | |||||
struct SUnit | |||||
{ | |||||
CEntityHandle entity; | |||||
CUnit* actor; | |||||
int flags; | |||||
/** | |||||
* m_FrameNumber from when the model's transform was last updated. | |||||
* This is used to avoid recomputing it multiple times per frame | |||||
* if a model is visible in multiple cull groups. | |||||
*/ | |||||
int lastTransformFrame; | |||||
/** | |||||
* Worst-case bounding shape, relative to position. Needs to account | |||||
* for all possible animations, orientations, etc. | |||||
*/ | |||||
CBoundingSphere boundsApprox; | |||||
/** | |||||
* Cached LOS visibility status. | |||||
*/ | |||||
LosVisibility visibility; | |||||
bool visibilityDirty; | |||||
/** | |||||
* Whether the unit has a valid position. If false, pos0 and pos1 | |||||
* are meaningless. | |||||
*/ | |||||
bool inWorld; | |||||
/** | |||||
* World-space positions to interpolate between. | |||||
*/ | |||||
CVector3D pos0; | |||||
CVector3D pos1; | |||||
/** | |||||
* Bounds encompassing the unit's bounds when it is anywhere between | |||||
* pos0 and pos1. | |||||
*/ | |||||
CBoundingSphere sweptBounds; | |||||
/** | |||||
* For debug overlay. | |||||
*/ | |||||
bool culled; | |||||
}; | |||||
std::vector<SUnit> m_Units; | |||||
std::vector<tag_t> m_UnitTagsFree; | |||||
int m_FrameNumber; | |||||
float m_FrameOffset; | |||||
bool m_EnableDebugOverlays; | |||||
std::vector<SOverlaySphere> m_DebugSpheres; | |||||
static void ClassInit(CComponentManager& componentManager) | |||||
{ | { | ||||
componentManager.SubscribeToMessageType(MT_TurnStart); | componentManager.SubscribeToMessageType(MT_TurnStart); | ||||
componentManager.SubscribeToMessageType(MT_Interpolate); | componentManager.SubscribeToMessageType(MT_Interpolate); | ||||
componentManager.SubscribeToMessageType(MT_RenderSubmit); | componentManager.SubscribeToMessageType(MT_RenderSubmit); | ||||
} | } | ||||
DEFAULT_COMPONENT_ALLOCATOR(UnitRenderer) | IComponent* CCmpUnitRenderer::Allocate(const ScriptInterface&, JS::HandleValue) | ||||
{ | |||||
return nullptr; | |||||
} | |||||
void CCmpUnitRenderer::Deallocate(IComponent*) | |||||
{} | |||||
static std::string GetSchema() | int CCmpUnitRenderer::GetComponentTypeId() const | ||||
{ | |||||
return CID_UnitRenderer; | |||||
} | |||||
std::string CCmpUnitRenderer::GetSchema() | |||||
{ | { | ||||
return "<a:component type='system'/><empty/>"; | return "<a:component type='system'/><empty/>"; | ||||
} | } | ||||
void Init(const CParamNode& UNUSED(paramNode)) override | void CCmpUnitRenderer::Init(const CParamNode& UNUSED(paramNode)) | ||||
{ | { | ||||
m_FrameNumber = 0; | m_FrameNumber = 0; | ||||
m_FrameOffset = 0.0f; | m_FrameOffset = 0.0f; | ||||
m_EnableDebugOverlays = false; | m_EnableDebugOverlays = false; | ||||
} | } | ||||
void Deinit() override | void CCmpUnitRenderer::Deinit() | ||||
{ | {} | ||||
} | |||||
void Serialize(ISerializer& UNUSED(serialize)) override | void CCmpUnitRenderer::Serialize(ISerializer& UNUSED(serialize)) | ||||
{ | {} | ||||
} | |||||
void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize)) override | void CCmpUnitRenderer::Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize)) | ||||
{ | { | ||||
Init(paramNode); | Init(paramNode); | ||||
} | } | ||||
void HandleMessage(const CMessage& msg, bool UNUSED(global)) override | void CCmpUnitRenderer::HandleMessage(const CMessage& msg, bool UNUSED(global)) | ||||
{ | { | ||||
switch (msg.GetType()) | switch (msg.GetType()) | ||||
{ | { | ||||
case MT_TurnStart: | case MT_TurnStart: | ||||
{ | { | ||||
TurnStart(); | TurnStart(); | ||||
break; | break; | ||||
} | } | ||||
case MT_Interpolate: | case MT_Interpolate: | ||||
{ | { | ||||
const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg); | const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg); | ||||
Interpolate(msgData.deltaSimTime, msgData.offset); | Interpolate(msgData.deltaSimTime, msgData.offset); | ||||
break; | break; | ||||
} | } | ||||
case MT_RenderSubmit: | case MT_RenderSubmit: | ||||
{ | { | ||||
const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg); | const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg); | ||||
RenderSubmit(msgData.collector, msgData.frustum, msgData.culling); | RenderSubmit(msgData.collector, msgData.frustum, msgData.culling); | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
SUnit* LookupUnit(tag_t tag) | auto CCmpUnitRenderer::LookupUnit(tag_t tag) -> SUnit* | ||||
{ | { | ||||
if (tag.n < 1 || tag.n - 1 >= m_Units.size()) | if (tag.n < 1 || tag.n - 1 >= m_Units.size()) | ||||
return NULL; | return NULL; | ||||
return &m_Units[tag.n - 1]; | return &m_Units[tag.n - 1]; | ||||
} | } | ||||
vladislavbelov: We use that syntax only for lambdas. Also it looks inconsistent with `void` functions. | |||||
tag_t AddUnit(CEntityHandle entity, CUnit* actor, const CBoundingSphere& boundsApprox, int flags) override | auto CCmpUnitRenderer::AddUnit(CEntityHandle entity, CUnit* actor, const CBoundingSphere& boundsApprox, | ||||
int flags) -> tag_t | |||||
{ | { | ||||
ENSURE(actor != NULL); | ENSURE(actor != NULL); | ||||
tag_t tag; | tag_t tag; | ||||
if (!m_UnitTagsFree.empty()) | if (!m_UnitTagsFree.empty()) | ||||
{ | { | ||||
tag = m_UnitTagsFree.back(); | tag = m_UnitTagsFree.back(); | ||||
m_UnitTagsFree.pop_back(); | m_UnitTagsFree.pop_back(); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
m_Units.push_back(SUnit()); | m_Units.push_back(SUnit()); | ||||
tag.n = m_Units.size(); | tag.n = m_Units.size(); | ||||
} | } | ||||
SUnit* unit = LookupUnit(tag); | SUnit* unit = LookupUnit(tag); | ||||
unit->entity = entity; | unit->entity = entity; | ||||
unit->actor = actor; | unit->actor = actor; | ||||
unit->lastTransformFrame = -1; | unit->lastTransformFrame = -1; | ||||
unit->flags = flags; | unit->flags = flags; | ||||
unit->boundsApprox = boundsApprox; | unit->boundsApprox = boundsApprox; | ||||
unit->inWorld = false; | unit->inWorld = false; | ||||
unit->visibilityDirty = true; | unit->visibilityDirty = true; | ||||
unit->pos0 = unit->pos1 = CVector3D(); | unit->pos0 = unit->pos1 = CVector3D(); | ||||
return tag; | return tag; | ||||
} | } | ||||
void RemoveUnit(tag_t tag) override | void CCmpUnitRenderer::RemoveUnit(tag_t tag) | ||||
{ | { | ||||
SUnit* unit = LookupUnit(tag); | SUnit* unit = LookupUnit(tag); | ||||
unit->actor = NULL; | unit->actor = NULL; | ||||
unit->inWorld = false; | unit->inWorld = false; | ||||
m_UnitTagsFree.push_back(tag); | m_UnitTagsFree.push_back(tag); | ||||
} | } | ||||
void RecomputeSweptBounds(SUnit* unit) | void CCmpUnitRenderer::RecomputeSweptBounds(SUnit* unit) | ||||
{ | { | ||||
// Compute the bounding sphere of the capsule formed by | // Compute the bounding sphere of the capsule formed by | ||||
// sweeping boundsApprox from pos0 to pos1 | // sweeping boundsApprox from pos0 to pos1 | ||||
CVector3D mid = (unit->pos0 + unit->pos1) * 0.5f + unit->boundsApprox.GetCenter(); | CVector3D mid = (unit->pos0 + unit->pos1) * 0.5f + unit->boundsApprox.GetCenter(); | ||||
float radius = (unit->pos1 - unit->pos0).Length() * 0.5f + unit->boundsApprox.GetRadius(); | float radius = (unit->pos1 - unit->pos0).Length() * 0.5f + unit->boundsApprox.GetRadius(); | ||||
unit->sweptBounds = CBoundingSphere(mid, radius); | unit->sweptBounds = CBoundingSphere(mid, radius); | ||||
} | } | ||||
void UpdateUnit(tag_t tag, CUnit* actor, const CBoundingSphere& boundsApprox) override | void CCmpUnitRenderer::UpdateUnit(tag_t tag, CUnit* actor, const CBoundingSphere& boundsApprox) | ||||
{ | { | ||||
SUnit* unit = LookupUnit(tag); | SUnit* unit = LookupUnit(tag); | ||||
unit->actor = actor; | unit->actor = actor; | ||||
unit->boundsApprox = boundsApprox; | unit->boundsApprox = boundsApprox; | ||||
RecomputeSweptBounds(unit); | RecomputeSweptBounds(unit); | ||||
} | } | ||||
void UpdateUnitPos(tag_t tag, bool inWorld, const CVector3D& pos0, const CVector3D& pos1) override | void CCmpUnitRenderer::UpdateUnitPos(tag_t tag, bool inWorld, const CVector3D& pos0, const CVector3D& pos1) | ||||
{ | { | ||||
SUnit* unit = LookupUnit(tag); | SUnit* unit = LookupUnit(tag); | ||||
unit->inWorld = inWorld; | unit->inWorld = inWorld; | ||||
unit->pos0 = pos0; | unit->pos0 = pos0; | ||||
unit->pos1 = pos1; | unit->pos1 = pos1; | ||||
unit->visibilityDirty = true; | unit->visibilityDirty = true; | ||||
RecomputeSweptBounds(unit); | RecomputeSweptBounds(unit); | ||||
} | } | ||||
void TurnStart(); | float CCmpUnitRenderer::GetFrameOffset() const | ||||
void Interpolate(float frameTime, float frameOffset); | |||||
void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling); | |||||
void UpdateVisibility(SUnit& unit) const; | |||||
float GetFrameOffset() const override | |||||
{ | { | ||||
return m_FrameOffset; | return m_FrameOffset; | ||||
} | } | ||||
void SetDebugOverlay(bool enabled) override | void CCmpUnitRenderer::SetDebugOverlay(bool enabled) | ||||
{ | { | ||||
m_EnableDebugOverlays = enabled; | m_EnableDebugOverlays = enabled; | ||||
} | } | ||||
void PickAllEntitiesAtPoint(std::vector<std::pair<CEntityHandle, CVector3D> >& outEntities, const CVector3D& origin, const CVector3D& dir, bool allowEditorSelectables) const override | void CCmpUnitRenderer::PickAllEntitiesAtPoint(std::vector<std::pair<CEntityHandle, CVector3D>>& outEntities, | ||||
const CVector3D& origin, const CVector3D& dir, bool allowEditorSelectables) const | |||||
{ | { | ||||
// First, make a rough test with the worst-case bounding boxes to pick all | // First, make a rough test with the worst-case bounding boxes to pick all | ||||
// entities/models that could possibly be hit by the ray. | // entities/models that could possibly be hit by the ray. | ||||
std::vector<const SUnit*> candidates; | std::vector<const SUnit*> candidates; | ||||
for (const SUnit& unit : m_Units) | for (const SUnit& unit : m_Units) | ||||
{ | { | ||||
if (!unit.actor || !unit.inWorld) | if (!unit.actor || !unit.inWorld) | ||||
continue; | continue; | ||||
if (unit.sweptBounds.RayIntersect(origin, dir)) | if (unit.sweptBounds.RayIntersect(origin, dir)) | ||||
candidates.push_back(&unit); | candidates.push_back(&unit); | ||||
} | } | ||||
// Now make a more precise test to get rid of the remaining false positives | // Now make a more precise test to get rid of the remaining false positives | ||||
float tmin, tmax; | float tmin, tmax; | ||||
CVector3D center; | CVector3D center; | ||||
for (size_t i = 0; i< candidates.size(); ++i) | for (size_t i = 0; i< candidates.size(); ++i) | ||||
{ | { | ||||
const SUnit& unit = *candidates[i]; | const SUnit& unit = *candidates[i]; | ||||
CmpPtr<ICmpVisual> cmpVisual(unit.entity); | CmpPtr<ICmpVisual> cmpVisual(unit.entity); | ||||
if (!cmpVisual) | if (!cmpVisual) | ||||
continue; | continue; | ||||
CBoundingBoxOriented selectionBox = cmpVisual->GetSelectionBox(); | CBoundingBoxOriented selectionBox = cmpVisual->GetSelectionBox(); | ||||
if (selectionBox.IsEmpty()) | if (selectionBox.IsEmpty()) | ||||
{ | { | ||||
if (!allowEditorSelectables) | if (!allowEditorSelectables) | ||||
continue; | continue; | ||||
// Fall back to using old AABB selection method for decals | // Fall back to using old AABB selection method for decals | ||||
// see: http://trac.wildfiregames.com/ticket/1032 | // see: http://trac.wildfiregames.com/ticket/1032 | ||||
// Decals are flat objects without a selectionShape defined, | // Decals are flat objects without a selectionShape defined, | ||||
// but they should still be selectable in the editor to move them | // but they should still be selectable in the editor to move them | ||||
// around or delete them after they are placed. | // around or delete them after they are placed. | ||||
// Check campaigns/labels/ in the Actors tab of atlas for examples. | // Check campaigns/labels/ in the Actors tab of atlas for examples. | ||||
CBoundingBoxAligned aABBox = cmpVisual->GetBounds(); | CBoundingBoxAligned aABBox = cmpVisual->GetBounds(); | ||||
if (aABBox.IsEmpty()) | if (aABBox.IsEmpty()) | ||||
continue; | continue; | ||||
if (!aABBox.RayIntersect(origin, dir, tmin, tmax)) | if (!aABBox.RayIntersect(origin, dir, tmin, tmax)) | ||||
continue; | continue; | ||||
aABBox.GetCenter(center); | aABBox.GetCenter(center); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
if (!selectionBox.RayIntersect(origin, dir, tmin, tmax)) | if (!selectionBox.RayIntersect(origin, dir, tmin, tmax)) | ||||
continue; | continue; | ||||
center = selectionBox.m_Center; | center = selectionBox.m_Center; | ||||
} | } | ||||
outEntities.emplace_back(unit.entity, center); | outEntities.emplace_back(unit.entity, center); | ||||
} | } | ||||
} | } | ||||
}; | |||||
void CCmpUnitRenderer::TurnStart() | void CCmpUnitRenderer::TurnStart() | ||||
{ | { | ||||
PROFILE3("UnitRenderer::TurnStart"); | PROFILE3("UnitRenderer::TurnStart"); | ||||
// Assume units have stopped moving after the previous turn. If that assumption is not | // Assume units have stopped moving after the previous turn. If that assumption is not | ||||
// correct, we will get a UpdateUnitPos to tell us about its movement in the new turn. | // correct, we will get a UpdateUnitPos to tell us about its movement in the new turn. | ||||
for (size_t i = 0; i < m_Units.size(); i++) | for (size_t i = 0; i < m_Units.size(); i++) | ||||
▲ Show 20 Lines • Show All 137 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator
We use that syntax only for lambdas. Also it looks inconsistent with void functions.