New unit renderer.

Instead of each CCmpVisualActor rendering itself individually, collect
all the units in a single CCmpUnitRenderer. This avoids the overhead of
doing Interpolate/RenderSubmit calls every frame for every object in the
world. It also allows more efficient culling.

CCmpUnitRenderer knows the positions of each object at the start and end
of each turn, and computes the bounding sphere of the object along that
path. That allows quick culling without recomputing the precise
interpolated transform every frame. (In the future it could be improved
much more.)

Clarify and clean up the sending of PositionChanged messages, and add
new InterpolatedPositionChanged.

Remove the forceFloating parameter from GetInterpolatedTransform, since
it doesn't fit the new design. Replace it with a (non-synchronised) flag
in CCmpPosition.

Move construction progress from CCmpVisualActor to CCmpPosition, so that
it consistently affects all position/transform computation.

Refs #2337.

This was SVN commit r15265.
This commit is contained in:
Ykkrosh 2014-06-01 18:24:50 +00:00
parent d117d96d22
commit 1882f28504
19 changed files with 856 additions and 251 deletions

View File

@ -41,9 +41,9 @@ Foundation.prototype.InitialiseConstruction = function(owner, template)
Foundation.prototype.OnHealthChanged = function(msg)
{
// Gradually reveal the final building preview
var cmpPreviewVisual = Engine.QueryInterface(this.previewEntity, IID_Visual);
if (cmpPreviewVisual)
cmpPreviewVisual.SetConstructionProgress(this.GetBuildProgress());
var cmpPosition = Engine.QueryInterface(this.previewEntity, IID_Position);
if (cmpPosition)
cmpPosition.SetConstructionProgress(this.GetBuildProgress());
Engine.PostMessage(this.entity, MT_FoundationProgressChanged, { "to": this.GetBuildPercentage() });
};
@ -218,10 +218,12 @@ Foundation.prototype.Build = function(builderEnt, work)
cmpPreviewOwnership.SetOwner(cmpFoundationOwnership.GetOwner());
// Initially hide the preview underground
var cmpPreviewPosition = Engine.QueryInterface(this.previewEntity, IID_Position);
cmpPreviewPosition.SetConstructionProgress(0.0);
var cmpPreviewVisual = Engine.QueryInterface(this.previewEntity, IID_Visual);
if (cmpPreviewVisual)
{
cmpPreviewVisual.SetConstructionProgress(0.0);
cmpPreviewVisual.SetActorSeed(cmpFoundationVisual.GetActorSeed());
cmpPreviewVisual.SelectAnimation("scaffold", false, 1.0, "");
}
@ -229,7 +231,6 @@ Foundation.prototype.Build = function(builderEnt, work)
var cmpFoundationPosition = Engine.QueryInterface(this.entity, IID_Position);
var pos = cmpFoundationPosition.GetPosition();
var rot = cmpFoundationPosition.GetRotation();
var cmpPreviewPosition = Engine.QueryInterface(this.previewEntity, IID_Position);
cmpPreviewPosition.SetYRotation(rot.y);
cmpPreviewPosition.SetXZRotation(rot.x, rot.z);
cmpPreviewPosition.JumpTo(pos.x, pos.z);

View File

@ -775,7 +775,7 @@ void CGameView::Update(const float deltaRealTime)
{
// Get the most recent interpolated position
float frameOffset = m->Game->GetSimulation2()->GetLastFrameOffset();
CMatrix3D transform = cmpPosition->GetInterpolatedTransform(frameOffset, false);
CMatrix3D transform = cmpPosition->GetInterpolatedTransform(frameOffset);
CVector3D pos = transform.GetTranslation();
if (m->FollowFirstPerson)

View File

@ -27,6 +27,8 @@
#include "simulation2/components/ICmpPathfinder.h"
#include "maths/Vector3D.h"
#define DEFAULT_MESSAGE_IMPL(name) \
virtual int GetType() const { return MT_##name; } \
virtual const char* GetScriptHandlerName() const { return "On" #name; } \
@ -255,7 +257,8 @@ public:
};
/**
* Sent during TurnStart.
* Sent by CCmpPosition whenever anything has changed that will affect the
* return value of GetPosition2D() or GetRotation().Y
*
* If @c inWorld is false, then the other fields are invalid and meaningless.
* Otherwise they represent the current position.
@ -276,13 +279,33 @@ public:
entity_angle_t a;
};
/**
* Sent by CCmpPosition whenever anything has changed that will affect the
* return value of GetInterpolatedTransform()
*/
class CMessageInterpolatedPositionChanged : public CMessage
{
public:
DEFAULT_MESSAGE_IMPL(InterpolatedPositionChanged)
CMessageInterpolatedPositionChanged(entity_id_t entity, bool inWorld, const CVector3D& pos0, const CVector3D& pos1) :
entity(entity), inWorld(inWorld), pos0(pos0), pos1(pos1)
{
}
entity_id_t entity;
bool inWorld;
CVector3D pos0;
CVector3D pos1;
};
/*Sent whenever the territory type (neutral,own,enemy) differs from the former type*/
class CMessageTerritoryPositionChanged : public CMessage
{
public:
DEFAULT_MESSAGE_IMPL(TerritoryPositionChanged)
CMessageTerritoryPositionChanged(entity_id_t entity, player_id_t newTerritory) :
CMessageTerritoryPositionChanged(entity_id_t entity, player_id_t newTerritory) :
entity(entity), newTerritory(newTerritory)
{
}

View File

@ -43,6 +43,7 @@ MESSAGE(Create)
MESSAGE(Destroy)
MESSAGE(OwnershipChanged)
MESSAGE(PositionChanged)
MESSAGE(InterpolatedPositionChanged)
MESSAGE(TerritoryPositionChanged)
MESSAGE(MotionChanged)
MESSAGE(RangeUpdate)
@ -162,6 +163,9 @@ INTERFACE(UnitMotion)
COMPONENT(UnitMotion) // must be after Obstruction
COMPONENT(UnitMotionScripted)
INTERFACE(UnitRenderer)
COMPONENT(UnitRenderer)
INTERFACE(Vision)
COMPONENT(Vision)

View File

@ -131,7 +131,7 @@ public:
}
// Find the precise position of the unit
CMatrix3D transform(cmpPosition->GetInterpolatedTransform(frameOffset, false));
CMatrix3D transform(cmpPosition->GetInterpolatedTransform(frameOffset));
CVector3D position(transform.GetTranslation());
// Move all the sprites to the desired offset relative to the unit

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2013 Wildfire Games.
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -23,6 +23,8 @@
#include "simulation2/MessageTypes.h"
#include "ICmpTerrain.h"
#include "ICmpTerritoryManager.h"
#include "ICmpVisual.h"
#include "ICmpWaterManager.h"
#include "graphics/Terrain.h"
@ -32,7 +34,6 @@
#include "maths/Vector3D.h"
#include "maths/Vector2D.h"
#include "ps/CLogger.h"
#include "ICmpTerritoryManager.h"
/**
* Basic ICmpPosition implementation.
@ -44,6 +45,9 @@ public:
{
componentManager.SubscribeToMessageType(MT_TurnStart);
componentManager.SubscribeToMessageType(MT_Interpolate);
componentManager.SubscribeToMessageType(MT_TerrainChanged);
componentManager.SubscribeToMessageType(MT_WaterChanged);
componentManager.SubscribeToMessageType(MT_Deserialized);
// TODO: if this component turns out to be a performance issue, it should
// be optimised by creating a new PositionStatic component that doesn't subscribe
@ -60,7 +64,7 @@ public:
UPRIGHT = 0,
PITCH = 1,
PITCH_ROLL = 2,
ROLL=3,
ROLL = 3,
} m_AnchorType;
bool m_Floating;
@ -76,6 +80,8 @@ public:
entity_pos_t m_Y, m_LastYDifference; // either the relative or the absolute Y coordinate
bool m_RelativeToGround; // whether m_Y is relative to terrain/water plane, or an absolute height
fixed m_ConstructionProgress;
// when the entity is a turret, only m_RotY is used, and this is the rotation
// relative to the parent entity
entity_angle_t m_RotX, m_RotY, m_RotZ;
@ -86,9 +92,10 @@ public:
CFixedVector3D m_TurretPosition;
std::set<entity_id_t> m_Turrets;
// not serialized:
// Not serialized:
float m_InterpolatedRotX, m_InterpolatedRotY, m_InterpolatedRotZ;
float m_LastInterpolatedRotX, m_LastInterpolatedRotZ; // not serialized
float m_LastInterpolatedRotX, m_LastInterpolatedRotZ;
bool m_ActorFloating;
static std::string GetSchema()
{
@ -147,6 +154,8 @@ public:
m_TurretParent = INVALID_ENTITY;
m_TurretPosition = CFixedVector3D();
m_ActorFloating = false;
}
virtual void Deinit()
@ -172,6 +181,7 @@ public:
serialize.NumberFixed_Unbounded("altitude", m_Y);
serialize.Bool("relative", m_RelativeToGround);
serialize.Bool("floating", m_Floating);
serialize.NumberFixed_Unbounded("constructionprogress", m_ConstructionProgress);
if (serialize.IsDebug())
{
@ -227,6 +237,7 @@ public:
deserialize.NumberFixed_Unbounded("altitude", m_Y);
deserialize.Bool("relative", m_RelativeToGround);
deserialize.Bool("floating", m_Floating);
deserialize.NumberFixed_Unbounded("constructionprogress", m_ConstructionProgress);
// TODO: should there be range checks on all these values?
m_InterpolatedRotY = m_RotY.ToFloat();
@ -302,6 +313,11 @@ public:
return m_TurretParent;
}
void Deserialized()
{
AdvertiseInterpolatedPositionChanges();
}
virtual bool IsInWorld()
{
return m_InWorld;
@ -312,6 +328,7 @@ public:
m_InWorld = false;
AdvertisePositionChanges();
AdvertiseInterpolatedPositionChanges();
}
virtual void MoveTo(entity_pos_t x, entity_pos_t z)
@ -328,6 +345,7 @@ public:
}
AdvertisePositionChanges();
AdvertiseInterpolatedPositionChanges();
}
virtual void MoveAndTurnTo(entity_pos_t x, entity_pos_t z, entity_angle_t ry)
@ -359,6 +377,8 @@ public:
m_LastInterpolatedRotZ = m_InterpolatedRotZ;
AdvertisePositionChanges();
AdvertiseInterpolatedPositionChanges();
AdvertiseInterpolatedPositionChanges();
}
virtual void SetHeightOffset(entity_pos_t dy)
@ -366,7 +386,7 @@ public:
// subtract the offset and replace with a new offset
m_LastYDifference = dy - GetHeightOffset();
m_Y += m_LastYDifference;
AdvertisePositionChanges();
AdvertiseInterpolatedPositionChanges();
}
virtual entity_pos_t GetHeightOffset()
@ -393,7 +413,7 @@ public:
// subtract the absolute height and replace it with a new absolute height
m_LastYDifference = y - GetHeightFixed();
m_Y += m_LastYDifference;
AdvertisePositionChanges();
AdvertiseInterpolatedPositionChanges();
}
virtual entity_pos_t GetHeightFixed()
@ -426,6 +446,7 @@ public:
m_Y = relative ? GetHeightOffset() : GetHeightFixed();
m_RelativeToGround = relative;
m_LastYDifference = entity_pos_t::Zero();
AdvertiseInterpolatedPositionChanges();
}
virtual bool IsFloating()
@ -436,6 +457,19 @@ public:
virtual void SetFloating(bool flag)
{
m_Floating = flag;
AdvertiseInterpolatedPositionChanges();
}
virtual void SetActorFloating(bool flag)
{
m_ActorFloating = flag;
AdvertiseInterpolatedPositionChanges();
}
virtual void SetConstructionProgress(fixed progress)
{
m_ConstructionProgress = progress;
AdvertiseInterpolatedPositionChanges();
}
virtual CFixedVector3D GetPosition()
@ -529,8 +563,6 @@ public:
m_LastInterpolatedRotX = m_InterpolatedRotX;
m_LastInterpolatedRotZ = m_InterpolatedRotZ;
}
AdvertisePositionChanges();
}
virtual CFixedVector3D GetRotation()
@ -556,6 +588,36 @@ public:
return CFixedVector2D(m_X - m_LastX, m_Z - m_LastZ).Length();
}
float GetConstructionProgressOffset(const CVector3D& pos)
{
if (m_ConstructionProgress.IsZero())
return 0.0f;
CmpPtr<ICmpVisual> cmpVisual(GetEntityHandle());
if (!cmpVisual)
return 0.0f;
// We use selection boxes to calculate the model size, since the model could be offset
// TODO: this annoyingly shows decals, would be nice to hide them
CBoundingBoxOriented bounds = cmpVisual->GetSelectionBox();
if (bounds.IsEmpty())
return 0.0f;
float dy = 2.0f * bounds.m_HalfSizes.Y;
// If this is a floating unit, we want it to start all the way under the terrain,
// so find the difference between its current position and the terrain
CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
if (cmpTerrain && (m_Floating || m_ActorFloating))
{
float ground = cmpTerrain->GetExactGroundLevel(pos.X, pos.Z);
dy += std::max(0.f, pos.Y - ground);
}
return (m_ConstructionProgress.ToFloat() - 1.0f) * dy;
}
virtual void GetInterpolatedPosition2D(float frameOffset, float& x, float& z, float& rotY)
{
if (!m_InWorld)
@ -570,7 +632,7 @@ public:
rotY = m_InterpolatedRotY;
}
virtual CMatrix3D GetInterpolatedTransform(float frameOffset, bool forceFloating)
virtual CMatrix3D GetInterpolatedTransform(float frameOffset)
{
if (m_TurretParent != INVALID_ENTITY)
{
@ -591,7 +653,7 @@ public:
}
else
{
CMatrix3D parentTransformMatrix = cmpPosition->GetInterpolatedTransform(frameOffset, forceFloating);
CMatrix3D parentTransformMatrix = cmpPosition->GetInterpolatedTransform(frameOffset);
CMatrix3D ownTransformation = CMatrix3D();
ownTransformation.SetYRotation(m_InterpolatedRotY);
ownTransformation.Translate(-m_TurretPosition.X.ToFloat(), m_TurretPosition.Y.ToFloat(), -m_TurretPosition.Z.ToFloat());
@ -617,7 +679,7 @@ public:
if (cmpTerrain)
baseY = cmpTerrain->GetExactGroundLevel(x, z);
if (m_Floating || forceFloating)
if (m_Floating || m_ActorFloating)
{
CmpPtr<ICmpWaterManager> cmpWaterManager(GetSystemEntity());
if (cmpWaterManager)
@ -634,12 +696,54 @@ public:
m.SetXRotation(Interpolate(m_LastInterpolatedRotX, m_InterpolatedRotX, frameOffset));
m.RotateZ(Interpolate(m_LastInterpolatedRotZ, m_InterpolatedRotZ, frameOffset));
CVector3D pos(x, y, z);
pos.Y += GetConstructionProgressOffset(pos);
m.RotateY(rotY + (float)M_PI);
m.Translate(CVector3D(x, y, z));
m.Translate(pos);
return m;
}
void GetInterpolatedPositions(CVector3D& pos0, CVector3D& pos1)
{
float baseY0 = 0;
float baseY1 = 0;
float x0 = m_LastX.ToFloat();
float z0 = m_LastZ.ToFloat();
float x1 = m_X.ToFloat();
float z1 = m_Z.ToFloat();
if (m_RelativeToGround)
{
CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
if (cmpTerrain)
{
baseY0 = cmpTerrain->GetExactGroundLevel(x0, z0);
baseY1 = cmpTerrain->GetExactGroundLevel(x1, z1);
}
if (m_Floating || m_ActorFloating)
{
CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY);
if (cmpWaterManager)
{
baseY0 = std::max(baseY0, cmpWaterManager->GetExactWaterLevel(x0, z0));
baseY1 = std::max(baseY1, cmpWaterManager->GetExactWaterLevel(x1, z1));
}
}
}
float y0 = baseY0 + m_Y.ToFloat() + m_LastYDifference.ToFloat();
float y1 = baseY1 + m_Y.ToFloat();
pos0 = CVector3D(x0, y0, z0);
pos1 = CVector3D(x1, y1, z1);
pos0.Y += GetConstructionProgressOffset(pos0);
pos1.Y += GetConstructionProgressOffset(pos1);
}
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
@ -716,10 +820,29 @@ public:
}
break;
}
case MT_TerrainChanged:
case MT_WaterChanged:
{
AdvertiseInterpolatedPositionChanges();
break;
}
case MT_Deserialized:
{
Deserialized();
break;
}
}
}
};
private:
/**
* This must be called after changing anything that will affect the
* return value of GetPosition2D() or GetRotation().Y:
* - m_InWorld
* - m_X, m_Z
* - m_RotY
*/
void AdvertisePositionChanges()
{
for (std::set<entity_id_t>::const_iterator it = m_Turrets.begin(); it != m_Turrets.end(); ++it)
@ -740,6 +863,33 @@ private:
}
}
/**
* This must be called after changing anything that will affect the
* return value of GetInterpolatedPositions():
* - m_InWorld
* - m_X, m_Z
* - m_LastX, m_LastZ
* - m_Y, m_LastYDifference, m_RelativeToGround
* - If m_RelativeToGround, then the ground under this unit
* - If m_RelativeToGround && m_Float, then the water level
*/
void AdvertiseInterpolatedPositionChanges()
{
if (m_InWorld)
{
CVector3D pos0, pos1;
GetInterpolatedPositions(pos0, pos1);
CMessageInterpolatedPositionChanged msg(GetEntityId(), true, pos0, pos1);
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
}
else
{
CMessageInterpolatedPositionChanged msg(GetEntityId(), false, CVector3D(), CVector3D());
GetSimContext().GetComponentManager().PostMessage(GetEntityId(), msg);
}
}
void UpdateXZRotation()
{
if (!m_InWorld)

View File

@ -0,0 +1,400 @@
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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/>.
*/
#include "precompiled.h"
#include "simulation2/system/Component.h"
#include "ICmpUnitRenderer.h"
#include "simulation2/MessageTypes.h"
#include "ICmpPosition.h"
#include "ICmpRangeManager.h"
#include "ICmpSelectable.h"
#include "ICmpVision.h"
#include "graphics/Frustum.h"
#include "graphics/ModelAbstract.h"
#include "graphics/ObjectEntry.h"
#include "graphics/Overlay.h"
#include "graphics/Unit.h"
#include "maths/BoundingSphere.h"
#include "maths/Matrix3D.h"
#include "renderer/Scene.h"
#include "tools/atlas/GameInterface/GameLoop.h"
/**
* 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 : public ICmpUnitRenderer
{
public:
struct SUnit
{
CEntityHandle entity;
CUnit* actor;
int flags;
/**
* Worst-case bounding shape, relative to position. Needs to account
* for all possible animations, orientations, etc.
*/
CBoundingSphere boundsApprox;
/**
* Cached LOS visibility status.
*/
ICmpRangeManager::ELosVisibility 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;
float m_FrameOffset;
bool m_EnableDebugOverlays;
std::vector<SOverlaySphere> m_DebugSpheres;
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(MT_TurnStart);
componentManager.SubscribeToMessageType(MT_Interpolate);
componentManager.SubscribeToMessageType(MT_RenderSubmit);
}
DEFAULT_COMPONENT_ALLOCATOR(UnitRenderer)
static std::string GetSchema()
{
return "<a:component type='system'/><empty/>";
}
virtual void Init(const CParamNode& UNUSED(paramNode))
{
m_FrameOffset = 0.0f;
m_EnableDebugOverlays = false;
}
virtual void Deinit()
{
}
virtual void Serialize(ISerializer& UNUSED(serialize))
{
}
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
{
Init(paramNode);
}
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{
case MT_TurnStart:
{
TurnStart();
break;
}
case MT_Interpolate:
{
const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
Interpolate(msgData.deltaSimTime, msgData.offset);
break;
}
case MT_RenderSubmit:
{
const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
RenderSubmit(msgData.collector, msgData.frustum, msgData.culling);
break;
}
}
}
SUnit* LookupUnit(tag_t tag)
{
if (tag.n < 1 || tag.n - 1 >= m_Units.size())
return NULL;
return &m_Units[tag.n - 1];
}
virtual tag_t AddUnit(CEntityHandle entity, CUnit* actor, const CBoundingSphere& boundsApprox, int flags)
{
ENSURE(actor != NULL);
tag_t tag;
if (!m_UnitTagsFree.empty())
{
tag = m_UnitTagsFree.back();
m_UnitTagsFree.pop_back();
}
else
{
m_Units.push_back(SUnit());
tag.n = m_Units.size();
}
SUnit* unit = LookupUnit(tag);
unit->entity = entity;
unit->actor = actor;
unit->flags = flags;
unit->boundsApprox = boundsApprox;
unit->inWorld = false;
unit->visibilityDirty = true;
unit->pos0 = unit->pos1 = CVector3D();
return tag;
}
virtual void RemoveUnit(tag_t tag)
{
SUnit* unit = LookupUnit(tag);
unit->actor = NULL;
unit->inWorld = false;
m_UnitTagsFree.push_back(tag);
}
void RecomputeSweptBounds(SUnit* unit)
{
// Compute the bounding sphere of the capsule formed by
// sweeping boundsApprox from pos0 to pos1
CVector3D mid = (unit->pos0 + unit->pos1) * 0.5f + unit->boundsApprox.GetCenter();
float radius = (unit->pos1 - unit->pos0).Length() * 0.5f + unit->boundsApprox.GetRadius();
unit->sweptBounds = CBoundingSphere(mid, radius);
}
virtual void UpdateUnit(tag_t tag, CUnit* actor, const CBoundingSphere& boundsApprox)
{
SUnit* unit = LookupUnit(tag);
unit->actor = actor;
unit->boundsApprox = boundsApprox;
RecomputeSweptBounds(unit);
}
virtual void UpdateUnitPos(tag_t tag, bool inWorld, const CVector3D& pos0, const CVector3D& pos1)
{
SUnit* unit = LookupUnit(tag);
unit->inWorld = inWorld;
unit->pos0 = pos0;
unit->pos1 = pos1;
unit->visibilityDirty = true;
RecomputeSweptBounds(unit);
}
void TurnStart();
void Interpolate(float frameTime, float frameOffset);
void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling);
void UpdateVisibility(SUnit& unit);
virtual float GetFrameOffset()
{
return m_FrameOffset;
}
virtual void SetDebugOverlay(bool enabled)
{
m_EnableDebugOverlays = enabled;
}
};
void CCmpUnitRenderer::TurnStart()
{
PROFILE3("UnitRenderer::TurnStart");
// 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.
for (size_t i = 0; i < m_Units.size(); i++)
{
SUnit& unit = m_Units[i];
unit.pos0 = unit.pos1;
unit.sweptBounds = CBoundingSphere(unit.pos1, unit.boundsApprox.GetRadius());
// Visibility must be recomputed on the first frame during this turn
unit.visibilityDirty = true;
}
}
void CCmpUnitRenderer::Interpolate(float frameTime, float frameOffset)
{
PROFILE3("UnitRenderer::Interpolate");
m_FrameOffset = frameOffset;
// TODO: we shouldn't update all the animations etc for units that are off-screen
// (but need to be careful about e.g. sounds triggered by animations of off-screen
// units)
for (size_t i = 0; i < m_Units.size(); i++)
{
SUnit& unit = m_Units[i];
if (unit.actor)
unit.actor->UpdateModel(frameTime);
}
m_DebugSpheres.clear();
if (m_EnableDebugOverlays)
{
for (size_t i = 0; i < m_Units.size(); i++)
{
SUnit& unit = m_Units[i];
if (!(unit.actor && unit.inWorld))
continue;
SOverlaySphere sphere;
sphere.m_Center = unit.sweptBounds.GetCenter();
sphere.m_Radius = unit.sweptBounds.GetRadius();
if (unit.culled)
sphere.m_Color = CColor(1.0f, 0.5f, 0.5f, 0.5f);
else
sphere.m_Color = CColor(0.5f, 0.5f, 1.0f, 0.5f);
m_DebugSpheres.push_back(sphere);
}
}
}
void CCmpUnitRenderer::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling)
{
// TODO: need a coarse culling pass based on some kind of spatial data
// structure - that's the main point of this design. Once we've got a
// rough list of possibly-visible units, then we can do the more precise
// culling. (And once it's cheap enough, we can do multiple culling passes
// per frame - one for shadow generation, one for water reflections, etc.)
PROFILE3("UnitRenderer::RenderSubmit");
for (size_t i = 0; i < m_Units.size(); ++i)
{
SUnit& unit = m_Units[i];
unit.culled = true;
if (!(unit.actor && unit.inWorld))
continue;
if (!g_AtlasGameLoop->running && !g_RenderActors && (unit.flags & ACTOR_ONLY))
continue;
if (!g_AtlasGameLoop->running && (unit.flags & VISIBLE_IN_ATLAS_ONLY))
continue;
if (culling && !frustum.IsSphereVisible(unit.sweptBounds.GetCenter(), unit.sweptBounds.GetRadius()))
continue;
if (unit.visibilityDirty)
UpdateVisibility(unit);
if (unit.visibility == ICmpRangeManager::VIS_HIDDEN)
continue;
unit.culled = false;
CmpPtr<ICmpPosition> cmpPosition(unit.entity);
if (!cmpPosition)
continue;
CMatrix3D transform(cmpPosition->GetInterpolatedTransform(m_FrameOffset));
CModelAbstract& unitModel = unit.actor->GetModel();
unitModel.SetTransform(transform);
if (culling && !frustum.IsBoxVisible(CVector3D(0, 0, 0), unitModel.GetWorldBoundsRec()))
continue;
collector.SubmitRecursive(&unitModel);
}
for (size_t i = 0; i < m_DebugSpheres.size(); ++i)
collector.Submit(&m_DebugSpheres[i]);
}
void CCmpUnitRenderer::UpdateVisibility(SUnit& unit)
{
if (unit.inWorld)
{
// The 'always visible' flag means we should always render the unit
// (regardless of whether the LOS system thinks it's visible)
CmpPtr<ICmpVision> cmpVision(unit.entity);
if (cmpVision && cmpVision->GetAlwaysVisible())
unit.visibility = ICmpRangeManager::VIS_VISIBLE;
else
{
CmpPtr<ICmpRangeManager> cmpRangeManager(GetSystemEntity());
// Uncomment the following lines to prevent the models from popping into existence
// near the LOS boundary. Is rather resource intensive.
//if (cmpVision->GetRetainInFog())
// unit.visibility = ICmpRangeManager::VIS_VISIBLE;
//else
unit.visibility = cmpRangeManager->GetLosVisibility(unit.entity,
GetSimContext().GetCurrentDisplayedPlayer());
}
}
else
unit.visibility = ICmpRangeManager::VIS_HIDDEN;
// Change the visibility of the visual actor's selectable if it has one.
CmpPtr<ICmpSelectable> cmpSelectable(unit.entity);
if (cmpSelectable)
cmpSelectable->SetVisibility(unit.visibility != ICmpRangeManager::VIS_HIDDEN);
unit.visibilityDirty = false;
}
REGISTER_COMPONENT_TYPE(UnitRenderer)

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2013 Wildfire Games.
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -23,10 +23,9 @@
#include "simulation2/MessageTypes.h"
#include "ICmpFootprint.h"
#include "ICmpUnitRenderer.h"
#include "ICmpOwnership.h"
#include "ICmpPosition.h"
#include "ICmpRangeManager.h"
#include "ICmpSelectable.h"
#include "ICmpTemplateManager.h"
#include "ICmpTerrain.h"
#include "ICmpUnitMotion.h"
@ -41,25 +40,24 @@
#include "graphics/Unit.h"
#include "graphics/UnitAnimation.h"
#include "graphics/UnitManager.h"
#include "maths/BoundingSphere.h"
#include "maths/Matrix3D.h"
#include "maths/Vector3D.h"
#include "ps/CLogger.h"
#include "ps/GameSetup/Config.h"
#include "renderer/Scene.h"
#include "tools/atlas/GameInterface/GameLoop.h"
class CCmpVisualActor : public ICmpVisual
{
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(MT_Update_Final);
componentManager.SubscribeToMessageType(MT_Interpolate);
componentManager.SubscribeToMessageType(MT_RenderSubmit);
componentManager.SubscribeToMessageType(MT_InterpolatedPositionChanged);
componentManager.SubscribeToMessageType(MT_OwnershipChanged);
componentManager.SubscribeToMessageType(MT_ValueModification);
componentManager.SubscribeGloballyToMessageType(MT_TerrainChanged);
componentManager.SubscribeToMessageType(MT_TerrainChanged);
componentManager.SubscribeToMessageType(MT_Destroy);
}
DEFAULT_COMPONENT_ALLOCATOR(VisualActor)
@ -73,8 +71,6 @@ private:
std::map<std::string, std::string> m_AnimOverride;
ICmpRangeManager::ELosVisibility m_Visibility; // only valid between Interpolate and RenderSubmit
// Current animation state
fixed m_AnimRunThreshold; // if non-zero this is the special walk/run mode
std::string m_AnimName;
@ -87,15 +83,11 @@ private:
u32 m_Seed; // seed used for random variations
bool m_ConstructionPreview;
fixed m_ConstructionProgress;
bool m_VisibleInAtlasOnly;
bool m_IsActorOnly; // an in-world entity should not have this or it might not be rendered.
/// Whether the visual actor has been rendered at least once.
/// Necessary because the visibility update runs on simulation update,
/// which may not occur immediately if the game starts paused.
bool m_PreviouslyRendered;
ICmpUnitRenderer::tag_t m_ModelTag;
public:
static std::string GetSchema()
@ -181,13 +173,10 @@ public:
virtual void Init(const CParamNode& paramNode)
{
m_PreviouslyRendered = false;
m_Unit = NULL;
m_Visibility = ICmpRangeManager::VIS_HIDDEN;
m_R = m_G = m_B = fixed::FromInt(1);
m_ConstructionPreview = paramNode.GetChild("ConstructionPreview").IsOk();
m_ConstructionProgress = fixed::Zero();
m_Seed = GetEntityId();
@ -234,8 +223,6 @@ public:
// TODO: variation/selection strings
serialize.String("actor", m_ActorName, 0, 256);
serialize.NumberFixed_Unbounded("constructionprogress", m_ConstructionProgress);
// TODO: store actor variables?
}
@ -294,18 +281,6 @@ public:
Update(msgData.turnLength);
break;
}
case MT_Interpolate:
{
const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
Interpolate(msgData.deltaSimTime, msgData.offset);
break;
}
case MT_RenderSubmit:
{
const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
RenderSubmit(msgData.collector, msgData.frustum, msgData.culling);
break;
}
case MT_OwnershipChanged:
{
const CMessageOwnershipChanged& msgData = static_cast<const CMessageOwnershipChanged&> (msg);
@ -336,6 +311,26 @@ public:
}
break;
}
case MT_InterpolatedPositionChanged:
{
const CMessageInterpolatedPositionChanged& msgData = static_cast<const CMessageInterpolatedPositionChanged&> (msg);
if (m_ModelTag.valid())
{
CmpPtr<ICmpUnitRenderer> cmpModelRenderer(GetSystemEntity());
cmpModelRenderer->UpdateUnitPos(m_ModelTag, msgData.inWorld, msgData.pos0, msgData.pos1);
}
break;
}
case MT_Destroy:
{
if (m_ModelTag.valid())
{
CmpPtr<ICmpUnitRenderer> cmpModelRenderer(GetSystemEntity());
cmpModelRenderer->RemoveUnit(m_ModelTag);
m_ModelTag = ICmpUnitRenderer::tag_t();
}
break;
}
}
}
@ -387,7 +382,15 @@ public:
if (m_Unit->GetModel().ToCModel())
{
// Ensure the prop transforms are correct
m_Unit->GetModel().ValidatePosition();
CmpPtr<ICmpUnitRenderer> cmpUnitRenderer(GetSystemEntity());
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
if (cmpUnitRenderer && cmpPosition)
{
float frameOffset = cmpUnitRenderer->GetFrameOffset();
CMatrix3D transform(cmpPosition->GetInterpolatedTransform(frameOffset));
m_Unit->GetModel().SetTransform(transform);
m_Unit->GetModel().ValidatePosition();
}
CModelAbstract* ammo = m_Unit->GetModel().ToCModel()->FindFirstAmmoProp();
if (ammo)
@ -473,6 +476,12 @@ public:
m_G = g;
m_B = b;
UNUSED2(a); // TODO: why is this even an argument?
if (m_Unit)
{
CModelAbstract& model = m_Unit->GetModel();
model.SetShadingColor(CColor(m_R.ToFloat(), m_G.ToFloat(), m_B.ToFloat(), 1.0f));
}
}
virtual void SetVariable(std::string name, float value)
@ -502,11 +511,6 @@ public:
return m_ConstructionPreview;
}
virtual void SetConstructionProgress(fixed progress)
{
m_ConstructionProgress = progress;
}
virtual void Hotload(const VfsPath& name)
{
if (!m_Unit)
@ -529,8 +533,6 @@ private:
void Update(fixed turnLength);
void UpdateVisibility();
void Interpolate(float frameTime, float frameOffset);
void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling);
};
REGISTER_COMPONENT_TYPE(VisualActor)
@ -539,49 +541,74 @@ REGISTER_COMPONENT_TYPE(VisualActor)
void CCmpVisualActor::InitModel(const CParamNode& paramNode)
{
if (GetSimContext().HasUnitManager())
if (!GetSimContext().HasUnitManager())
return;
std::set<CStr> selections;
std::wstring actorName = m_ActorName;
if (actorName.find(L".xml") == std::wstring::npos)
actorName += L".xml";
m_Unit = GetSimContext().GetUnitManager().CreateUnit(actorName, GetActorSeed(), selections);
if (!m_Unit)
return;
CModelAbstract& model = m_Unit->GetModel();
if (model.ToCModel())
{
std::set<CStr> selections;
std::wstring actorName = m_ActorName;
if (actorName.find(L".xml") == std::wstring::npos)
actorName += L".xml";
m_Unit = GetSimContext().GetUnitManager().CreateUnit(actorName, GetActorSeed(), selections);
if (m_Unit)
u32 modelFlags = 0;
if (paramNode.GetChild("SilhouetteDisplay").ToBool())
modelFlags |= MODELFLAG_SILHOUETTE_DISPLAY;
if (paramNode.GetChild("SilhouetteOccluder").ToBool())
modelFlags |= MODELFLAG_SILHOUETTE_OCCLUDER;
CmpPtr<ICmpVision> cmpVision(GetEntityHandle());
if (cmpVision && cmpVision->GetAlwaysVisible())
modelFlags |= MODELFLAG_IGNORE_LOS;
model.ToCModel()->AddFlagsRec(modelFlags);
}
if (paramNode.GetChild("DisableShadows").IsOk())
{
if (model.ToCModel())
model.ToCModel()->RemoveShadowsRec();
else if (model.ToCModelDecal())
model.ToCModelDecal()->RemoveShadows();
}
// Initialize the model's selection shape descriptor. This currently relies on the component initialization order; the
// Footprint component must be initialized before this component (VisualActor) to support the ability to use the footprint
// shape for the selection box (instead of the default recursive bounding box). See TypeList.h for the order in
// which components are initialized; if for whatever reason you need to get rid of this dependency, you can always just
// initialize the selection shape descriptor on-demand.
InitSelectionShapeDescriptor(paramNode);
m_Unit->SetID(GetEntityId());
bool floating = m_Unit->GetObject().m_Base->m_Properties.m_FloatOnWater;
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
if (cmpPosition)
cmpPosition->SetActorFloating(floating);
if (!m_ModelTag.valid())
{
CmpPtr<ICmpUnitRenderer> cmpModelRenderer(GetSystemEntity());
if (cmpModelRenderer)
{
CModelAbstract& model = m_Unit->GetModel();
if (model.ToCModel())
{
u32 modelFlags = 0;
// TODO: this should account for all possible props, animations, etc,
// else we might accidentally cull the unit when it should be visible
CBoundingBoxAligned bounds = m_Unit->GetModel().GetWorldBoundsRec();
CBoundingSphere boundSphere = CBoundingSphere::FromSweptBox(bounds);
if (paramNode.GetChild("SilhouetteDisplay").ToBool())
modelFlags |= MODELFLAG_SILHOUETTE_DISPLAY;
int flags = 0;
if (m_IsActorOnly)
flags |= ICmpUnitRenderer::ACTOR_ONLY;
if (m_VisibleInAtlasOnly)
flags |= ICmpUnitRenderer::VISIBLE_IN_ATLAS_ONLY;
if (paramNode.GetChild("SilhouetteOccluder").ToBool())
modelFlags |= MODELFLAG_SILHOUETTE_OCCLUDER;
CmpPtr<ICmpVision> cmpVision(GetEntityHandle());
if (cmpVision && cmpVision->GetAlwaysVisible())
modelFlags |= MODELFLAG_IGNORE_LOS;
model.ToCModel()->AddFlagsRec(modelFlags);
}
if (paramNode.GetChild("DisableShadows").IsOk())
{
if (model.ToCModel())
model.ToCModel()->RemoveShadowsRec();
else if (model.ToCModelDecal())
model.ToCModelDecal()->RemoveShadows();
}
// Initialize the model's selection shape descriptor. This currently relies on the component initialization order; the
// Footprint component must be initialized before this component (VisualActor) to support the ability to use the footprint
// shape for the selection box (instead of the default recursive bounding box). See TypeList.h for the order in
// which components are initialized; if for whatever reason you need to get rid of this dependency, you can always just
// initialize the selection shape descriptor on-demand.
InitSelectionShapeDescriptor(paramNode);
m_Unit->SetID(GetEntityId());
m_ModelTag = cmpModelRenderer->AddUnit(GetEntityHandle(), m_Unit, boundSphere, flags);
}
}
}
@ -662,15 +689,6 @@ void CCmpVisualActor::ReloadActor()
if (!m_Unit)
return;
std::set<CStr> selections;
std::wstring actorName = m_ActorName;
if (actorName.find(L".xml") == std::wstring::npos)
actorName += L".xml";
CUnit* newUnit = GetSimContext().GetUnitManager().CreateUnit(actorName, GetActorSeed(), selections);
if (!newUnit)
return;
// Save some data from the old unit
CColor shading = m_Unit->GetModel().GetShadingColor();
player_id_t playerID = m_Unit->GetModel().GetPlayerID();
@ -698,6 +716,14 @@ void CCmpVisualActor::ReloadActor()
m_Unit->GetModel().SetShadingColor(shading);
m_Unit->GetModel().SetPlayerID(playerID);
if (m_ModelTag.valid())
{
CmpPtr<ICmpUnitRenderer> cmpModelRenderer(GetSystemEntity());
CBoundingBoxAligned bounds = m_Unit->GetModel().GetWorldBoundsRec();
CBoundingSphere boundSphere = CBoundingSphere::FromSweptBox(bounds);
cmpModelRenderer->UpdateUnit(m_ModelTag, m_Unit, boundSphere);
}
}
void CCmpVisualActor::Update(fixed UNUSED(turnLength))
@ -705,8 +731,6 @@ void CCmpVisualActor::Update(fixed UNUSED(turnLength))
if (m_Unit == NULL)
return;
UpdateVisibility();
// If we're in the special movement mode, select an appropriate animation
if (!m_AnimRunThreshold.IsZero())
{
@ -745,119 +769,3 @@ void CCmpVisualActor::Update(fixed UNUSED(turnLength))
}
}
}
void CCmpVisualActor::UpdateVisibility()
{
ICmpRangeManager::ELosVisibility oldVisibility = m_Visibility;
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
if (cmpPosition && cmpPosition->IsInWorld())
{
// The 'always visible' flag means we should always render the unit
// (regardless of whether the LOS system thinks it's visible)
CmpPtr<ICmpVision> cmpVision(GetEntityHandle());
if (cmpVision && cmpVision->GetAlwaysVisible())
m_Visibility = ICmpRangeManager::VIS_VISIBLE;
else
{
CmpPtr<ICmpRangeManager> cmpRangeManager(GetSystemEntity());
// Uncomment the following lines to prevent the models from popping into existence
// near the LOS boundary. Is rather resource intensive.
//if (cmpVision->GetRetainInFog())
// m_Visibility = ICmpRangeManager::VIS_VISIBLE;
//else
m_Visibility = cmpRangeManager->GetLosVisibility(GetEntityHandle(),
GetSimContext().GetCurrentDisplayedPlayer());
}
}
else
m_Visibility = ICmpRangeManager::VIS_HIDDEN;
if (m_Visibility != oldVisibility)
{
// Change the visibility of the visual actor's selectable if it has one.
CmpPtr<ICmpSelectable> cmpSelectable(GetEntityHandle());
if (cmpSelectable)
cmpSelectable->SetVisibility(m_Visibility == ICmpRangeManager::VIS_HIDDEN ? false : true);
}
}
void CCmpVisualActor::Interpolate(float frameTime, float frameOffset)
{
if (m_Unit == NULL)
return;
// Disable rendering of the unit if it has no position
CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
if (!cmpPosition || !cmpPosition->IsInWorld())
return;
else if (!m_PreviouslyRendered)
{
UpdateVisibility();
m_PreviouslyRendered = true;
}
// Even if HIDDEN due to LOS, we need to set up the transforms
// so that projectiles will be launched from the right place
bool floating = m_Unit->GetObject().m_Base->m_Properties.m_FloatOnWater;
CMatrix3D transform(cmpPosition->GetInterpolatedTransform(frameOffset, floating));
if (!m_ConstructionProgress.IsZero())
{
// We use selection boxes to calculate the model size, since the model could be offset
// TODO: this annoyingly shows decals, would be nice to hide them
CBoundingBoxOriented bounds = GetSelectionBox();
if (!bounds.IsEmpty())
{
float dy = 2.0f * bounds.m_HalfSizes.Y;
// If this is a floating unit, we want it to start all the way under the terrain,
// so find the difference between its current position and the terrain
CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
if (floating && cmpTerrain)
{
CVector3D pos = transform.GetTranslation();
float ground = cmpTerrain->GetExactGroundLevel(pos.X, pos.Z);
dy += std::max(0.f, pos.Y - ground);
}
transform.Translate(0.0f, (m_ConstructionProgress.ToFloat() - 1.0f) * dy, 0.0f);
}
}
CModelAbstract& model = m_Unit->GetModel();
model.SetTransform(transform);
m_Unit->UpdateModel(frameTime);
// If not hidden, then we need to set up some extra state for rendering
if (m_Visibility != ICmpRangeManager::VIS_HIDDEN)
{
model.ValidatePosition();
model.SetShadingColor(CColor(m_R.ToFloat(), m_G.ToFloat(), m_B.ToFloat(), 1.0f));
}
}
void CCmpVisualActor::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling)
{
if (m_Unit == NULL)
return;
if (m_Visibility == ICmpRangeManager::VIS_HIDDEN)
return;
CModelAbstract& model = m_Unit->GetModel();
if (!g_AtlasGameLoop->running && !g_RenderActors && m_IsActorOnly)
return;
if (culling && !frustum.IsBoxVisible(CVector3D(0, 0, 0), model.GetWorldBoundsRec()))
return;
if (!g_AtlasGameLoop->running && m_VisibleInAtlasOnly)
return;
collector.SubmitRecursive(&model);
}

View File

@ -37,6 +37,7 @@ DEFINE_INTERFACE_METHOD_0("IsHeightRelative", bool, ICmpPosition, IsHeightRelati
DEFINE_INTERFACE_METHOD_1("SetHeightRelative", void, ICmpPosition, SetHeightRelative, bool)
DEFINE_INTERFACE_METHOD_0("IsFloating", bool, ICmpPosition, IsFloating)
DEFINE_INTERFACE_METHOD_1("SetFloating", void, ICmpPosition, SetFloating, bool)
DEFINE_INTERFACE_METHOD_1("SetConstructionProgress", void, ICmpPosition, SetConstructionProgress, fixed)
DEFINE_INTERFACE_METHOD_0("GetPosition", CFixedVector3D, ICmpPosition, GetPosition)
DEFINE_INTERFACE_METHOD_0("GetPosition2D", CFixedVector2D, ICmpPosition, GetPosition2D)
DEFINE_INTERFACE_METHOD_0("GetPreviousPosition", CFixedVector3D, ICmpPosition, GetPreviousPosition)

View File

@ -144,6 +144,18 @@ public:
*/
virtual void SetFloating(bool flag) = 0;
/**
* Set the entity to float on water, in a non-network-synchronised visual-only way.
* (This is to support the 'floating' flag in actor XMLs.)
*/
virtual void SetActorFloating(bool flag) = 0;
/**
* Set construction progress of the model, this affects the rendered position of the model.
* 0.0 will be fully underground, 1.0 will be fully visible, 0.5 will be half underground.
*/
virtual void SetConstructionProgress(fixed progress) = 0;
/**
* Returns the current x,y,z position (no interpolation).
* Depends on the current terrain heightmap.
@ -216,7 +228,7 @@ public:
* Get the current interpolated transform matrix, for rendering.
* Must not be called unless IsInWorld is true.
*/
virtual CMatrix3D GetInterpolatedTransform(float frameOffset, bool forceFloating) = 0;
virtual CMatrix3D GetInterpolatedTransform(float frameOffset) = 0;
DECLARE_INTERFACE_TYPE(Position)
};

View File

@ -0,0 +1,26 @@
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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/>.
*/
#include "precompiled.h"
#include "ICmpUnitRenderer.h"
#include "simulation2/system/InterfaceScripted.h"
BEGIN_INTERFACE_WRAPPER(UnitRenderer)
DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpUnitRenderer, SetDebugOverlay, bool)
END_INTERFACE_WRAPPER(UnitRenderer)

View File

@ -0,0 +1,70 @@
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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/>.
*/
#ifndef INCLUDED_ICMPMODELRENDERER
#define INCLUDED_ICMPMODELRENDERER
#include "simulation2/system/Interface.h"
class CUnit;
class CBoundingSphere;
class CVector3D;
class ICmpUnitRenderer : public IComponent
{
public:
/**
* External identifiers for models.
* (This is a struct rather than a raw u32 for type-safety.)
*/
struct tag_t
{
tag_t() : n(0) {}
explicit tag_t(u32 n) : n(n) {}
bool valid() { return n != 0; }
u32 n;
};
enum
{
ACTOR_ONLY = 1 << 0,
VISIBLE_IN_ATLAS_ONLY = 1 << 1,
};
virtual tag_t AddUnit(CEntityHandle entity, CUnit* unit, const CBoundingSphere& boundsApprox, int flags) = 0;
virtual void RemoveUnit(tag_t tag) = 0;
virtual void UpdateUnit(tag_t tag, CUnit* unit, const CBoundingSphere& boundsApprox) = 0;
virtual void UpdateUnitPos(tag_t tag, bool inWorld, const CVector3D& pos0, const CVector3D& pos1) = 0;
/**
* Returns the frame offset from the last Interpolate message.
*/
virtual float GetFrameOffset() = 0;
/**
* Toggle the rendering of debug info.
*/
virtual void SetDebugOverlay(bool enabled) = 0;
DECLARE_INTERFACE_TYPE(UnitRenderer)
};
#endif // INCLUDED_ICMPMODELRENDERER

View File

@ -33,5 +33,4 @@ DEFINE_INTERFACE_METHOD_2("SetVariable", void, ICmpVisual, SetVariable, std::str
DEFINE_INTERFACE_METHOD_0("GetActorSeed", u32, ICmpVisual, GetActorSeed)
DEFINE_INTERFACE_METHOD_1("SetActorSeed", void, ICmpVisual, SetActorSeed, u32)
DEFINE_INTERFACE_METHOD_0("HasConstructionPreview", bool, ICmpVisual, HasConstructionPreview)
DEFINE_INTERFACE_METHOD_1("SetConstructionProgress", void, ICmpVisual, SetConstructionProgress, fixed)
END_INTERFACE_WRAPPER(Visual)

View File

@ -161,12 +161,6 @@ public:
*/
virtual bool HasConstructionPreview() = 0;
/**
* Set construction progress of the model, this affects the rendered position of the model.
* 0.0 will be fully underground, 1.0 will be fully visible, 0.5 will be half underground.
*/
virtual void SetConstructionProgress(fixed progress) = 0;
/**
* Called when an actor file has been modified and reloaded dynamically.
* If this component uses the named actor file, it should regenerate its actor

View File

@ -72,9 +72,9 @@ public:
// Position computed from move plus terrain
TS_ASSERT_EQUALS(cmp->GetPosition(), fixedvec(100, 60, 200));
// Interpolated position should be constant
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.0f, false).GetTranslation(), CVector3D(100, 60, 200));
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.5f, false).GetTranslation(), CVector3D(100, 60, 200));
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(1.0f, false).GetTranslation(), CVector3D(100, 60, 200));
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.0f).GetTranslation(), CVector3D(100, 60, 200));
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.5f).GetTranslation(), CVector3D(100, 60, 200));
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(1.0f).GetTranslation(), CVector3D(100, 60, 200));
// No TurnStart message, so this move doesn't affect the interpolation
cmp->MoveTo(entity_pos_t::FromInt(0), entity_pos_t::FromInt(0));
@ -83,9 +83,9 @@ public:
// Position computed from move plus terrain
TS_ASSERT_EQUALS(cmp->GetPosition(), fixedvec(200, 60, 0));
// Interpolated position should vary, from original move into world to new move
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.0f, false).GetTranslation(), CVector3D(100, 60, 200));
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.5f, false).GetTranslation(), CVector3D(150, 60, 100));
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(1.0f, false).GetTranslation(), CVector3D(200, 60, 0));
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.0f).GetTranslation(), CVector3D(100, 60, 200));
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.5f).GetTranslation(), CVector3D(150, 60, 100));
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(1.0f).GetTranslation(), CVector3D(200, 60, 0));
// Latch new position for interpolation
CMessageTurnStart msg;
@ -93,18 +93,18 @@ public:
// Move smoothly to new position
cmp->MoveTo(entity_pos_t::FromInt(400), entity_pos_t::FromInt(300));
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.0f, false).GetTranslation(), CVector3D(200, 60, 0));
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.5f, false).GetTranslation(), CVector3D(300, 60, 150));
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(1.0f, false).GetTranslation(), CVector3D(400, 60, 300));
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.0f).GetTranslation(), CVector3D(200, 60, 0));
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.5f).GetTranslation(), CVector3D(300, 60, 150));
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(1.0f).GetTranslation(), CVector3D(400, 60, 300));
// Jump to new position
cmp->JumpTo(entity_pos_t::FromInt(300), entity_pos_t::FromInt(100));
// Position computed from jump plus terrain
TS_ASSERT_EQUALS(cmp->GetPosition(), fixedvec(300, 60, 100));
// Interpolated position should be constant after jump
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.0f, false).GetTranslation(), CVector3D(300, 60, 100));
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.5f, false).GetTranslation(), CVector3D(300, 60, 100));
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(1.0f, false).GetTranslation(), CVector3D(300, 60, 100));
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.0f).GetTranslation(), CVector3D(300, 60, 100));
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.5f).GetTranslation(), CVector3D(300, 60, 100));
TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(1.0f).GetTranslation(), CVector3D(300, 60, 100));
// TODO: Test the rotation methods
}

View File

@ -57,6 +57,8 @@ public:
virtual void SetHeightRelative(bool UNUSED(relative)) { }
virtual bool IsFloating() { return false; }
virtual void SetFloating(bool UNUSED(flag)) { }
virtual void SetActorFloating(bool UNUSED(flag)) { }
virtual void SetConstructionProgress(fixed UNUSED(progress)) { }
virtual CFixedVector3D GetPosition() { return CFixedVector3D(); }
virtual CFixedVector2D GetPosition2D() { return CFixedVector2D(); }
virtual CFixedVector3D GetPreviousPosition() { return CFixedVector3D(); }
@ -67,7 +69,7 @@ public:
virtual CFixedVector3D GetRotation() { return CFixedVector3D(); }
virtual fixed GetDistanceTravelled() { return fixed::Zero(); }
virtual void GetInterpolatedPosition2D(float UNUSED(frameOffset), float& x, float& z, float& rotY) { x = z = rotY = 0; }
virtual CMatrix3D GetInterpolatedTransform(float UNUSED(frameOffset), bool UNUSED(forceFloating)) { return CMatrix3D(); }
virtual CMatrix3D GetInterpolatedTransform(float UNUSED(frameOffset)) { return CMatrix3D(); }
};
class TestCmpRangeManager : public CxxTest::TestSuite

View File

@ -238,6 +238,20 @@ CMessage* CMessagePositionChanged::FromJSVal(ScriptInterface& scriptInterface, j
////////////////////////////////
jsval CMessageInterpolatedPositionChanged::ToJSVal(ScriptInterface& UNUSED(scriptInterface)) const
{
LOGWARNING(L"CMessageInterpolatedPositionChanged::ToJSVal not implemented");
return JSVAL_VOID;
}
CMessage* CMessageInterpolatedPositionChanged::FromJSVal(ScriptInterface& UNUSED(scriptInterface), jsval UNUSED(val))
{
LOGWARNING(L"CMessageInterpolatedPositionChanged::FromJSVal not implemented");
return NULL;
}
////////////////////////////////
jsval CMessageTerritoryPositionChanged::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();

View File

@ -646,6 +646,7 @@ void CComponentManager::AddSystemComponents(bool skipScriptedComponents, bool sk
AddComponent(m_SystemEntity, CID_SoundManager, noParam);
AddComponent(m_SystemEntity, CID_Terrain, noParam);
AddComponent(m_SystemEntity, CID_TerritoryManager, noParam);
AddComponent(m_SystemEntity, CID_UnitRenderer, noParam);
AddComponent(m_SystemEntity, CID_WaterManager, noParam);
// Add scripted system components:

View File

@ -605,7 +605,7 @@ BEGIN_COMMAND(RotateObject)
m_AngleOld = cmpPosition->GetRotation().Y.ToFloat();
if (msg->usetarget)
{
CMatrix3D transform = cmpPosition->GetInterpolatedTransform(0.f, false);
CMatrix3D transform = cmpPosition->GetInterpolatedTransform(0.f);
CVector3D pos = transform.GetTranslation();
CVector3D target = msg->target->GetWorldSpace(pos.Y);
m_AngleNew = atan2(target.X-pos.X, target.Z-pos.Z);