From 85186c98b22ef2df058f34b217ef9e099bb9a724 Mon Sep 17 00:00:00 2001 From: vts Date: Fri, 25 Nov 2011 06:36:13 +0000 Subject: [PATCH] Better selection boxes. Closes #914, #295, #810. This was SVN commit r10593. --- .../data/mods/public/gui/session/session.xml | 23 +-- .../templates/template_gaia_flora_tree.xml | 5 + source/graphics/Camera.cpp | 2 +- source/graphics/Camera.h | 4 +- source/graphics/Decal.cpp | 2 +- source/graphics/Frustum.cpp | 4 +- source/graphics/Frustum.h | 4 +- source/graphics/GameView.cpp | 4 +- source/graphics/HFTracer.cpp | 4 +- source/graphics/Model.cpp | 95 ++++++++++-- source/graphics/Model.h | 60 ++++++-- source/graphics/ModelAbstract.cpp | 92 ++++++++++++ source/graphics/ModelAbstract.h | 104 +++++++++++-- source/graphics/ModelDef.h | 45 +++++- source/graphics/ObjectEntry.cpp | 2 +- source/graphics/Overlay.h | 5 + source/graphics/ParticleEmitter.cpp | 4 +- source/graphics/ParticleEmitter.h | 4 +- source/graphics/ParticleEmitterType.cpp | 4 +- source/graphics/ParticleEmitterType.h | 6 +- source/graphics/Patch.cpp | 6 +- source/graphics/RenderableObject.h | 62 +++++--- source/graphics/SkeletonAnim.h | 4 +- source/graphics/Terrain.cpp | 4 +- source/graphics/Terrain.h | 4 +- source/graphics/UnitManager.cpp | 11 +- source/gui/scripting/ScriptFunctions.cpp | 6 + .../{Bound.cpp => BoundingBoxAligned.cpp} | 67 ++++++++- .../maths/{Bound.h => BoundingBoxAligned.h} | 62 ++++++-- source/maths/BoundingBoxOriented.cpp | 86 +++++++++++ source/maths/BoundingBoxOriented.h | 107 +++++++++++++ source/maths/Brush.cpp | 6 +- source/maths/Brush.h | 8 +- source/maths/Matrix3D.h | 7 + source/maths/tests/test_Bound.h | 10 +- source/renderer/ParticleRenderer.cpp | 2 +- source/renderer/PatchRData.h | 4 +- source/renderer/Renderer.cpp | 12 +- source/renderer/Renderer.h | 4 +- source/renderer/ShadowMap.cpp | 8 +- source/renderer/ShadowMap.h | 4 +- source/renderer/TerrainRenderer.cpp | 14 +- source/renderer/TerrainRenderer.h | 2 +- source/simulation2/TypeList.h | 6 + source/simulation2/components/CCmpDecay.cpp | 2 +- .../components/CCmpProjectileManager.cpp | 2 +- .../simulation2/components/CCmpSelectable.cpp | 38 ++++- .../components/CCmpVisualActor.cpp | 140 +++++++++++++++++- .../simulation2/components/ICmpSelectable.cpp | 2 + .../simulation2/components/ICmpSelectable.h | 5 + source/simulation2/components/ICmpVisual.h | 12 +- source/simulation2/helpers/Render.cpp | 118 +++++++++++++++ source/simulation2/helpers/Render.h | 20 +++ source/simulation2/helpers/Selection.cpp | 13 +- .../ScenarioEditor/Sections/Object/Object.cpp | 128 +++++++++++++--- .../tools/atlas/GameInterface/ActorViewer.cpp | 55 ++++++- .../tools/atlas/GameInterface/ActorViewer.h | 2 + source/tools/atlas/GameInterface/View.cpp | 4 + 58 files changed, 1315 insertions(+), 205 deletions(-) create mode 100644 source/graphics/ModelAbstract.cpp rename source/maths/{Bound.cpp => BoundingBoxAligned.cpp} (67%) rename source/maths/{Bound.h => BoundingBoxAligned.h} (55%) create mode 100644 source/maths/BoundingBoxOriented.cpp create mode 100644 source/maths/BoundingBoxOriented.h diff --git a/binaries/data/mods/public/gui/session/session.xml b/binaries/data/mods/public/gui/session/session.xml index 780c512efc..e403a2c6ae 100644 --- a/binaries/data/mods/public/gui/session/session.xml +++ b/binaries/data/mods/public/gui/session/session.xml @@ -97,7 +97,7 @@ /> - - Restrict camera - + Bounding box overlay + + Engine.SetBoundingBoxDebugOverlay(this.checked); + + + Restrict camera + gameView.constrainCamera = this.checked; - Reveal map - + Reveal map + Engine.PostNetworkCommand({"type": "reveal-map", "enable": this.checked}); - Enable time warp - + Enable time warp + Engine.EnableTimeWarpRecording(this.checked ? 10 : 0); - Promote selected units - + Promote selected units + Engine.PostNetworkCommand({"type": "promote", "entities": g_Selection.toList()}); diff --git a/binaries/data/mods/public/simulation/templates/template_gaia_flora_tree.xml b/binaries/data/mods/public/simulation/templates/template_gaia_flora_tree.xml index 1ca76674bb..62f31a0f51 100644 --- a/binaries/data/mods/public/simulation/templates/template_gaia_flora_tree.xml +++ b/binaries/data/mods/public/simulation/templates/template_gaia_flora_tree.xml @@ -16,4 +16,9 @@ wood.tree + + + + + diff --git a/source/graphics/Camera.cpp b/source/graphics/Camera.cpp index b93cf6e72c..974983060b 100644 --- a/source/graphics/Camera.cpp +++ b/source/graphics/Camera.cpp @@ -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; diff --git a/source/graphics/Camera.h b/source/graphics/Camera.h index c1163850be..029bf14e41 100644 --- a/source/graphics/Camera.h +++ b/source/graphics/Camera.h @@ -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; } diff --git a/source/graphics/Decal.cpp b/source/graphics/Decal.cpp index 85b6ef978d..06258ebd69 100644 --- a/source/graphics/Decal.cpp +++ b/source/graphics/Decal.cpp @@ -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) diff --git a/source/graphics/Frustum.cpp b/source/graphics/Frustum.cpp index d8f067fd88..fa0e426889 100644 --- a/source/graphics/Frustum.cpp +++ b/source/graphics/Frustum.cpp @@ -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 ¢er, 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 diff --git a/source/graphics/Frustum.h b/source/graphics/Frustum.h index 1b3efed024..a0cf1932b8 100644 --- a/source/graphics/Frustum.h +++ b/source/graphics/Frustum.h @@ -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 ¢er, 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]; } diff --git a/source/graphics/GameView.cpp b/source/graphics/GameView.cpp index 78e8bb56cb..6e0c2f5335 100644 --- a/source/graphics/GameView.cpp +++ b/source/graphics/GameView.cpp @@ -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; diff --git a/source/graphics/HFTracer.cpp b/source/graphics/HFTracer.cpp index c47f1ae188..2f580195de 100644 --- a/source/graphics/HFTracer.cpp +++ b/source/graphics/HFTracer.cpp @@ -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); diff --git a/source/graphics/Model.cpp b/source/graphics/Model.cpp index aa24a19bb3..226ee339ba 100644 --- a/source/graphics/Model.cpp +++ b/source/graphics/Model.cpp @@ -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() diff --git a/source/graphics/Model.h b/source/graphics/Model.h index 202378f04d..35fdffd471 100644 --- a/source/graphics/Model.h +++ b/source/graphics/Model.h @@ -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; diff --git a/source/graphics/ModelAbstract.cpp b/source/graphics/ModelAbstract.cpp new file mode 100644 index 0000000000..601b064980 --- /dev/null +++ b/source/graphics/ModelAbstract.cpp @@ -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 . + */ + +#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); + } + +} \ No newline at end of file diff --git a/source/graphics/ModelAbstract.h b/source/graphics/ModelAbstract.h index 2c01968dc5..1686847e31 100644 --- a/source/graphics/ModelAbstract.h +++ b/source/graphics/ModelAbstract.h @@ -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 diff --git a/source/graphics/ModelDef.h b/source/graphics/ModelDef.h index 9634277555..fefc7dbfc2 100644 --- a/source/graphics/ModelDef.h +++ b/source/graphics/ModelDef.h @@ -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 + diff --git a/source/graphics/ObjectEntry.cpp b/source/graphics/ObjectEntry.cpp index 25054abb09..069ea31b16 100644 --- a/source/graphics/ObjectEntry.cpp +++ b/source/graphics/ObjectEntry.cpp @@ -129,7 +129,7 @@ bool CObjectEntry::BuildVariation(const std::vector >& selections model->SetTexture(texture); // calculate initial object space bounds, based on vertex positions - model->CalcObjectBounds(); + model->CalcStaticObjectBounds(); // load the animations for (std::multimap::iterator it = variation.anims.begin(); it != variation.anims.end(); ++it) diff --git a/source/graphics/Overlay.h b/source/graphics/Overlay.h index e07b13baed..da47d19074 100644 --- a/source/graphics/Overlay.h +++ b/source/graphics/Overlay.h @@ -36,6 +36,11 @@ struct SOverlayLine CColor m_Color; std::vector 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); } }; /** diff --git a/source/graphics/ParticleEmitter.cpp b/source/graphics/ParticleEmitter.cpp index ab18c175a0..908daf984d 100644 --- a/source/graphics/ParticleEmitter.cpp +++ b/source/graphics/ParticleEmitter.cpp @@ -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() diff --git a/source/graphics/ParticleEmitter.h b/source/graphics/ParticleEmitter.h index 010f8ba394..f55121c01a 100644 --- a/source/graphics/ParticleEmitter.h +++ b/source/graphics/ParticleEmitter.h @@ -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; diff --git a/source/graphics/ParticleEmitterType.cpp b/source/graphics/ParticleEmitterType.cpp index c60376fbaf..ac8602593e 100644 --- a/source/graphics/ParticleEmitterType.cpp +++ b/source/graphics/ParticleEmitterType.cpp @@ -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; diff --git a/source/graphics/ParticleEmitterType.h b/source/graphics/ParticleEmitterType.h index be780132e8..25f0ec2886 100644 --- a/source/graphics/ParticleEmitterType.h +++ b/source/graphics/ParticleEmitterType.h @@ -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 IParticleVarPtr; std::vector m_Variables; diff --git a/source/graphics/Patch.cpp b/source/graphics/Patch.cpp index 38fbf90aee..e8a1e9c020 100644 --- a/source/graphics/Patch.cpp +++ b/source/graphics/Patch.cpp @@ -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;jCalcPosition(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() diff --git a/source/graphics/RenderableObject.h b/source/graphics/RenderableObject.h index 1b642f8f18..c3784fd5e6 100644 --- a/source/graphics/RenderableObject.h +++ b/source/graphics/RenderableObject.h @@ -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; }; diff --git a/source/graphics/SkeletonAnim.h b/source/graphics/SkeletonAnim.h index 78a4eb179b..b67eefb749 100644 --- a/source/graphics/SkeletonAnim.h +++ b/source/graphics/SkeletonAnim.h @@ -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 diff --git a/source/graphics/Terrain.cpp b/source/graphics/Terrain.cpp index 9b346abd46..c2ceebc200 100644 --- a/source/graphics/Terrain.cpp +++ b/source/graphics/Terrain.cpp @@ -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); diff --git a/source/graphics/Terrain.h b/source/graphics/Terrain.h index 5a05560242..2d44cd24ea 100644 --- a/source/graphics/Terrain.h +++ b/source/graphics/Terrain.h @@ -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) diff --git a/source/graphics/UnitManager.cpp b/source/graphics/UnitManager.cpp index d8604fe072..11a460cc44 100644 --- a/source/graphics/UnitManager.cpp +++ b/source/graphics/UnitManager.cpp @@ -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) { diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp index ac3d97e0d8..0fc6ddbf4b 100644 --- a/source/gui/scripting/ScriptFunctions.cpp +++ b/source/gui/scripting/ScriptFunctions.cpp @@ -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("DumpSimState"); scriptInterface.RegisterFunction("EnableTimeWarpRecording"); scriptInterface.RegisterFunction("RewindTimeWarp"); + scriptInterface.RegisterFunction("SetBoundingBoxDebugOverlay"); } diff --git a/source/maths/Bound.cpp b/source/maths/BoundingBoxAligned.cpp similarity index 67% rename from source/maths/Bound.cpp rename to source/maths/BoundingBoxAligned.cpp index bf6d79839c..6234335cc7 100644 --- a/source/maths/Bound.cpp +++ b/source/maths/BoundingBoxAligned.cpp @@ -21,23 +21,25 @@ #include "precompiled.h" -#include "Bound.h" +#include "BoundingBoxAligned.h" #include "lib/ogl.h" #include #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); diff --git a/source/maths/Bound.h b/source/maths/BoundingBoxAligned.h similarity index 55% rename from source/maths/Bound.h rename to source/maths/BoundingBoxAligned.h index 7a22eee066..96254a0e48 100644 --- a/source/maths/Bound.h +++ b/source/maths/BoundingBoxAligned.h @@ -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; + }; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/source/maths/BoundingBoxOriented.cpp b/source/maths/BoundingBoxOriented.cpp new file mode 100644 index 0000000000..c5ff27a61a --- /dev/null +++ b/source/maths/BoundingBoxOriented.cpp @@ -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 . + */ + +#include "precompiled.h" + +#include "BoundingBoxOriented.h" +#include "maths/BoundingBoxAligned.h" + +#include + +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; +} \ No newline at end of file diff --git a/source/maths/BoundingBoxOriented.h b/source/maths/BoundingBoxOriented.h new file mode 100644 index 0000000000..5efce10762 --- /dev/null +++ b/source/maths/BoundingBoxOriented.h @@ -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 . + */ + +#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 \ No newline at end of file diff --git a/source/maths/Brush.cpp b/source/maths/Brush.cpp index d253cdbd19..4ec7ae61bf 100644 --- a/source/maths/Brush.cpp +++ b/source/maths/Brush.cpp @@ -26,13 +26,13 @@ #include #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(); diff --git a/source/maths/Brush.h b/source/maths/Brush.h index 553d7bc78f..9d642c15e7 100644 --- a/source/maths/Brush.h +++ b/source/maths/Brush.h @@ -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) diff --git a/source/maths/Matrix3D.h b/source/maths/Matrix3D.h index 0a14bd3bb6..f3d2443815 100644 --- a/source/maths/Matrix3D.h +++ b/source/maths/Matrix3D.h @@ -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 diff --git a/source/maths/tests/test_Bound.h b/source/maths/tests/test_Bound.h index 31987bf5a4..54d3c7397f 100644 --- a/source/maths/tests/test_Bound.h +++ b/source/maths/tests/test_Bound.h @@ -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; diff --git a/source/renderer/ParticleRenderer.cpp b/source/renderer/ParticleRenderer.cpp index 84e46337cd..7154d32665 100644 --- a/source/renderer/ParticleRenderer.cpp +++ b/source/renderer/ParticleRenderer.cpp @@ -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(); } } diff --git a/source/renderer/PatchRData.h b/source/renderer/PatchRData.h index a2c85a0d01..cbfdfb1879 100644 --- a/source/renderer/PatchRData.h +++ b/source/renderer/PatchRData.h @@ -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 m_BlendSplats; // boundary of water in this patch - CBound m_WaterBounds; + CBoundingBoxAligned m_WaterBounds; // Water vertex buffer CVertexBuffer::VBChunk* m_VBWater; diff --git a/source/renderer/Renderer.cpp b/source/renderer/Renderer.cpp index dc9a2f926e..223a72899a 100644 --- a/source/renderer/Renderer.cpp +++ b/source/renderer/Renderer.cpp @@ -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; diff --git a/source/renderer/Renderer.h b/source/renderer/Renderer.h index f9ecc6021b..321738ab41 100644 --- a/source/renderer/Renderer.h +++ b/source/renderer/Renderer.h @@ -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(); diff --git a/source/renderer/ShadowMap.cpp b/source/renderer/ShadowMap.cpp index b01550d33b..aab722d423 100644 --- a/source/renderer/ShadowMap.cpp +++ b/source/renderer/ShadowMap.cpp @@ -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; diff --git a/source/renderer/ShadowMap.h b/source/renderer/ShadowMap.h index becab95695..2480747c6f 100644 --- a/source/renderer/ShadowMap.h +++ b/source/renderer/ShadowMap.h @@ -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. diff --git a/source/renderer/TerrainRenderer.cpp b/source/renderer/TerrainRenderer.cpp index 444e5e8262..64eff81caa 100644 --- a/source/renderer/TerrainRenderer.cpp +++ b/source/renderer/TerrainRenderer.cpp @@ -173,14 +173,14 @@ bool TerrainRenderer::CullPatches(const CFrustum* frustum) m->filteredPatches.clear(); for (std::vector::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::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)); } diff --git a/source/renderer/TerrainRenderer.h b/source/renderer/TerrainRenderer.h index 2f7a6d4970..48bb53e873 100644 --- a/source/renderer/TerrainRenderer.h +++ b/source/renderer/TerrainRenderer.h @@ -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. diff --git a/source/simulation2/TypeList.h b/source/simulation2/TypeList.h index c23093f656..60d1f54445 100644 --- a/source/simulation2/TypeList.h +++ b/source/simulation2/TypeList.h @@ -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) diff --git a/source/simulation2/components/CCmpDecay.cpp b/source/simulation2/components/CCmpDecay.cpp index 948b162e23..a9d33a1ee4 100644 --- a/source/simulation2/components/CCmpDecay.cpp +++ b/source/simulation2/components/CCmpDecay.cpp @@ -140,7 +140,7 @@ public: CmpPtr 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); } diff --git a/source/simulation2/components/CCmpProjectileManager.cpp b/source/simulation2/components/CCmpProjectileManager.cpp index 6c6652c7bc..e4c988bbdc 100644 --- a/source/simulation2/components/CCmpProjectileManager.cpp +++ b/source/simulation2/components/CCmpProjectileManager.cpp @@ -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) diff --git a/source/simulation2/components/CCmpSelectable.cpp b/source/simulation2/components/CCmpSelectable.cpp index e9aa3d29a7..7b41e02a86 100644 --- a/source/simulation2/components/CCmpSelectable.cpp +++ b/source/simulation2/components/CCmpSelectable.cpp @@ -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 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); + } } }; diff --git a/source/simulation2/components/CCmpVisualActor.cpp b/source/simulation2/components/CCmpVisualActor.cpp index 77bbaf3a4d..30b8b52330 100644 --- a/source/simulation2/components/CCmpVisualActor.cpp +++ b/source/simulation2/components/CCmpVisualActor.cpp @@ -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: "" "" "" - ""; + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; } 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 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); diff --git a/source/simulation2/components/ICmpSelectable.cpp b/source/simulation2/components/ICmpSelectable.cpp index 20f0b09e66..dc959eab80 100644 --- a/source/simulation2/components/ICmpSelectable.cpp +++ b/source/simulation2/components/ICmpSelectable.cpp @@ -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; \ No newline at end of file diff --git a/source/simulation2/components/ICmpSelectable.h b/source/simulation2/components/ICmpSelectable.h index eadd554da9..b8b55d0c26 100644 --- a/source/simulation2/components/ICmpSelectable.h +++ b/source/simulation2/components/ICmpSelectable.h @@ -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 diff --git a/source/simulation2/components/ICmpVisual.h b/source/simulation2/components/ICmpVisual.h index 3afa213359..86a518311c 100644 --- a/source/simulation2/components/ICmpVisual.h +++ b/source/simulation2/components/ICmpVisual.h @@ -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. diff --git a/source/simulation2/helpers/Render.cpp b/source/simulation2/helpers/Render.cpp index db63115ebf..7c83778a6e 100644 --- a/source/simulation2/helpers/Render.cpp +++ b/source/simulation2/helpers/Render.cpp @@ -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& 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& points, bool closed) { PROFILE("SmoothPointsAverage"); diff --git a/source/simulation2/helpers/Render.h b/source/simulation2/helpers/Render.h index 0129b013a0..bf668286e7 100644 --- a/source/simulation2/helpers/Render.h +++ b/source/simulation2/helpers/Render.h @@ -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. diff --git a/source/simulation2/helpers/Selection.cpp b/source/simulation2/helpers/Selection.cpp index 04058943bd..87836203eb 100644 --- a/source/simulation2/helpers/Selection.cpp +++ b/source/simulation2/helpers/Selection.cpp @@ -51,19 +51,18 @@ std::vector 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)); } diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp index 15f46ae19f..b7d3064378 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp @@ -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, Observable& mapSettings, ObjectSidebarImpl* p); + ObjectBottomBar( + wxWindow* parent, + ScenarioEditor& scenarioEditor, + Observable& objectSettings, + Observable& 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, Observable& mapSettings, ObjectSidebarImpl* p) +ObjectBottomBar::ObjectBottomBar( + wxWindow* parent, + ScenarioEditor& scenarioEditor, + Observable& objectSettings, + Observable& 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(); diff --git a/source/tools/atlas/GameInterface/ActorViewer.cpp b/source/tools/atlas/GameInterface/ActorViewer.cpp index 498ac0771b..4e336f2239 100644 --- a/source/tools/atlas/GameInterface/ActorViewer.cpp +++ b/source/tools/atlas/GameInterface/ActorViewer.cpp @@ -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 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) { diff --git a/source/tools/atlas/GameInterface/ActorViewer.h b/source/tools/atlas/GameInterface/ActorViewer.h index 729726063f..365e4e083b 100644 --- a/source/tools/atlas/GameInterface/ActorViewer.h +++ b/source/tools/atlas/GameInterface/ActorViewer.h @@ -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); diff --git a/source/tools/atlas/GameInterface/View.cpp b/source/tools/atlas/GameInterface/View.cpp index 537cdf2a90..d12e5f63df 100644 --- a/source/tools/atlas/GameInterface/View.cpp +++ b/source/tools/atlas/GameInterface/View.cpp @@ -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)