Terrain height painting with brush

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Luiz
Posts: 4
Joined: Sat Aug 20, 2011 4:54 pm

Terrain height painting with brush

Post by Luiz »

Hello everybody.

After reading Katsankat's post (that you can find here http://irrlicht.sourceforge.net/forum/v ... =9&t=32251), I wanted to do something more flexible, I wanted to deform my terrain without doing it vertex by vertex. So I changed Katsankat's code. Here it is :

Code: Select all

#include <irr/irrlicht.h>
 
using namespace irr;
 
 
video::IImage* heightmap;
video::IImage* brush;
 
scene::ITerrainSceneNode* terrain;
 
/*==============================================================================
  Receiver class
==============================================================================*/
class MyEventReceiver : public IEventReceiver {
    bool KeyIsDown[KEY_KEY_CODES_COUNT];
    bool LEFTBUTTONCLICKED;
    bool RIGHTBUTTONCLICKED;
 
    public:
        virtual bool OnEvent(const SEvent& event){
            if (event.EventType == irr::EET_KEY_INPUT_EVENT)
                KeyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
 
            if(event.EventType == EET_MOUSE_INPUT_EVENT){
                if(event.MouseInput.Event==EMIE_LMOUSE_PRESSED_DOWN) LEFTBUTTONCLICKED = true;
                else if(event.MouseInput.Event==EMIE_LMOUSE_LEFT_UP) LEFTBUTTONCLICKED = false;
                else if(event.MouseInput.Event==EMIE_RMOUSE_PRESSED_DOWN) RIGHTBUTTONCLICKED = true;
                else if(event.MouseInput.Event==EMIE_RMOUSE_LEFT_UP) RIGHTBUTTONCLICKED = false;
            }
 
            return false;
        }
 
        virtual bool IsKeyDown(EKEY_CODE keyCode) const { return KeyIsDown[keyCode]; }
        virtual bool IsLMBDown() const { return LEFTBUTTONCLICKED; }
        virtual bool IsRMBDown() const { return RIGHTBUTTONCLICKED; }
 
        MyEventReceiver(){
            for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
                KeyIsDown[i] = false;
 
            LEFTBUTTONCLICKED = RIGHTBUTTONCLICKED = false;
        }
};
 
/*==============================================================================
  Raise or lower terrain (selected vertice)
==============================================================================*/
void RaiseTerrainVertex(s32 index, f32 step, bool up){
    scene::IMesh* pMesh = terrain->getMesh();
 
    s32 heightmapWidth = heightmap->getDimension().Width;
    s32 heightmapHeight = heightmap->getDimension().Height;
 
    s32 b;
    for (b=0; b<pMesh->getMeshBufferCount(); ++b){
        scene::IMeshBuffer* pMeshBuffer = pMesh->getMeshBuffer(b);
        // skip mesh buffers that are not the right type
        if (pMeshBuffer->getVertexType() != video::EVT_2TCOORDS) continue;
 
        video::S3DVertex2TCoords* pVertices = (video::S3DVertex2TCoords*)pMeshBuffer->getVertices();
 
        s32 brushWidth = brush->getDimension().Width;
        s32 brushHeight = brush->getDimension().Height;
 
        for(int y = 0; y < brushHeight; y++){
            for(int x = 0; x < brushWidth; x++){
                video::SColor brushPixel = brush->getPixel(x, y);
 
                if((index-(brushWidth/2)-((brushWidth/2)*heightmapWidth) + (x+(y*heightmapWidth))) >= 0){
                    f32 hy = pVertices[index-(brushWidth/2)-((brushWidth/2)*heightmapWidth) + (x+(y*heightmapWidth))].Pos.Y;
                    f32 bp = brushPixel.getRed()/255.0*step;
                    bp = (up)?bp:-bp;
 
                    if(bp > 0 && hy+bp <= 255)
                        pVertices[index-(brushWidth/2)-((brushWidth/2)*heightmapWidth) + (x+(y*heightmapWidth))].Pos.Y = hy+bp;
                }
            }
        }
    }
 
    // force terrain render buffer to reload
    terrain->setPosition(terrain->getPosition());
}
 
/*==============================================================================
  Save file
==============================================================================*/
void save (video::IVideoDriver* driver){
    s32 heightmapWidth = heightmap->getDimension().Width;
    s32 heightmapHeight = heightmap->getDimension().Height;
 
    const core::dimension2d<u32> dim (heightmapWidth, heightmapHeight);
    video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, dim);
 
    video::S3DVertex2TCoords* verts = (video::S3DVertex2TCoords*)terrain->getMesh()->getMeshBuffer(0)->getVertices();
 
    for (u32 y= 0, i = 0; y < heightmapHeight; y++){
        for(u32 x = 0; x < heightmapWidth; i++, x++){
            u8 py = (u8)verts[i].Pos.Y;
            img->setPixel((heightmapHeight-1)-y, x, video::SColor(0, py, py, py));
        }
    }
 
    driver->writeImageToFile(img, "heightmap.bmp", 0);
    img->drop();
}
 
 
int main(){
    MyEventReceiver receiver;
 
    IrrlichtDevice* device = createDevice(video::EDT_OPENGL, core::dimension2d<u32>(800, 600), 32, false, true, false, &receiver);
 
    video::IVideoDriver* driver = device->getVideoDriver();
    scene::ISceneManager* smgr = device->getSceneManager();
 
    device->getCursorControl()->setVisible(false);
 
    io::path heightmapFile = "heightmap.bmp";
    heightmap = driver->createImageFromFile(heightmapFile);
    brush = driver->createImageFromFile("brush.png");
 
    terrain = smgr->addTerrainSceneNode(heightmapFile, 0, -1, core::vector3df(0, 0, 0));
    terrain->setScale(core::vector3df(32, 5, 32));
    terrain->setMaterialFlag(video::EMF_LIGHTING, false);
 
    terrain->setPosition(terrain->getPosition());
 
    scene::ITriangleSelector* selector = smgr->createTerrainTriangleSelector(terrain, 0);
 
    // Arrow
    scene::ISceneNode* arrow = smgr->addAnimatedMeshSceneNode(smgr->addArrowMesh("arrow", video::SColor(255, 255, 0, 0), video::SColor(255, 0, 255, 0)), NULL);
    arrow->setMaterialFlag(video::EMF_LIGHTING, false);
    arrow->setScale(core::vector3df(10, 10, 10));
    arrow->setRotation(core::vector3df(0,0,180));
 
    scene::ICameraSceneNode* cam = smgr->addCameraSceneNodeFPS(0, 100.0f, .1f);
    cam->setPosition(core::vector3df(-100,500,100));
    cam->setTarget(core::vector3df(0,0,0));
 
    ITimer* irrTimer = device->getTimer();
    u32 then = 0, then30 = 0;
    char c[24];
    f32 step = 2.f;
    bool wireframe = false;
 
    while(device->run()){
        if(device->isWindowActive()){
            u32 now = irrTimer->getTime();
 
            if (then30 < now){
                if(receiver.IsKeyDown(irr::KEY_ESCAPE)) break;
 
                if (receiver.IsKeyDown(irr::KEY_KEY_W) && then < now){
                    wireframe = !wireframe;
                    terrain->setMaterialFlag(video::EMF_WIREFRAME, wireframe);
 
                    then = now + 300;
                }
 
                if (receiver.IsKeyDown(irr::KEY_F4) && then < now){
                    step += 1.f;
 
                    then = now + 100;
                } else if (receiver.IsKeyDown(irr::KEY_F5) && then < now && step > 0){
                    step -= 1.f;
 
                    then = now + 100;
                }
 
                if(receiver.IsKeyDown(irr::KEY_KEY_S))
                    save (driver);
 
                // move the arrow to the nearest vertex ...
                //400, 300 si la résolution utilisée est 800x600
                const core::position2di clickPosition = core::position2di(400, 300);
                const core::line3df ray = smgr->getSceneCollisionManager()->getRayFromScreenCoordinates(clickPosition, cam);
                core::vector3df pos;
                core::triangle3df Tri;
 
                const scene::ISceneNode* out;
                if (smgr->getSceneCollisionManager()->getCollisionPoint(ray, selector, pos, Tri, out)){
                    //arrow->setPosition(pos);
                    static const s32 scale = 32; // terrain is scaled 32X
                    static const s32 size = 512; // heightmap is 512x512 pixels
                    s32 x = (s32)(pos.X / scale);
                    s32 z = (s32)(pos.Z / scale);
                    s32 index = x * size + z;
 
                    // ... Move it if clicked
                    if(receiver.IsLMBDown() || receiver.IsRMBDown() && then < now){
                        RaiseTerrainVertex(index, step, receiver.IsLMBDown());
                        then = now + 100;
                    }
 
                    x *= scale;
                    z *= scale;
 
                    arrow->setPosition(core::vector3df(x, terrain->getHeight(x, z) + 20, z));
                }
 
                driver->beginScene(true, true, video::SColor(255, 255, 255, 255));
                smgr->drawAll();
                driver->endScene();
 
                then30 = now + 30;
            }
        }
    }
 
    heightmap->drop();
    brush->drop();
 
    device->closeDevice();
    device->drop();
 
    return 0;
}
I used a black 512x512 image as heightmap, and that brush : Image

Have fun :) .
Last edited by Luiz on Sat Sep 10, 2011 1:01 pm, edited 2 times in total.
netpipe
Posts: 670
Joined: Fri Jun 06, 2008 12:50 pm
Location: Edmonton, Alberta, Canada
Contact:

Re: Terrain painting with brush

Post by netpipe »

awesomeness thanks!
Live long and phosphor!
-- https://github.com/netpipe/Luna Game Engine Status 95%
Luiz
Posts: 4
Joined: Sat Aug 20, 2011 4:54 pm

Re: Terrain height painting with brush

Post by Luiz »

Here you are 2 screenshots textured and wireframe :
Image
Image

And it would be cool to share the brushes if you make some.

mines are : ImageImageImageImage
tbw
Posts: 59
Joined: Sat Jan 15, 2011 9:51 am
Location: Germany

Re: Terrain height painting with brush

Post by tbw »

Really good job! thank you for sharing.
fmx

Re: Terrain height painting with brush

Post by fmx »

Interesting, thanks for sharing
Brainsaw
Posts: 1183
Joined: Wed Jan 07, 2004 12:57 pm
Location: Bavaria

Re: Terrain height painting with brush

Post by Brainsaw »

This is great. I think I'll give it a try and maybe create an editor of my own from it.
Dustbin::Games on the web: https://www.dustbin-online.de/

Dustbin::Games on facebook: https://www.facebook.com/dustbingames/
Dustbin::Games on twitter: https://twitter.com/dustbingames
netpipe
Posts: 670
Joined: Fri Jun 06, 2008 12:50 pm
Location: Edmonton, Alberta, Canada
Contact:

Re: Terrain height painting with brush

Post by netpipe »

if you guys figure out how to fix the scaling issue before i do plz lemme know. move the camera a bit and it no longer raises above the arrow
Live long and phosphor!
-- https://github.com/netpipe/Luna Game Engine Status 95%
shadowslair
Posts: 758
Joined: Mon Mar 31, 2008 3:32 pm
Location: Bulgaria

Re: Terrain height painting with brush

Post by shadowslair »

tecan wrote:if you guys figure out how to fix the scaling issue before i do plz lemme know. move the camera a bit and it no longer raises above the arrow
Probably your heightmap resolution is not the same as the one the example assumes?

Code: Select all

static const s32 size = 512; //heightmap is 512x512 pixels
In this case heightmap needs to be 512x512
"Although we walk on the ground and step in the mud... our dreams and endeavors reach the immense skies..."
Luiz
Posts: 4
Joined: Sat Aug 20, 2011 4:54 pm

Re: Terrain height painting with brush

Post by Luiz »

Glad to see that my code interest you, I'll continue to improve it (or die trying :twisted: ).
Brainsaw wrote:This is great. I think I'll give it a try and maybe create an editor of my own from it.
That's what I'm doing actually, because I was looking for a level editor for GNU/Linux and I finaly figured out that there's nothing else than Blender for terrain editing (may be i'm wrong, so if you got something i realy would like to know what it is, and what it worth as level/terrain editor).
Brainsaw
Posts: 1183
Joined: Wed Jan 07, 2004 12:57 pm
Location: Bavaria

Re: Terrain height painting with brush

Post by Brainsaw »

Luiz wrote:Glad to see that my code interest you, I'll continue to improve it (or die trying :twisted: ).
Brainsaw wrote:This is great. I think I'll give it a try and maybe create an editor of my own from it.
That's what I'm doing actually, because I was looking for a level editor for GNU/Linux and I finaly figured out that there's nothing else than Blender for terrain editing (may be i'm wrong, so if you got something i realy would like to know what it is, and what it worth as level/terrain editor).
I started a project yesterday, but I spent all the time in creating a texture from the heightmap (and failed), so I'll first add the necessary features for height painting. I'm programming on Windows, but as this project will also be Irrlicht-only it should compile on Linux as well. I'll post a link here once I got a first version ready.
Dustbin::Games on the web: https://www.dustbin-online.de/

Dustbin::Games on facebook: https://www.facebook.com/dustbingames/
Dustbin::Games on twitter: https://twitter.com/dustbingames
netpipe
Posts: 670
Joined: Fri Jun 06, 2008 12:50 pm
Location: Edmonton, Alberta, Canada
Contact:

Re: Terrain height painting with brush

Post by netpipe »

http://www.xup.in/dl,85839677/terrain-painting.7z/

not much more just a more complete project to work on , happy editing and thanks giving
Live long and phosphor!
-- https://github.com/netpipe/Luna Game Engine Status 95%
wiedzmin112
Posts: 30
Joined: Tue Oct 18, 2011 3:48 pm

Re: Terrain height painting with brush

Post by wiedzmin112 »

Hi!
I write new save and load function.
These not use heightmap but bin format.
Now you can create higher hills :)
Binary Format.h

Code: Select all

 
#ifndef _BINARY_FORMAT_H_
#define _BINARY_FORMAT_H_
 
#include <string>
#include <fstream>
#include <vector>
//Binary format VERSION 0.1 alpha
 
 
namespace BinaryFormat
{
        class Root
        {
        private:
                Root() {}
                Root(Root&) {}
        public:
                std::ofstream ostream;
                std::ifstream istream;
                static Root &Singleton()
                {
                        static Root Instance;
                        return Instance;
                }
        };
 
        //Binary - base class for all object's.
        //version 1.0 2011-09-14
        template<class type>
        class Binary
        {
        public:
                Binary() {}
 
                Binary(type obj) : object(obj) {}
 
                type object;
 
                static void save(Binary<type> *obj)
                {
                        Root::Singleton().ostream.write((char*)obj,sizeof(Binary<type>));
                }
 
                static void save(type obj)
                {
                        Root::Singleton().ostream.write((char*)new Binary<type>(obj),sizeof(Binary<type>));
                }
 
                static void load(Binary<type> *obj)
                {
                        Root::Singleton().istream.read((char*)obj,sizeof(Binary<type>));
                }
        };
 
        //Some useful types for creating object's.
        typedef Binary<int> Int;
        typedef Binary<float> Float;
        typedef Binary<bool> Bool;
        typedef Binary<char> Char;
 
        class String
        {
        public:
                std::string object;
                static void save(String* s)
                {
                        Int::save(s->object.length());
                        for(unsigned int i=0;i<s->object.length();i++)
                        {
                                Char::save(s->object[i]);
                        }
                }
 
                static void load(String *s)
                {
                        Int *length=new Int(-1);
                        Int::load(length);
                        for(int i=0;i<length->object;i++)
                        {
                                Char *temp=new Char();
                                Char::load(temp);
                                s->object+=temp->object;
                        }
                }
        };
 
 
 
        template<class type>
        class BinaryVector
        {
        public:
                BinaryVector() {}
                
                BinaryVector(std::vector<type> &obj) : object(obj) {}
 
                std::vector<type> object;
 
                static void saveptr(std::vector<type*> obj)
                {
                        Int::save(obj.size());//save vector size
                        for(unsigned int i=0; i < obj.size(); i++)
                                obj[i]->save();
                }
 
                static void saveptrObject(std::vector<type*> obj)
                {
                        Int::save(obj.size());//save vector size
                        for(unsigned int i=0; i < obj.size(); i++)
                                type::save(obj[i]);
                }
 
                static void save(std::vector<type> obj)
                {
                        Int::save(obj.size());//save vector size
                        for(unsigned int i=0; i < obj.size(); i++)
                                obj[i].save();
                }
 
                static void load(std::vector<type> &obj)
                {
                        Int *size=new Int();
                        Int::load(size);
                        for(int i=0; i < size->object; i++)
                        {
                                type temp;
                                temp.load();
                                obj.push_back(temp);
                        }
                }
 
                static void loadptr(std::vector<type*> &obj)
                {
                        Int *size=new Int();
                        Int::load(size);
                        for(int i=0; i < size->object; i++)
                        {
                                type *temp=new type();
                                temp->load();
                                obj.push_back(temp);
                        }
                }
 
                static void loadptrObject(std::vector<type*> &obj)
                {
                        Int *size=new Int();
                        Int::load(size);
                        for(int i=0; i < size->object; i++)
                        {
                                type *temp=new type();
                                type::load(temp);
                                obj.push_back(temp);
                        }
                }
        };
 
        class Terrain
        {
        public:
 
                Float ***Height;
 
                Terrain() {}
                int s;
                void GenerateTable(int size)
                {
                        s=size;
                        Height=new Float**[size];
                        for(int i=0;i<size;i++)
                        {
                                Height[i]=new Float*[size];
                                for(int x=0;x<size;x++)
                                        Height[i][x]=new Float();
                        }
                }
 
                void save()
                {
                        Int::save(s);
                        for(int i=0;i<s;i++)
                        {
                                for(int x=0;x<s;x++)
                                {
                                        Float::save(Height[i][x]);
                                }
                        }
                }
 
                void load()
                {
                        Int *size;
                        Int::load(size);
                        GenerateTable(size->object);
                        for(int i=0;i<s;i++)
                        {
                                for(int x=0;x<s;x++)
                                {
                                        Float::load(Height[i][x]);
                                }
                        }
                }
        };
}
 
 
#endif
//wiedzmin112 2011-09-15
//For non-commercial use
 

And modifed main.cpp

Code: Select all

 
...
#include "Binary Format.h"
 
...
 
/*==============================================================================
Save file
==============================================================================*/
void save (video::IVideoDriver* driver){
        BinaryFormat::Root::Singleton().ostream=std::ofstream("binTerrain",std::ios::binary);
        BinaryFormat::Terrain *bin=new BinaryFormat::Terrain();
 
        s32 heightmapWidth = heightmap->getDimension().Width;
        s32 heightmapHeight = heightmap->getDimension().Height;
 
        if(heightmapWidth!=heightmapHeight)
                return;
        bin->GenerateTable(heightmapWidth);
 
 
        video::S3DVertex2TCoords* verts = (video::S3DVertex2TCoords*)terrain->getMesh()->getMeshBuffer(0)->getVertices();
 
        for (s32 y= 0, i = 0; y < heightmapHeight; y++){
                for(s32 x = 0; x < heightmapWidth; i++, x++){
                        u8 py = (u8)verts[i].Pos.Y;
                        bin->Height[y][x]->object=verts[i].Pos.Y;
                }
        }
 
 
 
        //and save to binary
 
        bin->save();
        BinaryFormat::Root::Singleton().ostream.close();
}
 
/*==============================================================================
Load file
==============================================================================*/
 
void load()
{
        BinaryFormat::Root::Singleton().istream=std::ifstream("binTerrain",std::ios::binary);
        BinaryFormat::Terrain *bin=new BinaryFormat::Terrain();
        
        s32 heightmapWidth = heightmap->getDimension().Width;
        s32 heightmapHeight = heightmap->getDimension().Height;
 
        if(heightmapWidth!=heightmapHeight)
                return;
        bin->GenerateTable(heightmapWidth);
        bin->load();
        video::S3DVertex2TCoords* verts = (video::S3DVertex2TCoords*)terrain->getMesh()->getMeshBuffer(0)->getVertices();
 
        for (s32 y= 0, i = 0; y < heightmapHeight; y++)
        {
                for(s32 x = 0; x < heightmapWidth; i++, x++)
                {
                        verts[i].Pos.Y=bin->Height[y][x]->object;
                }
        }
        terrain->setPosition(terrain->getPosition());
        BinaryFormat::Root::Singleton().istream.close();
}
int main(){
        MyEventReceiver receiver;
 
        IrrlichtDevice* device = createDevice(video::EDT_DIRECT3D9, core::dimension2d<u32>(800, 600), 32, false, true, false, &receiver);
 
 
        video::IVideoDriver* driver = device->getVideoDriver();
        scene::ISceneManager* smgr = device->getSceneManager();
 
        device->getCursorControl()->setVisible(false);
 
        io::path heightmapFile = "heightmap.bmp";
        heightmap = driver->createImageFromFile(heightmapFile);
        brush = driver->createImageFromFile("brush.png");
 
        terrain = smgr->addTerrainSceneNode(heightmapFile);
        terrain->setScale(core::vector3df(32, 5, 32));
        terrain->setMaterialFlag(video::EMF_LIGHTING, false);
 
        terrain->setMaterialTexture(0,
                driver->getTexture("grass.jpg"));
        terrain->scaleTexture(100, 100);
        terrain->setPosition(terrain->getPosition());
 
        scene::ITriangleSelector* selector = smgr->createTerrainTriangleSelector(terrain, 0);
 
        // Arrow
        scene::ISceneNode* arrow = smgr->addAnimatedMeshSceneNode(smgr->addArrowMesh("arrow", video::SColor(255, 255, 0, 0), video::SColor(255, 0, 255, 0)), NULL);
        arrow->setMaterialFlag(video::EMF_LIGHTING, false);
        arrow->setScale(core::vector3df(10, 10, 10));
        arrow->setRotation(core::vector3df(0,0,180));
 
        scene::ICameraSceneNode* cam = smgr->addCameraSceneNodeFPS(0, 100.0f, .1f);
        cam->setPosition(core::vector3df(-100,500,100));
        cam->setTarget(core::vector3df(0,0,0));
 
        ITimer* irrTimer = device->getTimer();
        u32 then = 0, then30 = 0;
        f32 step = 2.f;
        bool wireframe = false;
 
        load();
        ...
}
 


Soory for my bad english :)


Have fun!
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Re: Terrain height painting with brush

Post by hybrid »

Not to stop you from re-inventing the wheel. But Irrlicht has a RAW loader for terrain, which also supports 32bit floats. You can simply pass in the binary file and some parameters to let Irrlicht know about the shape of the information.
wiedzmin112
Posts: 30
Joined: Tue Oct 18, 2011 3:48 pm

Re: Terrain height painting with brush

Post by wiedzmin112 »

Thanks for reply but i wrote this format specially for my world editor(i want to be all objects in one file).
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Re: Terrain height painting with brush

Post by hybrid »

Well, the format is not the point. You can just remove about 95% of your loading code by simply using the RAW loader (just one line after terrain creation). And it's also much faster, because you can create the point directly t the proper places.
Post Reply