CCloudSceneNode - clouds with levels of detail
Great Work!
Great Work Bitplane! This is just what I have been looking for... I will use it for my Azura Racer game. Maybe this could be included into the svn version of Irrlicht? I think it is a really useful addition which many other 3d engines already have.
-
- Posts: 322
- Joined: Tue Aug 30, 2005 10:34 am
- Location: slovakia
How do I compile this? Dev-cpp is set up for Irrlicht, but I get the following error when I try to compile:
[Linker error] undefined reference to `_imp___ZN3irr12createDeviceENS_5video13E_DRIVER_TYPEERKNS_4core11dimension2dIiEEjbbbPNS_14IEventReceiverEPKc'
EDIT- Nevermind, I had gotten one of the directories wrong. However, I'm not seeing any clouds.
[Linker error] undefined reference to `_imp___ZN3irr12createDeviceENS_5video13E_DRIVER_TYPEERKNS_4core11dimension2dIiEEjbbbPNS_14IEventReceiverEPKc'
EDIT- Nevermind, I had gotten one of the directories wrong. However, I'm not seeing any clouds.
Yes, I don't see any clouds either. If you change the material type from EMT_TRANSPARENT_APLHA_CHANNEL to EMT_TRANSPARENT_ADD_COLOR, you'll see dense, white clouds instead of the normal gray clouds. I've tried out the other EMT_TRANSPARENT_* with hit or miss (either see black borders and square billboard clouds or nothing at all).Baiame wrote:How do I compile this? Dev-cpp is set up for Irrlicht, but I get the following error when I try to compile:
[Linker error] undefined reference to `_imp___ZN3irr12createDeviceENS_5video13E_DRIVER_TYPEERKNS_4core11dimension2dIiEEjbbbPNS_14IEventReceiverEPKc'
EDIT- Nevermind, I had gotten one of the directories wrong. However, I'm not seeing any clouds.
Or you can change the texture from "cloud4.png" to either "cloud2.png" or "cloud3.png" to see tiny sparse cloud formations. I've tried this with the SVN rev 276 of irrlicht but I suspect you'll get a similar affect if using the release v1.1 of irrlicht.
-
- Posts: 34
- Joined: Wed Apr 25, 2007 3:48 am
Hi, I made it work in Irrlicht 1.3.1 with some changes.
First I moved the whole class and functions definitions from the cpp file to the header file.
Then I replaced the OnPrerender() function with the OnRegisterSceneNode()
Finally changed the s32 getMaterialCount() to u32 and I did the same with other s32 functions all now declared as u32.
It works fine.
Here is the code:
Now my question is how can I change the size of the clouds to something huge covering my...36sq miles terrain!!!![/quote]
First I moved the whole class and functions definitions from the cpp file to the header file.
Then I replaced the OnPrerender() function with the OnRegisterSceneNode()
Finally changed the s32 getMaterialCount() to u32 and I did the same with other s32 functions all now declared as u32.
It works fine.
Here is the code:
#define CLOUD_PARTICLE_LIMIT 2000
using namespace irr;
using namespace core;
using namespace io;
using namespace scene;
using namespace video;
using namespace gui;
//! Struct for holding particle data
struct SCloudParticle
{
//! distance from camera
f32 distance;
//! Position of the particle, or position relative to parent cloud
core::vector3d<f32> pos;
//! Cloud size
core::dimension2d<f32> size;
//! Cloud colour - (tsk tsk, AmEn)
video::SColor color;
// Number of children
s16 children;
// position of first child
s16 firstchild;
// how much children will be scaled
f32 childscale;
};
struct SCloudBuffer
{
};
//! A particle system scene node.
class CCloudSceneNode : public ISceneNode
{
public:
//! constructor
CCloudSceneNode(ISceneNode* parent, ISceneManager* mgr, ITimer *devicetimer , s32 id,
const core::vector3df& position = core::vector3d<f32>(0,0,0),
const core::vector3df& rotation = core::vector3d<f32>(0,0,0),
const core::vector3df& scale = core::vector3d<f32>(1,1,1))
: ISceneNode(parent, mgr, id, position, rotation, scale)
{
ZOrdering = true;
IgnoreChildColor = true;
MaxDepth = 2;
LOD = 4;
timer = devicetimer;
// create static buffers if required
if (Particles.size() < CLOUD_PARTICLE_LIMIT)
{
Particles.set_used(CLOUD_PARTICLE_LIMIT);
ParticlesToDraw = CLOUD_PARTICLE_LIMIT;
reallocateBuffers();
ParticlesToDraw = 0;
}
#ifdef _DEBUG
setDebugName("CCloudSceneNode");
#endif
//addition by Greg to make alphas below 128 feasible.
Material.MaterialTypeParam = 0.01f;
this->setMaterialFlag(video::EMF_ZWRITE_ENABLE,false);
}
//! destructor
~CCloudSceneNode()
{
}
//! Adds new particle effector to the particle system.
void addAffector(IParticleAffector* affector)
{
affector->grab();
AffectorList.push_back(affector);
}
//! Removes all particle affectors in the particle system.
void removeAllAffectors()
{
core::list<IParticleAffector*>::Iterator it = AffectorList.begin();
while (it != AffectorList.end())
{
(*it)->drop();
it = AffectorList.erase(it);
}
}
//! Returns the material based on the zero based index i.
video::SMaterial& getMaterial(u32 i)
{
return Material;
}
//! Returns amount of materials used by this scene node.
u32 getMaterialCount()
{
return 1;
}
//! pre render event
void OnRegisterSceneNode()
{
if (IsVisible)
{
SceneManager->registerNodeForRendering(this);
// make cloud particles
makeParticles();
lastcount = ParticlesToDraw;
// printf("built %d in %d\n", lastcount,lasttime);
// order them if needed
if (ZOrdering)
sortParticles();
// call iscenenode's pre-render
ISceneNode::OnRegisterSceneNode();
}
}
//! render
void render()
{
video::IVideoDriver* driver = SceneManager->getVideoDriver();
ICameraSceneNode* camera = SceneManager->getActiveCamera();
if (!camera || !driver)
return;
// calculate vectors for letting particles look to camera
core::vector3df campos = camera->getAbsolutePosition();
core::vector3df target = camera->getTarget();
core::vector3df up = camera->getUpVector();
core::vector3df view = target - campos;
view.normalize();
core::vector3df horizontal = up.crossProduct(view);
horizontal.normalize();
core::vector3df vertical = horizontal.crossProduct(view);
vertical.normalize();
view *= -1.0f;
// reallocate arrays, if they are too small
reallocateBuffers();
u32 sp=0;
// if there's more particles that what's allowed, cull the back ones
if (ParticlesToDraw > CLOUD_PARTICLE_LIMIT)
{
sp = ParticlesToDraw - CLOUD_PARTICLE_LIMIT;
}
// lets not use niko's arrays, use a pointer instead
video::S3DVertex *p = Vertices.pointer();
// create particle vertex data
for (u32 i=sp; i<ParticlesToDraw; i++)
{
SCloudParticle& particle = Particles;
core::vector3df h = horizontal * 0.5f * particle.size.Width;
core::vector3df v = vertical * 0.5f * particle.size.Height;
s32 idx = (i-sp)*4;
//p[0+idx].Pos = particle.pos + h + v;
p[0+idx].Pos.X = particle.pos.X + h.X + v.X;
p[0+idx].Pos.Y = particle.pos.Y + h.Y + v.Y;
p[0+idx].Pos.Z = particle.pos.Z + h.Z + v.Z;
p[0+idx].Color = particle.color;
// p[0+idx].Color = particle.color;
p[0+idx].Normal.X = view.X;
p[0+idx].Normal.Y = view.Y;
p[0+idx].Normal.Z = view.Z;
//p[1+idx].Pos = particle.pos + h - v;
p[1+idx].Pos.X = particle.pos.X + h.X - v.X;
p[1+idx].Pos.Y = particle.pos.Y + h.Y - v.Y;
p[1+idx].Pos.Z = particle.pos.Z + h.Z - v.Z;
//p[1+idx].Color = particle.color;
p[1+idx].Normal.X = view.X;
p[1+idx].Normal.Y = view.Y;
p[1+idx].Normal.Z = view.Z;
//p[2+idx].Pos = particle.pos - h - v;
p[2+idx].Pos.X = particle.pos.X - h.X - v.X;
p[2+idx].Pos.Y = particle.pos.Y - h.Y - v.Y;
p[2+idx].Pos.Z = particle.pos.Z - h.Z - v.Z;
p[2+idx].Color = particle.color;
//p[2+idx].Normal = view;
p[2+idx].Normal.X = view.X;
p[2+idx].Normal.Y = view.Y;
p[2+idx].Normal.Z = view.Z;
// p[3+idx].Pos = particle.pos - h + v;
p[3+idx].Pos.X = particle.pos.X - h.X + v.X;
p[3+idx].Pos.Y = particle.pos.Y - h.Y + v.Y;
p[3+idx].Pos.Z = particle.pos.Z - h.Z + v.Z;
p[3+idx].Color = particle.color;
// p[3+idx].Normal = view;
p[3+idx].Normal.X = view.X;
p[3+idx].Normal.Y = view.Y;
p[3+idx].Normal.Z = view.Z;
}
core::matrix4 mat;
driver->setTransform(video::ETS_WORLD, mat);
// driver->draw3DBox( Box, video::SColor(0,255,255,255));
driver->setMaterial(Material);
// get the time
u32 t = timer->getTime();
// draw the mesh
driver->drawIndexedTriangleList(Vertices.pointer(), (ParticlesToDraw-sp)*4,
Indices.pointer(), (ParticlesToDraw-sp)*2);
// get the time taken, so we can cull far clouds next loop
lasttime = timer->getTime()-t;
lastcount = ParticlesToDraw;
//printf("drew %d in %d\n", lastcount,lasttime);
// for debug purposes only:
if (DebugDataVisible)
{
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
video::SMaterial m;
m.Lighting = false;
driver->setMaterial(m);
driver->draw3DBox(Box, video::SColor(0,255,255,255));
}
}
//! returns the axis aligned bounding box of this node
const core::aabbox3d<f32>& getBoundingBox() const
{
return Box;
}
// gets the current number of clouds on screen
s16 getCurrentCloudCount()
{
return ParticlesToDraw;
}
void makeRandomCloud(s16 count)
{
// this sets the number of top level clouds
ParticleCount = count;
// ParticleData[<ParticleCount] = top level cloud data
// ParticleData[>=ParticleCount] = child node data
if (ParticleData.size() < count+50)
ParticleData.set_used(count+50);
u32 i;
// make particles
for (i=1; i<count+25; i++)
{
f32 a,b,c;
// random position
if (i < count)
{
a = rand() / 10 - (rand() / 10);
c = rand() / 10 - (rand() / 10);
b = rand() / 500 - (rand() / 500);
}
else
{
a = rand() / 100 - (rand() / 100);
c = rand() / 100 - (rand() / 100);
b = rand() / 100 - (rand() / 100);
}
ParticleData.pos = core::vector3d<f32>(a,b,c);
f32 f = rand() / 50;
if (f < 0) f = -f;
// set the size
ParticleData.size = core::dimension2d<f32>(200+f,200+f);
// distance to dark spot
int f2 = int(rand())%128;
f2+=1;
ParticleData.color = video::SColor(255,255-f2,255-f2,255-f2);
ParticleData.children = 0;
if (rand()/500 > 20) ParticleData.children = int(rand()) %3;
int j = int(rand()) %10;
ParticleData.firstchild = count +j;
ParticleData.childscale = 0.5;
}
// a recursive pattern here-
/*
f32 f = 100.0;
f32 cs = 0.5;
f32 d = rand()/1500;
f32 e = rand()/1500;
//1
ParticleData.color = video::SColor(255,0,0,0);
ParticleData.children = 0;
ParticleData[i].firstchild = 50+4;
ParticleData[i].childscale = cs;
ParticleData[i].pos = core::vector3d<f32>(d,0,e);
ParticleData[i].size = core::dimension2d<f32>(f,f);
i++;
//2
ParticleData[i].color = video::SColor(255,0,0,0);
ParticleData[i].children = 0;
ParticleData[i].firstchild = 50+4;
ParticleData[i].childscale = cs;
ParticleData[i].pos = core::vector3d<f32>(-d,0,e);
ParticleData[i].size = core::dimension2d<f32>(f,f);
i++;
//3
ParticleData[i].color = video::SColor(255,0,0,0);
ParticleData[i].children = 4;
ParticleData[i].firstchild = 50+4;
ParticleData[i].childscale = cs;
ParticleData[i].pos = core::vector3d<f32>(d,0,-e);
ParticleData[i].size = core::dimension2d<f32>(f,f);
i++;
//4
ParticleData[i].color = video::SColor(255,0,0,0);
ParticleData[i].children = 4;
ParticleData[i].firstchild = 50+4;
ParticleData[i].childscale = cs;
ParticleData[i].pos = core::vector3d<f32>(-d,0,-e);
ParticleData[i].size = core::dimension2d<f32>(f,f);
i++;
d = rand()/1500;
e = rand()/1500;
//1
ParticleData[i].color = video::SColor(255,0,0,0);
ParticleData[i].children = 0;
ParticleData[i].firstchild = 50;
ParticleData[i].childscale = cs;
ParticleData[i].pos = core::vector3d<f32>(d,e,0);
ParticleData[i].size = core::dimension2d<f32>(f,f);
i++;
//2
ParticleData[i].color = video::SColor(255,0,0,0);
ParticleData[i].children = 0;
ParticleData[i].firstchild = 50;
ParticleData[i].childscale = cs;
ParticleData[i].pos = core::vector3d<f32>(-d,e,0);
ParticleData[i].size = core::dimension2d<f32>(f,f);
i++;
d = rand()/1500;
//3
ParticleData[i].color = video::SColor(255,0,0,0);
ParticleData[i].children = 4;
ParticleData[i].firstchild = 50;
ParticleData[i].childscale =cs;
ParticleData[i].pos = core::vector3d<f32>(d,-e,0);
ParticleData[i].size = core::dimension2d<f32>(f,f);
i++;
//4
ParticleData[i].color = video::SColor(255,0,0,0);
ParticleData[i].children = 4;
ParticleData[i].firstchild = 50;
ParticleData[i].childscale = cs;
ParticleData[i].pos = core::vector3d<f32>(-d,-e,0);
ParticleData[i].size = core::dimension2d<f32>(f,f);
i++;
d = rand()/1500;
*/
}
//! turns z sorting on or off.
//! speed increase for alpha blend clouds, or clouds that are one colour
void setZOrdering(bool zo)
{
ZOrdering = zo;
}
//! decides whether child nodes are the same colour as their parents
void setIgnoreChildColor(bool b)
{
IgnoreChildColor = b;
}
//! sets the maximum detail depth to draw
void setMaxDepth(s16 maxdepth)
{
MaxDepth = maxdepth;
}
//! sets the level of detail ("distance/size" - scale of 1 to 10 works well)
void setLOD(f32 levelofdetail)
{
LOD = levelofdetail;
}
//! sets the maximum time allowed to draw the screen.
void setMaxTimePerFrame(u32 t)
{
MaxTimePerFrame = t;
}
private:
// functions
void reallocateBuffers()
{
if (ParticlesToDraw * 4 > Vertices.size() ||
ParticlesToDraw * 6 > Indices.size())
{
s32 oldSize = Vertices.size();
Vertices.set_used(ParticlesToDraw * 4);
u32 i;
// fill remaining vertices
for (i=oldSize; i<Vertices.size(); i+=4)
{
Vertices[0+i].TCoords.set(0.0f, 0.0f);
Vertices[1+i].TCoords.set(0.0f, 1.0f);
Vertices[2+i].TCoords.set(1.0f, 1.0f);
Vertices[3+i].TCoords.set(1.0f, 0.0f);
}
// fill remaining indices
s32 oldIdxSize = Indices.size();
s32 oldvertices = oldSize;
Indices.set_used(ParticlesToDraw * 6);
for (i=oldIdxSize; i<Indices.size(); i+=6)
{
Indices[0+i] = 0+oldvertices;
Indices[1+i] = 2+oldvertices;
Indices[2+i] = 1+oldvertices;
Indices[3+i] = 0+oldvertices;
Indices[4+i] = 3+oldvertices;
Indices[5+i] = 2+oldvertices;
oldvertices += 4;
}
}
}
void sortParticles()
{
u32 i,j, increment;
// because copying particles around in the shell sort was showing up as 99% of
// all cpu time, we'll deal with a list of pointers instead.
// init arrays
if (tmppointers.size() < ParticlesToDraw) tmppointers.set_used(ParticlesToDraw);
if (tmpclouds.size() < ParticlesToDraw) tmpclouds.set_used(ParticlesToDraw);
SCloudParticle *temp = Particles.pointer();
// copy pointers
for (i=0;i<ParticlesToDraw;i++)
tmppointers[i] = temp +i;
// sort the list (shell sort)
// Niko's arrays make function calls, so we'll use a double pointer
SCloudParticle **tp = tmppointers.pointer();
increment = 3;
while (increment > 0)
{
for (i=0; i < ParticlesToDraw; i++)
{
j = i;
temp = tp[i];
while ((j >= increment) && (tp[j-increment]->distance < temp->distance))
{
tp[j] = tp[j - increment];
j = j - increment;
}
tp[j] = temp;
}
if (increment/2 != 0)
increment = increment/2;
else if (increment == 1)
increment = 0;
else
increment = 1;
}
// take list to temp buffer
for (i=0;i<ParticlesToDraw;i++)
tmpclouds[i] = tp[i][0];
// memcopy back to particles
memcpy( Particles.pointer(),tmpclouds.const_pointer() , sizeof(SCloudParticle)*ParticlesToDraw);
// for (i=0;i<ParticlesToDraw;i++)
// Particles[i] = tmpclouds[i];
}
//! makes particle list for rendering/sorting
void makeParticles()
{
core::vector3df rot = getRotation();
core::vector3df pos = getPosition();
// printf("pos = %f, %f, %f\n", pos.X,pos.Y,pos.Z);
core::vector3df campos = SceneManager->getActiveCamera()->getAbsolutePosition();
core::vector3df camrot = SceneManager->getActiveCamera()->getRotation();
//f32 far;
//far = SceneManager->getActiveCamera()->getFarValue();
core::matrix4 m;
core::matrix4 m2;
s16 i,c=0;
m.setInverseRotationDegrees(rot);
m2.setInverseRotationDegrees(camrot);
core::vector3df t;
// reset bounding box
Box.reset(getPosition());
// make each particle
for (i=0; i < ParticleCount; i++)
{
core::vector3df tmppos = ParticleData[i].pos;
// rotate
m.transformVect(tmppos);
// move globally
tmppos += pos;
// set distance from camera
f64 tmpdist = campos.getDistanceFrom(tmppos);
// level of detail culled?
f32 lod = f32(tmpdist) / ParticleData[i].size.Width;
// cull if it's behind the camera
bool cull=false;
// far plane culled
/*if(far < tmpdist)
cull = true;*/
if (lod < LOD*5 && !cull)
{
// copy particle (swap for mem copy?)
Particles[c] = ParticleData[i];
// set it up
Particles[c].pos = tmppos;
Particles[c].distance = tmpdist;
// expand the box
Box.addInternalPoint(Particles[c].pos);
if (lod < LOD && Particles[c].children > 0 && c < CLOUD_PARTICLE_LIMIT)
{
// we add child nodes
depth = 0;
c+= makeChildClouds(Particles[c],c+1,m,campos);
}
if (Particles.size() < c+10)
{
// grow buffer
Particles.set_used(Particles.size()+50);
}
// we added at least one
c+=1;
}
}
ParticlesToDraw = c;
core::matrix4 absinv = AbsoluteTransformation;
absinv.makeInverse();
absinv.transformBox(Box);
}
// adds child clouds to the particle buffer
s16 makeChildClouds(SCloudParticle &parent,s16 start,core::matrix4 &m, core::vector3df &campos )
{
depth+=1;
if (depth>MaxDepth)
{
return (0);
}
if (Particles.size() < start+parent.children)
{
// grow buffer
Particles.set_used(Particles.size()+50);
// buffer too big?
if (start+parent.children > CLOUD_PARTICLE_LIMIT)
{
return (0);
}
}
s16 count;
s16 c = start;
for(count=0;count<parent.children;count++)
{
// copy
Particles[c] = ParticleData[ parent.firstchild + count ];
// scale
Particles[c].pos.X *= parent.childscale;
Particles[c].pos.Y *= parent.childscale;
Particles[c].pos.Z *= parent.childscale;
Particles[c].size.Width *= parent.childscale;
Particles[c].size.Height *= parent.childscale;
Particles[c].childscale *= parent.childscale;
// same colour?
if (IgnoreChildColor)
Particles[c].color = parent.color;
// rotate
m.transformVect(Particles[c].pos);
// position
Particles[c].pos += parent.pos;
// expand the box
Box.addInternalPoint(Particles[c].pos);
// set distance from camera
Particles[c].distance = campos.getDistanceFrom(Particles[c].pos);
// level of detail
f32 lod = f32(Particles[c].distance) / Particles[c].size.Width;
if (lod < LOD && Particles[c].children > 0)
{
// we add child nodes
c+= makeChildClouds(Particles[c],c+1,m,campos);
depth-=1;
// grow buffer?
if (Particles.size() < c+Particles[c].children)
{
Particles.set_used(c+Particles[c].children);
}
if (c > CLOUD_PARTICLE_LIMIT)
{
return (0);
}
}
// we added at least one
c+=1;
}
return (c-start);
}
// shared variables
core::array<SCloudParticle> Particles; //
core::array<video::S3DVertex> Vertices; // 4 vertices per particle
core::array<u16> Indices; // 6 indices per particle
// instance variables
core::list<IParticleAffector*> AffectorList;
core::array<SCloudParticle> ParticleData;
s16 ParticleCount;
s16 ParticlesToDraw;
s16 depth; // for max depth
bool ZOrdering;
bool IgnoreChildColor;
s16 MaxDepth;
f32 LOD;
ITimer *timer;
u32 MaxTimePerFrame; // maximum time allowed to render
u32 lasttime; // last time to draw frame
u32 lastcount; // last number of polys
video::SMaterial Material;
core::aabbox3d<f32> Box;
core::array<SCloudParticle*> tmppointers;
core::array<SCloudParticle> tmpclouds;
};
Now my question is how can I change the size of the clouds to something huge covering my...36sq miles terrain!!!![/quote]
-
- Posts: 34
- Joined: Wed Apr 25, 2007 3:48 am
I moved the class a while back, over to here:
http://irrlicht-plugins.googlecode.com/ ... SceneNode/
if you want access to change and make additions, just let me know by pm or email and i'll give you access
http://irrlicht-plugins.googlecode.com/ ... SceneNode/
if you want access to change and make additions, just let me know by pm or email and i'll give you access
It's probably the bounding box being wrong. I've got a new shader based idea for clouds with volumetric lighting, different cloud types (bought "the cloudspotter's guide" last week ) as well as the recursive detail levels.. It will probably be several months by the time I get round to coding it though.
I'm probably not gonna have time to fix this for 1.4 for a while, so if anyone else wants to have a go, feel free
I'm probably not gonna have time to fix this for 1.4 for a while, so if anyone else wants to have a go, feel free