Ticket #914: selection_bbox_07oct11.patch

File selection_bbox_07oct11.patch, 87.9 KB (added by vts, 13 years ago)

Updated version of the selection box patch (7 oct '11)

  • binaries/data/mods/public/gui/session/session.xml

    diff --git a/binaries/data/mods/public/gui/session/session.xml b/binaries/data/mods/public/gui/session/session.xml
    index bbf24cb..39e2b5c 100644
    a b  
    8989        />
    9090
    9191        <!-- Dev/cheat commands -->
    92         <object name="devCommands" size="100%-156 50%-80 100%-8 50%+80" type="image" sprite="devCommandsBackground"
     92        <object name="devCommands" size="100%-156 50%-88 100%-8 50%+88" type="image" sprite="devCommandsBackground"
    9393                hidden="true" hotkey="session.devcommands.toggle">
    9494            <action on="Press">
    9595                toggleDeveloperOverlay();
     
    126126                <action on="Press">Engine.GuiInterfaceCall("SetRangeDebugOverlay", this.checked);</action>
    127127            </object>
    128128
    129             <object size="0 96 100%-18 112" type="text" style="devCommandsText">Restrict camera</object>
    130             <object size="100%-16 96 100% 112" type="checkbox" style="StoneCrossBox" checked="true">
     129            <object size="0 96 100%-18 112" type="text" style="devCommandsText">Bounding box overlay</object>
     130            <object size="100%-16 96 100% 112" type="checkbox" style="StoneCrossBox">
     131                <action on="Press">Engine.SetBoundingBoxDebugOverlay(this.checked);</action>
     132            </object>
     133           
     134            <object size="0 112 100%-18 128" type="text" style="devCommandsText">Restrict camera</object>
     135            <object size="100%-16 112 100% 128" type="checkbox" style="StoneCrossBox" checked="true">
    131136                <action on="Press">gameView.constrainCamera = this.checked;</action>
    132137            </object>
    133138
    134             <object size="0 112 100%-18 128" type="text" style="devCommandsText">Reveal map</object>
    135             <object name="devCommandsRevealMap" size="100%-16 112 100% 128" type="checkbox" style="StoneCrossBox">
     139            <object size="0 128 100%-18 144" type="text" style="devCommandsText">Reveal map</object>
     140            <object size="100%-16 128 100% 144" type="checkbox" name="devCommandsRevealMap" style="StoneCrossBox">
    136141                <action on="Press">Engine.PostNetworkCommand({"type": "reveal-map", "enable": this.checked});</action>
    137142            </object>
    138143
    139             <object size="0 128 100%-18 144" type="text" style="devCommandsText">Enable time warp</object>
    140             <object size="100%-16 128 100% 144" type="checkbox" name="devTimeWarp" style="StoneCrossBox">
     144            <object size="0 144 100%-18 160" type="text" style="devCommandsText">Enable time warp</object>
     145            <object size="100%-16 144 100% 160" type="checkbox" name="devTimeWarp" style="StoneCrossBox">
    141146                <action on="Press">Engine.EnableTimeWarpRecording(this.checked ? 10 : 0);</action>
    142147            </object>
    143148
    144             <object size="0 144 100%-18 160" type="text" style="devCommandsText">Promote selected units</object>
    145             <object size="100%-16 144 100% 160" type="button" style="StoneCrossBox">
     149            <object size="0 160 100%-18 176" type="text" style="devCommandsText">Promote selected units</object>
     150            <object size="100%-16 160 100% 176" type="button" style="StoneCrossBox">
    146151                <action on="Press">Engine.PostNetworkCommand({"type": "promote", "entities": g_Selection.toList()});</action>
    147152            </object>
    148153        </object>
  • binaries/data/mods/public/simulation/templates/template_gaia_flora_tree.xml

    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 1ca7667..12874de 100644
    a b  
    1616    <Type>wood.tree</Type>
    1717  </ResourceSupply>
    1818  <Selectable/>
     19  <VisualActor>
     20    <SelectionShape type="footprint" />
     21  </VisualActor>
    1922</Entity>
  • source/graphics/Decal.cpp

    diff --git a/source/graphics/Decal.cpp b/source/graphics/Decal.cpp
    index 85b6ef9..06258eb 100644
    a b void CModelDecal::CalcBounds()  
    5555{
    5656    ssize_t i0, j0, i1, j1;
    5757    CalcVertexExtents(i0, j0, i1, j1);
    58     m_Bounds = m_Terrain->GetVertexesBound(i0, j0, i1, j1);
     58    m_WorldBounds = m_Terrain->GetVertexesBound(i0, j0, i1, j1);
    5959}
    6060
    6161void CModelDecal::SetTerrainDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1)
  • source/graphics/GameView.cpp

    diff --git a/source/graphics/GameView.cpp b/source/graphics/GameView.cpp
    index 7e17a4a..4e8205c 100644
    a b void CGameView::EnumerateObjects(const CFrustum& frustum, SceneCollector* c)  
    493493            CPatch* patch=pTerrain->GetPatch(i,j);  // can't fail
    494494
    495495            // If the patch is underwater, calculate a bounding box that also contains the water plane
    496             CBound bounds = patch->GetBounds();
     496            CBound bounds = patch->GetWorldBounds();
    497497            float waterHeight = g_Renderer.GetWaterManager()->m_WaterHeight + 0.001f;
    498498            if(bounds[1].Y < waterHeight) {
    499499                bounds[1].Y = waterHeight;
  • source/graphics/Model.cpp

    diff --git a/source/graphics/Model.cpp b/source/graphics/Model.cpp
    index 704e2b2..a6031c6 100644
    a b  
    3333#include "lib/res/graphics/ogl_tex.h"
    3434#include "lib/res/h_mgr.h"
    3535#include "ps/Profile.h"
    36 
    3736#include "ps/CLogger.h"
    3837
    3938/////////////////////////////////////////////////////////////////////////////////////////////////////////////
    void CModel::CalcBounds()  
    116115    if (! (m_Anim && m_Anim->m_AnimDef))
    117116    {
    118117        if (m_ObjectBounds.IsEmpty())
    119             CalcObjectBounds();
     118            CalcStaticObjectBounds();
    120119    }
    121120    else
    122121    {
    123122        if (m_Anim->m_ObjectBounds.IsEmpty())
    124             CalcAnimatedObjectBound(m_Anim->m_AnimDef, m_Anim->m_ObjectBounds);
     123            CalcAnimatedObjectBounds(m_Anim->m_AnimDef, m_Anim->m_ObjectBounds);
    125124        ENSURE(! m_Anim->m_ObjectBounds.IsEmpty()); // (if this happens, it'll be recalculating the bounds every time)
    126125        m_ObjectBounds = m_Anim->m_ObjectBounds;
    127126    }
    void CModel::CalcBounds()  
    129128    // Ensure the transform is set correctly before we use it
    130129    ValidatePosition();
    131130
    132     m_ObjectBounds.Transform(GetTransform(), m_Bounds);
     131    // Now transform the object-space bounds to world-space bounds
     132    m_ObjectBounds.Transform(GetTransform(), m_WorldBounds);
    133133}
    134134
    135135/////////////////////////////////////////////////////////////////////////////////////////////////////////////
    136136// CalcObjectBounds: calculate object space bounds of this model, based solely on vertex positions
    137 void CModel::CalcObjectBounds()
     137void CModel::CalcStaticObjectBounds()
    138138{
    139139    m_ObjectBounds.SetEmpty();
    140140
    void CModel::CalcObjectBounds()  
    148148
    149149/////////////////////////////////////////////////////////////////////////////////////////////////////////////
    150150// CalcAnimatedObjectBound: calculate bounds encompassing all vertex positions for given animation
    151 void CModel::CalcAnimatedObjectBound(CSkeletonAnimDef* anim,CBound& result)
     151void CModel::CalcAnimatedObjectBounds(CSkeletonAnimDef* anim, CBound& result)
    152152{
    153153    result.SetEmpty();
    154154
    void CModel::CalcAnimatedObjectBound(CSkeletonAnimDef* anim,CBound& result)  
    199199}
    200200
    201201/////////////////////////////////////////////////////////////////////////////////////////////////////////////
    202 const CBound CModel::GetBoundsRec()
     202const CBound CModel::GetWorldBoundsRec()
    203203{
    204     CBound bounds = GetBounds();
     204    CBound bounds = GetWorldBounds();
    205205    for (size_t i = 0; i < m_Props.size(); ++i)
    206         bounds += m_Props[i].m_Model->GetBoundsRec();
     206        bounds += m_Props[i].m_Model->GetWorldBoundsRec();
    207207    return bounds;
    208208}
    209209
     210const CBound CModel::GetObjectSelectionBoundsRec()
     211{
     212    CBound objBounds = GetObjectBounds();       // updates the (children-not-included) object-space bounds if necessary
     213
     214    // now extend these bounds to include the props' selection bounds (if any)
     215    for (size_t i = 0; i < m_Props.size(); ++i)
     216    {
     217        const Prop& prop = m_Props[i];
     218        if (prop.m_Hidden)
     219            continue; // prop is hidden from rendering, so it also shouldn't be used for selection
     220
     221        CBound propSelectionBounds = prop.m_Model->GetObjectSelectionBoundsRec();
     222        if (propSelectionBounds.IsEmpty())
     223            continue;   // submodel does not wish to participate in selection box, exclude it
     224
     225        // We have the prop's bounds in its own object-space; now we need to transform them into this coordinate space so
     226        // they can be properly added to our object-space. For that, we need the object-space transform of the prop
     227        // attachment point.
     228        //
     229        // We have the prop point information; however, it's not trivial to compute its exact location in our object-space
     230        // 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
     231        // an animation. The bone matrices might be of interest, but they're really only meant to be used for the animation
     232        // system and are quite opaque to use from the outside (see @ref ValidatePosition).
     233        //
     234        // However, a nice side effect of ValidatePosition is that it also computes the absolute world-space transform of
     235        // our props and sets it on their respective models. In particular, @ref ValidatePosition will compute the prop's
     236        // world-space transform as either
     237        //
     238        // T' = T x B x O
     239        // or
     240        // T' = T x O
     241        //
     242        // where T' is the prop's world-space transform, T is our world-space transform, O is the prop's local
     243        // offset/rotation matrix, and B is an optional transformation matrix of the bone the prop is attached to
     244        // (taking into account animation and everything).
     245        //
     246        // From this, it is clear that either O or B x O is the object-space transformation matrix of the prop. So,
     247        // all we need to do is apply our own inverse world-transform T^(-1) to T' to get our desired result. Luckily,
     248        // this is precomputed upon setting the transform matrix (see @ref SetTransform), so it is free to fetch.
     249       
     250        CMatrix3D propObjectTransform = prop.m_Model->GetTransform(); // T'
     251        propObjectTransform.Concatenate(GetInvTransform()); // T^(-1) x T'
     252
     253        // Transform the prop's bounds into our object coordinate space
     254        CBound transformedPropSelectionBounds;
     255        propSelectionBounds.Transform(propObjectTransform, transformedPropSelectionBounds);
     256
     257        objBounds += transformedPropSelectionBounds;
     258    }
     259
     260    return objBounds;
     261}
     262
    210263/////////////////////////////////////////////////////////////////////////////////////////////////////////////
    211264// BuildAnimation: load raw animation frame animation from given file, and build a
    212265// animation specific to this model
    void CModel::ValidatePosition()  
    293346   
    294347        m_Anim->m_AnimDef->BuildBoneMatrices(m_AnimTime, m_BoneMatrices, !(m_Flags & MODELFLAG_NOLOOPANIMATION));
    295348   
     349        // add world-space transformation to m_BoneMatrices
    296350        const CMatrix3D& transform = GetTransform();
    297351        for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++)
    298352            m_BoneMatrices[i].Concatenate(transform);
    void CModel::ValidatePosition()  
    312366        }
    313367    }
    314368   
     369    // our own position is now valid; now we can safely update our props' positions without fearing
     370    // that doing so will cause a revalidation of this model (see recursion above).
    315371    m_PositionValid = true;
    316372   
    317373    // re-position and validate all props
    void CModel::ValidatePosition()  
    321377
    322378        CMatrix3D proptransform = prop.m_Point->m_Transform;;
    323379        if (prop.m_Point->m_BoneIndex != 0xff)
     380        {
     381            // m_BoneMatrices[i] already have world transform pre-applied (see above)
    324382            proptransform.Concatenate(m_BoneMatrices[prop.m_Point->m_BoneIndex]);
     383        }
    325384        else
     385        {
     386            // not relative to any bone; just apply world-space transformation (i.e. relative to object-space origin)
    326387            proptransform.Concatenate(m_Transform);
     388        }
    327389       
    328390        prop.m_Model->SetTransform(proptransform);
    329391        prop.m_Model->ValidatePosition();
    void CModel::CopyAnimationFrom(CModel* source)  
    409471void CModel::AddProp(const SPropPoint* point, CModelAbstract* model, CObjectEntry* objectentry)
    410472{
    411473    // position model according to prop point position
     474
     475    // this next call will invalidate the bounds of "model", which will in turn also invalidate the selection box
    412476    model->SetTransform(point->m_Transform);
    413477    model->m_Parent = this;
    414478
    void CModel::AddAmmoProp(const SPropPoint* point, CModelAbstract* model, CObject  
    425489    m_AmmoPropPoint = point;
    426490    m_AmmoLoadedProp = m_Props.size() - 1;
    427491    m_Props[m_AmmoLoadedProp].m_Hidden = true;
     492
     493    // we only need to invalidate the selection box here if it is based on props and their visibilities
     494    if (!m_CustomSelectionShape)
     495        m_SelectionBoxValid = false;
    428496}
    429497
    430498void CModel::ShowAmmoProp()
    void CModel::ShowAmmoProp()  
    436504    for (size_t i = 0; i < m_Props.size(); ++i)
    437505        if (m_Props[i].m_Point == m_AmmoPropPoint)
    438506            m_Props[i].m_Hidden = (i != m_AmmoLoadedProp);
     507   
     508    //  we only need to invalidate the selection box here if it is based on props and their visibilities
     509    if (!m_CustomSelectionShape)
     510        m_SelectionBoxValid = false;
    439511}
    440512
    441513void CModel::HideAmmoProp()
    void CModel::HideAmmoProp()  
    447519    for (size_t i = 0; i < m_Props.size(); ++i)
    448520        if (m_Props[i].m_Point == m_AmmoPropPoint)
    449521            m_Props[i].m_Hidden = (i == m_AmmoLoadedProp);
     522
     523    //  we only need to invalidate here if the selection box is based on props and their visibilities
     524    if (!m_CustomSelectionShape)
     525        m_SelectionBoxValid = false;
    450526}
    451527
    452528CModelAbstract* CModel::FindFirstAmmoProp()
  • source/graphics/Model.h

    diff --git a/source/graphics/Model.h b/source/graphics/Model.h
    index 84efeb6..f75843a 100644
    a b public:  
    5454    {
    5555        Prop() : m_Point(0), m_Model(0), m_ObjectEntry(0), m_Hidden(false) {}
    5656
     57        /**
     58         * Location of the prop point within its parent model, relative to either a bone in the parent model or to the
     59         * parent model's origin. See the documentation for @ref SPropPoint for more details.
     60         * @see SPropPoint
     61         */
    5762        const SPropPoint* m_Point;
     63
     64        /**
     65         * Pointer to the model associated with this prop. Note that the transform matrix held by this model is the full object-to-world
     66         * space transform, taking into account all parent model positioning (see @ref CModel::ValidatePosition for positioning logic).
     67         * @see CModel::ValidatePosition
     68         */
    5869        CModelAbstract* m_Model;
    5970        CObjectEntry* m_ObjectEntry;
    6071
    61         bool m_Hidden; // temporarily removed from rendering
     72        bool m_Hidden; ///< Should this prop be temporarily removed from rendering?
    6273    };
    6374
    6475public:
    public:  
    7586
    7687    // setup model from given geometry
    7788    bool InitModel(const CModelDefPtr& modeldef);
    78     // calculate the world space bounds of this model
    79     virtual void CalcBounds();
    8089    // update this model's state; 'time' is the absolute time since the start of the animation, in MS
    8190    void UpdateTo(float time);
    8291
    public:  
    133142            m_Props[i].m_Model->SetEntityVariable(name, value);
    134143    }
    135144
    136     // calculate object space bounds of this model, based solely on vertex positions
    137     void CalcObjectBounds();
    138     // calculate bounds encompassing all vertex positions for given animation
    139     void CalcAnimatedObjectBound(CSkeletonAnimDef* anim,CBound& result);
     145    // --- WORLD/OBJECT SPACE BOUNDS -----------------------------------------------------------------
     146
     147    /// Overridden to calculate both the world-space and object-space bounds of this model, and stores the result in
     148    /// m_Bounds and m_ObjectBounds, respectively.
     149    virtual void CalcBounds();
    140150
    141     virtual const CBound GetBoundsRec();
     151    /// Returns the object-space bounds for this model, excluding its children.
     152    const CBound& GetObjectBounds()
     153    {
     154        RecalculateBoundsIfNecessary();             // recalculates both object-space and world-space bounds if necessary
     155        return m_ObjectBounds;
     156    }
     157
     158    virtual const CBound GetWorldBoundsRec();       // reimplemented here
     159
     160    /// Auxiliary method; calculates object space bounds of this model, based solely on vertex positions, and stores
     161    /// the result in m_ObjectBounds. Called by CalcBounds (instead of CalcAnimatedObjectBounds) if it has been determined
     162    /// that the object-space bounds are static.
     163    void CalcStaticObjectBounds();
     164   
     165    /// Auxiliary method; calculate object-space bounds encompassing all vertex positions for given animation, and stores
     166    /// the result in m_ObjectBounds. Called by CalcBounds (instead of CalcStaticBounds) if it has been determined that the
     167    /// object-space bounds need to take animations into account.
     168    void CalcAnimatedObjectBounds(CSkeletonAnimDef* anim,CBound& result);
     169
     170    // --- SELECTION BOX/BOUNDS ----------------------------------------------------------------------
     171
     172    /// Reimplemented here since proper models should participate in selection boxes.
     173    virtual const CBound GetObjectSelectionBoundsRec();
    142174
    143175    /**
    144176     * Set transform of this object.
    private:  
    240272    CSkeletonAnim* m_Anim;
    241273    // time (in MS) into the current animation
    242274    float m_AnimTime;
    243     // current state of all bones on this model; null if associated modeldef isn't skeletal
     275   
     276    /**
     277     * Current state of all bones on this model; null if associated modeldef isn't skeletal.
     278     * Props may attach to these bones by means of the SPropPoint::m_BoneIndex field; in this case their
     279     * transformation matrix held is relative to the bone transformation (see @ref SPropPoint and
     280     * @ref CModel::ValidatePosition).
     281     *
     282     * @see SPropPoint
     283     */
    244284    CMatrix3D* m_BoneMatrices;
    245285    // inverse matrices for the bind pose's bones; null if not skeletal
    246286    CMatrix3D* m_InverseBindBoneMatrices;
  • new file source/graphics/ModelAbstract.cpp

    diff --git a/source/graphics/ModelAbstract.cpp b/source/graphics/ModelAbstract.cpp
    new file mode 100644
    index 0000000..8995029
    - +  
     1/* Copyright (C) 2011 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18#include "precompiled.h"
     19
     20#include "ModelAbstract.h"
     21
     22#include "ps/CLogger.h"
     23
     24const CBox& CModelAbstract::GetSelectionBox()
     25{
     26    if (!m_SelectionBoxValid)
     27    {
     28        CalcSelectionBox();
     29        m_SelectionBoxValid = true;
     30    }
     31    return m_SelectionBox;
     32}
     33
     34void CModelAbstract::CalcSelectionBox()
     35{
     36    if (m_CustomSelectionShape)
     37    {
     38        // custom shape
     39        switch(m_CustomSelectionShape->m_Type)
     40        {
     41        case CustomSelectionShape::BOX:
     42            {
     43                // create object-space bounds according to the information in the descriptor, and transform them to world-space.
     44                // the box is centered on the X and Z axes, but extends from 0 to its height on the Y axis.
     45                const float width = m_CustomSelectionShape->m_Size0;
     46                const float depth = m_CustomSelectionShape->m_Size1;
     47                const float height = m_CustomSelectionShape->m_Height;
     48
     49                CBound bounds;
     50                bounds += CVector3D(-width/2.f, 0,     -depth/2.f);
     51                bounds += CVector3D( width/2.f, height, depth/2.f);
     52
     53                bounds.Transform(GetTransform(), m_SelectionBox);
     54            }
     55            break;
     56        case CustomSelectionShape::CYLINDER:
     57            {
     58                // TODO: unimplemented
     59                m_SelectionBox.SetEmpty();
     60                LOGWARNING(L"[ModelAbstract] TODO: Cylinder selection boxes are not yet implemented. Use BOX or BOUNDS instead.");
     61            }
     62        default:
     63            {
     64                m_SelectionBox.SetEmpty();
     65                LOGWARNING(L"[ModelAbstract] Unrecognized selection shape type: %ld", m_CustomSelectionShape->m_Type);
     66            }
     67            break;
     68        }
     69    }
     70    else
     71    {
     72        // standard method
     73
     74        // Get the object-space bounds that should be used to construct this model (and its children)'s selection box
     75        CBound objBounds = GetObjectSelectionBoundsRec();
     76        if (objBounds.IsEmpty())
     77        {
     78            m_SelectionBox.SetEmpty(); // model does not wish to participate in selection
     79            return;
     80        }
     81
     82        // Prevent the bounding box from extending through the terrain; clip the lower plane at Y=0 in object space.
     83        if (objBounds[1].Y > 0.f) // should always be the case, unless the models are defined really weirdly
     84            objBounds[0].Y = std::max(0.f, objBounds[0].Y);
     85        //objBounds[0].Y = 0;
     86
     87        // transform object-space axis-aligned bounds to world-space arbitrary-aligned box
     88        objBounds.Transform(GetTransform(), m_SelectionBox);
     89    }
     90   
     91}
     92 No newline at end of file
  • source/graphics/ModelAbstract.h

    diff --git a/source/graphics/ModelAbstract.h b/source/graphics/ModelAbstract.h
    index 2c01968..85ecb68 100644
    a b  
    1818#ifndef INCLUDED_MODELABSTRACT
    1919#define INCLUDED_MODELABSTRACT
    2020
     21#include "maths/Box.h"
    2122#include "graphics/RenderableObject.h"
    2223#include "ps/Overlay.h"
    2324#include "simulation2/helpers/Player.h"
    class CModelAbstract : public CRenderableObject  
    3738    NONCOPYABLE(CModelAbstract);
    3839
    3940public:
    40     CModelAbstract() :
    41         m_Parent(NULL), m_PositionValid(false),
    42         m_ShadingColor(1, 1, 1, 1), m_PlayerID(INVALID_PLAYER)
     41
     42    /**
     43     * Describes a custom selection shape to be used for a model's selection box instead of the default
     44     * recursive bounding boxes.
     45     */
     46    struct CustomSelectionShape
     47    {
     48        enum EType {
     49            /// The selection shape is determined by an oriented box of custom, user-specified size.
     50            BOX,
     51            /// The selection shape is determined by a cylinder of custom, user-specified size.
     52            CYLINDER
     53        };
     54
     55        EType m_Type; ///< Type of shape. @see ESource
     56        float m_Size0; ///< Box width if @ref BOX, or radius if @ref CYLINDER
     57        float m_Size1; ///< Box depth if @ref BOX, or radius if @ref CYLINDER
     58        float m_Height; ///< Box height if @ref BOX, cylinder height if @ref CYLINDER
     59    };
     60
     61public:
     62   
     63    CModelAbstract()
     64        : m_Parent(NULL), m_PositionValid(false), m_ShadingColor(1, 1, 1, 1), m_PlayerID(INVALID_PLAYER),
     65          m_SelectionBoxValid(false), m_CustomSelectionShape(NULL)
    4366    {
     67        m_SelectionBox.SetEmpty();
     68    }
     69
     70    ~CModelAbstract()
     71    {
     72        if (m_CustomSelectionShape) // allocated and set externally by CCmpVisualActor, but our responsibility to clean up
     73            delete m_CustomSelectionShape;
    4474    }
    4575
    4676    virtual CModelAbstract* Clone() const = 0;
    public:  
    5888    // and this seems the easiest way to integrate with other code that wants
    5989    // type-specific processing)
    6090
     91    /// Calls SetDirty on this model and all child objects.
     92    virtual void SetDirtyRec(int dirtyflags) = 0;
     93
     94    /// Returns world space bounds of this object and all child objects.
     95    virtual const CBound GetWorldBoundsRec() { return GetWorldBounds(); }
     96
    6197    /**
    62      * Calls SetDirty on this model and all child objects.
     98     * Returns the world-space selection box of this model. Used primarily for hittesting against against a selection ray. The
     99     * returned selection box may be empty to indicate that it does not wish to participate in the selection process.
    63100     */
    64     virtual void SetDirtyRec(int dirtyflags) = 0;
     101    virtual const CBox& GetSelectionBox();
     102
     103    virtual void InvalidateBounds()
     104    {
     105        m_BoundsValid = false;
     106        // 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
     107        // want to update the selection box accordingly regardless of the shape it is built from.
     108        m_SelectionBoxValid = false;
     109    }
     110
     111    /// Sets a custom selection shape as described by a @p descriptor. Argument may be NULL
     112    /// if you wish to keep the default behaviour of using the recursively-calculated bounding boxes.
     113    void SetCustomSelectionShape(CustomSelectionShape* descriptor)
     114    {
     115        if (m_CustomSelectionShape != descriptor)
     116        {
     117            m_CustomSelectionShape = descriptor;
     118            m_SelectionBoxValid = false; // update the selection box when it is next requested
     119        }
     120    }
    65121
    66122    /**
    67      * Returns world space bounds of this object and all child objects.
     123     * Returns the (object-space) bounds that should be used to construct a selection box for this model and its children.
     124     * May return an empty bound to indicate that this model and its children should not be selectable themselves, or should
     125     * not be included in its parent model's selection box. This method is used for constructing the default selection boxes,
     126     * as opposed to any boxes of custom shape specified by @ref m_CustomSelectionShape.
     127     *
     128     * If you wish your model type to be included in selection boxes, override this method and have it return the object-space
     129     * bounds of itself, augmented recursively (via this method) with the object-space selection bounds of its children.
    68130     */
    69     virtual const CBound GetBoundsRec() { return GetBounds(); }
     131    virtual const CBound GetObjectSelectionBoundsRec() { return CBound::EMPTY; }
    70132
    71133    /**
    72134     * Called when terrain has changed in the given inclusive bounds.
    public:  
    81143    virtual void SetEntityVariable(const std::string& UNUSED(name), float UNUSED(value)) { }
    82144
    83145    /**
    84      * Ensure that both the transformation and the bone
    85      * matrices are correct for this model and all its props.
     146     * Ensure that both the transformation and the bone matrices are correct for this model and all its props.
    86147     */
    87148    virtual void ValidatePosition() = 0;
    88149
    89150    /**
    90      * Mark this model's position and bone matrices,
    91      * and all props' positions as invalid.
     151     * Mark this model's position and bone matrices, and all props' positions as invalid.
    92152     */
    93153    virtual void InvalidatePosition() = 0;
    94154
    public:  
    100160    virtual void SetShadingColor(const CColor& colour) { m_ShadingColor = colour; }
    101161    virtual CColor GetShadingColor() const { return m_ShadingColor; }
    102162
    103     /// If non-null points to the model that we are attached to.
     163protected:
     164    void CalcSelectionBox();
     165
     166public:
     167    /// If non-null, points to the model that we are attached to.
    104168    CModelAbstract* m_Parent;
    105169
    106170    /// True if both transform and and bone matrices are valid.
    public:  
    108172
    109173    player_id_t m_PlayerID;
    110174
    111     // modulating color
     175    /// Modulating color
    112176    CColor m_ShadingColor;
     177
     178protected:
     179
     180    /// Selection box for this model.
     181    CBox m_SelectionBox;
     182
     183    /// Is the current selection box valid?
     184    bool m_SelectionBoxValid;
     185
     186    /// Pointer to a descriptor for a custom-defined selection box shape. If no custom selection box is required, this is NULL
     187    /// and the standard recursive-bounding-box-based selection box is used. Otherwise, a custom selection box described by this
     188    /// field will be used.
     189    /// @see SetCustomSelectionShape
     190    CustomSelectionShape* m_CustomSelectionShape;
     191
    113192};
    114193
    115194#endif // INCLUDED_MODELABSTRACT
  • source/graphics/ModelDef.h

    diff --git a/source/graphics/ModelDef.h b/source/graphics/ModelDef.h
    index e7ce494..64f509b 100644
    a b  
    3232
    3333class CBoneState;
    3434
    35 ///////////////////////////////////////////////////////////////////////////////
    36 // SPropPoint: structure describing a prop point
     35/**
     36 * Describes the position of a prop point within its parent model. A prop point is the location within a parent model
     37 * where the prop's origin will be attached.
     38 *
     39 * A prop point is specified by its transformation matrix (or separately by its position and rotation), which
     40 * can be relative to either the parent model's origin, or one of the parent's bones. If the parent model is boned,
     41 * then the @ref m_BoneIndex field may specify a bone to which the transformation matrix is relative (see
     42 * @ref CModel::m_BoneMatrices). Otherwise, the transformation matrix is assumed to be relative to the parent model's
     43 * origin.
     44 *
     45 * @see CModel::m_BoneMatrices
     46 */
    3747struct SPropPoint
    3848{
    39     // name of the prop point
     49    /// Name of the prop point
    4050    CStr m_Name;
    41     // position of the point
     51
     52    /**
     53     * Position of the point within the parent model, relative to either the parent model's origin or one of the parent
     54     * model's bones if applicable. Also specified as part of @ref m_Transform.
     55     * @see m_Transform
     56     */
    4257    CVector3D m_Position;
    43     // rotation of the point
     58
     59    /**
     60     * Rotation of the prop model that will be attached at this point. Also specified as part of @ref m_Transform.
     61     * @see m_Transform
     62     */
    4463    CQuaternion m_Rotation;
    45     // object to parent space transformation
     64
     65    /**
     66     * Object to parent space transformation. Combines both @ref m_Position and @ref m_Rotation in a single
     67     * transformation matrix. This transformation is relative to either the parent model's origin, or one of its
     68     * bones, depending on whether it is skeletal. If relative to a bone, then the bone in the parent model to
     69     * which this transformation is relative may be found by m_BoneIndex.
     70     * @see m_Position, m_Rotation
     71     */
    4672    CMatrix3D m_Transform;
    47     // index of parent bone; 0xff if unboned
     73
     74    /**
     75     * Index of parent bone to which this prop point is relative, if any. The value 0xFF specifies that either the parent
     76     * model is unboned, or that this prop point is relative to the parent model's origin rather than one if its bones.
     77     */
    4878    u8 m_BoneIndex;
    4979};
    5080
    private:  
    233263};
    234264
    235265#endif
     266 
     267 No newline at end of file
  • source/graphics/ObjectEntry.cpp

    diff --git a/source/graphics/ObjectEntry.cpp b/source/graphics/ObjectEntry.cpp
    index 25054ab..069ea31 100644
    a b bool CObjectEntry::BuildVariation(const std::vector<std::set<CStr> >& selections  
    129129    model->SetTexture(texture);
    130130
    131131    // calculate initial object space bounds, based on vertex positions
    132     model->CalcObjectBounds();
     132    model->CalcStaticObjectBounds();
    133133
    134134    // load the animations
    135135    for (std::multimap<CStr, CObjectBase::Anim>::iterator it = variation.anims.begin(); it != variation.anims.end(); ++it)
  • source/graphics/Overlay.h

    diff --git a/source/graphics/Overlay.h b/source/graphics/Overlay.h
    index e07b13b..da47d19 100644
    a b struct SOverlayLine  
    3636    CColor m_Color;
    3737    std::vector<float> m_Coords; // (x, y, z) vertex coordinate triples; shape is not automatically closed
    3838    u8 m_Thickness; // pixels
     39
     40    /// Utility function; pushes three vertex coordinates at once onto the coordinates array
     41    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); }
     42    /// Utility function; pushes a vertex location onto the coordinates array
     43    void PushCoords(const CVector3D& v) { PushCoords(v.X, v.Y, v.Z); }
    3944};
    4045
    4146/**
  • source/graphics/ParticleEmitter.cpp

    diff --git a/source/graphics/ParticleEmitter.cpp b/source/graphics/ParticleEmitter.cpp
    index ab18c17..866b97b 100644
    a b void CModelParticleEmitter::CalcBounds()  
    232232    // current computed particle positions plus the emitter type's largest
    233233    // potential bounding box at the current position
    234234
    235     m_Bounds = m_Type->CalculateBounds(m_Emitter->GetPosition(), m_Emitter->GetParticleBounds());
     235    m_WorldBounds = m_Type->CalculateBounds(m_Emitter->GetPosition(), m_Emitter->GetParticleBounds());
    236236}
    237237
    238238void CModelParticleEmitter::ValidatePosition()
  • source/graphics/Patch.cpp

    diff --git a/source/graphics/Patch.cpp b/source/graphics/Patch.cpp
    index 38fbf90..e8a1e9c 100644
    a b void CPatch::Initialize(CTerrain* parent,ssize_t x,ssize_t z)  
    5757// CalcBounds: calculating the bounds of this patch
    5858void CPatch::CalcBounds()
    5959{
    60     m_Bounds.SetEmpty();
     60    m_WorldBounds.SetEmpty();
    6161
    6262    for (ssize_t j=0;j<PATCH_SIZE+1;j++)
    6363    {
    void CPatch::CalcBounds()  
    6565        {
    6666            CVector3D pos;
    6767            m_Parent->CalcPosition(m_X*PATCH_SIZE+i,m_Z*PATCH_SIZE+j,pos);
    68             m_Bounds+=pos;
     68            m_WorldBounds+=pos;
    6969        }
    7070    }
    7171
    7272    // If this a side patch, the sides go down to height 0, so add them
    7373    // into the bounds
    7474    if (GetSideFlags())
    75         m_Bounds[0].Y = std::min(m_Bounds[0].Y, 0.f);
     75        m_WorldBounds[0].Y = std::min(m_WorldBounds[0].Y, 0.f);
    7676}
    7777
    7878int CPatch::GetSideFlags()
  • source/graphics/RenderableObject.h

    diff --git a/source/graphics/RenderableObject.h b/source/graphics/RenderableObject.h
    index 1b642f8..338eed5 100644
    a b public:  
    8686        if (m_RenderData) m_RenderData->m_UpdateFlags|=dirtyflags;
    8787    }
    8888
    89     // calculate (and store in m_Bounds) the world space bounds of this object
    90     // - must be implemented by all concrete subclasses
     89    /**
     90     * (Re)calculates and stores any bounds or bound-dependent data for this object. At this level, this is only the world-space bounds stored
     91     * in @ref m_WorldBounds; subclasses may use this method to (re)compute additional bounds if necessary, or any data that depends on the
     92     * bounds. Whenever bound-dependent data is requested through a public interface, @ref RecalculateBoundsIfNecessary should be called
     93     * first to ensure bound correctness, which will in turn call this method if it turns out that they're outdated.
     94     *
     95     * @see m_BoundsValid
     96     * @see RecalculateBoundsIfNecessary
     97     */
    9198    virtual void CalcBounds() = 0;
    9299
    93     // return world space bounds of this object
    94     const CBound& GetBounds() {
    95         if (! m_BoundsValid) {
    96             CalcBounds();
    97             m_BoundsValid = true;
    98         }
    99         return m_Bounds;
     100    /// Returns the world-space axis-aligned bounds of this object.
     101    const CBound& GetWorldBounds() {
     102        RecalculateBoundsIfNecessary();
     103        return m_WorldBounds;
    100104    }
    101105
    102     void InvalidateBounds() { m_BoundsValid = false; }
     106    /**
     107     * Marks the bounds as invalid. This will trigger @ref RecalculateBoundsIfNecessary to recompute any bound-related data the next time
     108     * any bound-related data is requested through a public interface -- at least, if you've made sure to call it before returning the
     109     * stored data.
     110     */
     111    virtual void InvalidateBounds() { m_BoundsValid = false; }
    103112
    104113    // Set the object renderdata and free previous renderdata, if any.
    105114    void SetRenderData(CRenderData* renderdata) {
    public:  
    107116        m_RenderData = renderdata;
    108117    }
    109118
    110     // return object renderdata - can be null if renderer hasn't yet
    111     // created the renderdata
     119    /// Return object renderdata - can be null if renderer hasn't yet created the renderdata
    112120    CRenderData* GetRenderData() { return m_RenderData; }
    113121
    114122protected:
    115     // object bounds
    116     CBound m_Bounds;
     123    /// Factored out so subclasses don't need to repeat this if they want to add additional getters for bounds-related methods
     124    /// (since they'll have to make sure to recalc the bounds if necessary before they return it).
     125    void RecalculateBoundsIfNecessary()
     126    {
     127        if (!m_BoundsValid) {
     128            CalcBounds();
     129            m_BoundsValid = true;
     130        }
     131    }
     132
     133protected:
     134    /// World-space bounds of this object
     135    CBound m_WorldBounds;
    117136    // local->world space transform
    118137    CMatrix3D m_Transform;
    119138    // world->local space transform
    protected:  
    121140    // object renderdata
    122141    CRenderData* m_RenderData;
    123142
    124 private:
    125     // remembers whether m_bounds needs to be recalculated
     143    /**
     144     * Remembers whether any bounds need to be recalculated. Subclasses that add any data that depends on the bounds should
     145     * take care to consider the validity of the bounds and recalculate their data when necessary -- overriding @ref CalcBounds
     146     * to do so would be a good idea, since it's already set up to be called by @ref RecalculateBoundsIfNecessary whenever the
     147     * bounds are marked as invalid. The latter should then be called before returning any bounds or bounds-derived data through
     148     * a public interface (see the implementation of @ref GetWorldBounds for an example).
     149     *
     150     * @see CalcBounds
     151     * @see InvalidateBounds
     152     * @see RecalculateBoundsIfNecessary
     153     */
    126154    bool m_BoundsValid;
    127155};
    128156
  • source/graphics/UnitManager.cpp

    diff --git a/source/graphics/UnitManager.cpp b/source/graphics/UnitManager.cpp
    index d8604fe..0e3d7d1 100644
    a b CUnit* CUnitManager::PickUnit(const CVector3D& origin, const CVector3D& dir) con  
    100100        CUnit* unit = m_Units[i];
    101101        float tmin, tmax;
    102102       
    103         if (unit->GetModel().GetBounds().RayIntersect(origin, dir, tmin, tmax))
     103        if (unit->GetModel().GetWorldBounds().RayIntersect(origin, dir, tmin, tmax))
    104104        {
    105105            // Point of closest approach
    106106            CVector3D obj;
    107             unit->GetModel().GetBounds().GetCentre(obj);
     107            unit->GetModel().GetWorldBounds().GetCentre(obj);
    108108            CVector3D delta = obj - origin;
    109109            float distance = delta.Dot(dir);
    110110            CVector3D closest = origin + dir * distance;
  • source/gui/scripting/ScriptFunctions.cpp

    diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp
    index 0054246..7df679a 100644
    a b  
    4848#include "simulation2/components/ICmpGuiInterface.h"
    4949#include "simulation2/components/ICmpRangeManager.h"
    5050#include "simulation2/components/ICmpTemplateManager.h"
     51#include "simulation2/components/ICmpSelectable.h"
    5152#include "simulation2/helpers/Selection.h"
    5253
    5354#include "js/jsapi.h"
    void RewindTimeWarp(void* UNUSED(cbdata))  
    456457    g_Game->GetTurnManager()->RewindTimeWarp();
    457458}
    458459
     460void SetBoundingBoxDebugOverlay(void* UNUSED(cbdata), bool enabled)
     461{
     462    ICmpSelectable::ms_EnableDebugOverlays = enabled;
     463}
     464
    459465} // namespace
    460466
    461467void GuiScriptingInit(ScriptInterface& scriptInterface)
    void GuiScriptingInit(ScriptInterface& scriptInterface)  
    520526    scriptInterface.RegisterFunction<void, &DumpSimState>("DumpSimState");
    521527    scriptInterface.RegisterFunction<void, unsigned int, &EnableTimeWarpRecording>("EnableTimeWarpRecording");
    522528    scriptInterface.RegisterFunction<void, &RewindTimeWarp>("RewindTimeWarp");
     529    scriptInterface.RegisterFunction<void, bool, &SetBoundingBoxDebugOverlay>("SetBoundingBoxDebugOverlay");
    523530}
  • source/maths/Bound.cpp

    diff --git a/source/maths/Bound.cpp b/source/maths/Bound.cpp
    index bf6d798..a9b8886 100644
    a b  
    2828#include <float.h>
    2929
    3030#include "graphics/Frustum.h"
     31#include "maths/Box.h"
    3132#include "maths/Brush.h"
    3233#include "maths/Matrix3D.h"
    3334
     35const CBound CBound::EMPTY = CBound(); // initializes to an empty bound
    3436
    3537///////////////////////////////////////////////////////////////////////////////
    3638// RayIntersect: intersect ray with this bound; return true
    bool CBound::IsEmpty() const  
    136138// Transform: transform this bound by given matrix; return transformed bound
    137139// in 'result' parameter - slightly modified version of code in Graphic Gems
    138140// (can't remember which one it was, though)
    139 void CBound::Transform(const CMatrix3D& m,CBound& result) const
     141void CBound::Transform(const CMatrix3D& m, CBound& result) const
    140142{
    141143    ENSURE(this!=&result);
    142144
    void CBound::Transform(const CMatrix3D& m,CBound& result) const  
    161163    }
    162164}
    163165
     166void CBound::Transform(const CMatrix3D& transform, CBox& result) const
     167{
     168    // The idea is this: compute the corners of this bounding box, transform them according to the specified matrix,
     169    // then derive the box center, orientation vectors, and half-sizes.
     170    const CVector3D& pMin = m_Data[0];
     171    const CVector3D& pMax = m_Data[1];
     172
     173    // Find the corners of these bounds. We only need some of the corners to derive the information we need, so let's
     174    // not actually compute all of them. The corners are numbered starting from the minimum position (m_Data[0]), going
     175    // counter-clockwise in the bottom plane, and then in the same order for the top plane (starting from the corner
     176    // that's directly above the minimum position). Hence, corner0 is pMin and corner6 is pMax, so we don't need to
     177    // custom-create those.
     178   
     179    CVector3D corner0; // corner0 is pMin, no need to copy it
     180    CVector3D corner1(pMax.X, pMin.Y, pMin.Z);
     181    CVector3D corner3(pMin.X, pMin.Y, pMax.Z);
     182    CVector3D corner4(pMin.X, pMax.Y, pMin.Z);
     183    CVector3D corner6; // corner6 is pMax, no need to copy it
     184
     185    // transform corners to world space
     186    corner0 = transform.Transform(pMin); // = corner0
     187    corner1 = transform.Transform(corner1);
     188    corner3 = transform.Transform(corner3);
     189    corner4 = transform.Transform(corner4);
     190    corner6 = transform.Transform(pMax); // = corner6
     191
     192    // Compute orientation vectors, half-size vector, and box center. We can get the orientation vectors by just taking
     193    // the directional vectors from a specific corner point (corner0) to the other corners, once in each direction. The
     194    // half-sizes are similarly computed by taking the distances of those sides and dividing them by 2. Finally, the
     195    // center is simply the middle between the transformed pMin and pMax corners.
     196
     197    const CVector3D sideU(corner1 - corner0);
     198    const CVector3D sideV(corner4 - corner0);
     199    const CVector3D sideW(corner3 - corner0);
     200
     201    result.m_Basis[0] = sideU.Normalized();
     202    result.m_Basis[1] = sideV.Normalized();
     203    result.m_Basis[2] = sideW.Normalized();
     204
     205    result.m_HalfSizes = CVector3D(
     206        sideU.Length()/2.f,
     207        sideV.Length()/2.f,
     208        sideW.Length()/2.f
     209    );
     210
     211    result.m_Center = (corner0 + corner6) * 0.5f;
     212}
     213
    164214
    165215///////////////////////////////////////////////////////////////////////////////
    166216// Intersect with the given frustum in a conservative manner
  • source/maths/Bound.h

    diff --git a/source/maths/Bound.h b/source/maths/Bound.h
    index 7a22eee..9a106b2 100644
    a b  
    2727
    2828class CFrustum;
    2929class CMatrix3D;
     30class CBox;
    3031
    3132///////////////////////////////////////////////////////////////////////////////
    3233// CBound: basic axis aligned bounding box class
    3334class CBound
    3435{
    3536public:
     37   
    3638    CBound() { SetEmpty(); }
    37     CBound(const CVector3D& min,const CVector3D& max) {
    38         m_Data[0]=min; m_Data[1]=max;
     39    CBound(const CVector3D& min, const CVector3D& max) {
     40        m_Data[0] = min;
     41        m_Data[1] = max;
    3942    }
    4043
    41     void Transform(const CMatrix3D& m,CBound& result) const;
     44    /**
     45     * Transforms these bounds according to the specified transformation matrix @m, and writes the axis-aligned bounds
     46     * of that result to @p result.
     47     */
     48    void Transform(const CMatrix3D& m, CBound& result) const;
     49
     50    /**
     51     * Transform these bounds using the matrix @p transform, and write out the result as an arbitrarily-aligned box.
     52     * The difference with @ref Transform(const CMatrix3D&, CBound&) is that that method is equivalent to first computing
     53     * this result, and then taking the axis-aligned bounding boxes again.
     54     */
     55    void Transform(const CMatrix3D& m, CBox& result) const;
    4256
    4357    CVector3D& operator[](int index) {  return m_Data[index]; }
    4458    const CVector3D& operator[](int index) const { return m_Data[index]; }
    public:  
    7084        return *this;
    7185    }
    7286
    73     bool RayIntersect(const CVector3D& origin,const CVector3D& dir,float& tmin,float& tmax) const;
     87    /**
     88     * Returns true if the ray originating in @p origin and with unit direction vector @p dir intersects this AABB, false otherwise.
     89     * Additionally, returns the positive distances from the origin of the ray to the entry and exit points in the bounding box in
     90     * @p tmin and @p tmax. See also Real-Time Rendering, Third Edition by T. Akenine-Moller, p. 741--742.
     91     * @param origin Origin of the ray.
     92     * @param dir Direction vector of the ray. Must be of unit length.
     93     */
     94    bool RayIntersect(const CVector3D& origin, const CVector3D& dir, float& tmin, float& tmax) const;
    7495
    7596    // return the volume of this bounding box
    76     float GetVolume() const {
    77         CVector3D v=m_Data[1]-m_Data[0];
    78         return std::max(v.X, 0.0f)*std::max(v.Y, 0.0f)*std::max(v.Z, 0.0f);
     97    float GetVolume() const
     98    {
     99        CVector3D v = m_Data[1] - m_Data[0];
     100        return (std::max(v.X, 0.0f) * std::max(v.Y, 0.0f) * std::max(v.Z, 0.0f));
    79101    }
    80102
    81103    // return the centre of this bounding box
    82     void GetCentre(CVector3D& centre) const {
    83         centre=(m_Data[0]+m_Data[1])*0.5f;
     104    void GetCentre(CVector3D& centre) const
     105    {
     106        centre = (m_Data[0] + m_Data[1]) * 0.5f;
    84107    }
    85108
    86109    /**
    public:  
    109132    void Render() const;
    110133
    111134private:
     135    // Holds the minimal and maximal coordinate points in m_Data[0] and m_Data[1], respectively.
    112136    CVector3D m_Data[2];
     137
     138public:
     139    static const CBound EMPTY;
     140
    113141};
    114142//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    115143
  • new file source/maths/Box.cpp

    diff --git a/source/maths/Box.cpp b/source/maths/Box.cpp
    new file mode 100644
    index 0000000..799ba48
    - +  
     1/* Copyright (C) 2011 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18#include "precompiled.h"
     19
     20#include "Box.h"
     21#include "maths/Bound.h"
     22
     23#include <float.h>
     24
     25const CBox CBox::EMPTY = CBox();
     26
     27CBox::CBox(const CBound& bound)
     28{
     29    if (bound.IsEmpty())
     30    {
     31        SetEmpty();
     32    }
     33    else
     34    {
     35        bound.GetCentre(m_Center);
     36
     37        // the axes of an AABB are the world-space axes
     38        m_Basis[0].X = 1.f; m_Basis[0].Y = 0.f; m_Basis[0].Z = 0.f;
     39        m_Basis[1].X = 0.f; m_Basis[1].Y = 1.f; m_Basis[1].Z = 0.f;
     40        m_Basis[2].X = 0.f; m_Basis[2].Y = 0.f; m_Basis[2].Z = 1.f;
     41
     42        // element-wise division by two to get half sizes (remember, [1] and [0] are the max and min coord points)
     43        m_HalfSizes = (bound[1] - bound[0]) * 0.5f;
     44    }
     45}
     46
     47bool CBox::RayIntersect(const CVector3D& origin, const CVector3D& dir, float& tMin_out, float& tMax_out) const
     48{
     49    // See Real-Time Rendering, Third Edition, p. 743
     50    float tMin = -FLT_MAX;
     51    float tMax = FLT_MAX;
     52
     53    CVector3D p = m_Center - origin;
     54
     55    for (int i = 0; i < 3; ++i)
     56    {
     57        float e = m_Basis[i].Dot(p);
     58        float f = m_Basis[i].Dot(dir);
     59
     60        if(fabs(f) > 1e-10f)
     61        {
     62            float invF = 1.f/f;
     63            float t1 = (e + m_HalfSizes[i]) * invF;
     64            float t2 = (e - m_HalfSizes[i]) * invF;
     65
     66            if (t1 > t2)
     67            {
     68                float tmp = t1;
     69                t1 = t2;
     70                t2 = tmp;
     71            }
     72            if (t1 > tMin) tMin = t1;
     73            if (t2 < tMax) tMax = t2;
     74            if (tMin > tMax) return false;
     75            if (tMax < 0) return false;
     76        }
     77        else
     78        {
     79            if(-e - m_HalfSizes[i] > 0 || -e + m_HalfSizes[i] < 0) return false;
     80        }
     81    }
     82
     83    tMin_out = tMin;
     84    tMax_out = tMax;
     85    return true;
     86}
     87 No newline at end of file
  • new file source/maths/Box.h

    diff --git a/source/maths/Box.h b/source/maths/Box.h
    new file mode 100644
    index 0000000..6caebbf
    - +  
     1/* Copyright (C) 2011 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18#ifndef INCLUDED_BOX
     19#define INCLUDED_BOX
     20
     21#include "maths/Vector3D.h"
     22
     23class CBound;
     24
     25/*
     26 * Generic oriented box. Originally intended to be used an Oriented Bounding Box (OBB), as opposed to CBound which is always aligned
     27 * to the world-space axes (hence axis-aligned bounding box, or AABB).
     28 */
     29class CBox
     30{
     31public:
     32   
     33    /// Empty constructor; creates an empty box
     34    CBox() { SetEmpty(); }
     35
     36    /**
     37     * Constructs a new oriented box centered at @p centered and with normalized side vectors @p u, @p v and @p w. These vectors should
     38     * be mutually orthonormal for a proper rectangular box. The half-widths of the box in each direction are given by @p hU, @p hV
     39     * and @p hW, respectively.
     40     */
     41    CBox(const CVector3D& center, const CVector3D& u, const CVector3D& v, const CVector3D& w, const CVector3D& halfSizes)
     42        : m_Center(center), m_HalfSizes(halfSizes)
     43    {
     44        m_Basis[0] = u;
     45        m_Basis[1] = v;
     46        m_Basis[2] = w;
     47    }
     48
     49    /// Constructs a new box from an axis-aligned bounding box (AABB).
     50    explicit CBox(const CBound& bound);
     51
     52    /**
     53     * Returns true if the ray originating in @p origin and with unit direction vector @p dir intersects this box, false otherwise.
     54     * Additionally, returns the positive distances from the origin of the ray to the entry and exit points in the box in
     55     * @p tmin and @p tmax. See also Real-Time Rendering, Third Edition by T. Akenine-Möller, p. 741--742.
     56     * Should not be used if IsEmpty() is true.
     57     *
     58     * @param origin Origin of the ray.
     59     * @param dir Direction vector of the ray. Must be of unit length.
     60     */
     61    bool RayIntersect(const CVector3D& origin, const CVector3D& dir, float& tMin, float& tMax) const;
     62
     63    /**
     64     * 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.
     65     * Should not be used if IsEmpty() is true.
     66     */
     67    void GetCorner(int u, int v, int w, CVector3D& out) const
     68    {
     69        out = m_Center + m_Basis[0]*(u*m_HalfSizes[0]) + m_Basis[1]*(v*m_HalfSizes[1]) + m_Basis[2]*(w*m_HalfSizes[2]);
     70    }
     71
     72    void SetEmpty()
     73    {
     74        // everything is zero
     75        m_Center = CVector3D();
     76        m_Basis[0] = CVector3D();
     77        m_Basis[1] = CVector3D();
     78        m_Basis[2] = CVector3D();
     79        m_HalfSizes = CVector3D();
     80    }
     81
     82    bool IsEmpty() const
     83    {
     84        return (   m_Center.X == 0 &&    m_Center.Y == 0 &&    m_Center.Z == 0 &&
     85                 m_Basis[0].X == 0 &&  m_Basis[0].Y == 0 &&  m_Basis[0].Z == 0 &&
     86                 m_Basis[1].X == 0 &&  m_Basis[1].Y == 0 &&  m_Basis[1].Z == 0 &&
     87                 m_Basis[2].X == 0 &&  m_Basis[2].Y == 0 &&  m_Basis[2].Z == 0 &&
     88                m_HalfSizes.X == 0 && m_HalfSizes.Y == 0 && m_HalfSizes.Z == 0);
     89    }
     90
     91public:
     92    CVector3D m_Center; ///< Centroid location of the box
     93    CVector3D m_HalfSizes; ///< Half the sizes of the box in each dimension (u,v,w). Positive values are expected.
     94    /// Basis vectors (u,v,w) of the sides. Must always be normalized, and should be
     95    /// orthogonal for a proper rectangular cuboid.
     96    CVector3D m_Basis[3];
     97
     98    static const CBox EMPTY;
     99};
     100
     101#endif INCLUDED_BOX
     102 No newline at end of file
  • source/maths/Matrix3D.h

    diff --git a/source/maths/Matrix3D.h b/source/maths/Matrix3D.h
    index 0a14bd3..84968db 100644
    a b class CMatrix3D  
    3535public:
    3636    // the matrix data itself - accessible as either longhand names
    3737    // or via a flat or 2d array
     38    // NOTE: _xy means row x, column y, so don't be fooled by the way they're listed below
    3839    union {
    3940        struct {
    4041            float _11, _21, _31, _41;
    public:  
    155156                 _14 == m._14 && _24 == m._24 && _34 == m._34 && _44 == m._44;
    156157    }
    157158
     159    // inequality
     160    bool operator!=(const CMatrix3D& m) const
     161    {
     162        return !(*this == m);
     163    }
     164
    158165    // set this matrix to the identity matrix
    159166    void SetIdentity();
    160167    // set this matrix to the zero matrix
  • source/renderer/Renderer.cpp

    diff --git a/source/renderer/Renderer.cpp b/source/renderer/Renderer.cpp
    index b8308bc..731f53d 100644
    a b public:  
    10411041
    10421042    bool Filter(CModel *model)
    10431043    {
    1044         return m_Frustum.IsBoxVisible(CVector3D(0, 0, 0), model->GetBoundsRec());
     1044        return m_Frustum.IsBoxVisible(CVector3D(0, 0, 0), model->GetWorldBoundsRec());
    10451045    }
    10461046
    10471047private:
    void CRenderer::SubmitNonRecursive(CModel* model)  
    17531753{
    17541754    if (model->GetFlags() & MODELFLAG_CASTSHADOWS) {
    17551755//      PROFILE( "updating shadow bounds" );
    1756         m->shadow->AddShadowedBound(model->GetBounds());
     1756        m->shadow->AddShadowedBound(model->GetWorldBounds());
    17571757    }
    17581758
    1759     // Tricky: The call to GetBounds() above can invalidate the position
     1759    // Tricky: The call to GetWorldBounds() above can invalidate the position
    17601760    model->ValidatePosition();
    17611761
    17621762    bool canUseInstancing = false;
  • source/renderer/TerrainRenderer.cpp

    diff --git a/source/renderer/TerrainRenderer.cpp b/source/renderer/TerrainRenderer.cpp
    index 13b71bc..56b1eca 100644
    a b bool TerrainRenderer::CullPatches(const CFrustum* frustum)  
    173173    m->filteredPatches.clear();
    174174    for (std::vector<CPatchRData*>::iterator it = m->visiblePatches.begin(); it != m->visiblePatches.end(); it++)
    175175    {
    176         if (frustum->IsBoxVisible(CVector3D(0, 0, 0), (*it)->GetPatch()->GetBounds()))
     176        if (frustum->IsBoxVisible(CVector3D(0, 0, 0), (*it)->GetPatch()->GetWorldBounds()))
    177177            m->filteredPatches.push_back(*it);
    178178    }
    179179
    180180    m->filteredDecals.clear();
    181181    for (std::vector<CDecalRData*>::iterator it = m->visibleDecals.begin(); it != m->visibleDecals.end(); it++)
    182182    {
    183         if (frustum->IsBoxVisible(CVector3D(0, 0, 0), (*it)->GetDecal()->GetBounds()))
     183        if (frustum->IsBoxVisible(CVector3D(0, 0, 0), (*it)->GetDecal()->GetWorldBounds()))
    184184            m->filteredDecals.push_back(*it);
    185185    }
    186186
  • source/simulation2/TypeList.h

    diff --git a/source/simulation2/TypeList.h b/source/simulation2/TypeList.h
    index a7ac94b..b2c3042 100644
    a b COMPONENT(CommandQueue)  
    7171INTERFACE(Decay)
    7272COMPONENT(Decay)
    7373
     74// Note: The VisualActor component relies on this component being initialized before itself, in order to support using
     75// an entity's footprint shape for the selection boxes. This dependency is not strictly necessary, but it does avoid
     76// some extra plumbing code to set up on-demand initialization. If you find yourself forced to break this dependency,
     77// see VisualActor's Init method for a description of how you can avoid it.
    7478INTERFACE(Footprint)
    7579COMPONENT(Footprint)
    7680
    COMPONENT(UnitMotionScripted)  
    142146INTERFACE(Vision)
    143147COMPONENT(Vision)
    144148
     149// Note: this component relies on the Footprint component being initialized before itself. See the comments above for
     150// the Footprint component to find out why.
    145151INTERFACE(Visual)
    146152COMPONENT(VisualActor)
    147153
  • source/simulation2/components/CCmpProjectileManager.cpp

    diff --git a/source/simulation2/components/CCmpProjectileManager.cpp b/source/simulation2/components/CCmpProjectileManager.cpp
    index e0ac1cc..c7ffccd 100644
    a b void CCmpProjectileManager::RenderSubmit(SceneCollector& collector, const CFrust  
    347347
    348348        model.ValidatePosition();
    349349
    350         if (culling && !frustum.IsBoxVisible(CVector3D(0, 0, 0), model.GetBounds()))
     350        if (culling && !frustum.IsBoxVisible(CVector3D(0, 0, 0), model.GetWorldBounds()))
    351351            continue;
    352352
    353353        // TODO: do something about LOS (copy from CCmpVisualActor)
  • source/simulation2/components/CCmpSelectable.cpp

    diff --git a/source/simulation2/components/CCmpSelectable.cpp b/source/simulation2/components/CCmpSelectable.cpp
    index e9aa3d2..d5865e0 100644
    a b  
    2222
    2323#include "ICmpPosition.h"
    2424#include "ICmpFootprint.h"
     25#include "ICmpVisual.h"
    2526#include "simulation2/MessageTypes.h"
    2627#include "simulation2/helpers/Render.h"
    2728
    public:  
    153154    void RenderSubmit(SceneCollector& collector)
    154155    {
    155156        // (This is only called if a > 0)
    156 
    157157        collector.Submit(&m_Overlay);
     158
     159        if (ICmpSelectable::ms_EnableDebugOverlays)
     160        {
     161            static SOverlayLine boundOverlayLine;
     162            static SOverlayLine selectionOverlayLine;
     163
     164            CmpPtr<ICmpVisual> cmpVisual(GetSimContext(), GetEntityId());
     165            if (!cmpVisual.null())
     166            {
     167                SimRender::ConstructBoxOutline(cmpVisual->GetBounds(), boundOverlayLine);
     168                boundOverlayLine.m_Thickness = 2;
     169                boundOverlayLine.m_Color = CColor(1.f, 0.f, 0.f, 1.f);
     170
     171                SimRender::ConstructBoxOutline(cmpVisual->GetSelectionBox(), selectionOverlayLine);
     172                selectionOverlayLine.m_Thickness = 2;
     173                selectionOverlayLine.m_Color = CColor(0.f, 1.f, 0.f, 1.f);
     174
     175                collector.Submit(&boundOverlayLine);
     176                collector.Submit(&selectionOverlayLine);
     177            }
     178        }
    158179    }
    159180};
    160181
  • source/simulation2/components/CCmpVisualActor.cpp

    diff --git a/source/simulation2/components/CCmpVisualActor.cpp b/source/simulation2/components/CCmpVisualActor.cpp
    index f35cb80..6965af2 100644
    a b  
    2424#include "ICmpRangeManager.h"
    2525#include "ICmpVision.h"
    2626#include "simulation2/MessageTypes.h"
     27#include "simulation2/components/ICmpFootprint.h"
    2728
    2829#include "graphics/Frustum.h"
    2930#include "graphics/Model.h"
    public:  
    9798            "</element>"
    9899            "<element name='SilhouetteOccluder'>"
    99100                "<data type='boolean'/>"
    100             "</element>";
     101            "</element>"
     102            "<optional>"
     103                "<element name='SelectionShape'>"
     104                    "<choice>"
     105                        "<attribute name='type'>"
     106                            "<choice>"
     107                                "<value>bounds</value>"
     108                                "<value>footprint</value>"
     109                            "</choice>"
     110                        "</attribute>"
     111                        "<group>"
     112                            "<attribute name='type'>"
     113                                "<text/>"
     114                            "</attribute>"
     115                            "<attribute name='width'>"
     116                                "<data type='decimal' />"
     117                            "</attribute>"
     118                            "<attribute name='height'>"
     119                                "<data type='decimal' />"
     120                            "</attribute>"
     121                            "<attribute name='depth'>"
     122                                "<data type='decimal' />"
     123                            "</attribute>"
     124                        "</group>"
     125                        "<group>"
     126                            "<attribute name='type'>"
     127                                "<text/>"
     128                            "</attribute>"
     129                            "<attribute name='radius'>"
     130                                "<data type='decimal' />"
     131                            "</attribute>"
     132                            "<attribute name='height'>"
     133                                "<data type='decimal' />"
     134                            "</attribute>"
     135                        "</group>"
     136                    "</choice>"
     137                "</element>"
     138            "</optional>";
    101139    }
    102140
    103141    virtual void Init(const CParamNode& paramNode)
    public:  
    130168        if (paramNode.GetChild("SilhouetteOccluder").ToBool())
    131169            modelFlags |= MODELFLAG_SILHOUETTE_OCCLUDER;
    132170
    133         if (m_Unit->GetModel().ToCModel())
    134             m_Unit->GetModel().ToCModel()->AddFlagsRec(modelFlags);
     171        CModelAbstract& model = m_Unit->GetModel();
     172        if (model.ToCModel())
     173            model.ToCModel()->AddFlagsRec(modelFlags);
     174
     175        // Initialize the model's selection shape descriptor. This currently relies on the component initialization order; the
     176        // Footprint component must be initialized before this component (VisualActor) to support the ability to use the footprint
     177        // shape for the selection box (instead of the default recursive bounding box). See TypeList.h for the order in
     178        // which components are initialized; if for whatever reason you need to get rid of this dependency, you can always just
     179        // initialize the selection shape descriptor on-demand.
     180        InitSelectionShapeDescriptor(model, paramNode);
    135181
    136182        m_Unit->SetID(GetEntityId());
    137183
    public:  
    223269    virtual CBound GetBounds()
    224270    {
    225271        if (!m_Unit)
    226             return CBound();
    227         return m_Unit->GetModel().GetBounds();
     272            return CBound::EMPTY;
     273        return m_Unit->GetModel().GetWorldBounds();
     274    }
     275
     276    virtual CBox GetSelectionBox()
     277    {
     278        if (!m_Unit)
     279            return CBox::EMPTY;
     280        return m_Unit->GetModel().GetSelectionBox();
    228281    }
    229282
    230283    virtual CVector3D GetPosition()
    private:  
    381434        return GetEntityId();
    382435    }
    383436
     437    /// Helper method; initializes the model selection shape descriptor from XML. Factored out for readability of @ref Init.
     438    /// The @p model argument is technically not really necessary since naturally this method is intended to initialize this
     439    /// visual actor's model (I wouldn't know which other one you'd pass), but it's included here to enforce that the
     440    /// component's model must have been created before using this method (i.e. to prevent accidentally calls to this method
     441    /// before the model was constructed).
     442    void InitSelectionShapeDescriptor(CModelAbstract& model, const CParamNode& paramNode);
     443
    384444    void Update(fixed turnLength);
    385445    void Interpolate(float frameTime, float frameOffset);
    386446    void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling);
    private:  
    388448
    389449REGISTER_COMPONENT_TYPE(VisualActor)
    390450
     451// ------------------------------------------------------------------------------------------------------------------
     452
     453void CCmpVisualActor::InitSelectionShapeDescriptor(CModelAbstract& model, const CParamNode& paramNode)
     454{
     455    // by default, we don't need a custom selection shape and we can just keep the default behaviour
     456    CModelAbstract::CustomSelectionShape* shapeDescriptor = NULL;
     457
     458    const CParamNode& xmlSelectionShapeNode = paramNode.GetChild("SelectionShape");
     459    if (xmlSelectionShapeNode.IsOk())
     460    {
     461        const std::wstring& selectionShapeType = xmlSelectionShapeNode.GetChild("@type").ToString(); // source attribute should always exist
     462        if (selectionShapeType == L"bounds")
     463        {
     464            // default; no need to take action
     465        }
     466        else
     467        {
     468            shapeDescriptor = new CModelAbstract::CustomSelectionShape;
     469            if (selectionShapeType == L"footprint")
     470            {
     471                CmpPtr<ICmpFootprint> cmpFootprint(GetSimContext(), GetEntityId());
     472                if (!cmpFootprint.null())
     473                {
     474                    ICmpFootprint::EShape fpShape;              // fp stands for "footprint"
     475                    entity_pos_t fpSize0, fpSize1, fpHeight;    // fp stands for "footprint"
     476                    cmpFootprint->GetShape(fpShape, fpSize0, fpSize1, fpHeight);
     477
     478                    float size0 = fpSize0.ToFloat();
     479                    float size1 = fpSize1.ToFloat();
     480
     481                    // TODO: we should properly distinguish between CIRCLE and SQUARE footprint shapes here, but since cylinders
     482                    // aren't implemented yet (and they're almost indistinguishable from boxes for small enough sizes anyway), so
     483                    // we'll just use boxes for either case. Although, as a basic measure, for circle footprints the size0 and size1
     484                    // represent the radius, so we do adjust that to match the (full-size) sizes of the square footprints.
     485                    if (fpShape == ICmpFootprint::CIRCLE)
     486                    {
     487                        size0 *= 2;
     488                        size1 *= 2;
     489                    }
     490               
     491                    shapeDescriptor->m_Type = CModelAbstract::CustomSelectionShape::BOX;
     492                    shapeDescriptor->m_Size0 = size0;
     493                    shapeDescriptor->m_Size1 = size1;
     494                    shapeDescriptor->m_Height = fpHeight.ToFloat();
     495                }
     496                else
     497                {
     498                    LOGWARNING(L"Footprint component not yet initialized; cannot apply footprint-based SelectionShape in VisualActor");
     499                }
     500            }
     501            else if (selectionShapeType == L"box")
     502            {
     503                shapeDescriptor->m_Type = CModelAbstract::CustomSelectionShape::BOX;
     504                shapeDescriptor->m_Size0 = xmlSelectionShapeNode.GetChild("@width").ToFixed().ToFloat();
     505                shapeDescriptor->m_Size1 = xmlSelectionShapeNode.GetChild("@depth").ToFixed().ToFloat();
     506                shapeDescriptor->m_Height = xmlSelectionShapeNode.GetChild("@height").ToFixed().ToFloat();
     507                // TODO: we might need to support the ability to specify a different box center
     508            }
     509            else if (selectionShapeType == L"cylinder")
     510            {
     511                LOGWARNING(L"[VisualActor] TODO: cylinder selection shapes are not yet implemented; defaulting to recursive bounding boxes");
     512            }
     513
     514        }
     515    }
     516
     517    // the model is now responsible for cleaning up the descriptor
     518    model.SetCustomSelectionShape(shapeDescriptor);
     519}
     520
    391521void CCmpVisualActor::Update(fixed turnLength)
    392522{
    393523    if (m_Unit == NULL)
    void CCmpVisualActor::RenderSubmit(SceneCollector& collector, const CFrustum& fr  
    482612
    483613    CModelAbstract& model = m_Unit->GetModel();
    484614
    485     if (culling && !frustum.IsBoxVisible(CVector3D(0, 0, 0), model.GetBoundsRec()))
     615    if (culling && !frustum.IsBoxVisible(CVector3D(0, 0, 0), model.GetWorldBoundsRec()))
    486616        return;
    487617
    488618    collector.SubmitRecursive(&model);
  • source/simulation2/components/ICmpSelectable.cpp

    diff --git a/source/simulation2/components/ICmpSelectable.cpp b/source/simulation2/components/ICmpSelectable.cpp
    index 20f0b09..dc959ea 100644
    a b  
    2626BEGIN_INTERFACE_WRAPPER(Selectable)
    2727DEFINE_INTERFACE_METHOD_1("SetSelectionHighlight", void, ICmpSelectable, SetSelectionHighlight, CColor)
    2828END_INTERFACE_WRAPPER(Selectable)
     29
     30bool ICmpSelectable::ms_EnableDebugOverlays = false;
     31 No newline at end of file
  • source/simulation2/components/ICmpSelectable.h

    diff --git a/source/simulation2/components/ICmpSelectable.h b/source/simulation2/components/ICmpSelectable.h
    index eadd554..0ada93a 100644
    a b public:  
    3232    virtual void SetSelectionHighlight(CColor color) = 0;
    3333
    3434    DECLARE_INTERFACE_TYPE(Selectable)
     35
     36    static bool ms_EnableDebugOverlays;
    3537};
    3638
    3739#endif // INCLUDED_ICMPSELECTABLE
  • source/simulation2/components/ICmpVisual.h

    diff --git a/source/simulation2/components/ICmpVisual.h b/source/simulation2/components/ICmpVisual.h
    index 68f1108..4d7a12a 100644
    a b  
    2020
    2121#include "simulation2/system/Interface.h"
    2222
     23#include "maths/Box.h"
    2324#include "maths/Bound.h"
    2425#include "maths/Fixed.h"
    2526#include "lib/file/vfs/vfs_path.h"
    public:  
    3738    virtual CBound GetBounds() = 0;
    3839
    3940    /**
     41     * Get the oriented world-space bounding box of the object's visual representation, clipped at the Y=0 plane in object space
     42     * to prevent it from extending into the terrain. The primary difference with GetBounds is that this bounding box is not aligned
     43     * to the world axes, but arbitrarily rotated according to the model transform.
     44     */
     45    virtual CBox GetSelectionBox() = 0;
     46
     47    /**
    4048     * Get the world-space position of the base point of the object's visual representation.
    4149     * (Not safe for use in simulation code.)
    4250     */
  • source/simulation2/helpers/Render.cpp

    diff --git a/source/simulation2/helpers/Render.cpp b/source/simulation2/helpers/Render.cpp
    index db63115..67e55df 100644
    a b  
    2424#include "simulation2/components/ICmpWaterManager.h"
    2525#include "graphics/Overlay.h"
    2626#include "graphics/Terrain.h"
     27#include "maths/Bound.h"
     28#include "maths/Box.h"
    2729#include "maths/MathUtil.h"
    2830#include "maths/Vector2D.h"
    2931#include "ps/Profile.h"
     32#include "maths/Quaternion.h"
    3033
    3134void SimRender::ConstructLineOnGround(const CSimContext& context, const std::vector<float>& xz,
    3235        SOverlayLine& overlay, bool floating, float heightOffset)
    void SimRender::ConstructSquareOnGround(const CSimContext& context, float x, flo  
    159162    }
    160163}
    161164
     165void SimRender::ConstructBoxOutline(const CBound& bound, SOverlayLine& overlayLine)
     166{
     167    overlayLine.m_Coords.clear();
     168
     169    if (bound.IsEmpty())
     170        return;
     171
     172    const CVector3D& pMin = bound[0];
     173    const CVector3D& pMax = bound[1];
     174   
     175    // floor square
     176    overlayLine.PushCoords(pMin.X, pMin.Y, pMin.Z);
     177    overlayLine.PushCoords(pMax.X, pMin.Y, pMin.Z);
     178    overlayLine.PushCoords(pMax.X, pMin.Y, pMax.Z);
     179    overlayLine.PushCoords(pMin.X, pMin.Y, pMax.Z);
     180    overlayLine.PushCoords(pMin.X, pMin.Y, pMin.Z);
     181    // roof square
     182    overlayLine.PushCoords(pMin.X, pMax.Y, pMin.Z);
     183    overlayLine.PushCoords(pMax.X, pMax.Y, pMin.Z);
     184    overlayLine.PushCoords(pMax.X, pMax.Y, pMax.Z);
     185    overlayLine.PushCoords(pMin.X, pMax.Y, pMax.Z);
     186    overlayLine.PushCoords(pMin.X, pMax.Y, pMin.Z);
     187}
     188
     189void SimRender::ConstructBoxOutline(const CBox& box, SOverlayLine& overlayLine)
     190{
     191    overlayLine.m_Coords.clear();
     192
     193    if (box.IsEmpty())
     194        return;
     195
     196    CVector3D corners[8];
     197    box.GetCorner(-1, -1, -1, corners[0]);
     198    box.GetCorner( 1, -1, -1, corners[1]);
     199    box.GetCorner( 1, -1,  1, corners[2]);
     200    box.GetCorner(-1, -1,  1, corners[3]);
     201    box.GetCorner(-1,  1, -1, corners[4]);
     202    box.GetCorner( 1,  1, -1, corners[5]);
     203    box.GetCorner( 1,  1,  1, corners[6]);
     204    box.GetCorner(-1,  1,  1, corners[7]);
     205
     206    overlayLine.PushCoords(corners[0]);
     207    overlayLine.PushCoords(corners[1]);
     208    overlayLine.PushCoords(corners[2]);
     209    overlayLine.PushCoords(corners[3]);
     210    overlayLine.PushCoords(corners[0]);
     211
     212    overlayLine.PushCoords(corners[4]);
     213    overlayLine.PushCoords(corners[5]);
     214    overlayLine.PushCoords(corners[6]);
     215    overlayLine.PushCoords(corners[7]);
     216    overlayLine.PushCoords(corners[4]);
     217}
     218
     219void SimRender::ConstructGimbal(const CVector3D& center, float radius, SOverlayLine& out, size_t numSteps)
     220{
     221    ENSURE(numSteps > 0 && numSteps % 4 == 0); // must be a positive multiple of 4
     222   
     223    out.m_Coords.clear();
     224
     225    size_t fullCircleSteps = numSteps;
     226    const float angleIncrement = 2.f*M_PI/fullCircleSteps;
     227
     228    const CVector3D X_UNIT(1, 0, 0);
     229    const CVector3D Y_UNIT(0, 1, 0);
     230    const CVector3D Z_UNIT(0, 0, 1);
     231    CVector3D rotationVector(0, 0, radius); // directional vector based in the center that we will be rotating to get the gimbal points
     232
     233    // first draw a quarter of XZ gimbal; then complete the XY gimbal; then continue the XZ gimbal and finally add the YZ gimbal
     234    // (that way we can keep a single continuous line)
     235   
     236    // -- XZ GIMBAL (PART 1/2) -----------------------------------------------
     237
     238    CQuaternion xzRotation;
     239    xzRotation.FromAxisAngle(Y_UNIT, angleIncrement);
     240
     241    for (size_t i = 0; i < fullCircleSteps/4; ++i) // complete only a quarter of the way
     242    {
     243        out.PushCoords(center + rotationVector);
     244        rotationVector = xzRotation.Rotate(rotationVector);
     245    }
     246
     247    // -- XY GIMBAL ----------------------------------------------------------
     248
     249    // now complete the XY gimbal while the XZ gimbal is interrupted
     250    CQuaternion xyRotation;
     251    xyRotation.FromAxisAngle(Z_UNIT, angleIncrement);
     252
     253    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
     254    {
     255        out.PushCoords(center + rotationVector);
     256        rotationVector = xyRotation.Rotate(rotationVector);
     257    }
     258
     259    // -- XZ GIMBAL (PART 2/2) -----------------------------------------------
     260
     261    // resume the XZ gimbal to completion
     262    for (size_t i = fullCircleSteps/4; i < fullCircleSteps; ++i) // exclude the last point of the circle so the YZ gimbal can add it
     263    {
     264        out.PushCoords(center + rotationVector);
     265        rotationVector = xzRotation.Rotate(rotationVector);
     266    }
     267
     268    // -- YZ GIMBAL ----------------------------------------------------------
     269
     270    CQuaternion yzRotation;
     271    yzRotation.FromAxisAngle(X_UNIT, angleIncrement);
     272
     273    for (size_t i = 0; i <= fullCircleSteps; ++i)
     274    {
     275        out.PushCoords(center + rotationVector);
     276        rotationVector = yzRotation.Rotate(rotationVector);
     277    }
     278}
     279
    162280void SimRender::SmoothPointsAverage(std::vector<CVector2D>& points, bool closed)
    163281{
    164282    PROFILE("SmoothPointsAverage");
  • source/simulation2/helpers/Render.h

    diff --git a/source/simulation2/helpers/Render.h b/source/simulation2/helpers/Render.h
    index 0129b01..a8c0a97 100644
    a b  
    2525
    2626class CSimContext;
    2727class CVector2D;
     28class CVector3D;
     29class CBound;
     30class CBox;
    2831struct SOverlayLine;
    2932
    3033namespace SimRender
    void ConstructSquareOnGround(const CSimContext& context, float x, float z, float  
    5457        bool floating, float heightOffset = 0.25f);
    5558
    5659/**
     60 * Constructs a solid outline of an arbitrarily-aligned box.
     61 */
     62void ConstructBoxOutline(const CBox& box, SOverlayLine& overlayLine);
     63
     64/**
     65 * Constructs a solid outline of an axis-aligned bounding box.
     66 */
     67void ConstructBoxOutline(const CBound& bound, SOverlayLine& overlayLine);
     68
     69/**
     70 * Constructs a simple gimbal outline of radius @p radius at @p center in @p out.
     71 * @param numSteps The amount of steps to trace a circle's complete outline. Must be a (strictly) positive multiple of four.
     72 *     For small radii, you can get away with small values; setting this to 4 will create a diamond shape.
     73 */
     74void ConstructGimbal(const CVector3D& center, float radius, SOverlayLine& out, size_t numSteps = 16);
     75
     76/**
    5777 * Updates @p points so each point is averaged with its neighbours, resulting in
    5878 * a somewhat smoother curve, assuming the points are roughly equally spaced.
    5979 * If @p closed then the points are treated as a closed path (the last is connected
  • source/simulation2/helpers/Selection.cpp

    diff --git a/source/simulation2/helpers/Selection.cpp b/source/simulation2/helpers/Selection.cpp
    index 0405894..d0c02e5 100644
    a b std::vector<entity_id_t> EntitySelection::PickEntitiesAtPoint(CSimulation2& simu  
    5151        if (cmpVisual.null())
    5252            continue;
    5353
    54         CBound bounds = cmpVisual->GetBounds();
     54        CBox selectionBox = cmpVisual->GetSelectionBox();
     55        if (selectionBox.IsEmpty())
     56            continue;
    5557
    5658        float tmin, tmax;
    57         if (!bounds.RayIntersect(origin, dir, tmin, tmax))
     59        if (!selectionBox.RayIntersect(origin, dir, tmin, tmax))
    5860            continue;
    5961
    6062        // Find the perpendicular distance from the object's centre to the picker ray
    6163
    62         CVector3D centre;
    63         bounds.GetCentre(centre);
    64 
    65         CVector3D closest = origin + dir * (centre - origin).Dot(dir);
    66         float dist2 = (closest - centre).LengthSquared();
     64        CVector3D closest = origin + dir * (selectionBox.m_Center - origin).Dot(dir);
     65        float dist2 = (closest - selectionBox.m_Center).LengthSquared();
    6766
    6867        hits.push_back(std::make_pair(dist2, ent));
    6968    }
  • source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp

    diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp
    index 15f46ae..b7d3064 100644
    a b enum  
    4444    ID_ViewerShadows,
    4545    ID_ViewerPolyCount,
    4646    ID_ViewerAnimation,
     47    ID_ViewerBoundingBox,
     48    ID_ViewerAxesMarker,
    4749    ID_ViewerPlay,
    4850    ID_ViewerPause,
    4951    ID_ViewerSlow
    static wxWindow* Tooltipped(wxWindow* window, const wxString& tip)  
    5961class ObjectBottomBar : public wxPanel
    6062{
    6163public:
    62     ObjectBottomBar(wxWindow* parent, ScenarioEditor& scenarioEditor, Observable<ObjectSettings>& objectSettings, Observable<AtObj>& mapSettings, ObjectSidebarImpl* p);
     64    ObjectBottomBar(
     65        wxWindow* parent,
     66        ScenarioEditor& scenarioEditor,
     67        Observable<ObjectSettings>& objectSettings,
     68        Observable<AtObj>& mapSettings,
     69        ObjectSidebarImpl* p
     70    );
    6371
    6472    void OnFirstDisplay();
    65 
    6673    void ShowActorViewer(bool show);
    6774
    6875private:
    private:  
    7582    bool m_ViewerGround;
    7683    bool m_ViewerShadows;
    7784    bool m_ViewerPolyCount;
     85    bool m_ViewerBoundingBox;
     86    bool m_ViewerAxesMarker;
    7887
    7988    wxPanel* m_ViewerPanel;
    8089
    8190    ObjectSidebarImpl* p;
    82 
    8391    ScenarioEditor& m_ScenarioEditor;
    8492
    8593    DECLARE_EVENT_TABLE();
    struct ObjectSidebarImpl  
    108116    }
    109117};
    110118
    111 ObjectSidebar::ObjectSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer)
    112 : Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer), p(new ObjectSidebarImpl())
     119ObjectSidebar::ObjectSidebar(
     120    ScenarioEditor& scenarioEditor,
     121    wxWindow* sidebarContainer,
     122    wxWindow* bottomBarContainer
     123)
     124    : Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer),
     125      p(new ObjectSidebarImpl())
    113126{
    114127    wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
    115128    sizer->Add(new wxStaticText(this, wxID_ANY, _("Filter")), wxSizerFlags().Align(wxALIGN_CENTER));
    116     sizer->Add(Tooltipped(new wxTextCtrl(this, ID_ObjectFilter),
    117         _("Enter text to filter object list")), wxSizerFlags().Expand().Proportion(1));
     129    sizer->Add(
     130        Tooltipped(
     131            new wxTextCtrl(this, ID_ObjectFilter),
     132            _("Enter text to filter object list")
     133        ),
     134        wxSizerFlags().Expand().Proportion(1)
     135    );
    118136    m_MainSizer->Add(sizer, wxSizerFlags().Expand());
     137    m_MainSizer->AddSpacer(3);
     138
     139    // ------------------------------------------------------------------------------------------
    119140
    120141    wxArrayString strings;
    121142    strings.Add(_("Entities"));
    ObjectSidebar::ObjectSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarCo  
    123144    wxChoice* objectType = new wxChoice(this, ID_ObjectType, wxDefaultPosition, wxDefaultSize, strings);
    124145    objectType->SetSelection(0);
    125146    m_MainSizer->Add(objectType, wxSizerFlags().Expand());
     147    m_MainSizer->AddSpacer(3);
     148   
     149    // ------------------------------------------------------------------------------------------
    126150
    127151    p->m_ObjectListBox = new wxListBox(this, ID_SelectObject, wxDefaultPosition, wxDefaultSize, 0, NULL, wxLB_SINGLE|wxLB_HSCROLL);
    128152    m_MainSizer->Add(p->m_ObjectListBox, wxSizerFlags().Proportion(1).Expand());
     153    m_MainSizer->AddSpacer(3);
     154
     155    // ------------------------------------------------------------------------------------------
    129156
    130157    m_MainSizer->Add(new wxButton(this, ID_ToggleViewer, _("Switch to Actor Viewer")), wxSizerFlags().Expand());
    131158
    132     m_BottomBar = new ObjectBottomBar(bottomBarContainer, scenarioEditor, scenarioEditor.GetObjectSettings(), scenarioEditor.GetMapSettings(), p);
     159    // ------------------------------------------------------------------------------------------
     160
     161    m_BottomBar = new ObjectBottomBar(
     162        bottomBarContainer,
     163        scenarioEditor,
     164        scenarioEditor.GetObjectSettings(),
     165        scenarioEditor.GetMapSettings(),
     166        p
     167    );
    133168
    134169    p->m_ToolConn = scenarioEditor.GetToolManager().GetCurrentTool().RegisterObserver(0, &ObjectSidebar::OnToolChange, this);
    135170}
    END_EVENT_TABLE();  
    312347
    313348//////////////////////////////////////////////////////////////////////////
    314349
    315 ObjectBottomBar::ObjectBottomBar(wxWindow* parent, ScenarioEditor& scenarioEditor,  Observable<ObjectSettings>& objectSettings, Observable<AtObj>& mapSettings, ObjectSidebarImpl* p)
     350ObjectBottomBar::ObjectBottomBar(
     351    wxWindow* parent,
     352    ScenarioEditor& scenarioEditor,
     353    Observable<ObjectSettings>& objectSettings,
     354    Observable<AtObj>& mapSettings,
     355    ObjectSidebarImpl* p
     356)
    316357    : wxPanel(parent, wxID_ANY), p(p), m_ScenarioEditor(scenarioEditor)
    317358{
    318359    m_ViewerWireframe = false;
    ObjectBottomBar::ObjectBottomBar(wxWindow* parent, ScenarioEditor& scenarioEdito  
    320361    m_ViewerGround = true;
    321362    m_ViewerShadows = true;
    322363    m_ViewerPolyCount = false;
     364    m_ViewerBoundingBox = false;
     365    m_ViewerAxesMarker = false;
    323366
    324     wxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
     367    wxSizer* mainSizer = new wxBoxSizer(wxHORIZONTAL);
    325368
     369    // --- viewer options panel -------------------------------------------------------------------------------
    326370
    327371    m_ViewerPanel = new wxPanel(this, wxID_ANY);
    328372    wxSizer* viewerSizer = new wxBoxSizer(wxHORIZONTAL);
    329373
    330     wxSizer* viewerButtonsSizer = new wxStaticBoxSizer(wxVERTICAL, m_ViewerPanel, _("Display settings"));
    331     viewerButtonsSizer->SetMinSize(140, -1);
    332     viewerButtonsSizer->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerWireframe, _("Wireframe")), _("Toggle wireframe / solid rendering")), wxSizerFlags().Expand());
    333     viewerButtonsSizer->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerMove, _("Move")), _("Toggle movement along ground when playing walk/run animations")), wxSizerFlags().Expand());
    334     viewerButtonsSizer->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerGround, _("Ground")), _("Toggle the ground plane")), wxSizerFlags().Expand());
    335     viewerButtonsSizer->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerShadows, _("Shadows")), _("Toggle shadow rendering")), wxSizerFlags().Expand());
    336     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());
     374    wxSizer* viewerButtonsSizer = new wxStaticBoxSizer(wxHORIZONTAL, m_ViewerPanel, _("Display settings"));
     375    {
     376        wxSizer* viewerButtonsLeft = new wxBoxSizer(wxVERTICAL);
     377        viewerButtonsLeft->SetMinSize(110, -1);
     378        viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerWireframe,   _("Wireframe")),      _("Toggle wireframe / solid rendering")), wxSizerFlags().Expand());
     379        viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerMove,        _("Move")),           _("Toggle movement along ground when playing walk/run animations")), wxSizerFlags().Expand());
     380        viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerGround,      _("Ground")),         _("Toggle the ground plane")), wxSizerFlags().Expand());
     381        viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerShadows,     _("Shadows")),        _("Toggle shadow rendering")), wxSizerFlags().Expand());
     382        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());
     383        viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerBoundingBox, _("Bounding Boxes")), _("Toggle bounding boxes")), wxSizerFlags().Expand());
     384
     385        wxSizer* viewerButtonsRight = new wxBoxSizer(wxVERTICAL);
     386        viewerButtonsRight->SetMinSize(110,-1);
     387        viewerButtonsRight->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerAxesMarker,  _("Axes Marker")),    _("Toggle the axes marker (R=X, G=Y, B=Z)")), wxSizerFlags().Expand());
     388
     389        viewerButtonsSizer->Add(viewerButtonsLeft, wxSizerFlags().Expand());
     390        viewerButtonsSizer->Add(viewerButtonsRight, wxSizerFlags().Expand());
     391    }
     392
    337393    viewerSizer->Add(viewerButtonsSizer, wxSizerFlags().Expand());
     394    viewerSizer->AddSpacer(3);
     395
     396    // --- animations panel -------------------------------------------------------------------------------
    338397
    339398    wxSizer* viewerAnimSizer = new wxStaticBoxSizer(wxVERTICAL, m_ViewerPanel, _("Animation"));
    340399
    ObjectBottomBar::ObjectBottomBar(wxWindow* parent, ScenarioEditor& scenarioEdito  
    357416
    358417    viewerSizer->Add(viewerAnimSizer, wxSizerFlags().Expand());
    359418
     419    // --- add viewer-specific options -------------------------------------------------------------------------------
     420
    360421    m_ViewerPanel->SetSizer(viewerSizer);
    361     sizer->Add(m_ViewerPanel, wxSizerFlags().Expand());
     422    mainSizer->Add(m_ViewerPanel, wxSizerFlags().Expand());
    362423
     424    m_ViewerPanel->Layout(); // prevents strange visibility glitch of the animation buttons on my machine (Vista 32-bit SP1) -- vtsj
    363425    m_ViewerPanel->Show(false);
    364426
     427    // --- add player/variation selection -------------------------------------------------------------------------------
    365428
     429    wxSizer* playerSelectionSizer = new wxBoxSizer(wxHORIZONTAL);
    366430    wxSizer* playerVariationSizer = new wxBoxSizer(wxVERTICAL);
    367431
    368432    // TODO: make this a wxChoice instead
    369433    wxComboBox* playerSelect = new PlayerComboBox(this, objectSettings, mapSettings);
    370     playerVariationSizer->Add(playerSelect);
     434    playerSelectionSizer->Add(new wxStaticText(this, wxID_ANY, _("Player:")), wxSizerFlags().Align(wxALIGN_CENTER));
     435    playerSelectionSizer->AddSpacer(3);
     436    playerSelectionSizer->Add(playerSelect);
     437
     438    playerVariationSizer->Add(playerSelectionSizer);
     439    playerVariationSizer->AddSpacer(3);
     440
    371441
    372442    wxWindow* variationSelect = new VariationControl(this, objectSettings);
    373443    variationSelect->SetMinSize(wxSize(160, -1));
    ObjectBottomBar::ObjectBottomBar(wxWindow* parent, ScenarioEditor& scenarioEdito  
    375445    variationSizer->Add(variationSelect, wxSizerFlags().Proportion(1).Expand());
    376446    playerVariationSizer->Add(variationSizer, wxSizerFlags().Proportion(1));
    377447
    378     sizer->Add(playerVariationSizer, wxSizerFlags().Expand());
     448    mainSizer->AddSpacer(3);
     449    mainSizer->Add(playerVariationSizer, wxSizerFlags().Expand());
     450
     451    // ----------------------------------------------------------------------------------
    379452
    380     SetSizer(sizer);
     453    SetSizer(mainSizer);
    381454}
    382455
    383456void ObjectBottomBar::OnFirstDisplay()
    void ObjectBottomBar::OnFirstDisplay()  
    402475    POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"ground", m_ViewerGround));
    403476    POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"shadows", m_ViewerShadows));
    404477    POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"stats", m_ViewerPolyCount));
     478    POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"bounding_box", m_ViewerBoundingBox));
    405479}
    406480
    407481void ObjectBottomBar::ShowActorViewer(bool show)
    void ObjectBottomBar::OnViewerSetting(wxCommandEvent& evt)  
    434508        m_ViewerPolyCount = !m_ViewerPolyCount;
    435509        POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"stats", m_ViewerPolyCount));
    436510        break;
     511    case ID_ViewerBoundingBox:
     512        m_ViewerBoundingBox = !m_ViewerBoundingBox;
     513        POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"bounding_box", m_ViewerBoundingBox));
     514        break;
     515    case ID_ViewerAxesMarker:
     516        m_ViewerAxesMarker = !m_ViewerAxesMarker;
     517        POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"axes_marker", m_ViewerAxesMarker));
     518        break;
    437519    }
    438520}
    439521
    BEGIN_EVENT_TABLE(ObjectBottomBar, wxPanel)  
    464546    EVT_BUTTON(ID_ViewerPlay, ObjectBottomBar::OnSpeed)
    465547    EVT_BUTTON(ID_ViewerPause, ObjectBottomBar::OnSpeed)
    466548    EVT_BUTTON(ID_ViewerSlow, ObjectBottomBar::OnSpeed)
     549    EVT_BUTTON(ID_ViewerBoundingBox, ObjectBottomBar::OnViewerSetting)
     550    EVT_BUTTON(ID_ViewerAxesMarker, ObjectBottomBar::OnViewerSetting)
    467551END_EVENT_TABLE();
  • source/tools/atlas/GameInterface/ActorViewer.cpp

    diff --git a/source/tools/atlas/GameInterface/ActorViewer.cpp b/source/tools/atlas/GameInterface/ActorViewer.cpp
    index 0a010aa..3c05af2 100644
    a b  
    3333#include "graphics/TerrainTextureManager.h"
    3434#include "graphics/TerritoryTexture.h"
    3535#include "graphics/UnitManager.h"
     36#include "graphics/Overlay.h"
    3637#include "maths/MathUtil.h"
    3738#include "ps/Font.h"
    3839#include "ps/GameSetup/Config.h"
     
    4849#include "simulation2/components/ICmpTerrain.h"
    4950#include "simulation2/components/ICmpUnitMotion.h"
    5051#include "simulation2/components/ICmpVisual.h"
     52#include "simulation2/helpers/Render.h"
    5153
    5254struct ActorViewerImpl : public Scene
    5355{
    public:  
    7577    bool WalkEnabled;
    7678    bool GroundEnabled;
    7779    bool ShadowsEnabled;
     80    bool SelectionBoxEnabled;
     81    bool AxesMarkerEnabled;
    7882
    7983    SColor4ub Background;
    8084   
    public:  
    8993    CLOSTexture LOSTexture;
    9094    CTerritoryTexture TerritoryTexture;
    9195
     96    SOverlayLine SelectionBoxOverlay;
     97    SOverlayLine AxesMarkerOverlays[3];
     98
    9299    // Simplistic implementation of the Scene interface
    93100    virtual void EnumerateObjects(const CFrustum& frustum, SceneCollector* c)
    94101    {
    public:  
    99106                    c->Submit(Terrain.GetPatch(pi, pj));
    100107        }
    101108
     109        CmpPtr<ICmpVisual> cmpVisual(Simulation2, Entity);
     110
     111        // add selection box outlines manually
     112        if (SelectionBoxEnabled && !cmpVisual.null())
     113        {
     114            SelectionBoxOverlay.m_Color = CColor(35/255.f, 86/255.f, 188/255.f, .75f); // pretty blue
     115            SelectionBoxOverlay.m_Thickness = 2;
     116
     117            SimRender::ConstructBoxOutline(cmpVisual->GetSelectionBox(), SelectionBoxOverlay);
     118            c->Submit(&SelectionBoxOverlay);
     119        }
     120
     121        // add origin axis thingy
     122        if (AxesMarkerEnabled && !cmpVisual.null())
     123        {
     124            AxesMarkerOverlays[0].m_Color = CColor(1, 0, 0, .5f); // X axis; red
     125            AxesMarkerOverlays[1].m_Color = CColor(0, 1, 0, .5f); // Y axis; green
     126            AxesMarkerOverlays[2].m_Color = CColor(0, 0, 1, .5f); // Z axis; blue
     127
     128            AxesMarkerOverlays[0].m_Thickness = 2;
     129            AxesMarkerOverlays[1].m_Thickness = 2;
     130            AxesMarkerOverlays[2].m_Thickness = 2;
     131
     132            AxesMarkerOverlays[0].m_Coords.clear();
     133            AxesMarkerOverlays[1].m_Coords.clear();
     134            AxesMarkerOverlays[2].m_Coords.clear();
     135
     136            CVector3D origin = cmpVisual->GetPosition() + CVector3D(0, 0.02f, 0); // offset from the ground a little bit to prevent fighting with the floor texture
     137            AxesMarkerOverlays[0].PushCoords(origin);
     138            AxesMarkerOverlays[1].PushCoords(origin);
     139            AxesMarkerOverlays[2].PushCoords(origin);
     140
     141            AxesMarkerOverlays[0].PushCoords(origin + CVector3D(1, 0, 0));
     142            AxesMarkerOverlays[1].PushCoords(origin + CVector3D(0, 1, 0));
     143            AxesMarkerOverlays[2].PushCoords(origin + CVector3D(0, 0, 1));
     144
     145            c->Submit(&AxesMarkerOverlays[0]);
     146            c->Submit(&AxesMarkerOverlays[1]);
     147            c->Submit(&AxesMarkerOverlays[2]);
     148        }
     149
     150        // send a RenderSubmit message so the components can submit their visuals to the renderer
    102151        Simulation2.RenderSubmit(*c, frustum, false);
    103152    }
    104153
    public:  
    114163};
    115164
    116165ActorViewer::ActorViewer()
    117 : m(*new ActorViewerImpl())
     166    : m(*new ActorViewerImpl())
    118167{
    119168    m.WalkEnabled = false;
    120169    m.GroundEnabled = true;
    121170    m.ShadowsEnabled = g_Renderer.GetOptionBool(CRenderer::OPT_SHADOWS);
     171    m.SelectionBoxEnabled = false;
     172    m.AxesMarkerEnabled = false;
    122173    m.Background = SColor4ub(0, 0, 0, 255);
    123174
    124175    // Create a tiny empty piece of terrain, just so we can put shadows
    void ActorViewer::SetBackgroundColour(const SColor4ub& colour)  
    304355void ActorViewer::SetWalkEnabled(bool enabled)    { m.WalkEnabled = enabled; }
    305356void ActorViewer::SetGroundEnabled(bool enabled)  { m.GroundEnabled = enabled; }
    306357void ActorViewer::SetShadowsEnabled(bool enabled) { m.ShadowsEnabled = enabled; }
     358void ActorViewer::SetBoundingBoxesEnabled(bool enabled) { m.SelectionBoxEnabled = enabled; }
     359void ActorViewer::SetAxesMarkerEnabled(bool enabled)    { m.AxesMarkerEnabled = enabled; }
    307360
    308361void ActorViewer::SetStatsEnabled(bool enabled)
    309362{
  • source/tools/atlas/GameInterface/ActorViewer.h

    diff --git a/source/tools/atlas/GameInterface/ActorViewer.h b/source/tools/atlas/GameInterface/ActorViewer.h
    index 7297260..365e4e0 100644
    a b public:  
    4141    void SetGroundEnabled(bool enabled);
    4242    void SetShadowsEnabled(bool enabled);
    4343    void SetStatsEnabled(bool enabled);
     44    void SetBoundingBoxesEnabled(bool enabled);
     45    void SetAxesMarkerEnabled(bool enabled);
    4446    void Render();
    4547    void Update(float dt);
    4648
  • source/tools/atlas/GameInterface/View.cpp

    diff --git a/source/tools/atlas/GameInterface/View.cpp b/source/tools/atlas/GameInterface/View.cpp
    index 03a71a6..55841da 100644
    a b void ViewActor::SetParam(const std::wstring& name, bool value)  
    132132        m_ActorViewer->SetShadowsEnabled(value);
    133133    else if (name == L"stats")
    134134        m_ActorViewer->SetStatsEnabled(value);
     135    else if (name == L"bounding_box")
     136        m_ActorViewer->SetBoundingBoxesEnabled(value);
     137    else if (name == L"axes_marker")
     138        m_ActorViewer->SetAxesMarkerEnabled(value);
    135139}
    136140
    137141void ViewActor::SetParam(const std::wstring& name, const AtlasMessage::Colour& value)