How to use the terrain engine libmini with Irrlicht

A forum to store posts deemed exceptionally wise and useful
zola -

Post by zola - »

@UzMaN, the node is work in progress and the detailtexture parameter in the create method is currently not used. Feel free to extend the node as You like :) I'm sure every one will appreciate it. You might want to check out the method "BeginFan" and "FanVertex"

@brcolow, sorry I can't help You with this. From what I see I think You're having also a problem with linking debug with nondebug code.
Guest

Post by Guest »

yes,i changed the S3DVertex as S3DVertex2TCoords,and SMeshBuffer to SMeshBufferLightmap,then in fanvertex i set the second texture coordinates,finally i select material type EMT_LIGHTMAP,i you wanna whole code i can post it.
knightoflight
Posts: 199
Joined: Sun Aug 24, 2003 5:47 pm
Location: Germany

Post by knightoflight »

Hi community:
with zolas permission, i put his code from this thread for download to
http://zenprogramming.tripod.com in downloadsection

Hi UzMaN and Guest,
yes sure please show us this new version!
UzMaN
Posts: 3
Joined: Fri Oct 29, 2004 11:50 pm

Post by UzMaN »

sorry the Guest above was me,so final code is just like this

CLMTerrainSceneNode.h
only SMeshBuffer and S3DVertex have been changed;

Code: Select all

#ifndef __CLMTerrainSceneNode_h__ 
#define __CLMTerrainSceneNode_h__ 

#include <irrlicht.h> 
#include "ministub.hpp"

#pragma comment(lib, "libMini.lib") 


namespace irr 
{ 
   namespace scene 
   { 

      using namespace core; 
      using namespace video; 

      class CLMTerrainSceneNode : public ISceneNode 
      { 

      protected: 

         ////////////////////////////////////////////////////////////////// 
         // globals for libMini callbacks 
         ////////////////////////////////////////////////////////////////// 

         // prepares a buffer if the buffer is nonempty draws and then empties the buffer 
         // this function needs to be called after stub->draw one more time 
         static void _BeginFan(); 

         // fills the buffer with vertecis 
         static void _FanVertex(float i,float y,float j); 

         // pointer to the scenenode 
         static CLMTerrainSceneNode* _self; 

         ////////////////////////////////////////////////////////////////// 
          
         ministub *stub; 
         IVideoDriver* driver; 
         SMeshBufferLightMap buffer;      // a buffer for fan vertecis 
         S3DVertex2TCoords vertex;      // a default vertex 

         // draw the meshbuffer 
         void BeginFan(); 
         // add point (i,y,j) to the meshbuffer 
         void FanVertex(float i,float y,float j); 

         // point spacing 
         float dim; 
         // height scaling factor 
         float scale; 
         // pixel width of the heightmap 
         s32 size; 
         // resolution 
         float res; 
         // field of view 
         float fovy; 
         // aspect ratio 
         float aspect; 
         // near value 
         float nearp; 
         // far value 
         float farp; 

         // the height field 
         float *hfield; 
         // material 
         SMaterial Material; 
         // invere of absolute transform to calculate hf coordinates from world coordinates 
         matrix4 invAbsoluteTransform; 
         // current camera pos in heightfield coordinate 
         vector3df pos; 
         // current camera target in heightfield coordinate 
         vector3df tgt; 
         // current camer up in heightfield coordinate 
         vector3df up; 

         // Bounding box of the heightfield 
         mutable aabbox3df Box; 

      public: 

         // constructor 
         CLMTerrainSceneNode(ISceneNode* parent, ISceneManager* mgr, s32 id); 

         // destructor 
         virtual ~CLMTerrainSceneNode(); 

         /*! 
         \brief create the terrain 
         \param heightmap, image with height values 
         \param texture, texture used for texturing the terrain 
         \param detailmap, not used 
         \param gridPointSpacing, spacing between the gridpoints of the heightfield 
         \param heightScale, scaling value for image height values 
         \param resolution, 1000.0f ... 1000000.0f 
         */ 
         bool create(IImage* heightmap, ITexture* texture, ITexture* detailmap, 
            f32 gridPointSpacing, f32 heightScale, f32 resolution); 

         // prerender register node for rendering and call child nodes for registering 
         virtual void OnPreRender(); 

         // render the node 
         virtual void render() ; 

         // post render, animation etc. 
         virtual void OnPostRender(u32 timeMs); 

         // get the material with given number 
         virtual SMaterial& getMaterial(s32 i) 
         { 
            return Material; 
         } 

         // get the boundingbox of the node 
         const aabbox3df& getBoundingBox() const 
         { 
            AbsoluteTransformation.transformBox(Box); 
            return Box; 
         } 

         // get the inverse of the absolute transformation 
         matrix4& getInvAbsoluteTransformation(){ return invAbsoluteTransform; } 

         // get the heightfield data 
         f32* getHeightField() { return hfield; } 

         // get the length of the square size of the heightfield 
         s32 getHeightFieldSize() { return size; } 

         // spacing between the sample points 
         f32 getPointSpacing() { return dim; } 

         // scaling factor to modify the height values 
         f32 getHeightScale() { return scale; } 

         // get height at grid point (i,j) 
         f32 getHeight(int i,int j); 

         // get height at heightfield coordinate (x,z) 
         // transformation is inv=AbsoluteTransform.getInverse(); 
         // inv.transformVect(pos); 
         f32 getHeight(float x,float z); 

         // get fog height at heightfield coordinate (x,z) 
         // transformation is inv=AbsoluteTransform.getInverse(); 
         // inv.transformVect(pos); 
         f32 getFogHeight(float x,float z); 

         // get normal at heightfield coordinate (x,z) 
         // transformation is inv=AbsoluteTransform.getInverse(); 
         // inv.transformVect(pos); 
         vector3df getNormal(float x,float z); 

      }; 

   } 

} 


#endif
CLMTerrainSceneNode.cpp
changes in fanvertex and create

Code: Select all

#include "CLMTerrainSceneNode.h" 
#include <stdio.h>

namespace irr 
{ 

   namespace scene 
   { 

      using namespace core; 
      using namespace video; 

      // self pointer for callback 
      CLMTerrainSceneNode* CLMTerrainSceneNode::_self=0; 
      // prepares a buffer if the buffer is nonempty draws and then empties the buffer 
      // this function needs to be called after stub->draw one more time 
      void CLMTerrainSceneNode::_BeginFan() 
      { 
         if(_self) _self->BeginFan(); 
      } 
      // fills the buffer with vertecis 
      void CLMTerrainSceneNode::_FanVertex(float i,float y,float j) 
      { 
         if(_self) _self->FanVertex(i,y,j); 
      } 

   ////////////////////////////////////////////////////////////////////////// 


      // constructor 
      CLMTerrainSceneNode::CLMTerrainSceneNode(ISceneNode* parent, ISceneManager* mgr, s32 id): 
      ISceneNode(parent,mgr,id) 
      { 
         driver=SceneManager->getVideoDriver(); 
         stub=0; 
         hfield=0; 
         AbsoluteTransformation.getInverse(invAbsoluteTransform); 
      } 

      CLMTerrainSceneNode::~CLMTerrainSceneNode() 
      { 
         if(stub) delete stub; 
         if(hfield) delete [] hfield; 
      } 

      //! loads the terrain 
      bool CLMTerrainSceneNode::create(IImage* heightmap, ITexture* texture, ITexture* detailmap, 
         f32 gridPointSpacing, f32 heightScale, f32 resolution) 
      { 
         if(stub) delete stub; 
         if(hfield) delete [] hfield; 

         res=resolution; 
         dim=gridPointSpacing; 
         scale=heightScale; 
         size = heightmap->getDimension().Width; 
         hfield = new float[size*size]; 

         vector3df ext(size*dim,0,size*dim); 
         f32 hmin=10000000; 
         f32 hmax=-10000000; 
         for (int i=0; i<size; i++) 
            for (int j=0; j<size; j++) 
            { 
               hfield[i+j*size] = heightmap->getPixel(i,size-1-j).getRed()*scale; 
               if(hmin>hfield[i+j*size]) hmin=hfield[i+j*size]; 
               if(hmax<hfield[i+j*size]) hmax=hfield[i+j*size]; 
            } 
         Box.MinEdge.set(-ext.X/2.0f,hmin,-ext.Z/2.0f); 
         Box.MaxEdge.set(ext.X/2.0f,hmax,ext.Z/2.0f); 

         /*
		 stub=new ministub(hfield, 
            &size,&dim,scale,1.0f, 
            CLMTerrainSceneNode::_BeginFan, 
            CLMTerrainSceneNode::_FanVertex, 
            0,0,0);
		 */ 
         stub=new ministub(hfield, 
            &size,&dim,1.0f,1.0f, 
            CLMTerrainSceneNode::_BeginFan, 
            CLMTerrainSceneNode::_FanVertex, 
            0,0,0); 

         vertex.Normal.set(0,1,0); 
         vertex.Color.set(128,128,128,128); 

         Material.Texture1=texture; 
         Material.Texture2=detailmap;
// try also other material types
	 Material.MaterialType=EMT_LIGHTMAP_M2;


         return true; 
      } 

      // prepares a buffer if the buffer is nonempty draws and then empties the buffer 
      // this function needs to be called after stub->draw one more time 
      void CLMTerrainSceneNode::BeginFan() 
      { 
         if(buffer.Vertices.size()>0 && driver) 
         {    
            driver->drawIndexedTriangleFan(buffer.Vertices.const_pointer(),buffer.Vertices.size(),buffer.Indices.const_pointer(),buffer.Indices.size()-2); 
            buffer.Vertices.set_used(0); 
            buffer.Indices.set_used(0); 
         } 
      } 
      // fills the buffer with vertecis 
      void CLMTerrainSceneNode::FanVertex(float i,float y,float j) 
      {
		  int detail=256;
         //vertex.Pos.set(dim*i-size/2*dim, y*scale, size/2*dim-dim*j); 
		 
         vertex.Pos.set(dim*i-size/2*dim, y, size/2*dim-dim*j); 
		 
         //vertex.TCoords.set(1.0f-(float)i/(size-1),1.0f-(float)j/(size-1)); 
         vertex.TCoords.set(((float)i/(size-1)),(float)j/(size-1));
		 
		 float t2i=((int)i%((size)/detail))*
							((float)detail/(size-1));
		 float t2j=((int)j%((size)/detail))*
							((float)detail/(size-1));
			
		 int offi=(int)i/(size/detail);
		 int offj=(int)j/(size/detail);
					 
		 vertex.TCoords2.set(offi+t2i,offj+t2j);
         buffer.Indices.push_back(buffer.Indices.size()); 
         buffer.Vertices.push_back(vertex); 
      } 

      void CLMTerrainSceneNode::OnPreRender() 
      { 
         if(IsVisible) 
            SceneManager->registerNodeForRendering(this,SNRT_DEFAULT); 
          
         ISceneNode::OnPreRender(); 

         AbsoluteTransformation.getInverse(invAbsoluteTransform); 
      } 

      void CLMTerrainSceneNode::render() 
      { 
         ICameraSceneNode* camera=SceneManager->getActiveCamera(); 
         driver->setTransform(video::ETS_WORLD, AbsoluteTransformation); 


         if(stub && camera) 
         { 

            aspect = camera->getAspectRatio(); 
            fovy = f32(GRAD_PI)*camera->getFOV(); 
            nearp = camera->getNearValue(); 
            farp = camera->getFarValue(); 
             
            pos.set(camera->getPosition()); 
            tgt.set(camera->getTarget()); 
            up.set(camera->getUpVector()); 

            invAbsoluteTransform.transformVect(pos); 
            invAbsoluteTransform.transformVect(tgt); 
            invAbsoluteTransform.transformVect(up); 

            vector3df dx=tgt-pos; 
            if(dx.getLengthSQ()==0.0) 
            { 
               dx.Z=-1.0f; 
            } 
            // make callbacks to this scenenode :) 
            _self=this; 
             
            driver->setMaterial(Material); 

            stub->draw(res, 
               pos.X,pos.Y,pos.Z, 
               dx.X,dx.Y,dx.Z, 
               up.X,up.Y,up.Z, 
               2.0f*fovy, 
               aspect, 
               nearp, 
               farp); 

            BeginFan(); 

            if(DebugDataVisible) 
            { 
               SMaterial mat; 
               mat.Lighting=false; 
               mat.Wireframe=true; 
               mat.FogEnable=false; 
               mat.EmissiveColor=SColor(255,0,255,0); 
               mat.AmbientColor=SColor(255,0,255,0); 
               mat.DiffuseColor=SColor(255,0,255,0); 
               mat.Texture1=0; 
               mat.Texture2=0; 
               driver->setMaterial(mat); 
               driver->draw3DBox(Box, SColor(255,0,0,255)); 

               stub->draw(res, 
                  pos.X,pos.Y,pos.Z, 
                  dx.X,dx.Y,dx.Z, 
                  up.X,up.Y,up.Z, 
                  2.0f*fovy, 
                  aspect, 
                  nearp, 
                  farp); 

               BeginFan(); 
            } 
             

         } 
      } 

      void CLMTerrainSceneNode::OnPostRender(u32 timeMs) 
      { 
         ISceneNode::OnPostRender(timeMs); 
          
      } 


      f32 CLMTerrainSceneNode::getHeight(int i,int j) 
      { 
         if(stub) 
            return stub->getheight(i,j); 
         else 
            return 0; 
      } 

      f32 CLMTerrainSceneNode::getHeight(float x,float z) 
      { 
         if(stub) 
            return stub->getheight(x,z); 
         else 
            return 0; 
      } 

      f32 CLMTerrainSceneNode::getFogHeight(float x,float z) 
      { 
         if(stub) 
            return stub->getfogheight(x,z); 
         else 
            return 0; 
      } 

      vector3df CLMTerrainSceneNode::getNormal(float x,float z) 
      { 
         vector3df n(0,1,0); 
         if(stub) 
         { 
            f32 nx,ny,nz; 
            stub->getnormal(x,z,&nx,&ny,&nz); 
            n.set(nx,ny,nz); 
         } 
         return n; 
      } 


   } 
}
knightoflight
Posts: 199
Joined: Sun Aug 24, 2003 5:47 pm
Location: Germany

Post by knightoflight »

thanks UzMaN, cool. maybe you can write a little header in the code, whats the difference in working to the node of zola, please ? (edit:) You know what i mean ? If a beginner/user sees the two nodes he eventually not understands the difference.
puh
Posts: 356
Joined: Tue Aug 26, 2003 3:53 pm

Post by puh »

It's great that we can use getMesh() from the terrain now, but seems that it's created with lowest LOD.
Look at the picture below - there is yellow lines of Mesh and rest red lines - current terrain. Is it possible to get Mesh from the highest LOD?

Image
ZDimitor
Posts: 202
Joined: Fri Jul 16, 2004 3:27 am
Location: Russia

Post by ZDimitor »

You can did it by hand, like this:

Code: Select all


// global declaration
core::vector2df minPos, maxPos;

//! loads the terrain 
bool CLMTerrainSceneNode::create(IImage* heightmap, ITexture* texture, ITexture* detailmap, 
    f32 gridPointSpacing, f32 heightScale, f32 resolution) 
{   
    ...

    maxPos.X =-1000000;
    maxPos.Y =-1000000;
    minPos.X = 1000000;
    minPos.Y = 1000000;

    bDrawToMesh=true; 
    mesh = new SMesh(); 
    _self=this; 
        stub->draw( 
        res,               // resolution 
        0,2.0f*hmax,0,         // pos 
        0,-1,0,            // dir 
        0,0,1,               // up 
        -ext.X,               // OTHO fovy 
        1,                  // aspect 
        1,                  // near 
        ext.X);                
    BeginFan(); 
    bDrawToMesh=false;           

    ...

    f32 width, length;
    width  = fabs(maxPos.X - minPos.X);    
    length = fabs(maxPos.Y - minPos.Y);    

    s32 collis_buf_size=128;

    f32 currX, currZ;
    vid::S3DVertex vert;

    // this mesh buffer we are using for collision response
    SMeshBuffer *collisionBuffer=new SMeshBuffer();  

    dX = width  / collis_buf_size;
    dZ = length / collis_buf_size;

    for (s32 x=0; x<collis_buf_size; x++)
    {
        for (s32 z=0; z<collis_buf_size; z++)
        {   
            currX = minPos.X+x*dX;
            currZ = minPos.Y+z*dZ;

            vert.Pos = core::vector3df(currX , getHeight(currX,currZ), currZ);
            vert.Normal = core::vector3df(0,1,0);
            vert.Color.set(255,255,255,255);  

            collisionBuffer->Vertices.push_back(vert);            
        }        
    }    

    s32 indA, indB, indC;
    for (x=0; x<collis_buf_size-1; x++)
    {   
        for (s32 z=0; z<collis_buf_size_1; z++)
        {   
            indA = x*collis_buf_size+z;
            indB = (x+1)*collis_buf_size+z+1;
            indC = (x+1)*collis_buf_size+z;

            collisionBuffer->Indices.push_back(indA);
            collisionBuffer->Indices.push_back(indB);
            collisionBuffer->Indices.push_back(indC);  

            indA = x*collis_buf_size+z;
            indB = x*collis_buf_size+z+1;
            indC = (x+1)*collis_buf_size+z+1;

            collisionBuffer->Indices.push_back(indA);
            collisionBuffer->Indices.push_back(indB);
            collisionBuffer->Indices.push_back(indC);  
        }
    } 

    mesh->addMeshBuffer(collisionBuffer);  

    collisionBuffer->drop();

    ....

You must calculate values (minPos, maxPos)
(this you can do with easily in function AddMeshBuffer)

Code: Select all

// adds the meshbuffer to the mesh 
void CLMTerrainSceneNode::addMeshBufferToMesh() 
{ 
   if(buffer.Vertices.size()>0) 
   { 
      //////////////////////////////////////////////
      ////////// you don't need this any more///////
      /*
      // copy the 
      SMeshBuffer* mb=new SMeshBuffer(); 
      */
      ////////// you don't need this any more///////
      //////////////////////////////////////////////

      u32 n=buffer.Vertices.size(); 

      for(u32 i=0; i<n; i++) 
      { 
         //////////////////////////////////////////////
         ////////// you don't need this any more///////
         /*
         // add vertex 
         mb->Vertices.push_back(buffer.Vertices[i]); 
         */
         ////////// you don't need this any more///////
         //////////////////////////////////////////////

         ////////////////////////////////////////
         ////////// add this ////////////////////
         if (buffer.Vertices[i].Pos.X < minPos.X)
            minPos.X = buffer.Vertices[i].Pos.X;

         if (buffer.Vertices[i].Pos.Z < minPos.Y)
            minPos.Y = buffer.Vertices[i].Pos.Z;

         if (buffer.Vertices[i].Pos.X > maxPos.X)
            maxPos.X = buffer.Vertices[i].Pos.X;

         if (buffer.Vertices[i].Pos.Z > maxPos.Y)
            maxPos.Y = buffer.Vertices[i].Pos.Z;   
        ////////// add this ////////////////////
        ////////////////////////////////////////
                
        //////////////////////////////////////////////
        ////////// you don't need this any more///////
        /*
        if(i>1) 
        { 
            mb->Indices.push_back(0); 
            mb->Indices.push_back(i-1); 
            mb->Indices.push_back(i); 
        } 
        */
        ////////// you don't need this any more///////
        //////////////////////////////////////////////

    } 

    //////////////////////////////////////////////
    ////////// you don't need this any more///////
    /*
    mb->Indices.push_back(0); 
    mb->Indices.push_back(n-1); 
    mb->Indices.push_back(1);           

    mesh->addMeshBuffer(mb); 

    mb->drop(); 
    */                                           
    ////////// you don't need this any more///////
    //////////////////////////////////////////////

} 
      
puh
Posts: 356
Joined: Tue Aug 26, 2003 3:53 pm

Post by puh »

Oh, thanks ZDimitor. I'll try that.
By the way in this case I think I can use BoundingBox for minPos and maxPos, as it's already set in function CLMTerrainSceneNode::create(), how do you think?
ZDimitor
Posts: 202
Joined: Fri Jul 16, 2004 3:27 am
Location: Russia

Post by ZDimitor »

You right man!
In that case we don't need function addMeshBufferToMesh() anymore.
zola
Posts: 52
Joined: Thu Jul 15, 2004 2:31 pm
Location: switzerland
Contact:

Post by zola »

Another method to get the right resolution for the mesh reternud by getMesh() would be to give the create method an additional parameter float staticRes which you use for drawing the mesh.

Code: Select all

//! loads the terrain
		bool CLMTerrainSceneNode::create(IImage* heightmap, ITexture* texture, ITexture* detailmap, 
			f32 gridPointSpacing, f32 heightScale, f32 resolution, f32 staticRes)
		{
			if(stub) delete stub;
			if(hfield) delete [] hfield;

			res=resolution;
			dim=gridPointSpacing;
			scale=heightScale;
			size = heightmap->getDimension().Width;
			hfield = new float[size*size];

			vector3df ext(size*dim,0,size*dim);
			f32 hmin=10000000;
			f32 hmax=-10000000;
			for (int i=0; i<size; i++)
				for (int j=0; j<size; j++)
				{
					hfield[i+j*size] = heightmap->getPixel(i,size-1-j).getRed()*scale;
					if(hmin>hfield[i+j*size]) hmin=hfield[i+j*size];
					if(hmax<hfield[i+j*size]) hmax=hfield[i+j*size];
				}
			Box.MinEdge.set(-ext.X/2.0f,hmin,-ext.Z/2.0f);
			Box.MaxEdge.set(ext.X/2.0f,hmax,ext.Z/2.0f);

			stub=new ministub(hfield,
				&size,&dim,1.0f,1.0f,
				CLMTerrainSceneNode::_BeginFan,
				CLMTerrainSceneNode::_FanVertex,
				0,0,0);

			vertex.Normal.set(0,1,0);
			vertex.Color.set(255,255,255,255);

			Material.Texture1=texture;
			Material.Texture2=detailmap;

			bDrawToMesh=true;
			mesh = new SMesh();
			_self=this;
			stub->draw(
				staticRes,					// resolution  ATTENTION THIS CHANGED!!!!!!
				0,2.0f*hmax,0,			// pos 
				0,-1,0,					// dir
				0,0,1,					// up
				-ext.X,					// OTHO fovy
				1,						// aspect
				1,						// near
				ext.X);					
			BeginFan();
			bDrawToMesh=false;

			return true;
		}
change the method signature in the header file accordingly.

You might keep in mind that a finer collision mesh will result in longer collision detection time, obviously. So, You might want to play around with the staticRes parameter. Btw. the factor must be really high if you want a finer mesh 100000000.0f or something like that, just to give an impression of the magnitude.

to get the bounding box you can also call getBoundingBox() on the mesh (maybe you also need to call recalculateBoundingBox() first )

Cheers
Tom
puh
Posts: 356
Joined: Tue Aug 26, 2003 3:53 pm

Post by puh »

You might keep in mind that a finer collision mesh will result in longer collision detection time, obviously.
Yeah, thats true. My FPS drops from 80 to 20 now, and time to create OctTree is much more time consumption.
I'll try to keep my camera moving on terrain without collision with SMesh, but base on getHeight.
Thank you ZDimitor and zola for such quick response.
[EDIT]
I've just try to use TerrainHeightAnimator on Camera node (with attached CollisionResponceAnimator) and seems to me it works OK. The only difficult thing is when one animator changes with another (when camera exits from the house with it's own flor to terrain).
Spidla
Posts: 31
Joined: Tue Nov 25, 2003 3:46 pm
Location: Czech Republic
Contact:

Post by Spidla »

Hey,
this terrain class is really great. I am using it instead
of Irrlicht`s terrain node with no problems.
and thanks for that collision animator.

The last think I need is detail map. :D :D :D
Can someone do it for me ??
I dont understand the code inside this class.
ttom
Posts: 43
Joined: Tue Aug 26, 2003 3:43 am
Location: Taiwan

Post by ttom »

Hi all:
This lib is realy great. I only download it and compiler it. It work great.
Also thanks to zola,ZDimitor,UzMaN... u really great to do thsi job. I think if i have a big detailmap (may be 1Gbyte),how to solve it.have any idea.
ttom
zola
Posts: 52
Joined: Thu Jul 15, 2004 2:31 pm
Location: switzerland
Contact:

Post by zola »

ttom wrote:Hi all:
This lib is realy great. I only download it and compiler it. It work great.
Also thanks to zola,ZDimitor,UzMaN... u really great to do thsi job. I think if i have a big detailmap (may be 1Gbyte),how to solve it.have any idea.
ttom
Hi

One solution would be to devide the terrain and the detailmap into smaller parts and then only display the terrain that is visible.
For a way how to do this You could take a look at Saigumis Seamless world tutorial. It's rather old but I guess You will see what the idea behind it is.

Btw, You forgot the mention KnightOfLight who started all this and Stefan who wrote libMini :)

Regards
Tom
Deg

Still alive?

Post by Deg »

Hello,

I read this thread and became quite interested in this but seeing that the last post was more then a year ago i am wondering if it's still viable as a terrainnode or that it is replaced by the next big thing(tm)

regards,
Deg
Post Reply