1
0
forked from 0ad/0ad

Better selection boxes. Closes #914, #295, #810.

This was SVN commit r10593.
This commit is contained in:
vts 2011-11-25 06:36:13 +00:00
parent 9e97c73544
commit 85186c98b2
58 changed files with 1315 additions and 205 deletions

View File

@ -97,7 +97,7 @@
/>
<!-- Dev/cheat commands -->
<object name="devCommands" size="100%-156 50%-80 100%-8 50%+80" type="image" sprite="devCommandsBackground"
<object name="devCommands" size="100%-156 50%-88 100%-8 50%+88" type="image" sprite="devCommandsBackground"
hidden="true" hotkey="session.devcommands.toggle">
<action on="Press">
toggleDeveloperOverlay();
@ -134,23 +134,28 @@
<action on="Press">Engine.GuiInterfaceCall("SetRangeDebugOverlay", this.checked);</action>
</object>
<object size="0 96 100%-18 112" type="text" style="devCommandsText">Restrict camera</object>
<object size="100%-16 96 100% 112" type="checkbox" style="StoneCrossBox" checked="true">
<object size="0 96 100%-18 112" type="text" style="devCommandsText">Bounding box overlay</object>
<object size="100%-16 96 100% 112" type="checkbox" style="StoneCrossBox">
<action on="Press">Engine.SetBoundingBoxDebugOverlay(this.checked);</action>
</object>
<object size="0 112 100%-18 128" type="text" style="devCommandsText">Restrict camera</object>
<object size="100%-16 112 100% 128" type="checkbox" style="StoneCrossBox" checked="true">
<action on="Press">gameView.constrainCamera = this.checked;</action>
</object>
<object size="0 112 100%-18 128" type="text" style="devCommandsText">Reveal map</object>
<object name="devCommandsRevealMap" size="100%-16 112 100% 128" type="checkbox" style="StoneCrossBox">
<object size="0 128 100%-18 144" type="text" style="devCommandsText">Reveal map</object>
<object size="100%-16 128 100% 144" type="checkbox" name="devCommandsRevealMap" style="StoneCrossBox">
<action on="Press">Engine.PostNetworkCommand({"type": "reveal-map", "enable": this.checked});</action>
</object>
<object size="0 128 100%-18 144" type="text" style="devCommandsText">Enable time warp</object>
<object size="100%-16 128 100% 144" type="checkbox" name="devTimeWarp" style="StoneCrossBox">
<object size="0 144 100%-18 160" type="text" style="devCommandsText">Enable time warp</object>
<object size="100%-16 144 100% 160" type="checkbox" name="devTimeWarp" style="StoneCrossBox">
<action on="Press">Engine.EnableTimeWarpRecording(this.checked ? 10 : 0);</action>
</object>
<object size="0 144 100%-18 160" type="text" style="devCommandsText">Promote selected units</object>
<object size="100%-16 144 100% 160" type="button" style="StoneCrossBox">
<object size="0 160 100%-18 176" type="text" style="devCommandsText">Promote selected units</object>
<object size="100%-16 160 100% 176" type="button" style="StoneCrossBox">
<action on="Press">Engine.PostNetworkCommand({"type": "promote", "entities": g_Selection.toList()});</action>
</object>
</object>

View File

@ -16,4 +16,9 @@
<Type>wood.tree</Type>
</ResourceSupply>
<Selectable/>
<VisualActor>
<SelectionShape>
<Footprint />
</SelectionShape>
</VisualActor>
</Entity>

View File

@ -80,7 +80,7 @@ void CCamera::SetProjectionTile(int tiles, int tile_x, int tile_y)
//Updates the frustum planes. Should be called
//everytime the view or projection matrices are
//altered.
void CCamera::UpdateFrustum(const CBound& scissor)
void CCamera::UpdateFrustum(const CBoundingBoxAligned& scissor)
{
CMatrix3D MatFinal;
CMatrix3D MatView;

View File

@ -24,7 +24,7 @@
#define INCLUDED_CAMERA
#include "Frustum.h"
#include "maths/Bound.h"
#include "maths/BoundingBoxAligned.h"
#include "maths/Matrix3D.h"
// view port
@ -56,7 +56,7 @@ class CCamera
// Updates the frustum planes. Should be called
// everytime the view or projection matrices are
// altered.
void UpdateFrustum(const CBound& scissor = CBound(CVector3D(-1.0f, -1.0f, -1.0f), CVector3D(1.0f, 1.0f, 1.0f)));
void UpdateFrustum(const CBoundingBoxAligned& scissor = CBoundingBoxAligned(CVector3D(-1.0f, -1.0f, -1.0f), CVector3D(1.0f, 1.0f, 1.0f)));
void ClipFrustum(const CPlane& clipPlane);
const CFrustum& GetFrustum() const { return m_ViewFrustum; }

View File

@ -55,7 +55,7 @@ void CModelDecal::CalcBounds()
{
ssize_t i0, j0, i1, j1;
CalcVertexExtents(i0, j0, i1, j1);
m_Bounds = m_Terrain->GetVertexesBound(i0, j0, i1, j1);
m_WorldBounds = m_Terrain->GetVertexesBound(i0, j0, i1, j1);
}
void CModelDecal::SetTerrainDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1)

View File

@ -28,7 +28,7 @@ portal rendering, where a portal may have 3 or more edges.
#include "precompiled.h"
#include "Frustum.h"
#include "maths/Bound.h"
#include "maths/BoundingBoxAligned.h"
#include "maths/MathUtil.h"
CFrustum::CFrustum ()
@ -113,7 +113,7 @@ bool CFrustum::IsSphereVisible (const CVector3D &center, float radius) const
}
bool CFrustum::IsBoxVisible (const CVector3D &position,const CBound &bounds) const
bool CFrustum::IsBoxVisible (const CVector3D &position,const CBoundingBoxAligned &bounds) const
{
//basically for every plane we calculate the furthest point
//in the box to that plane. If that point is beyond the plane

View File

@ -33,7 +33,7 @@ portal rendering, where a portal may have 3 or more edges.
//10 planes should be enough
#define MAX_NUM_FRUSTUM_PLANES (10)
class CBound;
class CBoundingBoxAligned;
class CFrustum
{
@ -55,7 +55,7 @@ public:
bool IsPointVisible (const CVector3D &point) const;
bool DoesSegmentIntersect(const CVector3D& start, const CVector3D &end);
bool IsSphereVisible (const CVector3D &center, float radius) const;
bool IsBoxVisible (const CVector3D &position,const CBound &bounds) const;
bool IsBoxVisible (const CVector3D &position,const CBoundingBoxAligned &bounds) const;
CPlane& operator[](size_t idx) { return m_aPlanes[idx]; }
const CPlane& operator[](size_t idx) const { return m_aPlanes[idx]; }

View File

@ -36,7 +36,7 @@
#include "graphics/UnitManager.h"
#include "lib/input.h"
#include "lib/timer.h"
#include "maths/Bound.h"
#include "maths/BoundingBoxAligned.h"
#include "maths/MathUtil.h"
#include "maths/Matrix3D.h"
#include "maths/Quaternion.h"
@ -499,7 +499,7 @@ void CGameView::EnumerateObjects(const CFrustum& frustum, SceneCollector* c)
CPatch* patch=pTerrain->GetPatch(i,j); // can't fail
// If the patch is underwater, calculate a bounding box that also contains the water plane
CBound bounds = patch->GetBounds();
CBoundingBoxAligned bounds = patch->GetWorldBounds();
float waterHeight = g_Renderer.GetWaterManager()->m_WaterHeight + 0.001f;
if(bounds[1].Y < waterHeight) {
bounds[1].Y = waterHeight;

View File

@ -23,7 +23,7 @@
#include "HFTracer.h"
#include "Terrain.h"
#include "maths/Bound.h"
#include "maths/BoundingBoxAligned.h"
#include "maths/Vector3D.h"
// To cope well with points that are slightly off the edge of the map,
@ -136,7 +136,7 @@ bool CHFTracer::RayIntersect(const CVector3D& origin, const CVector3D& dir, int&
}
// intersect first against bounding box
CBound bound;
CBoundingBoxAligned bound;
bound[0] = CVector3D(-MARGIN_SIZE * m_CellSize, 0, -MARGIN_SIZE * m_CellSize);
bound[1] = CVector3D((m_MapSize + MARGIN_SIZE) * m_CellSize, 65535 * m_HeightScale, (m_MapSize + MARGIN_SIZE) * m_CellSize);

View File

@ -24,7 +24,7 @@
#include "Model.h"
#include "ModelDef.h"
#include "maths/Quaternion.h"
#include "maths/Bound.h"
#include "maths/BoundingBoxAligned.h"
#include "SkeletonAnim.h"
#include "SkeletonAnimDef.h"
#include "SkeletonAnimManager.h"
@ -34,7 +34,6 @@
#include "lib/res/h_mgr.h"
#include "lib/sysdep/rtl.h"
#include "ps/Profile.h"
#include "ps/CLogger.h"
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -117,12 +116,12 @@ void CModel::CalcBounds()
if (! (m_Anim && m_Anim->m_AnimDef))
{
if (m_ObjectBounds.IsEmpty())
CalcObjectBounds();
CalcStaticObjectBounds();
}
else
{
if (m_Anim->m_ObjectBounds.IsEmpty())
CalcAnimatedObjectBound(m_Anim->m_AnimDef, m_Anim->m_ObjectBounds);
CalcAnimatedObjectBounds(m_Anim->m_AnimDef, m_Anim->m_ObjectBounds);
ENSURE(! m_Anim->m_ObjectBounds.IsEmpty()); // (if this happens, it'll be recalculating the bounds every time)
m_ObjectBounds = m_Anim->m_ObjectBounds;
}
@ -130,12 +129,13 @@ void CModel::CalcBounds()
// Ensure the transform is set correctly before we use it
ValidatePosition();
m_ObjectBounds.Transform(GetTransform(), m_Bounds);
// Now transform the object-space bounds to world-space bounds
m_ObjectBounds.Transform(GetTransform(), m_WorldBounds);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CalcObjectBounds: calculate object space bounds of this model, based solely on vertex positions
void CModel::CalcObjectBounds()
void CModel::CalcStaticObjectBounds()
{
m_ObjectBounds.SetEmpty();
@ -149,7 +149,7 @@ void CModel::CalcObjectBounds()
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CalcAnimatedObjectBound: calculate bounds encompassing all vertex positions for given animation
void CModel::CalcAnimatedObjectBound(CSkeletonAnimDef* anim,CBound& result)
void CModel::CalcAnimatedObjectBounds(CSkeletonAnimDef* anim, CBoundingBoxAligned& result)
{
result.SetEmpty();
@ -200,14 +200,66 @@ void CModel::CalcAnimatedObjectBound(CSkeletonAnimDef* anim,CBound& result)
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
const CBound CModel::GetBoundsRec()
const CBoundingBoxAligned CModel::GetWorldBoundsRec()
{
CBound bounds = GetBounds();
CBoundingBoxAligned bounds = GetWorldBounds();
for (size_t i = 0; i < m_Props.size(); ++i)
bounds += m_Props[i].m_Model->GetBoundsRec();
bounds += m_Props[i].m_Model->GetWorldBoundsRec();
return bounds;
}
const CBoundingBoxAligned CModel::GetObjectSelectionBoundsRec()
{
CBoundingBoxAligned objBounds = GetObjectBounds(); // updates the (children-not-included) object-space bounds if necessary
// now extend these bounds to include the props' selection bounds (if any)
for (size_t i = 0; i < m_Props.size(); ++i)
{
const Prop& prop = m_Props[i];
if (prop.m_Hidden)
continue; // prop is hidden from rendering, so it also shouldn't be used for selection
CBoundingBoxAligned propSelectionBounds = prop.m_Model->GetObjectSelectionBoundsRec();
if (propSelectionBounds.IsEmpty())
continue; // submodel does not wish to participate in selection box, exclude it
// We have the prop's bounds in its own object-space; now we need to transform them so they can be properly added
// to the bounds in our object-space. For that, we need the transform of the prop attachment point.
//
// We have the prop point information; however, it's not trivial to compute its exact location in our object-space
// since it may or may not be attached to a bone (see SPropPoint), which in turn may or may not be in the middle of
// an animation. The bone matrices might be of interest, but they're really only meant to be used for the animation
// system and are quite opaque to use from the outside (see @ref ValidatePosition).
//
// However, a nice side effect of ValidatePosition is that it also computes the absolute world-space transform of
// our props and sets it on their respective models. In particular, @ref ValidatePosition will compute the prop's
// world-space transform as either
//
// T' = T x B x O
// or
// T' = T x O
//
// where T' is the prop's world-space transform, T is our world-space transform, O is the prop's local
// offset/rotation matrix, and B is an optional transformation matrix of the bone the prop is attached to
// (taking into account animation and everything).
//
// From this, it is clear that either O or B x O is the object-space transformation matrix of the prop. So,
// all we need to do is apply our own inverse world-transform T^(-1) to T' to get our desired result. Luckily,
// this is precomputed upon setting the transform matrix (see @ref SetTransform), so it is free to fetch.
CMatrix3D propObjectTransform = prop.m_Model->GetTransform(); // T'
propObjectTransform.Concatenate(GetInvTransform()); // T^(-1) x T'
// Transform the prop's bounds into our object coordinate space
CBoundingBoxAligned transformedPropSelectionBounds;
propSelectionBounds.Transform(propObjectTransform, transformedPropSelectionBounds);
objBounds += transformedPropSelectionBounds;
}
return objBounds;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// BuildAnimation: load raw animation frame animation from given file, and build a
// animation specific to this model
@ -294,6 +346,7 @@ void CModel::ValidatePosition()
m_Anim->m_AnimDef->BuildBoneMatrices(m_AnimTime, m_BoneMatrices, !(m_Flags & MODELFLAG_NOLOOPANIMATION));
// add world-space transformation to m_BoneMatrices
const CMatrix3D& transform = GetTransform();
for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++)
m_BoneMatrices[i].Concatenate(transform);
@ -313,6 +366,8 @@ void CModel::ValidatePosition()
}
}
// our own position is now valid; now we can safely update our props' positions without fearing
// that doing so will cause a revalidation of this model (see recursion above).
m_PositionValid = true;
// re-position and validate all props
@ -322,9 +377,15 @@ void CModel::ValidatePosition()
CMatrix3D proptransform = prop.m_Point->m_Transform;;
if (prop.m_Point->m_BoneIndex != 0xff)
{
// m_BoneMatrices[i] already have world transform pre-applied (see above)
proptransform.Concatenate(m_BoneMatrices[prop.m_Point->m_BoneIndex]);
}
else
{
// not relative to any bone; just apply world-space transformation (i.e. relative to object-space origin)
proptransform.Concatenate(m_Transform);
}
prop.m_Model->SetTransform(proptransform);
prop.m_Model->ValidatePosition();
@ -410,6 +471,8 @@ void CModel::CopyAnimationFrom(CModel* source)
void CModel::AddProp(const SPropPoint* point, CModelAbstract* model, CObjectEntry* objectentry)
{
// position model according to prop point position
// this next call will invalidate the bounds of "model", which will in turn also invalidate the selection box
model->SetTransform(point->m_Transform);
model->m_Parent = this;
@ -426,6 +489,10 @@ void CModel::AddAmmoProp(const SPropPoint* point, CModelAbstract* model, CObject
m_AmmoPropPoint = point;
m_AmmoLoadedProp = m_Props.size() - 1;
m_Props[m_AmmoLoadedProp].m_Hidden = true;
// we only need to invalidate the selection box here if it is based on props and their visibilities
if (!m_CustomSelectionShape)
m_SelectionBoxValid = false;
}
void CModel::ShowAmmoProp()
@ -437,6 +504,10 @@ void CModel::ShowAmmoProp()
for (size_t i = 0; i < m_Props.size(); ++i)
if (m_Props[i].m_Point == m_AmmoPropPoint)
m_Props[i].m_Hidden = (i != m_AmmoLoadedProp);
// we only need to invalidate the selection box here if it is based on props and their visibilities
if (!m_CustomSelectionShape)
m_SelectionBoxValid = false;
}
void CModel::HideAmmoProp()
@ -448,6 +519,10 @@ void CModel::HideAmmoProp()
for (size_t i = 0; i < m_Props.size(); ++i)
if (m_Props[i].m_Point == m_AmmoPropPoint)
m_Props[i].m_Hidden = (i == m_AmmoLoadedProp);
// we only need to invalidate here if the selection box is based on props and their visibilities
if (!m_CustomSelectionShape)
m_SelectionBoxValid = false;
}
CModelAbstract* CModel::FindFirstAmmoProp()

View File

@ -54,11 +54,22 @@ public:
{
Prop() : m_Point(0), m_Model(0), m_ObjectEntry(0), m_Hidden(false) {}
/**
* 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; // temporarily removed from rendering
bool m_Hidden; ///< Should this prop be temporarily removed from rendering?
};
public:
@ -75,8 +86,6 @@ public:
// setup model from given geometry
bool InitModel(const CModelDefPtr& modeldef);
// calculate the world space bounds of this model
virtual void CalcBounds();
// update this model's state; 'time' is the absolute time since the start of the animation, in MS
void UpdateTo(float time);
@ -133,12 +142,35 @@ public:
m_Props[i].m_Model->SetEntityVariable(name, value);
}
// calculate object space bounds of this model, based solely on vertex positions
void CalcObjectBounds();
// calculate bounds encompassing all vertex positions for given animation
void CalcAnimatedObjectBound(CSkeletonAnimDef* anim,CBound& result);
// --- WORLD/OBJECT SPACE BOUNDS -----------------------------------------------------------------
virtual const CBound GetBoundsRec();
/// Overridden to calculate both the world-space and object-space bounds of this model, and stores the result in
/// m_Bounds and m_ObjectBounds, respectively.
virtual void CalcBounds();
/// Returns the object-space bounds for this model, excluding its children.
const CBoundingBoxAligned& GetObjectBounds()
{
RecalculateBoundsIfNecessary(); // recalculates both object-space and world-space bounds if necessary
return m_ObjectBounds;
}
virtual const CBoundingBoxAligned GetWorldBoundsRec(); // reimplemented here
/// Auxiliary method; calculates object space bounds of this model, based solely on vertex positions, and stores
/// the result in m_ObjectBounds. Called by CalcBounds (instead of CalcAnimatedObjectBounds) if it has been determined
/// that the object-space bounds are static.
void CalcStaticObjectBounds();
/// Auxiliary method; calculate object-space bounds encompassing all vertex positions for given animation, and stores
/// the result in m_ObjectBounds. Called by CalcBounds (instead of CalcStaticBounds) if it has been determined that the
/// object-space bounds need to take animations into account.
void CalcAnimatedObjectBounds(CSkeletonAnimDef* anim,CBoundingBoxAligned& result);
// --- SELECTION BOX/BOUNDS ----------------------------------------------------------------------
/// Reimplemented here since proper models should participate in selection boxes.
virtual const CBoundingBoxAligned GetObjectSelectionBoundsRec();
/**
* Set transform of this object.
@ -236,12 +268,20 @@ private:
// 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.
CBound m_ObjectBounds;
CBoundingBoxAligned m_ObjectBounds;
// animation currently playing on this model, if any
CSkeletonAnim* m_Anim;
// time (in MS) into the current animation
float m_AnimTime;
// current state of all bones on this model; null if associated modeldef isn't skeletal
/**
* Current state of all bones on this model; null if associated modeldef isn't skeletal.
* Props may attach to these bones by means of the SPropPoint::m_BoneIndex field; in this case their
* transformation matrix held is relative to the bone transformation (see @ref SPropPoint and
* @ref CModel::ValidatePosition).
*
* @see SPropPoint
*/
CMatrix3D* m_BoneMatrices;
// inverse matrices for the bind pose's bones; null if not skeletal
CMatrix3D* m_InverseBindBoneMatrices;

View File

@ -0,0 +1,92 @@
/* Copyright (C) 2011 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 "ModelAbstract.h"
#include "ps/CLogger.h"
const CBoundingBoxOriented& CModelAbstract::GetSelectionBox()
{
if (!m_SelectionBoxValid)
{
CalcSelectionBox();
m_SelectionBoxValid = true;
}
return m_SelectionBox;
}
void CModelAbstract::CalcSelectionBox()
{
if (m_CustomSelectionShape)
{
// custom shape
switch(m_CustomSelectionShape->m_Type)
{
case CustomSelectionShape::BOX:
{
// create object-space bounds according to the information in the descriptor, and transform them to world-space.
// the box is centered on the X and Z axes, but extends from 0 to its height on the Y axis.
const float width = m_CustomSelectionShape->m_Size0;
const float depth = m_CustomSelectionShape->m_Size1;
const float height = m_CustomSelectionShape->m_Height;
CBoundingBoxAligned bounds;
bounds += CVector3D(-width/2.f, 0, -depth/2.f);
bounds += CVector3D( width/2.f, height, depth/2.f);
bounds.Transform(GetTransform(), m_SelectionBox);
}
break;
case CustomSelectionShape::CYLINDER:
{
// TODO: unimplemented
m_SelectionBox.SetEmpty();
LOGWARNING(L"[ModelAbstract] TODO: Cylinder selection boxes are not yet implemented. Use BOX or BOUNDS selection shapes instead.");
}
break;
default:
{
m_SelectionBox.SetEmpty();
//LOGWARNING(L"[ModelAbstract] Unrecognized selection shape type: %ld", m_CustomSelectionShape->m_Type);
debug_warn("[ModelAbstract] Unrecognized selection shape type");
}
break;
}
}
else
{
// standard method
// Get the object-space bounds that should be used to construct this model (and its children)'s selection box
CBoundingBoxAligned objBounds = GetObjectSelectionBoundsRec();
if (objBounds.IsEmpty())
{
m_SelectionBox.SetEmpty(); // model does not wish to participate in selection
return;
}
// Prevent the bounding box from extending through the terrain; clip the lower plane at Y=0 in object space.
if (objBounds[1].Y > 0.f) // should always be the case, unless the models are defined really weirdly
objBounds[0].Y = std::max(0.f, objBounds[0].Y);
// transform object-space axis-aligned bounds to world-space arbitrary-aligned box
objBounds.Transform(GetTransform(), m_SelectionBox);
}
}

View File

@ -18,6 +18,7 @@
#ifndef INCLUDED_MODELABSTRACT
#define INCLUDED_MODELABSTRACT
#include "maths/BoundingBoxOriented.h"
#include "graphics/RenderableObject.h"
#include "ps/Overlay.h"
#include "simulation2/helpers/Player.h"
@ -37,10 +38,36 @@ class CModelAbstract : public CRenderableObject
NONCOPYABLE(CModelAbstract);
public:
CModelAbstract() :
m_Parent(NULL), m_PositionValid(false),
m_ShadingColor(1, 1, 1, 1), m_PlayerID(INVALID_PLAYER)
/**
* Describes a custom selection shape to be used for a model's selection box instead of the default
* recursive bounding boxes.
*/
struct CustomSelectionShape
{
enum EType {
/// The selection shape is determined by an oriented box of custom, user-specified size.
BOX,
/// The selection shape is determined by a cylinder of custom, user-specified size.
CYLINDER
};
EType m_Type; ///< Type of shape.
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_Height; ///< Box height if @ref BOX, cylinder height if @ref CYLINDER
};
public:
CModelAbstract()
: m_Parent(NULL), m_PositionValid(false), m_ShadingColor(1, 1, 1, 1), m_PlayerID(INVALID_PLAYER),
m_SelectionBoxValid(false), m_CustomSelectionShape(NULL)
{ }
~CModelAbstract()
{
delete m_CustomSelectionShape; // allocated and set externally by CCmpVisualActor, but our responsibility to clean up
}
virtual CModelAbstract* Clone() const = 0;
@ -58,15 +85,47 @@ public:
// and this seems the easiest way to integrate with other code that wants
// type-specific processing)
/**
* Calls SetDirty on this model and all child objects.
*/
/// Calls SetDirty on this model and all child objects.
virtual void SetDirtyRec(int dirtyflags) = 0;
/// Returns world space bounds of this object and all child objects.
virtual const CBoundingBoxAligned GetWorldBoundsRec() { return GetWorldBounds(); }
/**
* Returns world space bounds of this object and all child objects.
* 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 CBound GetBoundsRec() { return GetBounds(); }
virtual const CBoundingBoxOriented& GetSelectionBox();
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.
void SetCustomSelectionShape(CustomSelectionShape* descriptor)
{
if (m_CustomSelectionShape != descriptor)
{
m_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 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; }
/**
* Called when terrain has changed in the given inclusive bounds.
@ -81,14 +140,12 @@ public:
virtual void SetEntityVariable(const std::string& UNUSED(name), float UNUSED(value)) { }
/**
* Ensure that both the transformation and the bone
* matrices are correct for this model and all its props.
* Ensure that both the transformation and the bone matrices are correct for this model and all its props.
*/
virtual void ValidatePosition() = 0;
/**
* Mark this model's position and bone matrices,
* and all props' positions as invalid.
* Mark this model's position and bone matrices, and all props' positions as invalid.
*/
virtual void InvalidatePosition() = 0;
@ -100,7 +157,11 @@ public:
virtual void SetShadingColor(const CColor& colour) { m_ShadingColor = colour; }
virtual CColor GetShadingColor() const { return m_ShadingColor; }
/// If non-null points to the model that we are attached to.
protected:
void CalcSelectionBox();
public:
/// If non-null, points to the model that we are attached to.
CModelAbstract* m_Parent;
/// True if both transform and and bone matrices are valid.
@ -108,8 +169,23 @@ public:
player_id_t m_PlayerID;
// modulating color
/// Modulating color
CColor m_ShadingColor;
protected:
/// 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
CustomSelectionShape* m_CustomSelectionShape;
};
#endif // INCLUDED_MODELABSTRACT

View File

@ -32,19 +32,49 @@
class CBoneState;
///////////////////////////////////////////////////////////////////////////////
// SPropPoint: structure describing a prop point
/**
* Describes the position of a prop point within its parent model. A prop point is the location within a parent model
* where the prop's origin will be attached.
*
* A prop point is specified by its transformation matrix (or separately by its position and rotation), which
* can be relative to either the parent model's origin, or one of the parent's bones. If the parent model is boned,
* then the @ref m_BoneIndex field may specify a bone to which the transformation matrix is relative (see
* @ref CModel::m_BoneMatrices). Otherwise, the transformation matrix is assumed to be relative to the parent model's
* origin.
*
* @see CModel::m_BoneMatrices
*/
struct SPropPoint
{
// name of the prop point
/// Name of the prop point
CStr m_Name;
// position of the point
/**
* Position of the point within the parent model, relative to either the parent model's origin or one of the parent
* model's bones if applicable. Also specified as part of @ref m_Transform.
* @see m_Transform
*/
CVector3D m_Position;
// rotation of the point
/**
* Rotation of the prop model that will be attached at this point. Also specified as part of @ref m_Transform.
* @see m_Transform
*/
CQuaternion m_Rotation;
// object to parent space transformation
/**
* Object to parent space transformation. Combines both @ref m_Position and @ref m_Rotation in a single
* transformation matrix. This transformation is relative to either the parent model's origin, or one of its
* bones, depending on whether it is skeletal. If relative to a bone, then the bone in the parent model to
* which this transformation is relative may be found by m_BoneIndex.
* @see m_Position, m_Rotation
*/
CMatrix3D m_Transform;
// index of parent bone; 0xff if unboned
/**
* Index of parent bone to which this prop point is relative, if any. The value 0xFF specifies that either the parent
* model is unboned, or that this prop point is relative to the parent model's origin rather than one if its bones.
*/
u8 m_BoneIndex;
};
@ -246,3 +276,4 @@ private:
};
#endif

View File

@ -129,7 +129,7 @@ bool CObjectEntry::BuildVariation(const std::vector<std::set<CStr> >& selections
model->SetTexture(texture);
// calculate initial object space bounds, based on vertex positions
model->CalcObjectBounds();
model->CalcStaticObjectBounds();
// load the animations
for (std::multimap<CStr, CObjectBase::Anim>::iterator it = variation.anims.begin(); it != variation.anims.end(); ++it)

View File

@ -36,6 +36,11 @@ struct SOverlayLine
CColor m_Color;
std::vector<float> m_Coords; // (x, y, z) vertex coordinate triples; shape is not automatically closed
u8 m_Thickness; // pixels
/// Utility function; pushes three vertex coordinates at once onto the coordinates array
void PushCoords(const float x, const float y, const float z) { m_Coords.push_back(x); m_Coords.push_back(y); m_Coords.push_back(z); }
/// Utility function; pushes a vertex location onto the coordinates array
void PushCoords(const CVector3D& v) { PushCoords(v.X, v.Y, v.Z); }
};
/**

View File

@ -77,7 +77,7 @@ void CParticleEmitter::UpdateArrayData()
ENSURE(m_Particles.size() <= m_Type->m_MaxParticles);
CBound bounds;
CBoundingBoxAligned bounds;
for (size_t i = 0; i < m_Particles.size(); ++i)
{
@ -232,7 +232,7 @@ void CModelParticleEmitter::CalcBounds()
// current computed particle positions plus the emitter type's largest
// potential bounding box at the current position
m_Bounds = m_Type->CalculateBounds(m_Emitter->GetPosition(), m_Emitter->GetParticleBounds());
m_WorldBounds = m_Type->CalculateBounds(m_Emitter->GetPosition(), m_Emitter->GetParticleBounds());
}
void CModelParticleEmitter::ValidatePosition()

View File

@ -83,7 +83,7 @@ public:
/**
* Get the bounding box of the center points of particles at their current positions.
*/
CBound GetParticleBounds() { return m_ParticleBounds; }
CBoundingBoxAligned GetParticleBounds() { return m_ParticleBounds; }
/**
* Push a new particle onto the ring buffer. (May overwrite an old particle.)
@ -133,7 +133,7 @@ public:
private:
/// Bounding box of the current particle center points
CBound m_ParticleBounds;
CBoundingBoxAligned m_ParticleBounds;
VertexArray m_VertexArray;
VertexArray::Attribute m_AttributePos;

View File

@ -579,9 +579,9 @@ void CParticleEmitterType::UpdateEmitterStep(CParticleEmitter& emitter, float dt
}
}
CBound CParticleEmitterType::CalculateBounds(CVector3D emitterPos, CBound emittedBounds)
CBoundingBoxAligned CParticleEmitterType::CalculateBounds(CVector3D emitterPos, CBoundingBoxAligned emittedBounds)
{
CBound bounds = m_MaxBounds;
CBoundingBoxAligned bounds = m_MaxBounds;
bounds[0] += emitterPos;
bounds[1] += emitterPos;

View File

@ -21,7 +21,7 @@
#include "graphics/Texture.h"
#include "lib/ogl.h"
#include "lib/file/vfs/vfs_path.h"
#include "maths/Bound.h"
#include "maths/BoundingBoxAligned.h"
class CVector3D;
class CParticleEmitter;
@ -88,7 +88,7 @@ private:
*/
void UpdateEmitterStep(CParticleEmitter& emitter, float dt);
CBound CalculateBounds(CVector3D emitterPos, CBound emittedBounds);
CBoundingBoxAligned CalculateBounds(CVector3D emitterPos, CBoundingBoxAligned emittedBounds);
CTexturePtr m_Texture;
@ -99,7 +99,7 @@ private:
float m_MaxLifetime;
size_t m_MaxParticles;
CBound m_MaxBounds;
CBoundingBoxAligned m_MaxBounds;
typedef shared_ptr<IParticleVar> IParticleVarPtr;
std::vector<IParticleVarPtr> m_Variables;

View File

@ -57,7 +57,7 @@ void CPatch::Initialize(CTerrain* parent,ssize_t x,ssize_t z)
// CalcBounds: calculating the bounds of this patch
void CPatch::CalcBounds()
{
m_Bounds.SetEmpty();
m_WorldBounds.SetEmpty();
for (ssize_t j=0;j<PATCH_SIZE+1;j++)
{
@ -65,14 +65,14 @@ void CPatch::CalcBounds()
{
CVector3D pos;
m_Parent->CalcPosition(m_X*PATCH_SIZE+i,m_Z*PATCH_SIZE+j,pos);
m_Bounds+=pos;
m_WorldBounds+=pos;
}
}
// If this a side patch, the sides go down to height 0, so add them
// into the bounds
if (GetSideFlags())
m_Bounds[0].Y = std::min(m_Bounds[0].Y, 0.f);
m_WorldBounds[0].Y = std::min(m_WorldBounds[0].Y, 0.f);
}
int CPatch::GetSideFlags()

View File

@ -23,7 +23,7 @@
#define INCLUDED_RENDERABLEOBJECT
#include "maths/Bound.h"
#include "maths/BoundingBoxAligned.h"
#include "maths/Matrix3D.h"
@ -86,20 +86,29 @@ public:
if (m_RenderData) m_RenderData->m_UpdateFlags|=dirtyflags;
}
// calculate (and store in m_Bounds) the world space bounds of this object
// - must be implemented by all concrete subclasses
/**
* (Re)calculates and stores any bounds or bound-dependent data for this object. At this abstraction level, this is only the world-space
* bounds stored in @ref m_WorldBounds; subclasses may use this method to (re)compute additional bounds if necessary, or any data that
* depends on the bounds. Whenever bound-dependent data is requested through a public interface, @ref RecalculateBoundsIfNecessary should
* be called first to ensure bound correctness, which will in turn call this method if it turns out that they're outdated.
*
* @see m_BoundsValid
* @see RecalculateBoundsIfNecessary
*/
virtual void CalcBounds() = 0;
// return world space bounds of this object
const CBound& GetBounds() {
if (! m_BoundsValid) {
CalcBounds();
m_BoundsValid = true;
}
return m_Bounds;
/// Returns the world-space axis-aligned bounds of this object.
const CBoundingBoxAligned& GetWorldBounds() {
RecalculateBoundsIfNecessary();
return m_WorldBounds;
}
void InvalidateBounds() { m_BoundsValid = false; }
/**
* Marks the bounds as invalid. This will trigger @ref RecalculateBoundsIfNecessary to recompute any bound-related data the next time
* any bound-related data is requested through a public interface -- at least, if you've made sure to call it before returning the
* stored data.
*/
virtual void InvalidateBounds() { m_BoundsValid = false; }
// Set the object renderdata and free previous renderdata, if any.
void SetRenderData(CRenderData* renderdata) {
@ -107,13 +116,23 @@ public:
m_RenderData = renderdata;
}
// return object renderdata - can be null if renderer hasn't yet
// created the renderdata
/// Return object renderdata - can be null if renderer hasn't yet created the renderdata
CRenderData* GetRenderData() { return m_RenderData; }
protected:
// object bounds
CBound m_Bounds;
/// Factored out so subclasses don't need to repeat this if they want to add additional getters for bounds-related methods
/// (since they'll have to make sure to recalc the bounds if necessary before they return it).
void RecalculateBoundsIfNecessary()
{
if (!m_BoundsValid) {
CalcBounds();
m_BoundsValid = true;
}
}
protected:
/// World-space bounds of this object
CBoundingBoxAligned m_WorldBounds;
// local->world space transform
CMatrix3D m_Transform;
// world->local space transform
@ -121,8 +140,17 @@ protected:
// object renderdata
CRenderData* m_RenderData;
private:
// remembers whether m_bounds needs to be recalculated
/**
* Remembers whether any bounds need to be recalculated. Subclasses that add any data that depends on the bounds should
* take care to consider the validity of the bounds and recalculate their data when necessary -- overriding @ref CalcBounds
* to do so would be a good idea, since it's already set up to be called by @ref RecalculateBoundsIfNecessary whenever the
* bounds are marked as invalid. The latter should then be called before returning any bounds or bounds-derived data through
* a public interface (see the implementation of @ref GetWorldBounds for an example).
*
* @see CalcBounds
* @see InvalidateBounds
* @see RecalculateBoundsIfNecessary
*/
bool m_BoundsValid;
};

View File

@ -22,7 +22,7 @@
#ifndef INCLUDED_SKELETONANIM
#define INCLUDED_SKELETONANIM
#include "maths/Bound.h"
#include "maths/BoundingBoxAligned.h"
class CSkeletonAnimDef;
@ -47,7 +47,7 @@ public:
float m_ActionPos;
float m_ActionPos2;
// object space bounds of the model when this animation is applied to it
CBound m_ObjectBounds;
CBoundingBoxAligned m_ObjectBounds;
};
#endif

View File

@ -625,7 +625,7 @@ void CTerrain::MakeDirty(int dirtyFlags)
}
}
CBound CTerrain::GetVertexesBound(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1)
CBoundingBoxAligned CTerrain::GetVertexesBound(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1)
{
i0 = clamp(i0, (ssize_t)0, m_MapSize-1);
j0 = clamp(j0, (ssize_t)0, m_MapSize-1);
@ -644,7 +644,7 @@ CBound CTerrain::GetVertexesBound(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1
}
}
CBound bound;
CBoundingBoxAligned bound;
bound[0].X = (float)(i0*CELL_SIZE);
bound[0].Y = (float)(minH*HEIGHT_SCALE);
bound[0].Z = (float)(j0*CELL_SIZE);

View File

@ -30,7 +30,7 @@ class CPatch;
class CMiniPatch;
class CFixedVector3D;
class CStr8;
class CBound;
class CBoundingBoxAligned;
///////////////////////////////////////////////////////////////////////////////
// Terrain Constants:
@ -140,7 +140,7 @@ public:
/**
* Returns a 3D bounding box encompassing the given vertex range (inclusive)
*/
CBound GetVertexesBound(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1);
CBoundingBoxAligned GetVertexesBound(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1);
// get the base colour for the terrain (typically pure white - other colours
// will interact badly with LOS - but used by the Actor Viewer tool)

View File

@ -100,15 +100,16 @@ CUnit* CUnitManager::PickUnit(const CVector3D& origin, const CVector3D& dir) con
CUnit* unit = m_Units[i];
float tmin, tmax;
if (unit->GetModel().GetBounds().RayIntersect(origin, dir, tmin, tmax))
const CBoundingBoxOriented& selectionBox = unit->GetModel().GetSelectionBox();
if (selectionBox.RayIntersect(origin, dir, tmin, tmax))
{
// Point of closest approach
CVector3D obj;
unit->GetModel().GetBounds().GetCentre(obj);
CVector3D delta = obj - origin;
// TODO: this next bit is virtually identical to Selection::PickEntitiesAtPoint; might be useful to factor it out and
// reuse it
CVector3D delta = selectionBox.m_Center - origin;
float distance = delta.Dot(dir);
CVector3D closest = origin + dir * distance;
CVector3D offset = obj - closest;
CVector3D offset = selectionBox.m_Center - closest;
float rel = offset.Length();
if (rel < minrel) {

View File

@ -49,6 +49,7 @@
#include "simulation2/components/ICmpGuiInterface.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/components/ICmpTemplateManager.h"
#include "simulation2/components/ICmpSelectable.h"
#include "simulation2/helpers/Selection.h"
#include "js/jsapi.h"
@ -518,6 +519,10 @@ void QuickLoad(void* UNUSED(cbdata))
{
g_Game->GetTurnManager()->QuickLoad();
}
void SetBoundingBoxDebugOverlay(void* UNUSED(cbdata), bool enabled)
{
ICmpSelectable::ms_EnableDebugOverlays = enabled;
}
} // namespace
@ -590,4 +595,5 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
scriptInterface.RegisterFunction<void, &DumpSimState>("DumpSimState");
scriptInterface.RegisterFunction<void, unsigned int, &EnableTimeWarpRecording>("EnableTimeWarpRecording");
scriptInterface.RegisterFunction<void, &RewindTimeWarp>("RewindTimeWarp");
scriptInterface.RegisterFunction<void, bool, &SetBoundingBoxDebugOverlay>("SetBoundingBoxDebugOverlay");
}

View File

@ -21,23 +21,25 @@
#include "precompiled.h"
#include "Bound.h"
#include "BoundingBoxAligned.h"
#include "lib/ogl.h"
#include <float.h>
#include "graphics/Frustum.h"
#include "maths/BoundingBoxOriented.h"
#include "maths/Brush.h"
#include "maths/Matrix3D.h"
const CBoundingBoxAligned CBoundingBoxAligned::EMPTY = CBoundingBoxAligned(); // initializes to an empty bound
///////////////////////////////////////////////////////////////////////////////
// RayIntersect: intersect ray with this bound; return true
// if ray hits (and store entry and exit times), or false
// otherwise
// note: incoming ray direction must be normalised
bool CBound::RayIntersect(const CVector3D& origin,const CVector3D& dir,
bool CBoundingBoxAligned::RayIntersect(const CVector3D& origin,const CVector3D& dir,
float& tmin,float& tmax) const
{
float t1,t2;
@ -118,7 +120,7 @@ bool CBound::RayIntersect(const CVector3D& origin,const CVector3D& dir,
///////////////////////////////////////////////////////////////////////////////
// SetEmpty: initialise this bound as empty
void CBound::SetEmpty()
void CBoundingBoxAligned::SetEmpty()
{
m_Data[0]=CVector3D( FLT_MAX, FLT_MAX, FLT_MAX);
m_Data[1]=CVector3D(-FLT_MAX,-FLT_MAX,-FLT_MAX);
@ -126,7 +128,7 @@ void CBound::SetEmpty()
///////////////////////////////////////////////////////////////////////////////
// IsEmpty: tests whether this bound is empty
bool CBound::IsEmpty() const
bool CBoundingBoxAligned::IsEmpty() const
{
return (m_Data[0].X == FLT_MAX && m_Data[0].Y == FLT_MAX && m_Data[0].Z == FLT_MAX
&& m_Data[1].X == -FLT_MAX && m_Data[1].Y == -FLT_MAX && m_Data[1].Z == -FLT_MAX);
@ -136,7 +138,7 @@ bool CBound::IsEmpty() const
// Transform: transform this bound by given matrix; return transformed bound
// in 'result' parameter - slightly modified version of code in Graphic Gems
// (can't remember which one it was, though)
void CBound::Transform(const CMatrix3D& m,CBound& result) const
void CBoundingBoxAligned::Transform(const CMatrix3D& m, CBoundingBoxAligned& result) const
{
ENSURE(this!=&result);
@ -161,10 +163,59 @@ void CBound::Transform(const CMatrix3D& m,CBound& result) const
}
}
void CBoundingBoxAligned::Transform(const CMatrix3D& transform, CBoundingBoxOriented& result) const
{
// The idea is this: compute the corners of this bounding box, transform them according to the specified matrix,
// then derive the box center, orientation vectors, and half-sizes.
// TODO: this implementation can be further optimized; see Philip's comments on http://trac.wildfiregames.com/ticket/914
const CVector3D& pMin = m_Data[0];
const CVector3D& pMax = m_Data[1];
// Find the corners of these bounds. We only need some of the corners to derive the information we need, so let's
// not actually compute all of them. The corners are numbered starting from the minimum position (m_Data[0]), going
// counter-clockwise in the bottom plane, and then in the same order for the top plane (starting from the corner
// that's directly above the minimum position). Hence, corner0 is pMin and corner6 is pMax, so we don't need to
// custom-create those.
CVector3D corner0; // corner0 is pMin, no need to copy it
CVector3D corner1(pMax.X, pMin.Y, pMin.Z);
CVector3D corner3(pMin.X, pMin.Y, pMax.Z);
CVector3D corner4(pMin.X, pMax.Y, pMin.Z);
CVector3D corner6; // corner6 is pMax, no need to copy it
// transform corners to world space
corner0 = transform.Transform(pMin); // = corner0
corner1 = transform.Transform(corner1);
corner3 = transform.Transform(corner3);
corner4 = transform.Transform(corner4);
corner6 = transform.Transform(pMax); // = corner6
// Compute orientation vectors, half-size vector, and box center. We can get the orientation vectors by just taking
// the directional vectors from a specific corner point (corner0) to the other corners, once in each direction. The
// half-sizes are similarly computed by taking the distances of those sides and dividing them by 2. Finally, the
// center is simply the middle between the transformed pMin and pMax corners.
const CVector3D sideU(corner1 - corner0);
const CVector3D sideV(corner4 - corner0);
const CVector3D sideW(corner3 - corner0);
result.m_Basis[0] = sideU.Normalized();
result.m_Basis[1] = sideV.Normalized();
result.m_Basis[2] = sideW.Normalized();
result.m_HalfSizes = CVector3D(
sideU.Length()/2.f,
sideV.Length()/2.f,
sideW.Length()/2.f
);
result.m_Center = (corner0 + corner6) * 0.5f;
}
///////////////////////////////////////////////////////////////////////////////
// Intersect with the given frustum in a conservative manner
void CBound::IntersectFrustumConservative(const CFrustum& frustum)
void CBoundingBoxAligned::IntersectFrustumConservative(const CFrustum& frustum)
{
CBrush brush(*this);
CBrush buf;
@ -176,7 +227,7 @@ void CBound::IntersectFrustumConservative(const CFrustum& frustum)
///////////////////////////////////////////////////////////////////////////////
void CBound::Expand(float amount)
void CBoundingBoxAligned::Expand(float amount)
{
m_Data[0] -= CVector3D(amount, amount, amount);
m_Data[1] += CVector3D(amount, amount, amount);
@ -184,7 +235,7 @@ void CBound::Expand(float amount)
///////////////////////////////////////////////////////////////////////////////
// Render the bounding box
void CBound::Render() const
void CBoundingBoxAligned::Render() const
{
glBegin(GL_QUADS);
glTexCoord2f(0, 0); glVertex3f(m_Data[0].X, m_Data[0].Y, m_Data[0].Z);

View File

@ -27,20 +27,34 @@
class CFrustum;
class CMatrix3D;
class CBoundingBoxOriented;
///////////////////////////////////////////////////////////////////////////////
// CBound: basic axis aligned bounding box class
class CBound
// basic axis aligned bounding box (AABB) class
class CBoundingBoxAligned
{
public:
CBound() { SetEmpty(); }
CBound(const CVector3D& min,const CVector3D& max) {
m_Data[0]=min; m_Data[1]=max;
CBoundingBoxAligned() { SetEmpty(); }
CBoundingBoxAligned(const CVector3D& min, const CVector3D& max) {
m_Data[0] = min;
m_Data[1] = max;
}
void Transform(const CMatrix3D& m,CBound& result) const;
/**
* Transforms these bounds according to the specified transformation matrix @m, and writes the axis-aligned bounds
* of that result to @p result.
*/
void Transform(const CMatrix3D& m, CBoundingBoxAligned& result) const;
CVector3D& operator[](int index) { return m_Data[index]; }
/**
* Transform these bounds using the matrix @p transform, and write out the result as an oriented (i.e. non-axis-aligned) box.
* The difference with @ref Transform(const CMatrix3D&, CBoundingBoxAligned&) is that that method is equivalent to first
* computing this result, and then from that taking the axis-aligned bounding boxes again.
*/
void Transform(const CMatrix3D& m, CBoundingBoxOriented& result) const;
CVector3D& operator[](int index) { return m_Data[index]; }
const CVector3D& operator[](int index) const { return m_Data[index]; }
void SetEmpty();
@ -57,30 +71,43 @@ public:
}
// operator+=: extend this bound to include given bound
CBound& operator+=(const CBound& b)
CBoundingBoxAligned& operator+=(const CBoundingBoxAligned& b)
{
Extend(b.m_Data[0], b.m_Data[1]);
return *this;
}
// operator+=: extend this bound to include given point
CBound& operator+=(const CVector3D& pt)
CBoundingBoxAligned& operator+=(const CVector3D& pt)
{
Extend(pt, pt);
return *this;
}
bool RayIntersect(const CVector3D& origin,const CVector3D& dir,float& tmin,float& tmax) const;
/**
* Returns true if the ray originating in @p origin and with unit direction vector @p dir intersects this AABB, false otherwise.
* Additionally, returns the distance in the positive direction from the origin of the ray to the entry and exit points in
* the bounding box in @p tmin and @p tmax. If the origin is inside the box, then this is counted as an intersection and one
* of @p tMin and @p tMax may be negative.
*
* See also Real-Time Rendering, Third Edition by T. Akenine-Moller, p. 741--742.
*
* @param origin Origin of the ray.
* @param dir Direction vector of the ray, defining the positive direction of the ray. Must be of unit length.
*/
bool RayIntersect(const CVector3D& origin, const CVector3D& dir, float& tmin, float& tmax) const;
// return the volume of this bounding box
float GetVolume() const {
CVector3D v=m_Data[1]-m_Data[0];
return std::max(v.X, 0.0f)*std::max(v.Y, 0.0f)*std::max(v.Z, 0.0f);
float GetVolume() const
{
CVector3D v = m_Data[1] - m_Data[0];
return (std::max(v.X, 0.0f) * std::max(v.Y, 0.0f) * std::max(v.Z, 0.0f));
}
// return the centre of this bounding box
void GetCentre(CVector3D& centre) const {
centre=(m_Data[0]+m_Data[1])*0.5f;
void GetCentre(CVector3D& centre) const
{
centre = (m_Data[0] + m_Data[1]) * 0.5f;
}
/**
@ -109,7 +136,12 @@ public:
void Render() const;
private:
// Holds the minimal and maximal coordinate points in m_Data[0] and m_Data[1], respectively.
CVector3D m_Data[2];
public:
static const CBoundingBoxAligned EMPTY;
};
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -0,0 +1,86 @@
/* Copyright (C) 2011 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 "BoundingBoxOriented.h"
#include "maths/BoundingBoxAligned.h"
#include <float.h>
const CBoundingBoxOriented CBoundingBoxOriented::EMPTY = CBoundingBoxOriented();
CBoundingBoxOriented::CBoundingBoxOriented(const CBoundingBoxAligned& bound)
{
if (bound.IsEmpty())
{
SetEmpty();
}
else
{
bound.GetCentre(m_Center);
// the axes of an AABB are the world-space axes
m_Basis[0].X = 1.f; m_Basis[0].Y = 0.f; m_Basis[0].Z = 0.f;
m_Basis[1].X = 0.f; m_Basis[1].Y = 1.f; m_Basis[1].Z = 0.f;
m_Basis[2].X = 0.f; m_Basis[2].Y = 0.f; m_Basis[2].Z = 1.f;
// element-wise division by two to get half sizes (remember, [1] and [0] are the max and min coord points)
m_HalfSizes = (bound[1] - bound[0]) * 0.5f;
}
}
bool CBoundingBoxOriented::RayIntersect(const CVector3D& origin, const CVector3D& dir, float& tMin_out, float& tMax_out) const
{
// See Real-Time Rendering, Third Edition, p. 743
float tMin = -FLT_MAX;
float tMax = FLT_MAX;
CVector3D p = m_Center - origin;
for (int i = 0; i < 3; ++i)
{
float e = m_Basis[i].Dot(p);
float f = m_Basis[i].Dot(dir);
if(fabs(f) > 1e-10f)
{
float invF = 1.f/f;
float t1 = (e + m_HalfSizes[i]) * invF;
float t2 = (e - m_HalfSizes[i]) * invF;
if (t1 > t2)
{
float tmp = t1;
t1 = t2;
t2 = tmp;
}
if (t1 > tMin) tMin = t1;
if (t2 < tMax) tMax = t2;
if (tMin > tMax) return false;
if (tMax < 0) return false;
}
else
{
if(-e - m_HalfSizes[i] > 0 || -e + m_HalfSizes[i] < 0) return false;
}
}
tMin_out = tMin;
tMax_out = tMax;
return true;
}

View File

@ -0,0 +1,107 @@
/* Copyright (C) 2011 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_BOX
#define INCLUDED_BOX
#include "maths/Vector3D.h"
class CBoundingBoxAligned;
/*
* Generic oriented box. Originally intended to be used an Oriented Bounding Box (OBB), as opposed to CBoundingBoxAligned which is
* always aligned to the world-space axes (AABB). However, it can also be used to represent more generic shapes, such as
* parallelepipeds, for other purposes.
*/
class CBoundingBoxOriented
{
public:
/// Empty constructor; creates an empty box
CBoundingBoxOriented() { SetEmpty(); }
/**
* Constructs a new oriented box centered at @p center and with normalized side vectors @p u, @p v and @p w. These vectors should
* be mutually orthonormal for a proper rectangular box. The half-widths of the box in each dimension are given by the corresponding
* components of @p halfSizes.
*/
CBoundingBoxOriented(const CVector3D& center, const CVector3D& u, const CVector3D& v, const CVector3D& w, const CVector3D& halfSizes)
: m_Center(center), m_HalfSizes(halfSizes)
{
m_Basis[0] = u;
m_Basis[1] = v;
m_Basis[2] = w;
}
/// Constructs a new box from an axis-aligned bounding box (AABB).
explicit CBoundingBoxOriented(const CBoundingBoxAligned& bound);
/**
* Returns true if the ray originating in @p origin and with unit direction vector @p dir intersects this box, false otherwise.
* Additionally, returns the distance in the positive direction from the origin of the ray to the entry and exit points in the
* box in @p tMin and @p tMax. If the origin is inside the box, then this is counted as an intersection and one of @p tMin and
* @p tMax may be negative.
*
* Should not be used if IsEmpty() is true.
* See also Real-Time Rendering, Third Edition by T. Akenine-Möller, p. 741--742.
*
*
* @param origin Origin of the ray.
* @param dir Direction vector of the ray, defining the positive direction of the ray. Must be of unit length.
*/
bool RayIntersect(const CVector3D& origin, const CVector3D& dir, float& tMin, float& tMax) const;
/**
* Returns the corner at coordinate (@p u, @p v, @p w). Each of @p u, @p v and @p w must be exactly 1 or -1.
* Should not be used if IsEmpty() is true.
*/
void GetCorner(int u, int v, int w, CVector3D& out) const
{
out = m_Center + m_Basis[0]*(u*m_HalfSizes[0]) + m_Basis[1]*(v*m_HalfSizes[1]) + m_Basis[2]*(w*m_HalfSizes[2]);
}
void SetEmpty()
{
// everything is zero
m_Center = CVector3D();
m_Basis[0] = CVector3D();
m_Basis[1] = CVector3D();
m_Basis[2] = CVector3D();
m_HalfSizes = CVector3D();
}
bool IsEmpty() const
{
CVector3D empty;
return (m_Center == empty &&
m_Basis[0] == empty &&
m_Basis[1] == empty &&
m_Basis[2] == empty &&
m_HalfSizes == empty);
}
public:
CVector3D m_Center; ///< Centroid location of the box
CVector3D m_HalfSizes; ///< Half the sizes of the box in each dimension (u,v,w). Positive values are expected.
/// Basis vectors (u,v,w) of the sides. Must always be normalized, and should be
/// orthogonal for a proper rectangular cuboid.
CVector3D m_Basis[3];
static const CBoundingBoxOriented EMPTY;
};
#endif // INCLUDED_BOX

View File

@ -26,13 +26,13 @@
#include <float.h>
#include "Brush.h"
#include "Bound.h"
#include "BoundingBoxAligned.h"
#include "graphics/Frustum.h"
///////////////////////////////////////////////////////////////////////////////
// Convert the given bounds into a brush
CBrush::CBrush(const CBound& bounds)
CBrush::CBrush(const CBoundingBoxAligned& bounds)
{
m_Vertices.resize(8);
@ -58,7 +58,7 @@ CBrush::CBrush(const CBound& bounds)
///////////////////////////////////////////////////////////////////////////////
// Calculate bounds of this brush
void CBrush::Bounds(CBound& result) const
void CBrush::Bounds(CBoundingBoxAligned& result) const
{
result.SetEmpty();

View File

@ -24,7 +24,7 @@
#include "Vector3D.h"
class CBound;
class CBoundingBoxAligned;
class CFrustum;
class CPlane;
@ -40,9 +40,9 @@ public:
/**
* CBrush: Construct a brush from a bounds object.
*
* @param bounds the CBound object to construct the brush from.
* @param bounds the CBoundingBoxAligned object to construct the brush from.
*/
CBrush(const CBound& bounds);
CBrush(const CBoundingBoxAligned& bounds);
/**
* IsEmpty: Returns whether the brush is empty.
@ -56,7 +56,7 @@ public:
*
* @param result the resulting bounding box is stored here
*/
void Bounds(CBound& result) const;
void Bounds(CBoundingBoxAligned& result) const;
/**
* Slice: Cut the object along the given plane, resulting in a smaller (or even empty)

View File

@ -35,6 +35,7 @@ class CMatrix3D
public:
// the matrix data itself - accessible as either longhand names
// or via a flat or 2d array
// NOTE: _xy means row x, column y, so don't be fooled by the way they're listed below
union {
struct {
float _11, _21, _31, _41;
@ -155,6 +156,12 @@ public:
_14 == m._14 && _24 == m._24 && _34 == m._34 && _44 == m._44;
}
// inequality
bool operator!=(const CMatrix3D& m) const
{
return !(*this == m);
}
// set this matrix to the identity matrix
void SetIdentity();
// set this matrix to the zero matrix

View File

@ -17,14 +17,14 @@
#include "lib/self_test.h"
#include "maths/Bound.h"
#include "maths/BoundingBoxAligned.h"
class TestBound : public CxxTest::TestSuite
{
public:
void test_empty()
{
CBound bound;
CBoundingBoxAligned bound;
TS_ASSERT(bound.IsEmpty());
bound += CVector3D(1, 2, 3);
TS_ASSERT(! bound.IsEmpty());
@ -34,7 +34,7 @@ public:
void test_extend_vector()
{
CBound bound;
CBoundingBoxAligned bound;
CVector3D v (1, 2, 3);
bound += v;
@ -45,9 +45,9 @@ public:
void test_extend_bound()
{
CBound bound;
CBoundingBoxAligned bound;
CVector3D v (1, 2, 3);
CBound b (v, v);
CBoundingBoxAligned b (v, v);
bound += b;
CVector3D centre;

View File

@ -158,7 +158,7 @@ void ParticleRenderer::RenderBounds()
{
CParticleEmitter* emitter = m->emitters[i];
CBound bounds = emitter->m_Type->CalculateBounds(emitter->GetPosition(), emitter->GetParticleBounds());
CBoundingBoxAligned bounds = emitter->m_Type->CalculateBounds(emitter->GetPosition(), emitter->GetParticleBounds());
bounds.Render();
}
}

View File

@ -49,7 +49,7 @@ public:
CPatch* GetPatch() { return m_Patch; }
const CBound& GetWaterBounds() const { return m_WaterBounds; }
const CBoundingBoxAligned& GetWaterBounds() const { return m_WaterBounds; }
private:
friend struct SBlendStackItem;
@ -145,7 +145,7 @@ private:
std::vector<SSplat> m_BlendSplats;
// boundary of water in this patch
CBound m_WaterBounds;
CBoundingBoxAligned m_WaterBounds;
// Water vertex buffer
CVertexBuffer::VBChunk* m_VBWater;

View File

@ -1041,7 +1041,7 @@ public:
bool Filter(CModel *model)
{
return m_Frustum.IsBoxVisible(CVector3D(0, 0, 0), model->GetBoundsRec());
return m_Frustum.IsBoxVisible(CVector3D(0, 0, 0), model->GetWorldBoundsRec());
}
private:
@ -1198,7 +1198,7 @@ void CRenderer::SetObliqueFrustumClipping(const CVector4D& worldPlane)
///////////////////////////////////////////////////////////////////////////////////////////////////
// RenderReflections: render the water reflections to the reflection texture
SScreenRect CRenderer::RenderReflections(const CBound& scissor)
SScreenRect CRenderer::RenderReflections(const CBoundingBoxAligned& scissor)
{
PROFILE3_GPU("water reflections");
@ -1285,7 +1285,7 @@ SScreenRect CRenderer::RenderReflections(const CBound& scissor)
///////////////////////////////////////////////////////////////////////////////////////////////////
// RenderRefractions: render the water refractions to the refraction texture
SScreenRect CRenderer::RenderRefractions(const CBound &scissor)
SScreenRect CRenderer::RenderRefractions(const CBoundingBoxAligned &scissor)
{
PROFILE3_GPU("water refractions");
@ -1540,7 +1540,7 @@ void CRenderer::RenderSubmissions()
ogl_WarnIfError();
CBound waterScissor;
CBoundingBoxAligned waterScissor;
if (m_WaterManager->m_RenderWater)
{
waterScissor = m->terrainRenderer->ScissorWater(m_ViewCamera.GetViewProjection());
@ -1756,10 +1756,10 @@ void CRenderer::SubmitNonRecursive(CModel* model)
{
if (model->GetFlags() & MODELFLAG_CASTSHADOWS) {
// PROFILE( "updating shadow bounds" );
m->shadow->AddShadowedBound(model->GetBounds());
m->shadow->AddShadowedBound(model->GetWorldBounds());
}
// Tricky: The call to GetBounds() above can invalidate the position
// Tricky: The call to GetWorldBounds() above can invalidate the position
model->ValidatePosition();
bool canUseInstancing = false;

View File

@ -348,8 +348,8 @@ protected:
void RenderShadowMap();
// render water reflection and refraction textures
SScreenRect RenderReflections(const CBound& scissor);
SScreenRect RenderRefractions(const CBound& scissor);
SScreenRect RenderReflections(const CBoundingBoxAligned& scissor);
SScreenRect RenderRefractions(const CBoundingBoxAligned& scissor);
// debugging
void DisplayFrustum();

View File

@ -28,7 +28,7 @@
#include "graphics/LightEnv.h"
#include "maths/Bound.h"
#include "maths/BoundingBoxAligned.h"
#include "maths/MathUtil.h"
#include "maths/Matrix3D.h"
@ -67,7 +67,7 @@ struct ShadowMapInternals
// transform light space into world space
CMatrix3D InvLightTransform;
// bounding box of shadowed objects in light space
CBound ShadowBound;
CBoundingBoxAligned ShadowBound;
// Camera transformed into light space
CCamera LightspaceCamera;
@ -203,9 +203,9 @@ void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir)
//////////////////////////////////////////////////////////////////////////////
// AddShadowedBound: add a world-space bounding box to the bounds of shadowed
// objects
void ShadowMap::AddShadowedBound(const CBound& bounds)
void ShadowMap::AddShadowedBound(const CBoundingBoxAligned& bounds)
{
CBound lightspacebounds;
CBoundingBoxAligned lightspacebounds;
bounds.Transform(m->LightTransform, lightspacebounds);
m->ShadowBound += lightspacebounds;

View File

@ -24,7 +24,7 @@
#include "lib/ogl.h"
class CBound;
class CBoundingBoxAligned;
class CMatrix3D;
struct ShadowMapInternals;
@ -80,7 +80,7 @@ public:
*
* @param bounds world space bounding box
*/
void AddShadowedBound(const CBound& bounds);
void AddShadowedBound(const CBoundingBoxAligned& bounds);
/**
* BeginRender: Set OpenGL state for rendering into the shadow map texture.

View File

@ -173,14 +173,14 @@ bool TerrainRenderer::CullPatches(const CFrustum* frustum)
m->filteredPatches.clear();
for (std::vector<CPatchRData*>::iterator it = m->visiblePatches.begin(); it != m->visiblePatches.end(); it++)
{
if (frustum->IsBoxVisible(CVector3D(0, 0, 0), (*it)->GetPatch()->GetBounds()))
if (frustum->IsBoxVisible(CVector3D(0, 0, 0), (*it)->GetPatch()->GetWorldBounds()))
m->filteredPatches.push_back(*it);
}
m->filteredDecals.clear();
for (std::vector<CDecalRData*>::iterator it = m->visibleDecals.begin(); it != m->visibleDecals.end(); it++)
{
if (frustum->IsBoxVisible(CVector3D(0, 0, 0), (*it)->GetDecal()->GetBounds()))
if (frustum->IsBoxVisible(CVector3D(0, 0, 0), (*it)->GetDecal()->GetWorldBounds()))
m->filteredDecals.push_back(*it);
}
@ -557,13 +557,13 @@ void TerrainRenderer::RenderOutlines(bool filtered)
///////////////////////////////////////////////////////////////////
// Scissor rectangle of water patches
CBound TerrainRenderer::ScissorWater(const CMatrix3D &viewproj)
CBoundingBoxAligned TerrainRenderer::ScissorWater(const CMatrix3D &viewproj)
{
CBound scissor;
CBoundingBoxAligned scissor;
for (size_t i = 0; i < m->visiblePatches.size(); ++i)
{
CPatchRData* data = m->visiblePatches[i];
const CBound& waterBounds = data->GetWaterBounds();
const CBoundingBoxAligned& waterBounds = data->GetWaterBounds();
if (waterBounds.IsEmpty())
continue;
@ -571,7 +571,7 @@ CBound TerrainRenderer::ScissorWater(const CMatrix3D &viewproj)
CVector4D v2 = viewproj.Transform(CVector4D(waterBounds[1].X, waterBounds[1].Y, waterBounds[0].Z, 1.0f));
CVector4D v3 = viewproj.Transform(CVector4D(waterBounds[0].X, waterBounds[1].Y, waterBounds[1].Z, 1.0f));
CVector4D v4 = viewproj.Transform(CVector4D(waterBounds[1].X, waterBounds[1].Y, waterBounds[1].Z, 1.0f));
CBound screenBounds;
CBoundingBoxAligned screenBounds;
#define ADDBOUND(v1, v2, v3, v4) \
if (v1[2] >= -v1[3]) \
screenBounds += CVector3D(v1[0], v1[1], v1[2]) * (1.0f / v1[3]); \
@ -603,7 +603,7 @@ CBound TerrainRenderer::ScissorWater(const CMatrix3D &viewproj)
continue;
scissor += screenBounds;
}
return CBound(CVector3D(clamp(scissor[0].X, -1.0f, 1.0f), clamp(scissor[0].Y, -1.0f, 1.0f), -1.0f),
return CBoundingBoxAligned(CVector3D(clamp(scissor[0].X, -1.0f, 1.0f), clamp(scissor[0].Y, -1.0f, 1.0f), -1.0f),
CVector3D(clamp(scissor[1].X, -1.0f, 1.0f), clamp(scissor[1].Y, -1.0f, 1.0f), 1.0f));
}

View File

@ -130,7 +130,7 @@ public:
/**
* Calculate a scissor rectangle for the visible water patches.
*/
CBound ScissorWater(const CMatrix3D& viewproj);
CBoundingBoxAligned ScissorWater(const CMatrix3D& viewproj);
/**
* Render priority text for all submitted patches, for debugging.

View File

@ -71,6 +71,10 @@ COMPONENT(CommandQueue)
INTERFACE(Decay)
COMPONENT(Decay)
// Note: The VisualActor component relies on this component being initialized before itself, in order to support using
// an entity's footprint shape for the selection boxes. This dependency is not strictly necessary, but it does avoid
// some extra plumbing code to set up on-demand initialization. If you find yourself forced to break this dependency,
// see VisualActor's Init method for a description of how you can avoid it.
INTERFACE(Footprint)
COMPONENT(Footprint)
@ -142,6 +146,8 @@ COMPONENT(UnitMotionScripted)
INTERFACE(Vision)
COMPONENT(Vision)
// Note: this component relies on the Footprint component being initialized before itself. See the comments above for
// the Footprint component to find out why.
INTERFACE(Visual)
COMPONENT(VisualActor) // must be after Ownership (dependency in Deserialize)

View File

@ -140,7 +140,7 @@ public:
CmpPtr<ICmpVisual> cmpVisual(GetSimContext(), GetEntityId());
if (!cmpVisual.null())
{
CBound bound = cmpVisual->GetBounds();
CBoundingBoxAligned bound = cmpVisual->GetBounds();
m_TotalSinkDepth = std::max(m_TotalSinkDepth, bound[1].Y - bound[0].Y);
}

View File

@ -347,7 +347,7 @@ void CCmpProjectileManager::RenderSubmit(SceneCollector& collector, const CFrust
model.ValidatePosition();
if (culling && !frustum.IsBoxVisible(CVector3D(0, 0, 0), model.GetBounds()))
if (culling && !frustum.IsBoxVisible(CVector3D(0, 0, 0), model.GetWorldBounds()))
continue;
// TODO: do something about LOS (copy from CCmpVisualActor)

View File

@ -22,6 +22,7 @@
#include "ICmpPosition.h"
#include "ICmpFootprint.h"
#include "ICmpVisual.h"
#include "simulation2/MessageTypes.h"
#include "simulation2/helpers/Render.h"
@ -45,13 +46,21 @@ public:
DEFAULT_COMPONENT_ALLOCATOR(Selectable)
SOverlayLine m_Overlay;
SOverlayLine* m_DebugBoundingBoxOverlay;
SOverlayLine* m_DebugSelectionBoxOverlay;
CCmpSelectable()
: m_DebugBoundingBoxOverlay(NULL), m_DebugSelectionBoxOverlay(NULL)
{
m_Overlay.m_Thickness = 2;
m_Overlay.m_Color = CColor(0, 0, 0, 0);
}
~CCmpSelectable(){
delete m_DebugBoundingBoxOverlay;
delete m_DebugSelectionBoxOverlay;
}
static std::string GetSchema()
{
return
@ -153,8 +162,35 @@ public:
void RenderSubmit(SceneCollector& collector)
{
// (This is only called if a > 0)
collector.Submit(&m_Overlay);
if (ICmpSelectable::ms_EnableDebugOverlays)
{
// allocate debug overlays on-demand
if (!m_DebugBoundingBoxOverlay) m_DebugBoundingBoxOverlay = new SOverlayLine;
if (!m_DebugSelectionBoxOverlay) m_DebugSelectionBoxOverlay = new SOverlayLine;
CmpPtr<ICmpVisual> cmpVisual(GetSimContext(), GetEntityId());
if (!cmpVisual.null())
{
SimRender::ConstructBoxOutline(cmpVisual->GetBounds(), *m_DebugBoundingBoxOverlay);
m_DebugBoundingBoxOverlay->m_Thickness = 2;
m_DebugBoundingBoxOverlay->m_Color = CColor(1.f, 0.f, 0.f, 1.f);
SimRender::ConstructBoxOutline(cmpVisual->GetSelectionBox(), *m_DebugSelectionBoxOverlay);
m_DebugSelectionBoxOverlay->m_Thickness = 2;
m_DebugSelectionBoxOverlay->m_Color = CColor(0.f, 1.f, 0.f, 1.f);
collector.Submit(m_DebugBoundingBoxOverlay);
collector.Submit(m_DebugSelectionBoxOverlay);
}
}
else
{
// reclaim debug overlay line memory when no longer debugging (and make sure to set to zero after deletion)
if (m_DebugBoundingBoxOverlay) SAFE_DELETE(m_DebugBoundingBoxOverlay);
if (m_DebugSelectionBoxOverlay) SAFE_DELETE(m_DebugSelectionBoxOverlay);
}
}
};

View File

@ -25,6 +25,7 @@
#include "ICmpRangeManager.h"
#include "ICmpVision.h"
#include "simulation2/MessageTypes.h"
#include "simulation2/components/ICmpFootprint.h"
#include "graphics/Frustum.h"
#include "graphics/Model.h"
@ -97,7 +98,38 @@ public:
"</element>"
"<element name='SilhouetteOccluder'>"
"<data type='boolean'/>"
"</element>";
"</element>"
"<optional>"
"<element name='SelectionShape'>"
"<choice>"
"<element name='Bounds' a:help='Determines the selection box based on the model bounds'>"
"<empty/>"
"</element>"
"<element name='Footprint' a:help='Determines the selection box based on the entity Footprint component'>"
"<empty/>"
"</element>"
"<element name='Box' a:help='Sets the selection shape to a box of specified dimensions'>"
"<attribute name='width'>"
"<ref name='positiveDecimal' />"
"</attribute>"
"<attribute name='height'>"
"<ref name='positiveDecimal' />"
"</attribute>"
"<attribute name='depth'>"
"<ref name='positiveDecimal' />"
"</attribute>"
"</element>"
"<element name='Cylinder' a:help='Sets the selection shape to a cylinder of specified dimensions'>"
"<attribute name='radius'>"
"<ref name='positiveDecimal' />"
"</attribute>"
"<attribute name='height'>"
"<ref name='positiveDecimal' />"
"</attribute>"
"</element>"
"</choice>"
"</element>"
"</optional>";
}
virtual void Init(const CParamNode& paramNode)
@ -126,8 +158,16 @@ public:
if (paramNode.GetChild("SilhouetteOccluder").ToBool())
modelFlags |= MODELFLAG_SILHOUETTE_OCCLUDER;
if (m_Unit->GetModel().ToCModel())
m_Unit->GetModel().ToCModel()->AddFlagsRec(modelFlags);
CModelAbstract& model = m_Unit->GetModel();
if (model.ToCModel())
model.ToCModel()->AddFlagsRec(modelFlags);
// 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(model, paramNode);
m_Unit->SetID(GetEntityId());
}
@ -245,11 +285,18 @@ public:
}
}
virtual CBound GetBounds()
virtual CBoundingBoxAligned GetBounds()
{
if (!m_Unit)
return CBound();
return m_Unit->GetModel().GetBounds();
return CBoundingBoxAligned::EMPTY;
return m_Unit->GetModel().GetWorldBounds();
}
virtual CBoundingBoxOriented GetSelectionBox()
{
if (!m_Unit)
return CBoundingBoxOriented::EMPTY;
return m_Unit->GetModel().GetSelectionBox();
}
virtual CVector3D GetPosition()
@ -403,6 +450,13 @@ private:
return GetEntityId();
}
/// Helper method; initializes the model selection shape descriptor from XML. Factored out for readability of @ref Init.
/// The @p model argument is technically not really necessary since naturally this method is intended to initialize this
/// visual actor's model (I wouldn't know which other one you'd pass), but it's included here to enforce that the
/// component's model must have been created before using this method (i.e. to prevent accidentally calls to this method
/// before the model was constructed).
void InitSelectionShapeDescriptor(CModelAbstract& model, const CParamNode& paramNode);
void Update(fixed turnLength);
void Interpolate(float frameTime, float frameOffset);
void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling);
@ -410,6 +464,78 @@ private:
REGISTER_COMPONENT_TYPE(VisualActor)
// ------------------------------------------------------------------------------------------------------------------
void CCmpVisualActor::InitSelectionShapeDescriptor(CModelAbstract& model, const CParamNode& paramNode)
{
// by default, we don't need a custom selection shape and we can just keep the default behaviour
CModelAbstract::CustomSelectionShape* shapeDescriptor = NULL;
const CParamNode& shapeNode = paramNode.GetChild("SelectionShape");
if (shapeNode.IsOk())
{
if (shapeNode.GetChild("Bounds").IsOk())
{
// default; no need to take action
}
else if (shapeNode.GetChild("Footprint").IsOk())
{
CmpPtr<ICmpFootprint> cmpFootprint(GetSimContext(), GetEntityId());
if (!cmpFootprint.null())
{
ICmpFootprint::EShape fpShape; // fp stands for "footprint"
entity_pos_t fpSize0, fpSize1, fpHeight; // fp stands for "footprint"
cmpFootprint->GetShape(fpShape, fpSize0, fpSize1, fpHeight);
float size0 = fpSize0.ToFloat();
float size1 = fpSize1.ToFloat();
// TODO: we should properly distinguish between CIRCLE and SQUARE footprint shapes here, but since cylinders
// aren't implemented yet and are almost indistinguishable from boxes for small enough sizes anyway,
// we'll just use boxes for either case. However, for circular footprints the size0 and size1 values both
// represent the radius, so we do have to adjust them to match the size1 and size0's of square footprints
// (which represent the full width and depth).
if (fpShape == ICmpFootprint::CIRCLE)
{
size0 *= 2;
size1 *= 2;
}
shapeDescriptor = new CModelAbstract::CustomSelectionShape;
shapeDescriptor->m_Type = CModelAbstract::CustomSelectionShape::BOX;
shapeDescriptor->m_Size0 = size0;
shapeDescriptor->m_Size1 = size1;
shapeDescriptor->m_Height = fpHeight.ToFloat();
}
else
{
LOGERROR(L"[VisualActor] Cannot apply footprint-based SelectionShape; Footprint component not initialized.");
}
}
else if (shapeNode.GetChild("Box").IsOk())
{
// TODO: we might need to support the ability to specify a different box center in the future
shapeDescriptor = new CModelAbstract::CustomSelectionShape;
shapeDescriptor->m_Type = CModelAbstract::CustomSelectionShape::BOX;
shapeDescriptor->m_Size0 = shapeNode.GetChild("Box").GetChild("@width").ToFixed().ToFloat();
shapeDescriptor->m_Size1 = shapeNode.GetChild("Box").GetChild("@depth").ToFixed().ToFloat();
shapeDescriptor->m_Height = shapeNode.GetChild("Box").GetChild("@height").ToFixed().ToFloat();
}
else if (shapeNode.GetChild("Cylinder").IsOk())
{
LOGWARNING(L"[VisualActor] TODO: Cylinder selection shapes are not yet implemented; defaulting to recursive bounding boxes");
}
else
{
// shouldn't happen by virtue of validation against schema
LOGERROR(L"[VisualActor] No selection shape specified");
}
}
// the model is now responsible for cleaning up the descriptor
model.SetCustomSelectionShape(shapeDescriptor);
}
void CCmpVisualActor::Update(fixed turnLength)
{
if (m_Unit == NULL)
@ -504,7 +630,7 @@ void CCmpVisualActor::RenderSubmit(SceneCollector& collector, const CFrustum& fr
CModelAbstract& model = m_Unit->GetModel();
if (culling && !frustum.IsBoxVisible(CVector3D(0, 0, 0), model.GetBoundsRec()))
if (culling && !frustum.IsBoxVisible(CVector3D(0, 0, 0), model.GetWorldBoundsRec()))
return;
collector.SubmitRecursive(&model);

View File

@ -26,3 +26,5 @@
BEGIN_INTERFACE_WRAPPER(Selectable)
DEFINE_INTERFACE_METHOD_1("SetSelectionHighlight", void, ICmpSelectable, SetSelectionHighlight, CColor)
END_INTERFACE_WRAPPER(Selectable)
bool ICmpSelectable::ms_EnableDebugOverlays = false;

View File

@ -32,6 +32,11 @@ public:
virtual void SetSelectionHighlight(CColor color) = 0;
DECLARE_INTERFACE_TYPE(Selectable)
// TODO: this is slightly ugly design; it would be nice to change the component system to support per-component-type data
// and methods, where we can keep settings like these. Note that any such data store would need to be per-component-manager
// and not entirely global, to support multiple simulation instances.
static bool ms_EnableDebugOverlays; // ms for member static
};
#endif // INCLUDED_ICMPSELECTABLE

View File

@ -20,7 +20,8 @@
#include "simulation2/system/Interface.h"
#include "maths/Bound.h"
#include "maths/BoundingBoxOriented.h"
#include "maths/BoundingBoxAligned.h"
#include "maths/Fixed.h"
#include "lib/file/vfs/vfs_path.h"
@ -34,7 +35,14 @@ public:
* Get the world-space bounding box of the object's visual representation.
* (Not safe for use in simulation code.)
*/
virtual CBound GetBounds() = 0;
virtual CBoundingBoxAligned GetBounds() = 0;
/**
* Get the oriented world-space bounding box of the object's visual representation, clipped at the Y=0 plane in object space
* to prevent it from extending into the terrain. The primary difference with GetBounds is that this bounding box is not aligned
* to the world axes, but arbitrarily rotated according to the model transform.
*/
virtual CBoundingBoxOriented GetSelectionBox() = 0;
/**
* Get the world-space position of the base point of the object's visual representation.

View File

@ -24,9 +24,12 @@
#include "simulation2/components/ICmpWaterManager.h"
#include "graphics/Overlay.h"
#include "graphics/Terrain.h"
#include "maths/BoundingBoxAligned.h"
#include "maths/BoundingBoxOriented.h"
#include "maths/MathUtil.h"
#include "maths/Vector2D.h"
#include "ps/Profile.h"
#include "maths/Quaternion.h"
void SimRender::ConstructLineOnGround(const CSimContext& context, const std::vector<float>& xz,
SOverlayLine& overlay, bool floating, float heightOffset)
@ -159,6 +162,121 @@ void SimRender::ConstructSquareOnGround(const CSimContext& context, float x, flo
}
}
void SimRender::ConstructBoxOutline(const CBoundingBoxAligned& bound, SOverlayLine& overlayLine)
{
overlayLine.m_Coords.clear();
if (bound.IsEmpty())
return;
const CVector3D& pMin = bound[0];
const CVector3D& pMax = bound[1];
// floor square
overlayLine.PushCoords(pMin.X, pMin.Y, pMin.Z);
overlayLine.PushCoords(pMax.X, pMin.Y, pMin.Z);
overlayLine.PushCoords(pMax.X, pMin.Y, pMax.Z);
overlayLine.PushCoords(pMin.X, pMin.Y, pMax.Z);
overlayLine.PushCoords(pMin.X, pMin.Y, pMin.Z);
// roof square
overlayLine.PushCoords(pMin.X, pMax.Y, pMin.Z);
overlayLine.PushCoords(pMax.X, pMax.Y, pMin.Z);
overlayLine.PushCoords(pMax.X, pMax.Y, pMax.Z);
overlayLine.PushCoords(pMin.X, pMax.Y, pMax.Z);
overlayLine.PushCoords(pMin.X, pMax.Y, pMin.Z);
}
void SimRender::ConstructBoxOutline(const CBoundingBoxOriented& box, SOverlayLine& overlayLine)
{
overlayLine.m_Coords.clear();
if (box.IsEmpty())
return;
CVector3D corners[8];
box.GetCorner(-1, -1, -1, corners[0]);
box.GetCorner( 1, -1, -1, corners[1]);
box.GetCorner( 1, -1, 1, corners[2]);
box.GetCorner(-1, -1, 1, corners[3]);
box.GetCorner(-1, 1, -1, corners[4]);
box.GetCorner( 1, 1, -1, corners[5]);
box.GetCorner( 1, 1, 1, corners[6]);
box.GetCorner(-1, 1, 1, corners[7]);
overlayLine.PushCoords(corners[0]);
overlayLine.PushCoords(corners[1]);
overlayLine.PushCoords(corners[2]);
overlayLine.PushCoords(corners[3]);
overlayLine.PushCoords(corners[0]);
overlayLine.PushCoords(corners[4]);
overlayLine.PushCoords(corners[5]);
overlayLine.PushCoords(corners[6]);
overlayLine.PushCoords(corners[7]);
overlayLine.PushCoords(corners[4]);
}
void SimRender::ConstructGimbal(const CVector3D& center, float radius, SOverlayLine& out, size_t numSteps)
{
ENSURE(numSteps > 0 && numSteps % 4 == 0); // must be a positive multiple of 4
out.m_Coords.clear();
size_t fullCircleSteps = numSteps;
const float angleIncrement = 2.f*M_PI/fullCircleSteps;
const CVector3D X_UNIT(1, 0, 0);
const CVector3D Y_UNIT(0, 1, 0);
const CVector3D Z_UNIT(0, 0, 1);
CVector3D rotationVector(0, 0, radius); // directional vector based in the center that we will be rotating to get the gimbal points
// first draw a quarter of XZ gimbal; then complete the XY gimbal; then continue the XZ gimbal and finally add the YZ gimbal
// (that way we can keep a single continuous line)
// -- XZ GIMBAL (PART 1/2) -----------------------------------------------
CQuaternion xzRotation;
xzRotation.FromAxisAngle(Y_UNIT, angleIncrement);
for (size_t i = 0; i < fullCircleSteps/4; ++i) // complete only a quarter of the way
{
out.PushCoords(center + rotationVector);
rotationVector = xzRotation.Rotate(rotationVector);
}
// -- XY GIMBAL ----------------------------------------------------------
// now complete the XY gimbal while the XZ gimbal is interrupted
CQuaternion xyRotation;
xyRotation.FromAxisAngle(Z_UNIT, angleIncrement);
for (size_t i = 0; i < fullCircleSteps; ++i) // note the <; the last point of the XY gimbal isn't added, because the XZ gimbal will add it
{
out.PushCoords(center + rotationVector);
rotationVector = xyRotation.Rotate(rotationVector);
}
// -- XZ GIMBAL (PART 2/2) -----------------------------------------------
// resume the XZ gimbal to completion
for (size_t i = fullCircleSteps/4; i < fullCircleSteps; ++i) // exclude the last point of the circle so the YZ gimbal can add it
{
out.PushCoords(center + rotationVector);
rotationVector = xzRotation.Rotate(rotationVector);
}
// -- YZ GIMBAL ----------------------------------------------------------
CQuaternion yzRotation;
yzRotation.FromAxisAngle(X_UNIT, angleIncrement);
for (size_t i = 0; i <= fullCircleSteps; ++i)
{
out.PushCoords(center + rotationVector);
rotationVector = yzRotation.Rotate(rotationVector);
}
}
void SimRender::SmoothPointsAverage(std::vector<CVector2D>& points, bool closed)
{
PROFILE("SmoothPointsAverage");

View File

@ -25,6 +25,9 @@
class CSimContext;
class CVector2D;
class CVector3D;
class CBoundingBoxAligned;
class CBoundingBoxOriented;
struct SOverlayLine;
namespace SimRender
@ -53,6 +56,23 @@ void ConstructSquareOnGround(const CSimContext& context, float x, float z, float
SOverlayLine& overlay,
bool floating, float heightOffset = 0.25f);
/**
* Constructs a solid outline of an arbitrarily-aligned box.
*/
void ConstructBoxOutline(const CBoundingBoxOriented& box, SOverlayLine& overlayLine);
/**
* Constructs a solid outline of an axis-aligned bounding box.
*/
void ConstructBoxOutline(const CBoundingBoxAligned& bound, SOverlayLine& overlayLine);
/**
* Constructs a simple gimbal outline of radius @p radius at @p center in @p out.
* @param numSteps The amount of steps to trace a circle's complete outline. Must be a (strictly) positive multiple of four.
* For small radii, you can get away with small values; setting this to 4 will create a diamond shape.
*/
void ConstructGimbal(const CVector3D& center, float radius, SOverlayLine& out, size_t numSteps = 16);
/**
* Updates @p points so each point is averaged with its neighbours, resulting in
* a somewhat smoother curve, assuming the points are roughly equally spaced.

View File

@ -51,19 +51,18 @@ std::vector<entity_id_t> EntitySelection::PickEntitiesAtPoint(CSimulation2& simu
if (cmpVisual.null())
continue;
CBound bounds = cmpVisual->GetBounds();
CBoundingBoxOriented selectionBox = cmpVisual->GetSelectionBox();
if (selectionBox.IsEmpty())
continue;
float tmin, tmax;
if (!bounds.RayIntersect(origin, dir, tmin, tmax))
if (!selectionBox.RayIntersect(origin, dir, tmin, tmax))
continue;
// Find the perpendicular distance from the object's centre to the picker ray
CVector3D centre;
bounds.GetCentre(centre);
CVector3D closest = origin + dir * (centre - origin).Dot(dir);
float dist2 = (closest - centre).LengthSquared();
CVector3D closest = origin + dir * (selectionBox.m_Center - origin).Dot(dir);
float dist2 = (closest - selectionBox.m_Center).LengthSquared();
hits.push_back(std::make_pair(dist2, ent));
}

View File

@ -44,6 +44,8 @@ enum
ID_ViewerShadows,
ID_ViewerPolyCount,
ID_ViewerAnimation,
ID_ViewerBoundingBox,
ID_ViewerAxesMarker,
ID_ViewerPlay,
ID_ViewerPause,
ID_ViewerSlow
@ -59,10 +61,15 @@ static wxWindow* Tooltipped(wxWindow* window, const wxString& tip)
class ObjectBottomBar : public wxPanel
{
public:
ObjectBottomBar(wxWindow* parent, ScenarioEditor& scenarioEditor, Observable<ObjectSettings>& objectSettings, Observable<AtObj>& mapSettings, ObjectSidebarImpl* p);
ObjectBottomBar(
wxWindow* parent,
ScenarioEditor& scenarioEditor,
Observable<ObjectSettings>& objectSettings,
Observable<AtObj>& mapSettings,
ObjectSidebarImpl* p
);
void OnFirstDisplay();
void ShowActorViewer(bool show);
private:
@ -75,11 +82,12 @@ private:
bool m_ViewerGround;
bool m_ViewerShadows;
bool m_ViewerPolyCount;
bool m_ViewerBoundingBox;
bool m_ViewerAxesMarker;
wxPanel* m_ViewerPanel;
ObjectSidebarImpl* p;
ScenarioEditor& m_ScenarioEditor;
DECLARE_EVENT_TABLE();
@ -108,14 +116,27 @@ struct ObjectSidebarImpl
}
};
ObjectSidebar::ObjectSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer)
: Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer), p(new ObjectSidebarImpl())
ObjectSidebar::ObjectSidebar(
ScenarioEditor& scenarioEditor,
wxWindow* sidebarContainer,
wxWindow* bottomBarContainer
)
: Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer),
p(new ObjectSidebarImpl())
{
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(new wxStaticText(this, wxID_ANY, _("Filter")), wxSizerFlags().Align(wxALIGN_CENTER));
sizer->Add(Tooltipped(new wxTextCtrl(this, ID_ObjectFilter),
_("Enter text to filter object list")), wxSizerFlags().Expand().Proportion(1));
sizer->Add(
Tooltipped(
new wxTextCtrl(this, ID_ObjectFilter),
_("Enter text to filter object list")
),
wxSizerFlags().Expand().Proportion(1)
);
m_MainSizer->Add(sizer, wxSizerFlags().Expand());
m_MainSizer->AddSpacer(3);
// ------------------------------------------------------------------------------------------
wxArrayString strings;
strings.Add(_("Entities"));
@ -123,13 +144,27 @@ ObjectSidebar::ObjectSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarCo
wxChoice* objectType = new wxChoice(this, ID_ObjectType, wxDefaultPosition, wxDefaultSize, strings);
objectType->SetSelection(0);
m_MainSizer->Add(objectType, wxSizerFlags().Expand());
m_MainSizer->AddSpacer(3);
// ------------------------------------------------------------------------------------------
p->m_ObjectListBox = new wxListBox(this, ID_SelectObject, wxDefaultPosition, wxDefaultSize, 0, NULL, wxLB_SINGLE|wxLB_HSCROLL);
m_MainSizer->Add(p->m_ObjectListBox, wxSizerFlags().Proportion(1).Expand());
m_MainSizer->AddSpacer(3);
// ------------------------------------------------------------------------------------------
m_MainSizer->Add(new wxButton(this, ID_ToggleViewer, _("Switch to Actor Viewer")), wxSizerFlags().Expand());
m_BottomBar = new ObjectBottomBar(bottomBarContainer, scenarioEditor, scenarioEditor.GetObjectSettings(), scenarioEditor.GetMapSettings(), p);
// ------------------------------------------------------------------------------------------
m_BottomBar = new ObjectBottomBar(
bottomBarContainer,
scenarioEditor,
scenarioEditor.GetObjectSettings(),
scenarioEditor.GetMapSettings(),
p
);
p->m_ToolConn = scenarioEditor.GetToolManager().GetCurrentTool().RegisterObserver(0, &ObjectSidebar::OnToolChange, this);
}
@ -312,7 +347,13 @@ END_EVENT_TABLE();
//////////////////////////////////////////////////////////////////////////
ObjectBottomBar::ObjectBottomBar(wxWindow* parent, ScenarioEditor& scenarioEditor, Observable<ObjectSettings>& objectSettings, Observable<AtObj>& mapSettings, ObjectSidebarImpl* p)
ObjectBottomBar::ObjectBottomBar(
wxWindow* parent,
ScenarioEditor& scenarioEditor,
Observable<ObjectSettings>& objectSettings,
Observable<AtObj>& mapSettings,
ObjectSidebarImpl* p
)
: wxPanel(parent, wxID_ANY), p(p), m_ScenarioEditor(scenarioEditor)
{
m_ViewerWireframe = false;
@ -320,21 +361,39 @@ ObjectBottomBar::ObjectBottomBar(wxWindow* parent, ScenarioEditor& scenarioEdito
m_ViewerGround = true;
m_ViewerShadows = true;
m_ViewerPolyCount = false;
m_ViewerBoundingBox = false;
m_ViewerAxesMarker = false;
wxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
wxSizer* mainSizer = new wxBoxSizer(wxHORIZONTAL);
// --- viewer options panel -------------------------------------------------------------------------------
m_ViewerPanel = new wxPanel(this, wxID_ANY);
wxSizer* viewerSizer = new wxBoxSizer(wxHORIZONTAL);
wxSizer* viewerButtonsSizer = new wxStaticBoxSizer(wxVERTICAL, m_ViewerPanel, _("Display settings"));
viewerButtonsSizer->SetMinSize(140, -1);
viewerButtonsSizer->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerWireframe, _("Wireframe")), _("Toggle wireframe / solid rendering")), wxSizerFlags().Expand());
viewerButtonsSizer->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerMove, _("Move")), _("Toggle movement along ground when playing walk/run animations")), wxSizerFlags().Expand());
viewerButtonsSizer->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerGround, _("Ground")), _("Toggle the ground plane")), wxSizerFlags().Expand());
viewerButtonsSizer->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerShadows, _("Shadows")), _("Toggle shadow rendering")), wxSizerFlags().Expand());
viewerButtonsSizer->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerPolyCount, _("Poly count")), _("Toggle polygon-count statistics - turn off ground and shadows for more useful data")), wxSizerFlags().Expand());
wxSizer* viewerButtonsSizer = new wxStaticBoxSizer(wxHORIZONTAL, m_ViewerPanel, _("Display settings"));
{
wxSizer* viewerButtonsLeft = new wxBoxSizer(wxVERTICAL);
viewerButtonsLeft->SetMinSize(110, -1);
viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerWireframe, _("Wireframe")), _("Toggle wireframe / solid rendering")), wxSizerFlags().Expand());
viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerMove, _("Move")), _("Toggle movement along ground when playing walk/run animations")), wxSizerFlags().Expand());
viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerGround, _("Ground")), _("Toggle the ground plane")), wxSizerFlags().Expand());
viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerShadows, _("Shadows")), _("Toggle shadow rendering")), wxSizerFlags().Expand());
viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerPolyCount, _("Poly count")), _("Toggle polygon-count statistics - turn off ground and shadows for more useful data")), wxSizerFlags().Expand());
viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerBoundingBox, _("Bounding Boxes")), _("Toggle bounding boxes")), wxSizerFlags().Expand());
wxSizer* viewerButtonsRight = new wxBoxSizer(wxVERTICAL);
viewerButtonsRight->SetMinSize(110,-1);
viewerButtonsRight->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerAxesMarker, _("Axes Marker")), _("Toggle the axes marker (R=X, G=Y, B=Z)")), wxSizerFlags().Expand());
viewerButtonsSizer->Add(viewerButtonsLeft, wxSizerFlags().Expand());
viewerButtonsSizer->Add(viewerButtonsRight, wxSizerFlags().Expand());
}
viewerSizer->Add(viewerButtonsSizer, wxSizerFlags().Expand());
viewerSizer->AddSpacer(3);
// --- animations panel -------------------------------------------------------------------------------
wxSizer* viewerAnimSizer = new wxStaticBoxSizer(wxVERTICAL, m_ViewerPanel, _("Animation"));
@ -357,17 +416,28 @@ ObjectBottomBar::ObjectBottomBar(wxWindow* parent, ScenarioEditor& scenarioEdito
viewerSizer->Add(viewerAnimSizer, wxSizerFlags().Expand());
m_ViewerPanel->SetSizer(viewerSizer);
sizer->Add(m_ViewerPanel, wxSizerFlags().Expand());
// --- add viewer-specific options -------------------------------------------------------------------------------
m_ViewerPanel->SetSizer(viewerSizer);
mainSizer->Add(m_ViewerPanel, wxSizerFlags().Expand());
m_ViewerPanel->Layout(); // prevents strange visibility glitch of the animation buttons on my machine (Vista 32-bit SP1) -- vtsj
m_ViewerPanel->Show(false);
// --- add player/variation selection -------------------------------------------------------------------------------
wxSizer* playerSelectionSizer = new wxBoxSizer(wxHORIZONTAL);
wxSizer* playerVariationSizer = new wxBoxSizer(wxVERTICAL);
// TODO: make this a wxChoice instead
wxComboBox* playerSelect = new PlayerComboBox(this, objectSettings, mapSettings);
playerVariationSizer->Add(playerSelect);
playerSelectionSizer->Add(new wxStaticText(this, wxID_ANY, _("Player:")), wxSizerFlags().Align(wxALIGN_CENTER));
playerSelectionSizer->AddSpacer(3);
playerSelectionSizer->Add(playerSelect);
playerVariationSizer->Add(playerSelectionSizer);
playerVariationSizer->AddSpacer(3);
wxWindow* variationSelect = new VariationControl(this, objectSettings);
variationSelect->SetMinSize(wxSize(160, -1));
@ -375,9 +445,12 @@ ObjectBottomBar::ObjectBottomBar(wxWindow* parent, ScenarioEditor& scenarioEdito
variationSizer->Add(variationSelect, wxSizerFlags().Proportion(1).Expand());
playerVariationSizer->Add(variationSizer, wxSizerFlags().Proportion(1));
sizer->Add(playerVariationSizer, wxSizerFlags().Expand());
mainSizer->AddSpacer(3);
mainSizer->Add(playerVariationSizer, wxSizerFlags().Expand());
SetSizer(sizer);
// ----------------------------------------------------------------------------------
SetSizer(mainSizer);
}
void ObjectBottomBar::OnFirstDisplay()
@ -402,6 +475,7 @@ void ObjectBottomBar::OnFirstDisplay()
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"ground", m_ViewerGround));
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"shadows", m_ViewerShadows));
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"stats", m_ViewerPolyCount));
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"bounding_box", m_ViewerBoundingBox));
}
void ObjectBottomBar::ShowActorViewer(bool show)
@ -434,6 +508,14 @@ void ObjectBottomBar::OnViewerSetting(wxCommandEvent& evt)
m_ViewerPolyCount = !m_ViewerPolyCount;
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"stats", m_ViewerPolyCount));
break;
case ID_ViewerBoundingBox:
m_ViewerBoundingBox = !m_ViewerBoundingBox;
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"bounding_box", m_ViewerBoundingBox));
break;
case ID_ViewerAxesMarker:
m_ViewerAxesMarker = !m_ViewerAxesMarker;
POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"axes_marker", m_ViewerAxesMarker));
break;
}
}
@ -464,4 +546,6 @@ BEGIN_EVENT_TABLE(ObjectBottomBar, wxPanel)
EVT_BUTTON(ID_ViewerPlay, ObjectBottomBar::OnSpeed)
EVT_BUTTON(ID_ViewerPause, ObjectBottomBar::OnSpeed)
EVT_BUTTON(ID_ViewerSlow, ObjectBottomBar::OnSpeed)
EVT_BUTTON(ID_ViewerBoundingBox, ObjectBottomBar::OnViewerSetting)
EVT_BUTTON(ID_ViewerAxesMarker, ObjectBottomBar::OnViewerSetting)
END_EVENT_TABLE();

View File

@ -33,6 +33,7 @@
#include "graphics/TerrainTextureManager.h"
#include "graphics/TerritoryTexture.h"
#include "graphics/UnitManager.h"
#include "graphics/Overlay.h"
#include "maths/MathUtil.h"
#include "ps/Font.h"
#include "ps/GameSetup/Config.h"
@ -48,6 +49,7 @@
#include "simulation2/components/ICmpTerrain.h"
#include "simulation2/components/ICmpUnitMotion.h"
#include "simulation2/components/ICmpVisual.h"
#include "simulation2/helpers/Render.h"
struct ActorViewerImpl : public Scene
{
@ -75,6 +77,8 @@ public:
bool WalkEnabled;
bool GroundEnabled;
bool ShadowsEnabled;
bool SelectionBoxEnabled;
bool AxesMarkerEnabled;
SColor4ub Background;
@ -89,6 +93,9 @@ public:
CLOSTexture LOSTexture;
CTerritoryTexture TerritoryTexture;
SOverlayLine SelectionBoxOverlay;
SOverlayLine AxesMarkerOverlays[3];
// Simplistic implementation of the Scene interface
virtual void EnumerateObjects(const CFrustum& frustum, SceneCollector* c)
{
@ -99,6 +106,48 @@ public:
c->Submit(Terrain.GetPatch(pi, pj));
}
CmpPtr<ICmpVisual> cmpVisual(Simulation2, Entity);
// add selection box outlines manually
if (SelectionBoxEnabled && !cmpVisual.null())
{
SelectionBoxOverlay.m_Color = CColor(35/255.f, 86/255.f, 188/255.f, .75f); // pretty blue
SelectionBoxOverlay.m_Thickness = 2;
SimRender::ConstructBoxOutline(cmpVisual->GetSelectionBox(), SelectionBoxOverlay);
c->Submit(&SelectionBoxOverlay);
}
// add origin axis thingy
if (AxesMarkerEnabled && !cmpVisual.null())
{
AxesMarkerOverlays[0].m_Color = CColor(1, 0, 0, .5f); // X axis; red
AxesMarkerOverlays[1].m_Color = CColor(0, 1, 0, .5f); // Y axis; green
AxesMarkerOverlays[2].m_Color = CColor(0, 0, 1, .5f); // Z axis; blue
AxesMarkerOverlays[0].m_Thickness = 2;
AxesMarkerOverlays[1].m_Thickness = 2;
AxesMarkerOverlays[2].m_Thickness = 2;
AxesMarkerOverlays[0].m_Coords.clear();
AxesMarkerOverlays[1].m_Coords.clear();
AxesMarkerOverlays[2].m_Coords.clear();
CVector3D origin = cmpVisual->GetPosition() + CVector3D(0, 0.02f, 0); // offset from the ground a little bit to prevent fighting with the floor texture
AxesMarkerOverlays[0].PushCoords(origin);
AxesMarkerOverlays[1].PushCoords(origin);
AxesMarkerOverlays[2].PushCoords(origin);
AxesMarkerOverlays[0].PushCoords(origin + CVector3D(1, 0, 0));
AxesMarkerOverlays[1].PushCoords(origin + CVector3D(0, 1, 0));
AxesMarkerOverlays[2].PushCoords(origin + CVector3D(0, 0, 1));
c->Submit(&AxesMarkerOverlays[0]);
c->Submit(&AxesMarkerOverlays[1]);
c->Submit(&AxesMarkerOverlays[2]);
}
// send a RenderSubmit message so the components can submit their visuals to the renderer
Simulation2.RenderSubmit(*c, frustum, false);
}
@ -114,11 +163,13 @@ public:
};
ActorViewer::ActorViewer()
: m(*new ActorViewerImpl())
: m(*new ActorViewerImpl())
{
m.WalkEnabled = false;
m.GroundEnabled = true;
m.ShadowsEnabled = g_Renderer.GetOptionBool(CRenderer::OPT_SHADOWS);
m.SelectionBoxEnabled = false;
m.AxesMarkerEnabled = false;
m.Background = SColor4ub(0, 0, 0, 255);
// Create a tiny empty piece of terrain, just so we can put shadows
@ -307,6 +358,8 @@ void ActorViewer::SetBackgroundColour(const SColor4ub& colour)
void ActorViewer::SetWalkEnabled(bool enabled) { m.WalkEnabled = enabled; }
void ActorViewer::SetGroundEnabled(bool enabled) { m.GroundEnabled = enabled; }
void ActorViewer::SetShadowsEnabled(bool enabled) { m.ShadowsEnabled = enabled; }
void ActorViewer::SetBoundingBoxesEnabled(bool enabled) { m.SelectionBoxEnabled = enabled; }
void ActorViewer::SetAxesMarkerEnabled(bool enabled) { m.AxesMarkerEnabled = enabled; }
void ActorViewer::SetStatsEnabled(bool enabled)
{

View File

@ -41,6 +41,8 @@ public:
void SetGroundEnabled(bool enabled);
void SetShadowsEnabled(bool enabled);
void SetStatsEnabled(bool enabled);
void SetBoundingBoxesEnabled(bool enabled);
void SetAxesMarkerEnabled(bool enabled);
void Render();
void Update(float dt);

View File

@ -132,6 +132,10 @@ void ViewActor::SetParam(const std::wstring& name, bool value)
m_ActorViewer->SetShadowsEnabled(value);
else if (name == L"stats")
m_ActorViewer->SetStatsEnabled(value);
else if (name == L"bounding_box")
m_ActorViewer->SetBoundingBoxesEnabled(value);
else if (name == L"axes_marker")
m_ActorViewer->SetAxesMarkerEnabled(value);
}
void ViewActor::SetParam(const std::wstring& name, const AtlasMessage::Colour& value)