Is it possible to morph a mesh in Irrlicht?

If you are a new Irrlicht Engine user, and have a newbie-question, this is the forum for you. You may also post general programming questions here.
Post Reply
pir
Posts: 24
Joined: Wed Apr 29, 2009 8:15 pm

Is it possible to morph a mesh in Irrlicht?

Post by pir »

Hi!

I have a character which I want to make thin or fat. And I want to be able to pick different body parts to modify in this way. I've found posts talking about morphing meshes, but I can't find that functionality in Irrlicht.
Is it possible to morph a mesh in Irrlicht?
Do someone know any code snippets that shows how to do this?


This is an old post I've found about the subject:
http://irrlicht.sourceforge.net/phpBB2/ ... ph&start=0

I'm greatful for any suggestions.
BR
christianclavet
Posts: 1638
Joined: Mon Apr 30, 2007 3:24 am
Location: Montreal, CANADA
Contact:

Post by christianclavet »

Yes. I think it's possible. But the morph targets must be created in a 3D application before. The morphing animation will also need to be created in the 3D application (as making a fat man going slim). This animation will be saved then in the proper format that support vertex animation.

The MD2 use this king of vertex animation, and I think DirectX and B3D can use Bone and vertex animations.

Then in IRRlicht, you simply play back that animation.
pir
Posts: 24
Joined: Wed Apr 29, 2009 8:15 pm

Post by pir »

ok, I was hoping that I only needed to create the morph targets with a 3d application and then compute the morph deformation in Irrlicht. If I use the same mesh as base for modeling, I'm hoping that the index of the indices are not changed.

Too bad that fmx didn't show any code on how he solved it. It would be interesting to see his solution.
kh_
Posts: 78
Joined: Fri May 19, 2006 4:29 pm

Post by kh_ »

It is fairly easy to do in Irrlicht. I use it for sails on a sailboat - with blending.
pir wrote:ok, I was hoping that I only needed to create the morph targets with a 3d application and then compute the morph deformation in Irrlicht. If I use the same mesh as base for modeling, I'm hoping that the index of the indices are not changed.
.
That should work.
just use IMesh->getMeshBuffer(0)->getVertices() for each pose and copy them all into 1 array. Then you can use 2 pointers and pointer arithmetic to get the start positions(in the array) for 2 poses to morph between and use vector3d.interpolate to get the final vertex position and set it to the mesh buffer of the visible mesh. You might need to do normals too. You can also blend between morphs using interpolation.
I use Milkshape and just start each pose with the relaxed pose, and after moving the verts, save it with a new name.( Haven't tried it yet,but it could be possible to have them all in 1 file, with unique materials,it might also possible to use bones then export the mesh only.)
Once its working you can save them to a file so you won't need to reload the meshes.
I'm a bit busy but I'll try to dig up some code this weekend.
You can probably find code if you do enough searching. try "MeshBuffer"
P.S.
The first thing to do is look-up interpolation on Wikipedia if you don't already understand it.
pir
Posts: 24
Joined: Wed Apr 29, 2009 8:15 pm

Post by pir »

Thank you for your tips!

I'll try to interpolate between two meshes. At the moment I'm trying to find out how to manipulate the mesh used by the skinned animation. Apparently I'm missunderstanding something important when doing this, since my manipulations do not show. I guess I need to study the SSkinnedMeshBuffer and CSkinnedMesh code more...

This is what I've tried so far (I'm only trying to see if I can deform the mesh in some way).

Code: Select all

	

// m_mesh is a IAnimatedMeshSceneNode
void deform( IAnimatedMeshSceneNode* aMesh)
{
     ISkinnedMesh* mesh = 
         static_cast< ISkinnedMesh*>( aMesh->getMesh( ));

     array<SSkinMeshBuffer*>& skin = mesh->getMeshBuffers( );
	
     SSkinMeshBuffer* skinBuffer = skin[ 0];

     const u32 vertexCount = skinBuffer->getVertexCount( );
    S3DVertex* vertices     = static_cast< S3DVertex*>(
    skinBuffer->getVertices( ));

    // try to move half of the vertices to deform the skinned mesh
    for( u32 i = 0; i < vertexCount / 2; i++)
   {
	    vertices[ i].Pos += 1000.0f;
   }// for i

}// deform()
It looks like CAnimatedMeshMD3::buildVertexArray() contain code for interpolation.
pir
Posts: 24
Joined: Wed Apr 29, 2009 8:15 pm

Post by pir »

seems like calling CSkinnedMesh::finalize() after manipulating the mesh lets me see the changes I made. The drawback is that the animations stop working after doing this...
kh_
Posts: 78
Joined: Fri May 19, 2006 4:29 pm

Post by kh_ »

OK here is a quick and dirty example. It should at least get you started. I'll try to cleanup/finish it when I get time.
There are 3 meshes used. The visible one and 2 morhped ones.You will need to supply your own meshes. It should be possible to do it with only 2.

Code: Select all

/** Example 004 Movement
modified by KH. June 13, 2009
*/


#include <irrlicht.h>
#include <iostream>

using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;


class CMorphTargetAnimation
{
   public:

   scene::ISceneManager* smgr;
   scene::IMeshSceneNode * node;
   s32 poseCount;
   s32 vertexCount;
   ///Here I'm using a vector3df array .You could also use a Vertex  or float array.
   array<vector3df> VectorArray;
   vector3df* pSourceVerts;
   vector3df* pDestinationVerts;

   video::S3DVertex* pFinalVerts;
   s32  src,dest;
   f32 time;


    CMorphTargetAnimation(scene::ISceneManager* smgr_,scene::IMeshSceneNode * node_):smgr(smgr_),node(node_),poseCount(0),vertexCount(0)
    ,pSourceVerts(0),pDestinationVerts(0),pFinalVerts(0),src(0),dest(0),time(0)
    {
         _IRR_DEBUG_BREAK_IF(smgr)
         _IRR_DEBUG_BREAK_IF(node)
          node->grab();
          smgr->grab();
          IMesh* msh = node->getMesh();
          msh->grab();

//from what I've seen in the forum if you don't remove it from the cache
//any other nodes that use the same mesh will also get morphed
          smgr->getMeshCache()->removeMesh (msh);
          pFinalVerts = (S3DVertex*)msh->getMeshBuffer(0)->getVertices();
          vertexCount = msh->getMeshBuffer(0)->getVertexCount();
///scene::IMeshCache::removeMesh  ( const IMesh *const   mesh   )

    }

    s32 addPose(IMesh* mesh,bool remove = 1)
    {
       if(!mesh) return 0;
       IMeshBuffer* meshBuff = mesh->getMeshBuffer(0);
       video::S3DVertex* pvertBuff = (video::S3DVertex*) meshBuff->getVertices();

       if(vertexCount != meshBuff->getVertexCount())
            return 0;

       for (u32 i=0; i <vertexCount; i++)
       {
          VectorArray.push_back(pvertBuff[i].Pos);
          ///VertexArray.push_back(pvertBuff[i]);
       }
       poseCount++;

       dest=poseCount-1;

       pSourceVerts = VectorArray.pointer();
       pDestinationVerts = VectorArray.pointer() + (dest*vertexCount);

       if(remove)
      {
           smgr->getMeshCache()->removeMesh (mesh);
      }
       return poseCount;

    }

    void destroy()
    {
        if(node)
        {
            node->getMesh()->drop();
            node->removeAll();
            node->remove();
        }
         node=0;
         if(smgr) smgr->drop();
    }

    ~CMorphTargetAnimation()
    {
         destroy();
    }


    void swapPoses()
    {
          vector3df* tmp = pSourceVerts;
          pSourceVerts = pDestinationVerts;
          pDestinationVerts = tmp;
    }

    void  animatePoses(f32 speed)
    {
        time+=speed;
        if(time>1.f)
        {
           ///to do this as simply as I can,I'll just swap poses
           ///another way is to mult. time by a + or - direction
          swapPoses();
          time=0;
        }

        if (poseCount>1)
        {
            for (int i=0; i< vertexCount; i++)
            {
                 pFinalVerts[i].Pos = pSourceVerts[i].getInterpolated(pDestinationVerts[i],time);
            }
        }
    }


};


class MyEventReceiver : public IEventReceiver
{
public:
	// This is the one method that we have to implement
	virtual bool OnEvent(const SEvent& event)
	{
		// Remember whether each key is down or up
		if (event.EventType == irr::EET_KEY_INPUT_EVENT)
			KeyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;

		return false;
	}

	// This is used to check whether a key is being held down
	virtual bool IsKeyDown(EKEY_CODE keyCode) const
	{
		return KeyIsDown[keyCode];
	}

	MyEventReceiver()
	{
		for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
			KeyIsDown[i] = false;
	}

private:
	// We use this array to store the current state of each key
	bool KeyIsDown[KEY_KEY_CODES_COUNT];
};




CMorphTargetAnimation* Morph;


int main()
{
	// let user select driver type

	video::E_DRIVER_TYPE driverType = video::EDT_DIRECT3D9;

	printf("Please select the driver you want for this example:\n"\
		" (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
		" (d) Software Renderer\n (e) Burning's Software Renderer\n"\
		" (f) NullDevice\n (otherKey) exit\n\n");

	char i;
	std::cin >> i;

	switch(i)
	{
		case 'a': driverType = video::EDT_DIRECT3D9;break;
		case 'b': driverType = video::EDT_DIRECT3D8;break;
		case 'c': driverType = video::EDT_OPENGL;   break;
		case 'd': driverType = video::EDT_SOFTWARE; break;
		case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
		case 'f': driverType = video::EDT_NULL;     break;
		default: return 0;
	}

	// create device
	MyEventReceiver receiver;

	IrrlichtDevice* device = createDevice(driverType,
			core::dimension2d<u32>(640, 480), 16, false, false, false, &receiver);

	if (device == 0)
		return 1; // could not create selected driver.

	video::IVideoDriver* driver = device->getVideoDriver();
	scene::ISceneManager* smgr = device->getSceneManager();

	/*
	Create the node which will be moved with the WSAD keys. We create a
	sphere node, which is a built-in geometry primitive. We place the node
	at (0,0,30) and assign a texture to it to let it look a little bit more
	interesting. Because we have no dynamic lights in this scene we disable
	lighting for each model (otherwise the models would be black).
	*/
///this is the mesh you will see
	scene::IMeshSceneNode * node = smgr->addMeshSceneNode(smgr->getMesh("../../media/sphere_0.ms3d"));
	if (node)
	{
		//node->setPosition(core::vector3df(0,0,0));
		node->setMaterialTexture(0, driver->getTexture("../../media/wall.bmp"));
		node->setMaterialFlag(video::EMF_LIGHTING, false);


		Morph = new CMorphTargetAnimation(smgr,node);
///these 2 are just temporary to get vertex positions from
		IAnimatedMesh* mesh = smgr->getMesh("../../media/sphere_1.ms3d");
		Morph->addPose(mesh->getMesh(0));


		mesh = smgr->getMesh("../../media/sphere_2.ms3d");
		Morph->addPose(mesh->getMesh(0));



	}



    scene::ISceneNode * PlaneMesh = smgr->addMeshSceneNode(smgr->addHillPlaneMesh("ground",core::dimension2d<f32>(64,64),core::dimension2d<u32>(8,8))->getMesh(0));
    PlaneMesh->setMaterialTexture(0, driver->getTexture("../../media/stones.jpg"));
    PlaneMesh->setMaterialFlag(EMF_LIGHTING, false);


	/*
	To be able to look at and move around in this scene, we create a first
	person shooter style camera and make the mouse cursor invisible.
	*/
	smgr->addCameraSceneNodeFPS();
	smgr->getActiveCamera()->setPosition(core::vector3df(0,20,-50));
	device->getCursorControl()->setVisible(false);



//	gui::IGUIStaticText* help = device->getGUIEnvironment()->addStaticText(
//		L"Press UP/down  left/right keys to start morph", core::rect<s32>(10, 10, 400, 20));
//	diagnostics->setOverrideColor(video::SColor(255, 255, 255, 0));

	/*
	We have done everything, so lets draw it. We also write the current
	frames per second and the name of the driver to the caption of the
	window.
	*/
	int lastFPS = -1;

	// In order to do framerate independent movement, we have to know
	// how long it was since the last frame
	u32 then = device->getTimer()->getTime();

	// This is the movemen speed in units per second.
	const f32 MOVEMENT_SPEED = 0.1f;

	while(device->run())
	{
		// Work out a frame delta time.
		const u32 now = device->getTimer()->getTime();
		const f32 frameDeltaTime = (f32)(now - then) / 1000.f; // Time in seconds
		then = now;

		/* Check if keys W, S, A or D are being held down, and move the
		sphere node around respectively. */
		core::vector3df nodePosition = node->getPosition();
//
//		if(receiver.IsKeyDown(irr::KEY_KEY_W))
//			nodePosition.Y += MOVEMENT_SPEED * frameDeltaTime;
//		else if(receiver.IsKeyDown(irr::KEY_KEY_S))
//			nodePosition.Y -= MOVEMENT_SPEED * frameDeltaTime;
//
//		if(receiver.IsKeyDown(irr::KEY_KEY_A))
//			nodePosition.X -= MOVEMENT_SPEED * frameDeltaTime;
//		else if(receiver.IsKeyDown(irr::KEY_KEY_D))
//			nodePosition.X += MOVEMENT_SPEED * frameDeltaTime;

//		node->setPosition(nodePosition);

        Morph->animatePoses(MOVEMENT_SPEED * frameDeltaTime);

		driver->beginScene(true, true, video::SColor(255,113,113,133));

		smgr->drawAll(); // draw the 3d scene
		device->getGUIEnvironment()->drawAll(); // draw the gui environment (the logo)

		driver->endScene();

		int fps = driver->getFPS();

		if (lastFPS != fps)
		{
			core::stringw tmp(L"Movement Example - Irrlicht Engine [");
			tmp += driver->getName();
			tmp += L"] fps: ";
			tmp += fps;

			device->setWindowCaption(tmp.c_str());
			lastFPS = fps;
		}
	}

	/*
	In the end, delete the Irrlicht device.
	*/
	delete Morph;
	device->drop();

	return 0;
}

/*
That's it. Compile and play around with the program.
**/


edit:
Sorry for the lack of comments. Don't hesitate to ask questions.
Note that normally I would use an enum to list different animation states and set the poses with it and if I used a class like this it would be customized to fit the situation.
christianclavet
Posts: 1638
Joined: Mon Apr 30, 2007 3:24 am
Location: Montreal, CANADA
Contact:

Post by christianclavet »

pir, If you want to do this directly in IRRlicht (Wow! Thats impressive!), your mesh need to be the same as:

The mesh need the same numbers of vertices and the same index for thoses vertices.

You should take an object, save it, then modify it then save the other version (not removing any vertices while modifiying the object). Then after that you load the 2 objects and interpollate the vertices position from one to the other. As an example, you take a character in the 3D application, use a magnet tool to modify the shape, to make him fatter. Then save the result as another mesh.

If your 2 objects don't share the same vertices, then I don't think it would work. (Would morph but it would look horrible)
pir
Posts: 24
Joined: Wed Apr 29, 2009 8:15 pm

Post by pir »

Thanks KH!

I'll take a look at your code. Right now I'm really stuck trying to update my mesh without damaging the animations. Hoping that I can use ISkinnedMesh::finalize() in some way.
kh_
Posts: 78
Joined: Fri May 19, 2006 4:29 pm

Post by kh_ »

Good luck with that.
1 minor thing I need to point out, because of the way vector.interpolate() works, you [edit] might need to count the timer down from 1 not up from 0.
pir
Posts: 24
Joined: Wed Apr 29, 2009 8:15 pm

Post by pir »

Ok, I finally understood how to morph the SkinnedMesh. The solution is not good it is very primitive, but does what I want to do. I'm posting it here because I'm tired reading posts from people asking for help and ending with "it works now", without any hint on how to solve the topic of the thread.

The code morphs two meshes that have the same mesh as base. So the vertices are one to one mapped by index values.

Code: Select all

    void Player::morph( AnimatedMeshSceneNodeExt* aMeshBase,
			AnimatedMeshSceneNodeExt* aMeshMorph,
			AnimatedMeshSceneNodeExt* aMeshTarget,
			f32 aInterpolation)
    {

	ISkinnedMesh* skinBase   = static_cast< ISkinnedMesh*>( aMeshBase->getMesh( ));
	ISkinnedMesh* skinMorph  = static_cast< ISkinnedMesh*>( aMeshMorph->getMesh( ));
	ISkinnedMesh* skinTarget = static_cast< ISkinnedMesh*>( aMeshTarget->getMesh( ));

	array< ISkinnedMesh::SJoint*> jointsBase   = skinBase->getAllJoints( );
	array< ISkinnedMesh::SJoint*> jointsMorph  = skinMorph->getAllJoints( );
	array< ISkinnedMesh::SJoint*> jointsTarget = skinTarget->getAllJoints( );
		
	for( u32 j = 0; j < jointsBase.size( ); j++)
	{
	    for( u32 w = 0; w < jointsBase[ j]->Weights.size( ); w++)
	    {
		ISkinnedMesh::SWeight& weightBase   = jointsBase[ j]->Weights[ w];
		ISkinnedMesh::SWeight& weightMorph  = jointsMorph[ j]->Weights[ w];
		ISkinnedMesh::SWeight& weightTarget = jointsTarget[ j]->Weights[ w];
		
		weightTarget.StaticPos = 
		    weightBase.StaticPos 
		    + aInterpolation 
		    * ( weightMorph.StaticPos - weightBase.StaticPos);

		weightTarget.StaticNormal = 
		    weightBase.StaticNormal 
		    + aInterpolation 
		    * ( weightMorph.StaticNormal - weightBase.StaticNormal);
	    }// for w
	}// for j
    }// morph()
BR
pir
khatarat
Posts: 18
Joined: Wed Dec 19, 2012 10:41 am

Re: Is it possible to morph a mesh in Irrlicht?

Post by khatarat »

StaticPos is a private member of class SWeight. how its possible to access it?
khatarat
Posts: 18
Joined: Wed Dec 19, 2012 10:41 am

Re: Is it possible to morph a mesh in Irrlicht?

Post by khatarat »

i used this method to morph my mesh to a target mesh. but this just works with one target.and always i have just the last target applied to base mesh.
how can i apply multiple targets to one base mesh?
rubenwardy
Posts: 91
Joined: Mon Mar 05, 2012 4:51 pm
Location: Bristol, UK
Contact:

Re: Is it possible to morph a mesh in Irrlicht?

Post by rubenwardy »

Dont bump old topics!

This topic is 4 years old!
chronologicaldot
Competition winner
Posts: 685
Joined: Mon Sep 10, 2012 8:51 am

Re: Is it possible to morph a mesh in Irrlicht?

Post by chronologicaldot »

@rubenwardy - It's fine. It can be annoying at times, but his question seems to be on topic.

@khatarat -
First question - I imagine the implementation of SWeight may have changed over the course of irrlicht's development.
Second question - I'm not sure what you mean by "always i have just the last target applied to base mesh" and "multiple targets". Are you looking to have several transitions? (For example, as if you had a person you wanted to scale - One mesh target makes them fat and the next one makes them tall.) Or are you looking for a cross between (or blending of) two or more target meshes?
Post Reply