Changeset View
Changeset View
Standalone View
Standalone View
source/graphics/ModelAbstract.h
/* Copyright (C) 2021 Wildfire Games. | /* Copyright (C) 2022 Wildfire Games. | ||||
* This file is part of 0 A.D. | * This file is part of 0 A.D. | ||||
* | * | ||||
* 0 A.D. is free software: you can redistribute it and/or modify | * 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 | * it under the terms of the GNU General Public License as published by | ||||
* the Free Software Foundation, either version 2 of the License, or | * the Free Software Foundation, either version 2 of the License, or | ||||
* (at your option) any later version. | * (at your option) any later version. | ||||
* | * | ||||
* 0 A.D. is distributed in the hope that it will be useful, | * 0 A.D. is distributed in the hope that it will be useful, | ||||
Show All 12 Lines | |||||
#include "graphics/RenderableObject.h" | #include "graphics/RenderableObject.h" | ||||
#include "maths/BoundingBoxOriented.h" | #include "maths/BoundingBoxOriented.h" | ||||
#include "simulation2/helpers/Player.h" | #include "simulation2/helpers/Player.h" | ||||
class CModelDummy; | class CModelDummy; | ||||
class CModel; | class CModel; | ||||
class CModelDecal; | class CModelDecal; | ||||
class CModelParticleEmitter; | class CModelParticleEmitter; | ||||
class CObjectEntry; | |||||
struct SPropPoint; | |||||
/** | #include <memory> | ||||
* Abstract base class for graphical objects that are used by units, | #include <vector> | ||||
* or as props attached to other CModelAbstract objects. | |||||
* This includes meshes, terrain decals, and sprites. | class CSelectableObject : public CRenderableObject | ||||
* These objects exist in a tree hierarchy. | |||||
*/ | |||||
class CModelAbstract : public CRenderableObject | |||||
{ | { | ||||
NONCOPYABLE(CModelAbstract); | NONCOPYABLE(CSelectableObject); | ||||
public: | public: | ||||
CSelectableObject() = default; | |||||
virtual ~CSelectableObject() = default; | |||||
/** | /** | ||||
* Describes a custom selection shape to be used for a model's selection box instead of the default | * Describes a custom selection shape to be used for a model's selection box instead of the default | ||||
* recursive bounding boxes. | * recursive bounding boxes. | ||||
*/ | */ | ||||
struct CustomSelectionShape | struct CustomSelectionShape | ||||
{ | { | ||||
enum EType { | enum EType { | ||||
/// The selection shape is determined by an oriented box of custom, user-specified size. | /// The selection shape is determined by an oriented box of custom, user-specified size. | ||||
BOX, | BOX, | ||||
/// The selection shape is determined by a cylinder of custom, user-specified size. | /// The selection shape is determined by a cylinder of custom, user-specified size. | ||||
CYLINDER | CYLINDER | ||||
}; | }; | ||||
EType m_Type; ///< Type of shape. | EType m_Type; ///< Type of shape. | ||||
float m_Size0; ///< Box width if @ref BOX, or radius if @ref CYLINDER | float m_Size0; ///< Box width if @ref BOX, or radius if @ref CYLINDER | ||||
float m_Size1; ///< Box depth if @ref BOX, or radius if @ref CYLINDER | float m_Size1; ///< Box depth if @ref BOX, or radius if @ref CYLINDER | ||||
float m_Height; ///< Box height if @ref BOX, cylinder height if @ref CYLINDER | float m_Height; ///< Box height if @ref BOX, cylinder height if @ref CYLINDER | ||||
}; | }; | ||||
/** | |||||
* Returns the world-space selection box of this model. Used primarily for hittesting against against a selection ray. The | |||||
* returned selection box may be empty to indicate that it does not wish to participate in the selection process. | |||||
*/ | |||||
virtual const CBoundingBoxOriented& GetSelectionBox(); | |||||
/// Sets a custom selection shape as described by a @p descriptor. Argument may be NULL | |||||
/// if you wish to keep the default behaviour of using the recursively-calculated bounding boxes. | |||||
void SetCustomSelectionShape(CustomSelectionShape* descriptor) | |||||
{ | |||||
if (m_CustomSelectionShape.get() != descriptor) | |||||
{ | |||||
m_CustomSelectionShape = std::unique_ptr<CustomSelectionShape>(descriptor); | |||||
m_SelectionBoxValid = false; // update the selection box when it is next requested | |||||
} | |||||
} | |||||
/** | |||||
* Returns the (object-space) bounds that should be used to construct a selection box for this model and its children. | |||||
* May return an empty bound to indicate that this model and its children should not be selectable themselves, or should | |||||
* not be included in its parent model's selection box. This method is used for constructing the default selection boxes, | |||||
* as opposed to any boxes of custom shape specified by @ref m_CustomSelectionShape. | |||||
* | |||||
* If you wish your model type to be included in selection boxes, override GetObjectsBounds. | |||||
*/ | |||||
virtual const CBoundingBoxAligned GetObjectBounds() | |||||
{ | |||||
RecalculateBoundsIfNecessary(); // Should recalculate both object-space and world-space bounds if necessary. | |||||
return m_ObjectBounds; | |||||
} | |||||
virtual void InvalidateBounds() | |||||
{ | |||||
m_BoundsValid = false; | |||||
// a call to this method usually means that the model's transform has changed, i.e. it has moved or rotated, so we'll also | |||||
// want to update the selection box accordingly regardless of the shape it is built from. | |||||
m_SelectionBoxValid = false; | |||||
} | |||||
protected: | |||||
void CalcSelectionBox(); | |||||
// object space bounds of model - accounts for bounds of all possible animations | |||||
// that can play on a model. Not always up-to-date - currently CalcBounds() | |||||
// updates it when necessary. | |||||
CBoundingBoxAligned m_ObjectBounds; | |||||
/// Selection box for this model. | |||||
CBoundingBoxOriented m_SelectionBox; | |||||
/// Is the current selection box valid? | |||||
bool m_SelectionBoxValid; | |||||
/// Pointer to a descriptor for a custom-defined selection box shape. If no custom selection box is required, this is NULL | |||||
/// and the standard recursive-bounding-box-based selection box is used. Otherwise, a custom selection box described by this | |||||
/// field will be used. | |||||
/// @see SetCustomSelectionShape | |||||
std::unique_ptr<CustomSelectionShape> m_CustomSelectionShape; | |||||
}; | |||||
/** | |||||
* Abstract base class for graphical objects that are used by units, | |||||
* or as props attached to other CModelAbstract objects. | |||||
* This includes meshes, terrain decals, and sprites. | |||||
* These objects exist in a tree hierarchy. | |||||
*/ | |||||
class CModelAbstract : public CSelectableObject | |||||
{ | |||||
NONCOPYABLE(CModelAbstract); | |||||
public: | |||||
struct Prop | |||||
{ | |||||
Prop() : m_MinHeight(0.f), m_MaxHeight(0.f), m_Point(0), m_Model(0), m_ObjectEntry(0), m_Hidden(false), m_Selectable(true) {} | |||||
float m_MinHeight; | |||||
float m_MaxHeight; | |||||
/** | |||||
* Location of the prop point within its parent model, relative to either a bone in the parent model or to the | |||||
* parent model's origin. See the documentation for @ref SPropPoint for more details. | |||||
* @see SPropPoint | |||||
*/ | |||||
const SPropPoint* m_Point; | |||||
/** | |||||
* Pointer to the model associated with this prop. Note that the transform matrix held by this model is the full object-to-world | |||||
* space transform, taking into account all parent model positioning (see @ref CModel::ValidatePosition for positioning logic). | |||||
* @see CModel::ValidatePosition | |||||
*/ | |||||
CModelAbstract* m_Model; | |||||
CObjectEntry* m_ObjectEntry; | |||||
bool m_Hidden; ///< Should this prop be temporarily removed from rendering? | |||||
bool m_Selectable; /// < should this prop count in the selection size? | |||||
}; | |||||
public: | public: | ||||
CModelAbstract() | CModelAbstract() : m_Parent(NULL), m_PositionValid(false), m_ShadingColor(1, 1, 1, 1), m_PlayerID(INVALID_PLAYER) | ||||
: m_Parent(NULL), m_PositionValid(false), m_ShadingColor(1, 1, 1, 1), m_PlayerID(INVALID_PLAYER), | |||||
m_SelectionBoxValid(false), m_CustomSelectionShape(NULL) | |||||
{ } | { } | ||||
~CModelAbstract() | ~CModelAbstract() | ||||
{ | { | ||||
delete m_CustomSelectionShape; // allocated and set externally by CCmpVisualActor, but our responsibility to clean up | |||||
} | } | ||||
// See below for the common implementation. | |||||
virtual CModelAbstract* Clone() const = 0; | virtual CModelAbstract* Clone() const = 0; | ||||
/// Dynamic cast | /// Dynamic cast | ||||
virtual CModelDummy* ToCModelDummy() { return nullptr; } | virtual CModelDummy* ToCModelDummy() { return nullptr; } | ||||
/// Dynamic cast | /// Dynamic cast | ||||
virtual CModel* ToCModel() { return nullptr; } | virtual CModel* ToCModel() { return nullptr; } | ||||
/// Dynamic cast | /// Dynamic cast | ||||
virtual CModelDecal* ToCModelDecal() { return nullptr; } | virtual CModelDecal* ToCModelDecal() { return nullptr; } | ||||
/// Dynamic cast | /// Dynamic cast | ||||
virtual CModelParticleEmitter* ToCModelParticleEmitter() { return nullptr; } | virtual CModelParticleEmitter* ToCModelParticleEmitter() { return nullptr; } | ||||
// (This dynamic casting is a bit ugly, but we won't have many subclasses | // (This dynamic casting is a bit ugly, but we won't have many subclasses | ||||
// and this seems the easiest way to integrate with other code that wants | // and this seems the easiest way to integrate with other code that wants | ||||
// type-specific processing) | // type-specific processing) | ||||
/// Calls SetDirty on this model and all child objects. | /** | ||||
virtual void SetDirtyRec(int dirtyflags) = 0; | * Add a prop to the model on the given point. | ||||
*/ | |||||
void AddProp(const SPropPoint* point, CModelAbstract* model, CObjectEntry* objectentry, float minHeight = 0.f, float maxHeight = 0.f, bool selectable = true); | |||||
/// Returns world space bounds of this object and all child objects. | // return prop list | ||||
virtual const CBoundingBoxAligned GetWorldBoundsRec() { return GetWorldBounds(); } // default implementation | std::vector<Prop>& GetProps() { return m_Props; } | ||||
const std::vector<Prop>& GetProps() const { return m_Props; } | |||||
/** | /** | ||||
* Returns the world-space selection box of this model. Used primarily for hittesting against against a selection ray. The | * @see CSelectableObject::GetObjectBounds | ||||
* returned selection box may be empty to indicate that it does not wish to participate in the selection process. | |||||
*/ | */ | ||||
virtual const CBoundingBoxOriented& GetSelectionBox(); | virtual const CBoundingBoxAligned GetObjectBounds(); | ||||
virtual void InvalidateBounds() | |||||
{ | |||||
m_BoundsValid = false; | |||||
// a call to this method usually means that the model's transform has changed, i.e. it has moved or rotated, so we'll also | |||||
// want to update the selection box accordingly regardless of the shape it is built from. | |||||
m_SelectionBoxValid = false; | |||||
} | |||||
/// Sets a custom selection shape as described by a @p descriptor. Argument may be NULL | /** | ||||
/// if you wish to keep the default behaviour of using the recursively-calculated bounding boxes. | * @return world space bounds of this object and all child objects. | ||||
void SetCustomSelectionShape(CustomSelectionShape* descriptor) | */ | ||||
{ | virtual const CBoundingBoxAligned GetWorldBoundsRec() | ||||
if (m_CustomSelectionShape != descriptor) | |||||
{ | { | ||||
m_CustomSelectionShape = descriptor; | CBoundingBoxAligned bounds = GetWorldBounds(); | ||||
m_SelectionBoxValid = false; // update the selection box when it is next requested | for (const Prop& prop : m_Props) | ||||
} | bounds += prop.m_Model->GetWorldBoundsRec(); | ||||
return bounds; | |||||
} | } | ||||
/** | /** | ||||
* Returns the (object-space) bounds that should be used to construct a selection box for this model and its children. | * Calls SetDirty on this model and all child objects. | ||||
* May return an empty bound to indicate that this model and its children should not be selectable themselves, or should | |||||
* not be included in its parent model's selection box. This method is used for constructing the default selection boxes, | |||||
* as opposed to any boxes of custom shape specified by @ref m_CustomSelectionShape. | |||||
* | |||||
* If you wish your model type to be included in selection boxes, override this method and have it return the object-space | |||||
* bounds of itself, augmented recursively (via this method) with the object-space selection bounds of its children. | |||||
*/ | */ | ||||
virtual const CBoundingBoxAligned GetObjectSelectionBoundsRec() { return CBoundingBoxAligned::EMPTY; } | virtual void SetDirtyRec(int dirtyflags) | ||||
{ | |||||
SetDirty(dirtyflags); | |||||
for (Prop& prop : m_Props) | |||||
prop.m_Model->SetDirtyRec(dirtyflags); | |||||
} | |||||
/** | /** | ||||
* Called when terrain has changed in the given inclusive bounds. | * Called when terrain has changed in the given inclusive bounds. | ||||
* Might call SetDirty if the change affects this model. | * Might call SetDirty if the change affects this model. | ||||
*/ | */ | ||||
virtual void SetTerrainDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1) = 0; | virtual void SetTerrainDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1) | ||||
{ | |||||
for (Prop& prop : m_Props) | |||||
prop.m_Model->SetTerrainDirty(i0, j0, i1, j1); | |||||
} | |||||
/** | /** | ||||
* Called when the entity tries to set some variable to affect the display of this model | * Called when the entity tries to set some variable to affect the display of this model | ||||
* and/or its child objects. | * and/or its child objects. | ||||
*/ | */ | ||||
virtual void SetEntityVariable(const std::string& UNUSED(name), float UNUSED(value)) { } | virtual void SetEntityVariable(const std::string& name, float value) | ||||
{ | |||||
for (Prop& prop : m_Props) | |||||
prop.m_Model->SetEntityVariable(name, value); | |||||
} | |||||
/** | void SetParentRelativeTransform(const CMatrix3D& matrix) | ||||
* Ensure that both the transformation and the bone matrices are correct for this model and all its props. | { | ||||
*/ | m_ParentRelativeTransform = matrix; | ||||
virtual void ValidatePosition() = 0; | InvalidatePositionRec(); | ||||
} | |||||
/** | /** | ||||
* Mark this model's position and bone matrices, and all props' positions as invalid. | * Ensure that both the transformation matrices are correct for this model and all its props. | ||||
*/ | */ | ||||
virtual void InvalidatePosition() = 0; | virtual void ValidatePosition() | ||||
{ | |||||
if (m_PositionValid) | |||||
{ | |||||
ENSURE(!m_Parent || m_Parent->m_PositionValid); | |||||
return; | |||||
} | |||||
// Make sure we don't base our calculations on | |||||
// a parent animation state that is out of date. | |||||
// Don't update recursively, we only care about the parent. | |||||
if (m_Parent) | |||||
{ | |||||
if (!m_Parent->m_PositionValid) | |||||
m_Parent->ValidatePosition(); | |||||
ENSURE(m_Parent->m_PositionValid); | |||||
SetTransform(m_ParentRelativeTransform * m_Parent->m_Transform); | |||||
} | |||||
m_PositionValid = true; | |||||
} | |||||
void ValidatePositionRec() | |||||
{ | |||||
ValidatePosition(); | |||||
for (Prop& prop : m_Props) | |||||
prop.m_Model->ValidatePositionRec(); | |||||
} | |||||
void InvalidatePositionRec() | |||||
{ | |||||
InvalidatePosition(); | |||||
for (Prop& prop : m_Props) | |||||
prop.m_Model->InvalidatePositionRec(); | |||||
} | |||||
virtual void SetPlayerID(player_id_t id) { m_PlayerID = id; } | virtual void SetPlayerID(player_id_t id) { m_PlayerID = id; } | ||||
// get the model's player ID; initial default is INVALID_PLAYER | // get the model's player ID; initial default is INVALID_PLAYER | ||||
virtual player_id_t GetPlayerID() const { return m_PlayerID; } | virtual player_id_t GetPlayerID() const { return m_PlayerID; } | ||||
virtual void SetShadingColor(const CColor& color) { m_ShadingColor = color; } | virtual void SetShadingColor(const CColor& color) { m_ShadingColor = color; } | ||||
virtual CColor GetShadingColor() const { return m_ShadingColor; } | virtual CColor GetShadingColor() const { return m_ShadingColor; } | ||||
protected: | protected: | ||||
void CalcSelectionBox(); | /** | ||||
* Clone props & other generic data. | |||||
*/ | |||||
virtual void Clone(CModelAbstract& o) const; | |||||
/** | |||||
* Mark this model's position and bone matrices, and all props' positions as invalid. | |||||
*/ | |||||
virtual void InvalidatePosition() | |||||
{ | |||||
m_PositionValid = false; | |||||
} | |||||
public: | public: | ||||
/// If non-null, points to the model that we are attached to. | /// If non-null, points to the model that we are attached to. | ||||
CModelAbstract* m_Parent; | CModelAbstract* m_Parent; | ||||
/// True if both transform and and bone matrices are valid. | /// True if both transform and and bone matrices are valid. | ||||
bool m_PositionValid; | bool m_PositionValid; | ||||
player_id_t m_PlayerID; | player_id_t m_PlayerID; | ||||
/// Modulating color | /// Modulating color | ||||
CColor m_ShadingColor; | CColor m_ShadingColor; | ||||
protected: | protected: | ||||
// list of current props on model | |||||
std::vector<Prop> m_Props; | |||||
/// Selection box for this model. | /** | ||||
CBoundingBoxOriented m_SelectionBox; | * Transformation relative to our parent, if any. Updated by the parent. | ||||
* Props may attach to these bones by means of the SPropPoint::m_BoneIndex field; in this case their | |||||
/// Is the current selection box valid? | * transformation matrix held is relative to the bone transformation (see @ref SPropPoint and | ||||
bool m_SelectionBoxValid; | * @ref CModel::ValidatePosition). | ||||
* @see SPropPoint | |||||
/// Pointer to a descriptor for a custom-defined selection box shape. If no custom selection box is required, this is NULL | */ | ||||
/// and the standard recursive-bounding-box-based selection box is used. Otherwise, a custom selection box described by this | CMatrix3D m_ParentRelativeTransform; | ||||
/// field will be used. | |||||
/// @see SetCustomSelectionShape | |||||
CustomSelectionShape* m_CustomSelectionShape; | |||||
}; | }; | ||||
#endif // INCLUDED_MODELABSTRACT | #endif // INCLUDED_MODELABSTRACT |
Wildfire Games · Phabricator