Thanks for the nice implementation Klasker!
I made a few changes and added an imposter billboard creation method, which works pretty well for my project.
I thought i'd share it, here it is.
Code: Select all
/*
Written by Asger Feldthaus
February 2007
*/
#ifndef _C_TREE_SCENE_NODE_H_
#define _C_TREE_SCENE_NODE_H_
#include <ISceneNode.h>
#include "CBillboardGroupSceneNode.h"
#include "CTreeGenerator.h"
enum PROCEDURAL_TREE_TYPE {
LE_PT_ASPEN, LE_PT_OAK, LE_PT_PINE, LE_PT_WILLOW
};
namespace irr {
namespace scene {
#ifdef MAKE_IRR_ID
const int TREE_SCENE_NODE_ID = MAKE_IRR_ID('t','r','e','e');
#else
const int TREE_SCENE_NODE_ID = 'tree';
#endif // MAKE_IRR_ID
/*!
\brief A tree with three levels of detail.
CTreeSceneNode is a tree with three levels of detail. The highest LOD is the mesh with full detail. The second LOD is another mesh with fewer polygons.
The last LOD is a billboard. The leaves are displayed in the two first levels of detail.
Call setup() to initialize the scene node.
*/
class CTreeSceneNode: public ISceneNode {
public:
//! Standard constructor for scene nodes. Call setup() to initialize the scene node.
CTreeSceneNode(f32 midRange, f32 farRange, ISceneNode* parent, ISceneManager* manager, s32 id = -1, const core::vector3df& position = core::vector3df(0, 0, 0), const core::vector3df& rotation = core::vector3df(0, 0, 0), const core::vector3df& scale = core::vector3df(1, 1, 1));
virtual ~CTreeSceneNode();
//! Sets the meshes used by the scene node, the leaf node used and the billboard texture.
//! \param highLod: The mesh to use when the tree is close to the camera. Must not be 0!
//! \param midLod: The mesh to use when the camera is at mediocre distance. If 0 highLod is used instead.
//! \param leafNode: The scene node displaying the leaves on the tree. If 0 no leaves will be displayed.
//! \param billboardTexture: The texture to display on the billboard when the tree is very far from the camera. If 0 it will not turn into a billboard.
void setup(const STreeMesh* highLOD, const STreeMesh* midLOD, video::ITexture* billboardTexture = 0);
//! Generates meshes and leaves for the scene node automatically, and specifies the billboard texture.
//! \param seed: The random number seed used to generate the tree.
//! \param billboardTexture: The texture to display on the billboard when the tree is very far from the camera. If 0 it will not turn into a billboard.
void setup(CTreeGenerator* generator, s32 seed = 0, video::ITexture* billboardTexture = 0);
//! Specifies where the tree should switch to medium LOD, and where it should switch to a billboard.
//! \param midRange: If the camera is closer than this, it will use high LOD. If it is greater than this, but lower than farRange, it will use medium LOD.
//! \param farRange: If the camera is further away than this, a billboard will be displayed instead of the tree.
void setDistances(f32 midRange, f32 farRange);
virtual void OnRegisterSceneNode();
virtual void render();
virtual video::SMaterial& getMaterial(u32 i);
virtual u32 getMaterialCount() const;
virtual const core::aabbox3d<f32>& getBoundingBox() const;
/**
* Get type
* @return type
*/
virtual scene::ESCENE_NODE_TYPE getType() const;
s32 getVertexCount() const;
//! Returns the leaf node associated with the tree. Useful for settings the correct texture and material settings.
CBillboardGroupSceneNode* getLeafNode();
//! Returns root stem mesh for collision shape
scene::IMesh* getTreeCollisionMesh();
//! Sets the root stem mesh for collision shape
void setTreeCollisionMesh(scene::IMesh* mesh);
//! Returns root stem mesh for collision shape
scene::IMesh* getMesh();
void createImposter();
private:
void updateBillboard();
void updateSizeAndRange();
video::SMaterial TreeMaterial;
video::SMaterial BillboardMaterial;
f32 MidRange;
f32 FarRange;
f32 Size;
CBillboardGroupSceneNode* LeafNode;
scene::SMesh* mesh;
SMeshBuffer* HighLODMeshBuffer;
SMeshBuffer* MidLODMeshBuffer;
SMeshBuffer BillboardMeshBuffer;
f64 DistSQ;
core::vector3df LastViewVec;
scene::IMesh* treeStemMesh;
};
} // namespace scene
} // namespace irr
#endif
Code: Select all
/*
Written by Asger Feldthaus
February 2007
*/
#include "CTreeSceneNode.h"
#include "../../Logger/Logger.h"
#include <ICameraSceneNode.h>
#include <IVideoDriver.h>
namespace irr {
namespace scene {
const f32 CAMERA_UPDATE_DISTANCE = 100.0f;
//MidRange = 500.0f;
//FarRange = 1300.0f;
CTreeSceneNode::CTreeSceneNode(f32 midRange, f32 farRange, ISceneNode* parent, ISceneManager* manager, s32 id, const core::vector3df& position, const core::vector3df& rotation, const core::vector3df& scale) :
ISceneNode(parent, manager, id, position, rotation, scale) {
MidRange = midRange;
FarRange = farRange;
Size = 0.0f;
HighLODMeshBuffer = 0;
MidLODMeshBuffer = 0;
LeafNode = 0;
BillboardMaterial.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
BillboardMaterial.Lighting = false;
BillboardMeshBuffer.Indices.push_back(2);
BillboardMeshBuffer.Indices.push_back(1);
BillboardMeshBuffer.Indices.push_back(0);
BillboardMeshBuffer.Indices.push_back(0);
BillboardMeshBuffer.Indices.push_back(3);
BillboardMeshBuffer.Indices.push_back(2);
BillboardMeshBuffer.Vertices.push_back(video::S3DVertex(0, 0, 0, 0, 1.0f, 0, video::SColor(255, 255, 255, 255), 0.0f, 1.0f));
BillboardMeshBuffer.Vertices.push_back(video::S3DVertex(0, 0, 0, 0, 1.0f, 0, video::SColor(255, 255, 255, 255), 0.0f, 0.0f));
BillboardMeshBuffer.Vertices.push_back(video::S3DVertex(0, 0, 0, 0, 1.0f, 0, video::SColor(255, 255, 255, 255), 1.0f, 0.0f));
BillboardMeshBuffer.Vertices.push_back(video::S3DVertex(0, 0, 0, 0, 1.0f, 0, video::SColor(255, 255, 255, 255), 1.0f, 1.0f));
}
CTreeSceneNode::~CTreeSceneNode() {
if (HighLODMeshBuffer) {
HighLODMeshBuffer->drop();
}
if (MidLODMeshBuffer) {
MidLODMeshBuffer->drop();
}
if (treeStemMesh) {
treeStemMesh->drop();
}
}
void CTreeSceneNode::setup(const STreeMesh* highLOD, const STreeMesh* midLOD, video::ITexture* billboardTexture) {
if (HighLODMeshBuffer) {
HighLODMeshBuffer->drop();
}
if (MidLODMeshBuffer) {
MidLODMeshBuffer->drop();
}
if (LeafNode) {
LeafNode->remove();
}
HighLODMeshBuffer = highLOD->MeshBuffer;
MidLODMeshBuffer = midLOD->MeshBuffer;
//Set collision mesh
scene::SMesh* treeCollisionMesh = new SMesh;
treeCollisionMesh->addMeshBuffer(MidLODMeshBuffer);
setTreeCollisionMesh(treeCollisionMesh);
LeafNode = 0;
if (highLOD->Leaves.size() > 0) {
LeafNode = new CBillboardGroupSceneNode(this, SceneManager);
LeafNode->drop();
for (u32 i = 0; i < highLOD->Leaves.size(); i++) {
if (highLOD->Leaves[i].HasAxis) {
LeafNode->addBillboardWithAxis(highLOD->Leaves[i].Position, highLOD->Leaves[i].Size, highLOD->Leaves[i].Axis, highLOD->Leaves[i].Roll, highLOD->Leaves[i].Color);
} else {
LeafNode->addBillboard(highLOD->Leaves[i].Position, highLOD->Leaves[i].Size, highLOD->Leaves[i].Roll, highLOD->Leaves[i].Color);
}
}
}
if (HighLODMeshBuffer) {
HighLODMeshBuffer->grab();
Size = HighLODMeshBuffer->BoundingBox.getExtent().getLength() / 2.0f;
}
if (MidLODMeshBuffer) {
MidLODMeshBuffer->grab();
}
LastViewVec = core::vector3df(0, 0, 0);
mesh = new scene::SMesh();
mesh->addMeshBuffer(HighLODMeshBuffer);
mesh->recalculateBoundingBox();
}
void CTreeSceneNode::createImposter() {
video::IVideoDriver* driver = SceneManager->getVideoDriver();
video::ITexture* billboardTexture = driver->addRenderTargetTexture(core::dimension2du(1024, 1024), "Imposter1024RTT");
scene::ICameraSceneNode* originalCam = SceneManager->getActiveCamera();
scene::ICameraSceneNode* impostorCam = SceneManager->addCameraSceneNode();
//Set position and target on the camera
updateAbsolutePosition();
core::vector3df camPos = getAbsolutePosition();
camPos.X += getBoundingBox().getExtent().Y;
camPos.Y += getBoundingBox().getExtent().Y / 2.f;
camPos.Y += 2.5f;
impostorCam->setPosition(camPos);
camPos = getAbsolutePosition();
camPos.Y += getBoundingBox().getExtent().Y / 2.f;
camPos.Y += 2.5f;
impostorCam->setTarget(camPos);
//Set near, far and aspect ratio of the camera, so the tree comes in full view
impostorCam->updateAbsolutePosition();
impostorCam->setNearValue(0.001f);
impostorCam->setFarValue(500.f);
impostorCam->setAspectRatio(irr::core::max_(getBoundingBox().getExtent().X + 1, getBoundingBox().getExtent().Z + 1) / getBoundingBox().getExtent().Y);
SceneManager->setActiveCamera(impostorCam);
//Set the render target to the billboardTexture
driver->setRenderTarget(billboardTexture, true, true, video::SColor(0, 0, 0, 0));
//Hide all other nodes
core::array<scene::ISceneNode *> nodes;
SceneManager->getSceneNodesFromType((scene::ESCENE_NODE_TYPE) scene::TREE_SCENE_NODE_ID, nodes);
SceneManager->getSceneNodesFromType(scene::ESNT_TERRAIN, nodes);
SceneManager->getSceneNodesFromType(scene::ESNT_SKY_BOX, nodes);
SceneManager->getSceneNodesFromType(scene::ESNT_ANIMATED_MESH, nodes);
SceneManager->getSceneNodesFromType(scene::ESNT_MESH, nodes);
SceneManager->getSceneNodesFromType(scene::ESNT_BILLBOARD, nodes);
for (u32 i = 0; i < nodes.size(); i++) {
if (nodes[i]->isVisible()) {
nodes[i]->setVisible(false);
} else {
nodes[i]->setID(999);
}
}
//Make tree and leafnode visible
setVisible(true);
OnRegisterSceneNode();
updateAbsolutePosition();
LeafNode->setVisible(true);
LeafNode->OnRegisterSceneNode();
LeafNode->updateAbsolutePosition();
//Render the nodes to the render target
SceneManager->drawAll();
//Reset the render target and set the created billboard texture
driver->setRenderTarget(0, true, true, video::SColor(0, 0, 0, 0));
SceneManager->setActiveCamera(originalCam);
BillboardMaterial.TextureLayer[0].Texture = billboardTexture;
updateBillboard();
impostorCam->remove();
//Restore all other nodes
for (u32 i = 0; i < nodes.size(); i++) {
if (nodes[i]->getID() != 999 || nodes[i]->getType() == scene::TREE_SCENE_NODE_ID) {
nodes[i]->setVisible(true);
}
}
}
void CTreeSceneNode::setup(CTreeGenerator* generator, s32 seed, video::ITexture* billboardTexture) {
STreeMesh* highLOD = generator->generateTree(8, seed, true, 0);
STreeMesh* midLOD = generator->generateTree(4, seed, false, 1);
setup(highLOD, midLOD, billboardTexture);
highLOD->drop();
midLOD->drop();
}
void CTreeSceneNode::setDistances(f32 midRange, f32 farRange) {
MidRange = midRange;
FarRange = farRange;
}
void CTreeSceneNode::OnRegisterSceneNode() {
if (IsVisible) {
ICameraSceneNode* camera = SceneManager->getActiveCamera();
DistSQ = 0.0f;
if (camera) {
core::vector3df campos = camera->getAbsolutePosition();
core::vector3df center = HighLODMeshBuffer->BoundingBox.getCenter();
AbsoluteTransformation.rotateVect(center);
center += getAbsolutePosition();
DistSQ = (campos - center).getLengthSQ();
}
if (LeafNode) {
f32 far = FarRange + Size;
LeafNode->setVisible(far * far >= DistSQ);
}
SceneManager->registerNodeForRendering(this);
}
ISceneNode::OnRegisterSceneNode();
}
void CTreeSceneNode::render() {
video::IVideoDriver* driver = SceneManager->getVideoDriver();
f32 far = FarRange + Size;
f32 mid = MidRange + Size;
if (far * far < DistSQ && BillboardMaterial.TextureLayer[0].Texture != 0) {
driver->setTransform(video::ETS_WORLD, core::matrix4());
driver->setMaterial(BillboardMaterial);
ICameraSceneNode* camera = SceneManager->getActiveCamera();
core::vector3df view = camera->getAbsolutePosition() - getAbsolutePosition();
if (view.getDistanceFromSQ(LastViewVec) >= CAMERA_UPDATE_DISTANCE * CAMERA_UPDATE_DISTANCE) {
updateBillboard();
LastViewVec = view;
}
driver->drawMeshBuffer(&BillboardMeshBuffer);
} else if (mid * mid < DistSQ && MidLODMeshBuffer != 0) {
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
driver->setMaterial(TreeMaterial);
//Create imposter with high detail
if (BillboardMaterial.TextureLayer[0].Texture != 0) {
driver->drawMeshBuffer(MidLODMeshBuffer);
} else {
driver->drawMeshBuffer(HighLODMeshBuffer);
}
} else {
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
driver->setMaterial(TreeMaterial);
driver->drawMeshBuffer(HighLODMeshBuffer);
}
}
video::SMaterial& CTreeSceneNode::getMaterial(u32 i) {
return TreeMaterial;
}
u32 CTreeSceneNode::getMaterialCount() const {
return 1;
}
const core::aabbox3d<f32>& CTreeSceneNode::getBoundingBox() const {
return HighLODMeshBuffer->BoundingBox;
}
scene::ESCENE_NODE_TYPE CTreeSceneNode::getType() const {
return (scene::ESCENE_NODE_TYPE) TREE_SCENE_NODE_ID;
}
s32 CTreeSceneNode::getVertexCount() const {
return HighLODMeshBuffer->getVertexCount();
}
CBillboardGroupSceneNode* CTreeSceneNode::getLeafNode() {
return LeafNode;
}
//! Returns root stem mesh for collision shape
scene::IMesh* CTreeSceneNode::getTreeCollisionMesh() {
return treeStemMesh;
}
//! Sets the root stem mesh for collision shape
void CTreeSceneNode::setTreeCollisionMesh(scene::IMesh* mesh) {
this->treeStemMesh = mesh;
}
scene::IMesh* CTreeSceneNode::getMesh() {
return mesh;
}
void CTreeSceneNode::updateBillboard() {
ICameraSceneNode* camera = SceneManager->getActiveCamera();
if (!camera) {
return;
}
core::vector3df campos = camera->getAbsolutePosition();
core::vector3df pos = getAbsolutePosition();
core::vector3df view = pos - campos;
view.normalize();
core::vector3df up = core::vector3df(0, 1, 0);
AbsoluteTransformation.rotateVect(up);
up.normalize();
core::vector3df left = view.crossProduct(up);
left.normalize();
core::vector3df extent = HighLODMeshBuffer->BoundingBox.getExtent();
core::vector3df yscale = core::vector3df(0, 1.5f, 0);
AbsoluteTransformation.rotateVect(yscale); // Find the y scale and apply to the height
pos.Y -= extent.Y / 5.f;
up *= extent.Y * yscale.getLength();
core::vector3df xz = core::vector3df(1.1f, 0, 1.1f); // Find the xz scale and apply to the width
AbsoluteTransformation.rotateVect(xz);
extent.Y = 0.0f;
f32 len = extent.getLength() * xz.getLength() / 1.4f; // Divide by 1.4f to compensate for the extra length of xz.
left *= len / 2.0f;
/*
1--2
| |
0--3
*/
BillboardMeshBuffer.Vertices[0].Pos = pos - left;
BillboardMeshBuffer.Vertices[1].Pos = pos - left + up;
BillboardMeshBuffer.Vertices[2].Pos = pos + left + up;
BillboardMeshBuffer.Vertices[3].Pos = pos + left;
}
} // namespace scene
} // namespace irr
After you create the treescenenode, you can call TreeSceneNode::createImposter(). It will render the tree in high detail to a texture that will be set on the billboardmaterial.
This was necessary for our game, because we have multiple tree types and further randomization, so it was easier to let the game provide the billboard textures instead of us creating one for every tree.
This was a quick fix, so the code is not so clean =P
It also may be the case that I got this working for our tree models only and it won't work on yours. I do some "nasty magic with numbers" and there's probably a better way. If there is, please say so ^^
If I managed to clean this up I'll post an update.