Okay, take a look at this. This code is stand alone.
main.cpp
Code: Select all
#include <irrlicht.h>
#include "CScreenSpaceQuadsNode.h"
using namespace irr;
#define MAX_NODES 7
int main(int argc, char* argv[])
{
irr::SIrrlichtCreationParameters prm;
prm.DriverType = video::EDT_OPENGL;
prm.WindowSize = core::dimension2du(1280, 720);
IrrlichtDevice* device = createDeviceEx(prm);
//No device? UPS!
if (!device)
return 1;
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* manager = device->getSceneManager();
manager->addCameraSceneNodeFPS();
//Create a sphere mesh
scene::IMesh* sphereMesh = manager->addSphereMesh("Sphere", 10.0f);
//Add the quad renderer...
scene::CScreenQuadsSceneNode* quads = new scene::CScreenQuadsSceneNode(manager->getRootSceneNode(), manager, -32);
//Add some nodes...
for (u32 j = 1; j < 4;++j)
for (u32 i = 0; i < MAX_NODES; ++i)
{
scene::IMeshSceneNode* node = manager->addMeshSceneNode(sphereMesh);
node->setPosition(core::vector3df(j*300 * sin(2 * core::PI*i / MAX_NODES), 0, j*300 * cos(2 * core::PI*i / MAX_NODES)));
node->setScale(core::vector3df(j,j,j));
node->getMaterial(0).DiffuseColor = video::SColor(0xFFFFFFFF);
node->getMaterial(0).NormalizeNormals = true;
quads->registerNode(node);
}
//Add a light
scene::ILightSceneNode* light = manager->addLightSceneNode(manager->getRootSceneNode(), core::vector3df(500, 500, 500), video::SColorf(1, 1, 1, 1), 1000.0f);
quads->drop();
quads = 0;
while (device->run())
{
driver->beginScene();
driver->draw2DRectangle(core::recti(0, 0, 1280, 720), video::SColor(0xFF00007F), video::SColor(0xFF00007F), video::SColor(0xFF007F7F), video::SColor(0xFF007F7F));
manager->drawAll();
driver->endScene();
}
device->drop();
return 0;
}
"CScreenSpaceQuadsNode.h"
Code: Select all
#ifndef _CSCREENQUADSSCENENODE_H_
#define _CSCREENQUADSSCENENODE_H_
#include <irrlicht.h>
#include <map>
//This is a scene node that will draw quads around registered scene nodes covering their screen space, also, you can retrieve the quads given a scene node via a irr::map
namespace irr
{
namespace scene
{
typedef std::map<ISceneNode*, core::rectf> nodeRectMap;
class CScreenQuadsSceneNode : public ISceneNode
{
//Nodes whose screenspace we're going to calculate
nodeRectMap nodes;
//Dummy box
core::aabbox3df box;
//Video driver
video::IVideoDriver* driver;
//Active camera
scene::ICameraSceneNode* camera;
//Current resolution
core::dimension2du resolution;
public:
//CTOR
CScreenQuadsSceneNode(ISceneNode* parent, ISceneManager* manager, s32 id);
//DTOR
~CScreenQuadsSceneNode();
//Register scene nodes
void registerNode(ISceneNode* node);
//Clears the node map
void clearNodes();
//Render method
void render();
//Get Bounding box. Nothing too fancy here
const core::aabbox3d<f32>& getBoundingBox() const;
//Get the quad belonging to a given scene node
core::rectf getScreenSpace(ISceneNode* node);
//Animates this node. Updates the screen quads positions but there is a frame delay... Is okay, produces a cool "tracking" like effect :)
void OnAnimate(u32 ms);
//Registers this node for rendering
void OnRegisterSceneNode();
private:
//Renders the rectangles
void renderQuad(core::rectf& rect);
//Calculates all the visible quads
void calculateScreenQuads();
//Caluculates a quad
core::rectf calculateQuad(ISceneNode* node, ICameraSceneNode* camera);
};
}
}
#endif
"CScreenSpaceQuadsNode.cpp"
Code: Select all
#include "CScreenSpaceQuadsNode.h"
namespace irr
{
namespace scene
{
//CTOR
CScreenQuadsSceneNode::CScreenQuadsSceneNode(ISceneNode* parent, ISceneManager* manager, s32 id)
:ISceneNode(parent,manager,id)
{
driver = getSceneManager()->getVideoDriver();
resolution = driver->getScreenSize();
printf("Node Tracker Ready for operation at %d x %d\n",resolution.Width,resolution.Height);
setAutomaticCulling(EAC_OFF);
}
//DTOR
CScreenQuadsSceneNode::~CScreenQuadsSceneNode()
{
nodes.clear();
}
//Register scene nodes
void CScreenQuadsSceneNode::registerNode(ISceneNode* node)
{
//Not that it wouldn't be possible to do it, but it is kinda pointless to do this to a potential screen sized render XD
if (node == this)
return;
if (node == nullptr)
return;
nodes[node] = core::rectf();
}
//Clears the node map
void CScreenQuadsSceneNode::clearNodes()
{
nodes.clear();
}
//Get the quad belonging to a given scene node. If not found, returns an empty rectangle.
core::rectf CScreenQuadsSceneNode::getScreenSpace(ISceneNode* node)
{
if (nodes.find(node) != nodes.end())
return nodes[node];
else
return core::rectf();
}
//Render method
void CScreenQuadsSceneNode::render()
{
//nothing to do here :)
if (camera == nullptr)
return;
//No quads? no work
if (nodes.size() == 0)
return;
nodeRectMap::iterator it;
for (it = nodes.begin(); it != nodes.end(); ++it)
{
renderQuad((*it).second);
}
}
//Get Bounding box. Nothing too fancy here
const core::aabbox3d<f32>& CScreenQuadsSceneNode::getBoundingBox() const
{
return box;
}
//Animates this node. Updates the screen quads positions but there is a frame delay... Is okay, produces a cool "tracking" like effect :)
void CScreenQuadsSceneNode::OnAnimate(u32 ms)
{
//Is there any active camera? if not, it is very easy to know where to continue after this...
camera = getSceneManager()->getActiveCamera();
if (camera == nullptr)
{
ISceneNode::OnAnimate(ms);
return;
}
//Update all the rectangles
//Using the standard map which implements iterative access
nodeRectMap::iterator it;
for (it = nodes.begin(); it != nodes.end(); ++it)
(*it).second = calculateQuad((*it).first, camera);
ISceneNode::OnAnimate(ms);
}
//Renders a quad
void CScreenQuadsSceneNode::renderQuad(core::rectf& rect)
{
core::recti r = core::recti(rect.UpperLeftCorner.X, rect.UpperLeftCorner.Y, rect.LowerRightCorner.X, rect.LowerRightCorner.Y);
u8 red, green;
red = pow(abs((rect.getArea() / resolution.getArea())),1.0f/6.0f) * 255; //gets redder the bigger the quad is on screen ;) hint for LOD? :P
green = 255 - red;
driver->draw2DRectangleOutline(r, video::SColor(255, red, green, 0));
}
//Calculates the bounding quad
core::rectf CScreenQuadsSceneNode::calculateQuad(ISceneNode* node, ICameraSceneNode* camera)
{
const scene::SViewFrustum* fr = camera->getViewFrustum();
core::aabbox3df box, viewBox; //Necesary to have a box in view space and then in projection space
core::matrix4 view, proj;//Necesary to obtain the box in view space and then in projection space
f32 near, far;
f32 vec[4]; //Irr won't do the homogeinization step for us... in any form! O.o
//Get the node's aabb.
box = node->getTransformedBoundingBox();
//If the node is invisible, it has no screen space
if (!node->isVisible())
return core::rectf();
//if the node is completely outside the view frustum, it has no screen space
bool outside = false;
for (u32 i = 0; i < 6 && !outside; ++i)
outside = box.classifyPlaneRelation(fr->planes[i]) == core::ISREL3D_FRONT;
if (outside)
return core::rectf();
//At this point the box is either completely inside the view volume, or clipped somewhere, in any case, it is worth the attempt.
core::array<core::vector3df> points;//Box corners
points.reallocate(8);
node->getTransformedBoundingBoxEdges(points);
//Get the AABB in view space.
view = camera->getViewMatrix();
//Transform into view space
view.transformVect(vec, points[0]);
viewBox.reset(vec[0] / vec[3], vec[1] / vec[3], vec[2] / vec[3]);
for (u32 i = 1; i < 8; ++i)
{
view.transformVect(vec, points[i]);
viewBox.addInternalPoint(vec[0] / vec[3], vec[1] / vec[3], vec[2] / vec[3]);
}
//Viewbox is aligned to the Z near and far planes
near = camera->getNearValue();
far = camera->getFarValue();
//Clamp to minimum view Z
if (viewBox.MinEdge.Z < near)
viewBox.MinEdge.Z = near;
//Clamp to maximum view Z
if (viewBox.MaxEdge.Z > far)
viewBox.MaxEdge.Z = far;
//Copy the points again so we can transform them into projection space
viewBox.getEdges(points.pointer());
proj = camera->getProjectionMatrix();
//Transform into projection space
proj.transformVect(vec, points[0]);
box.reset(vec[0] / vec[3], vec[1] / vec[3], vec[2] / vec[3]);
for (u32 i = 1; i < 8; ++i)
{
proj.transformVect(vec, points[i]);
box.addInternalPoint(vec[0] / vec[3], vec[1] / vec[3], vec[2] / vec[3]);
}
//The box is in projection space, clamp it to -1,1
if (box.MinEdge.X < -1)
box.MinEdge.X = -1;
if (box.MaxEdge.X > 1)
box.MaxEdge.X = 1;
//Clamp the Y coordinate...
if (box.MinEdge.Y < -1)
box.MinEdge.Y = -1;
if (box.MaxEdge.Y > 1)
box.MaxEdge.Y = 1;
//Transform into 0-range and swap Y to be in screen coordinates
box.MinEdge.X = box.MinEdge.X*0.5f + 0.5f;
box.MaxEdge.X = box.MaxEdge.X*0.5f + 0.5f;
box.MinEdge.Y = -box.MinEdge.Y*0.5f + 0.5f;
box.MaxEdge.Y = -box.MaxEdge.Y*0.5f + 0.5f;
//Return this box in screen coordinates
return core::rectf(box.MinEdge.X*resolution.Width, box.MinEdge.Y*resolution.Height, box.MaxEdge.X*resolution.Width, box.MaxEdge.Y*resolution.Height);
}
//Registers this node for rendering
void CScreenQuadsSceneNode::OnRegisterSceneNode()
{
//Register in the last pass of the render
getSceneManager()->registerNodeForRendering(this, ESNRP_TRANSPARENT_EFFECT);
ISceneNode::OnRegisterSceneNode();
}
}
}
This should provide the following effect:
Which is a bit conservative in the side that it is always larger than the actual bounding box, but it is because it is the bounding box of the viewspace bounding volume. I hope you can test it for yourselves! and if you find it useful, go ahead and use it
Just in case you want to use it for lights... Irr won't calculate the lights bounding volume, but it can calculate the screen space of ANY SCENE NODE that has a bounding box, which is every of them, as the scene nodes have in their interface a "getBoundingBox" method
I've intentionally made the tint of the quad become redder the bigger it is on the screen
(uses the area to calculate it) So using the quad area / screen area proportion you may have a cabal idea of how large is a node compared to the screen. And it works both in GL and DirectX alike.