EditorGridSystem

From Ogre Wiki

Jump to: navigation, search

Contents

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 here.

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.


Updates

  • (2010-02-24) Changed the license to MIT (copied from OGRE)


Source Code

ViewportGrid.h

/*
-----------------------------------------------------------------------------
This source file is supposed to be used with OGRE
(Object-oriented Graphics Rendering Engine)
For the latest info, see http://www.ogre3d.org/

Copyright (c) 2007 Jeroen Dierckx

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-----------------------------------------------------------------------------
*/
#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

ViewportGrid.cpp

/*
-----------------------------------------------------------------------------
This source file is supposed to be used with OGRE
(Object-oriented Graphics Rendering Engine)
For the latest info, see http://www.ogre3d.org/

Copyright (c) 2007 Jeroen Dierckx

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-----------------------------------------------------------------------------
*/
// 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;
}
Personal tools
administration