Skip to main content

History: EditorGridSystem

Source of version: 3

Copy to clipboard
            !!Introduction
I have created a dynamic grid system to use in our Ogre-based world editor. Because Ogre is such a great engine and has such a great community, we decided to give this piece of code to the community. You can find the original forum post [http://www.ogre3d.org/forums/viewtopic.php?t=35859|here].

{maketoc}

!!!Orthographic View
When using an orthographic camera (top, left, right, ...), the grid system has the following features:
* Grid is automatically updated when necessary (and only when necessary)
* Grid lines are automatically inserted / removed when the pixel spacing between the lines reaches a certain treshold
* Major grid lines (full alpha) and minor grid lines (alpha-faded according to their pixel spacing)
* Options: grid color, division (in how many lines a grid is divided when zooming in), render layer (behind or in front of all objects)



!!!Perspective Cameras
When using a perspective camera, we use a simple 3D grid for now.

!!!TODO
* Finish support for an arbitrary orthographic camera direction (what to do with axes lines, ...)
* Implement dynamic splitting and merging of grid cells in perspective view (based on what?)
* Implement mini-axes rendering (so you can easily see the camera rotation)
* Implement mini-scale rendering (like on a road map, to easily see unit sizes)
* Support for multiple grids?
* Add example code to move objects over one axis or a two-axes plane, so the object stays under the mouse cursor in screen space

!!Usage Information
To use this, you just create an instance of the class, providing it with an ogre scene manager and viewport. Then you just enable it (ViewportGrid::enable and such).

You can set some options:
* __grid colour__: the color of the grid lines. Minor grid lines are alpha-faded according to their pixel spacing. Zero coordinate grid lines automatically get the color of the orthogonal axis, if the camera is pointed along one of these (red for X, green for Y, blue for Z).
* __Division level__: this determines when major grid lines are drawn (defaults to 10). It also has an influence on when grid lines are split or merged (they are split into 'division' new lines when zooming in, and 'division' lines are merged when zooming out)
* __Render layer__: you can determine where the grid is drawn in orthographic view: behind or in front of all objects. Defaults to behind, and ignored in perspective view (there, the grid is an actual 3d grid on the XZ-plane)
* __Perspective size__: the size (width and height) of the grid in perspective mode. Defaults to 100 units.
* __Scale rendering__ (not implemented yet): set to true if you want scaling info to be rendered on an overlay (like on maps, so you can see how many units one grid is)
* __Mini axes rendering__ (not implemented yet): set to to true if you want mini-axes to be rendered in an overlay (so you can easily see the direction of your camera, even when the zero-gridlines aren't visible)



It just takes information about the camera attached to the viewport to calculate the grid lines. The actual movement of the camera is up to you. In orthographic view, we always start with the camera positioned 10000 units from the origin, use a fixed FOV, and change the near clipping plane to zoom in/out. Panning is done by moving the camera in its up/right plane.

!!Source Code
!!!ViewportGrid.h
{CODE(wrap="1", colors="c++")}/*
-----------------------------------------------------------------------------
Copyright (c) 2007 Jeroen Dierckx

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA, or go to
http://www.gnu.org/copyleft/lesser.txt.
-----------------------------------------------------------------------------
*/
#ifndef OGRE_VIEWPORTGRID_H
#define OGRE_VIEWPORTGRID_H

// Includes
#include <Ogre/OgreRenderTargetlistener.h>
#include <Ogre/OgreColourValue.h>
#include <Ogre/OgreMath.h>
#include <Ogre/OgreVector3.h>

namespace Ogre
{
    /** Use this class to render a dynamically adjusting grid inside an Ogre viewport.
        @todo
        Make the grid work with an arbitrary rotated orthogonal camera (e.g. for working in object space mode).
        Or should the grid be rendered in object space too then?
    */
    class ViewportGrid: public RenderTargetListener
    {
    public:
        //! The render layer of the grid in orthogonal view
        enum RenderLayer
        {
            RL_BEHIND, //!< Behind all objects
            RL_INFRONT //!< In front of all objects
        };

    protected:
        // Member variables
        SceneManager *m_pSceneMgr;
        Viewport *m_pViewport;
        bool m_enabled;
        RenderLayer m_layer;

        Camera *m_pPrevCamera;
        bool m_prevOrtho;
        Vector3 m_prevCamPos;
        Real m_prevNear;
        Radian m_prevFOVy;
        Real m_prevAspectRatio;
        bool m_forceUpdate;

        ManualObject *m_pGrid;
        bool m_created;
        SceneNode *m_pNode;

        ColourValue m_colour1, m_colour2;
        unsigned int m_division;
        Real m_perspSize;
        bool m_renderScale, m_renderMiniAxes;

    public:
        // Constructors and destructor
        ViewportGrid(SceneManager *pSceneMgr, Viewport *pViewport);
        virtual ~ViewportGrid();

        //! Grid colour
        const ColourValue &getColour() const { return m_colour1; }
        void setColour(const ColourValue &colour);

        //! Grid division (the number of new lines that are created when zooming in).
        unsigned int getDivision() const { return m_division; }
        void setDivision(unsigned int division);

        //! Grid render layer (behind of in front of the objects).
        RenderLayer getRenderLayer() const { return m_layer; }
        void setRenderLayer(RenderLayer layer);

        //! Size of the grid in perspective view
        Real getPerspectiveSize() const { return m_perspSize; }
        void setPerspectiveSize(Real size);

        //! Render scaling info? Defaults to true.
        //! @todo Implement this
        bool getRenderScale() const { return m_renderScale; }
        void setRenderScale(bool enabled = true);

        //! Render mini axes? Defaults to true.
        //! @todo Implement this
        bool getRenderMiniAxes() const { return m_renderMiniAxes; }
        void setRenderMiniAxes(bool enabled = true);

        // Enable / disable
        bool isEnabled() const;
        void enable();
        void disable();
        void toggle();
        void setEnabled(bool enabled);

        // Other functions
        void forceUpdate() { m_forceUpdate = true; }

    protected:
        // RenderTargetListener
        void preViewportUpdate(const RenderTargetViewportEvent &evt);
        void postViewportUpdate(const RenderTargetViewportEvent &evt);

        // Other protected functions
        void createGrid();
        void destroyGrid();

        void update();
        void updateOrtho();
        void updatePersp();

        bool checkUpdate();
        bool checkUpdateOrtho();
        bool checkUpdatePersp();
    };
}

#endif // OGRE_VIEWPORTGRID_H{CODE}

!!!ViewportGrid.cpp
{CODE(wrap="1", colors="c++")}/*
-----------------------------------------------------------------------------
Copyright (c) 2007 Jeroen Dierckx

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA, or go to
http://www.gnu.org/copyleft/lesser.txt.
-----------------------------------------------------------------------------
*/
// Includes
#include "ViewportGrid.h"
#include <Ogre/OgreManualObject.h>
#include <Ogre/OgreViewport.h>
#include <Ogre/OgreRenderTarget.h>
#include <Ogre/OgreSceneManager.h>
#include <Ogre/OgreStringConverter.h>
#include <Ogre/OgreMaterialManager.h>
using namespace Ogre;

// Constants
static const String sMatName = "ViewportGrid";

/******************************
* Constructors and destructor *
******************************/

ViewportGrid::ViewportGrid(SceneManager *pSceneMgr, Viewport *pViewport)
: m_pSceneMgr(pSceneMgr), m_pViewport(pViewport), m_enabled(false)
, m_pPrevCamera(0), m_prevOrtho(false), m_prevNear(0), m_prevFOVy(0), m_prevAspectRatio(0), m_forceUpdate(true)
, m_pGrid(0), m_created(false), m_pNode(0)
, m_colour1(0.7, 0.7, 0.7), m_colour2(0.7, 0.7, 0.7), m_division(10), m_perspSize(100)
, m_renderScale(true), m_renderMiniAxes(true)
{
    assert(m_pSceneMgr);
    assert(m_pViewport);

    createGrid();
    setRenderLayer(RL_BEHIND);

    // Add this as a render target listener
    m_pViewport->getTarget()->addListener(this);
}

ViewportGrid::~ViewportGrid()
{
    // Remove this as a render target listener
    m_pViewport->getTarget()->removeListener(this);

    destroyGrid();
}

/************************
* Get and set functions *
************************/

/** Sets the colour of the major grid lines (the minor lines are alpha-faded out/in when zooming out/in)
    @note The alpha value is automatically set to one
*/
void ViewportGrid::setColour(const ColourValue &colour)
{
    // Force alpha = 1 for the primary colour
    m_colour1 = colour; m_colour1.a = 1.0f;
    m_colour2 = m_colour1;
    forceUpdate();
}

/** Sets in how many lines a grid has to be divided when zoomed in.
    Defaults to 10.
*/
void ViewportGrid::setDivision(unsigned int division)
{
    m_division = division;
    forceUpdate();
}

/** Sets the render layer of the grid
    @note Ignored in perspective view.
    @see Ogre::ViewportGrid::RenderLayer
*/
void ViewportGrid::setRenderLayer(RenderLayer layer)
{
    m_layer = layer;

    switch(m_layer)
    {
    default:
    case RL_BEHIND:
        // Render just before the world geometry
        m_pGrid->setRenderQueueGroup(RENDER_QUEUE_WORLD_GEOMETRY_1 - 1);
        break;

    case RL_INFRONT:
        // Render just before the overlays
        m_pGrid->setRenderQueueGroup(RENDER_QUEUE_OVERLAY - 1);
        break;
    }
}

/** Sets the size of the grid in perspective view.
    Defaults to 100 units.
    @note Ignored in orthographic view.
*/
void ViewportGrid::setPerspectiveSize(Real size)
{
    m_perspSize = size;
    forceUpdate();
}

/** Sets whether to render scaling info in an overlay.
    This looks a bit like the typical scaling info on a map.
*/
void ViewportGrid::setRenderScale(bool enabled)
{
    m_renderScale = enabled;
    forceUpdate();
}

/** Sets whether to render mini-axes in an overlay.
*/
void ViewportGrid::setRenderMiniAxes(bool enabled)
{
    m_renderMiniAxes = enabled;
    forceUpdate();
}

/******************
* Other functions *
******************/

bool ViewportGrid::isEnabled() const
{
    return m_enabled;
}

void ViewportGrid::enable()
{
    m_enabled = true;

    if(!m_pGrid->isAttached())
        m_pNode->attachObject(m_pGrid);

    forceUpdate();
}

void ViewportGrid::disable()
{
    m_enabled = false;

    if(m_pGrid->isAttached())
        m_pNode->detachObject(m_pGrid);
}

void ViewportGrid::toggle()
{
    setEnabled(!m_enabled);
}

void ViewportGrid::setEnabled(bool enabled)
{
    if(enabled)
        enable();
    else
        disable();
}

/***********************
* RenderTargetListener *
***********************/

void ViewportGrid::preViewportUpdate(const RenderTargetViewportEvent &evt)
{
    if(evt.source != m_pViewport) return;

    m_pGrid->setVisible(true);
    
    if(m_enabled)
        update();
}

void ViewportGrid::postViewportUpdate(const RenderTargetViewportEvent &evt)
{
    if(evt.source != m_pViewport) return;

    m_pGrid->setVisible(false);
}

/****************************
* Other protected functions *
****************************/

void ViewportGrid::createGrid()
{
    String name = m_pViewport->getTarget()->getName() + "::";
    name += StringConverter::toString(m_pViewport->getZOrder()) + "::ViewportGrid";

    // Create the manual object
    m_pGrid = m_pSceneMgr->createManualObject(name);
    m_pGrid->setDynamic(true);

    // Create the scene node (not attached yet)
    m_pNode = m_pSceneMgr->getRootSceneNode()->createChildSceneNode(name);
    m_enabled = false;

    // Make sure the material is created
    //! @todo Should we destroy the material somewhere?
    MaterialManager &matMgr = MaterialManager::getSingleton();
    if(!matMgr.resourceExists(sMatName))
    {
        MaterialPtr pMaterial = matMgr.create(sMatName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
        pMaterial->setLightingEnabled(false);
        pMaterial->setSceneBlending(SBT_TRANSPARENT_ALPHA);
    }
}

void ViewportGrid::destroyGrid()
{
    // Destroy the manual object
    m_pSceneMgr->destroyManualObject(m_pGrid);
    m_pGrid = 0;

    // Destroy the scene node
    m_pSceneMgr->destroySceneNode(m_pNode->getName());
    m_pNode = 0;
}

void ViewportGrid::update()
{
    if(!m_enabled) return;

    Camera *pCamera = m_pViewport->getCamera();
    if(!pCamera) return;

    // Check if an update is necessary
    if(!checkUpdate() && !m_forceUpdate)
        return;

    if(pCamera->getProjectionType() == PT_ORTHOGRAPHIC)
        updateOrtho();
    else
        updatePersp();

    m_forceUpdate = false;
}

void ViewportGrid::updateOrtho()
{
    // Screen dimensions
    int width = m_pViewport->getActualWidth();
    int height = m_pViewport->getActualHeight();

    // Camera information
    Camera *pCamera = m_pViewport->getCamera();
    const Vector3 &camPos = pCamera->getPosition();
    Vector3 camDir = pCamera->getDirection();
    Vector3 camUp = pCamera->getUp();
    Vector3 camRight = pCamera->getRight();

    // Translation in grid space
    Real dx = camPos.dotProduct(camRight);
    Real dy = camPos.dotProduct(camUp);

    // Frustum dimensions
    // Note: Tan calculates the opposite side of a _right_ triangle given its angle, so we make sure it is one, and double the result
    Real worldWidth = 2 * Math::Tan(pCamera->getFOVy() / 2) * pCamera->getAspectRatio() * pCamera->getNearClipDistance();
    Real worldHeight = worldWidth / pCamera->getAspectRatio();
    Real worldLeft = dx - worldWidth / 2;
    Real worldRight = dx + worldWidth / 2;
    Real worldBottom = dy - worldHeight / 2;
    Real worldTop = dy + worldHeight / 2;

    // Conversion values (note: same as working with the height values)
    Real worldToScreen = width / worldWidth;
    Real screenToWorld = worldWidth / width;

    //! @todo Treshold should be dependent on window width/height (min? max?) so there are no more then m_division full alpha-lines
    static const int treshold = 10; // Treshhold in pixels

    // Calculate the spacing multiplier
    Real mult = 0;
    int exp = 0;
    Real temp = worldToScreen; // 1 world unit
    if(worldToScreen < treshold)
    {
        while(temp < treshold)
        {
            ++exp;
            temp *= treshold;
        }
        
        mult = Math::Pow(m_division, exp);
    }
    else
    {
        while(temp > m_division * treshold)
        {
            ++exp;
            temp /= treshold;
        }

        mult = Math::Pow(1.0f / m_division, exp);
    }
    
    // Interpolate alpha for (multiplied) spacing between treshold and m_division * treshold
    m_colour2.a = worldToScreen * mult / (m_division * treshold - treshold);
    if(m_colour2.a > 1.0f) m_colour2.a = 1.0f;

    // Calculate the horizontal zero-axis color
    Real camRightX = Math::Abs(camRight.x);
    Real camRightY = Math::Abs(camRight.y);
    Real camRightZ = Math::Abs(camRight.z);
    const ColourValue &horAxisColor = Math::RealEqual(camRightX, 1.0f) ? ColourValue::Red
        : Math::RealEqual(camRightY, 1.0f) ? ColourValue::Green
        : Math::RealEqual(camRightZ, 1.0f) ? ColourValue::Blue : m_colour1;

    // Calculate the vertical zero-axis color
    Real camUpX = Math::Abs(camUp.x);
    Real camUpY = Math::Abs(camUp.y);
    Real camUpZ = Math::Abs(camUp.z);
    const ColourValue &vertAxisColor = Math::RealEqual(camUpX, 1.0f) ? ColourValue::Red
        : Math::RealEqual(camUpY, 1.0f) ? ColourValue::Green
        : Math::RealEqual(camUpZ, 1.0f) ? ColourValue::Blue : m_colour1;

    // The number of lines
    int numLinesWidth = (int) (worldWidth / mult) + 1;
    int numLinesHeight = (int) (worldHeight / mult) + 1;

    // Start creating or updating the grid
    m_pGrid->estimateVertexCount(2 * numLinesWidth + 2 * numLinesHeight);
    if(m_created)
        m_pGrid->beginUpdate(0);
    else
    {
        m_pGrid->begin(sMatName, Ogre::RenderOperation::OT_LINE_LIST);
        m_created = true;
    }

    // Vertical lines
    Real startX = mult * (int) (worldLeft / mult);
    Real x = startX;
    while(x <= worldRight)
    {
        // Get the right color for this line
        int multX = (x == 0) ? x : (x < 0) ? (int) (x / mult - 0.5f) : (int) (x / mult + 0.5f);
        const ColourValue &colour = (multX == 0) ? vertAxisColor : (multX % (int) m_division) ? m_colour2 : m_colour1;

        // Add the line
        m_pGrid->position(x, worldBottom, 0);
        m_pGrid->colour(colour);
        m_pGrid->position(x, worldTop, 0);
        m_pGrid->colour(colour);

        x += mult;
    }

    // Horizontal lines
    Real startY = mult * (int) (worldBottom / mult);
    Real y = startY;
    while(y <= worldTop)
    {
        // Get the right color for this line
        int multY = (y == 0) ? y : (y < 0) ? (int) (y / mult - 0.5f) : (int) (y / mult + 0.5f);
        const ColourValue &colour = (multY == 0) ? horAxisColor : (multY % (int) m_division) ? m_colour2 : m_colour1;

        // Add the line
        m_pGrid->position(worldLeft, y, 0);
        m_pGrid->colour(colour);
        m_pGrid->position(worldRight, y, 0);
        m_pGrid->colour(colour);

        y += mult;
    }

    m_pGrid->end();

    m_pNode->setOrientation(pCamera->getOrientation());
}

void ViewportGrid::updatePersp()
{
    //! @todo Calculate the spacing multiplier
    Real mult = 1;
    
    //! @todo Interpolate alpha
    m_colour2.a = 0.5f;
    //if(m_colour2.a > 1.0f) m_colour2.a = 1.0f;

    // Calculate the horizontal zero-axis color
    const ColourValue &horAxisColor = ColourValue::Red;

    // Calculate the vertical zero-axis color
    const ColourValue &vertAxisColor = ColourValue::Blue;

    // The number of lines
    int numLines = (int) (m_perspSize / mult) + 1;

    // Start creating or updating the grid
    m_pGrid->estimateVertexCount(4 * numLines);
    if(m_created)
        m_pGrid->beginUpdate(0);
    else
    {
        m_pGrid->begin(sMatName, Ogre::RenderOperation::OT_LINE_LIST);
        m_created = true;
    }

    // Vertical lines
    Real start = mult * (int) (-m_perspSize / 2 / mult);
    Real x = start;
    while(x <= m_perspSize / 2)
    {
        // Get the right color for this line
        int multX = (x == 0) ? x : (x < 0) ? (int) (x / mult - 0.5f) : (int) (x / mult + 0.5f);
        const ColourValue &colour = (multX == 0) ? vertAxisColor : (multX % (int) m_division) ? m_colour2 : m_colour1;

        // Add the line
        m_pGrid->position(x, 0, -m_perspSize / 2);
        m_pGrid->colour(colour);
        m_pGrid->position(x, 0, m_perspSize / 2);
        m_pGrid->colour(colour);

        x += mult;
    }

    // Horizontal lines
    Real y = start;
    while(y <= m_perspSize / 2)
    {
        // Get the right color for this line
        int multY = (y == 0) ? y : (y < 0) ? (int) (y / mult - 0.5f) : (int) (y / mult + 0.5f);
        const ColourValue &colour = (multY == 0) ? horAxisColor : (multY % (int) m_division) ? m_colour2 : m_colour1;

        // Add the line
        m_pGrid->position(-m_perspSize / 2, 0, y);
        m_pGrid->colour(colour);
        m_pGrid->position(m_perspSize / 2, 0, y);
        m_pGrid->colour(colour);

        y += mult;
    }

    m_pGrid->end();

    // Normal orientation, grid in the X-Z plane
    m_pNode->resetOrientation();
}

/* Checks if an update is necessary */
bool ViewportGrid::checkUpdate()
{
    bool update = false;

    Camera *pCamera = m_pViewport->getCamera();
    if(!pCamera) return false;

    if(pCamera != m_pPrevCamera)
    {
        m_pPrevCamera = pCamera;
        update = true;
    }

    bool ortho = (pCamera->getProjectionType() == PT_ORTHOGRAPHIC);
    if(ortho != m_prevOrtho)
    {
        m_prevOrtho = ortho;
        update = true;

        // Set correct material properties
        MaterialPtr pMaterial = MaterialManager::getSingleton().getByName(sMatName);
        if(!pMaterial.isNull())
        {
            pMaterial->setDepthWriteEnabled(!ortho);
            pMaterial->setDepthCheckEnabled(!ortho);
        }
    }

    return update || ortho ? checkUpdateOrtho() : checkUpdatePersp();
}

bool ViewportGrid::checkUpdateOrtho()
{
    bool update = false;

    Camera *pCamera = m_pViewport->getCamera();
    if(!pCamera) return false;

    if(pCamera->getPosition() != m_prevCamPos)
    {
        m_prevCamPos = pCamera->getPosition();
        update = true;
    }
    
    if(pCamera->getNearClipDistance() != m_prevNear)
    {
        m_prevNear = pCamera->getNearClipDistance();
        update = true;
    }

    if(pCamera->getFOVy() != m_prevFOVy)
    {
        m_prevFOVy = pCamera->getFOVy();
        update = true;
    }

    if(pCamera->getAspectRatio() != m_prevAspectRatio)
    {
        m_prevAspectRatio = pCamera->getAspectRatio();
        update = true;
    }

    return update;
}

bool ViewportGrid::checkUpdatePersp()
{
    //! @todo Add a check if grid line splitting/merging is implemented
    return false;
}{CODE}


        

History

Information Version
Fri 28 of Sep, 2012 13:28 GMT-0000 JeDi 6
Fri 28 of Sep, 2012 13:21 GMT-0000 JeDi Made the source code collapsable 5
Sat 01 of May, 2010 01:40 GMT-0000 jacmoe 4
Sat 02 of Jan, 2010 03:10 GMT-0000 jacmoe 3
Tue 22 of Dec, 2009 01:22 GMT-0000 jacmoe 2
Fri 09 of Oct, 2009 15:18 GMT-0000 OgreWikiBot Robot: Cosmetic changes 1