How to create a wireframe material? [SOLVED]

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.
oringe
Posts: 41
Joined: Tue Jul 05, 2011 7:06 am
Location: San Francisco

Re: How to create a wireframe material?

Post by oringe »

mongoose7 wrote:You need

Code: Select all

vec = cam->getPosition() - node->getPosition();  <== points to the camera
vec.normalise();
vec *= 0.1f;
@mongoose7
normalize() == setLength(1);
mongoose7 wrote:But why don't you have

Code: Select all

node1->setMesh(mesh);
node1->setPosition(v);
node2->setMesh(mesh);
node2->setPosition(v + vec);
node2->setMaterialFlag(video::EMF_WIREFRAME);   <== not sure about this
Here is my setup before calling the nudge() function:

Code: Select all

 
        IMeshSceneNode* mesh_node = smgr->addMeshSceneNode( mesh );
        mesh_node->setMaterialTexture(0, driver->getTexture("./textures/sydney.bmp"));
 
        IMeshSceneNode* wireframe_node = smgr->addMeshSceneNode( mesh );
        wireframe_node->setMaterialFlag (video::EMF_WIREFRAME, true);
 
oringe
Posts: 41
Joined: Tue Jul 05, 2011 7:06 am
Location: San Francisco

Re: How to create a wireframe material?

Post by oringe »

However I just realized a fundamental flaw in my function;
I'm only creating one nudge vector from the center of a wireframe mesh to the camera. This obviously means that vertices far from the center will not align properly with the camera. As the wireframe gets closer to the camera it should get smaller to keep vertices aligned with their counterparts on the mesh node behind them.
If the scale of the mesh is 1 at the original position, as it approaches the camera it will scale down eventually approaching zero at the origin of the camera. Thus we have a simple ratio! So easy :D

Code: Select all

 
    float nudge_length = (wireframe_node->getBoundingBox().getExtent()).getLength();
    nudge_length *= 0.005;  
 
    vector3df nudge_vec (cam->getPosition() - wireframe_node->getPosition());
    float scale_factor = ((nudge_vec.getLength() - nudge_length) / nudge_vec.getLength());  // <--!
    nudge_vec.setLength (nudge_length);
    wireframe_node->setPosition (wireframe_node->getPosition() + nudge_vec);
    wireframe_node->setScale (vector3df (scale_factor, scale_factor, scale_factor)); 
 
    smgr->drawAll();
 
    wireframe_node->setPosition (mesh_node->getPosition());
 
This works great! All wires look good.
Image

Now I need to figure out how to iterate through all meshes in a scene, filter out the wireframes, and apply this function... :arrow:
oringe
Posts: 41
Joined: Tue Jul 05, 2011 7:06 am
Location: San Francisco

Re: How to create a wireframe material?

Post by oringe »

Here is my complete nudge() function.
It iterates through all meshes in the scene manager passed as argument, nudges the wireframes toward the camera a distance of 0.005 that of each mesh's bounding box, scales appropriately, renders the scene, and then returns all wireframes back to their original position.
In order for it to work, you must implement the following naming scheme for your meshes:
every mesh must have a copy mesh in the same position but with no texture and wireframe set to true. (see earlier post in this thread)
for each pair of mesh_nodes append the following suffixes to their names:
myMeshName_mn (mesh node)
myMeshName_wrfn (wireframe node)

Code: Select all

 
void nudge (ISceneManager* smgr) {
    // get nodes
    array<ISceneNode*> mesh_array;
    smgr->getSceneNodesFromType (ESNT_MESH, mesh_array);
 
    // nudge wireframes
    for (int i=0; i<(mesh_array.size()-1); i+=2)
    {
        string<char> name_m (mesh_array[i]->getName());
        string<char> name_wrf (mesh_array[i+1]->getName());
        
        s32 u_pos = name_m.findFirst ('_'); 
        if (u_pos>-1) 
        {
            if (name_m.equalsn (name_wrf, u_pos) && (name_wrf.find ("wrf") > -1))
            {
                IMeshSceneNode* mesh_node = (IMeshSceneNode*) smgr->getSceneNodeFromName (name_m.c_str());
                IMeshSceneNode* wireframe_node = (IMeshSceneNode*) smgr->getSceneNodeFromName (name_wrf.c_str());
                ICameraSceneNode* cam = (ICameraSceneNode*) smgr->getSceneNodeFromName ("cam");
 
                // nudge_length is .005 of the extent of the mesh's bounding box
                float nudge_length = (wireframe_node->getBoundingBox().getExtent()).getLength();
                nudge_length *= 0.005;  
 
                vector3df nudge_vec (cam->getPosition() - wireframe_node->getPosition());
                float scale_factor = ((nudge_vec.getLength() - nudge_length) / nudge_vec.getLength());
                nudge_vec.setLength (nudge_length);
 
                wireframe_node->setPosition (wireframe_node->getPosition() + nudge_vec);
                wireframe_node->setScale (vector3df (scale_factor, scale_factor, scale_factor)); 
            }
        }
    }
 
    smgr->drawAll();
 
    // move wireframes back to original position
    for (int i=0; i<(mesh_array.size()-1); i+=2)
    {
        string<char> name_m (mesh_array[i]->getName());
        string<char> name_wrf (mesh_array[i+1]->getName());
        
        s32 u_pos = name_m.findFirst ('_'); 
        if (u_pos>-1) 
        {
            if (name_m.equalsn (name_wrf, u_pos) && (name_wrf.find ("wrf") > -1))
            {
                IMeshSceneNode* mesh_node = (IMeshSceneNode*) smgr->getSceneNodeFromName (name_m.c_str());
                IMeshSceneNode* wireframe_node = (IMeshSceneNode*) smgr->getSceneNodeFromName (name_wrf.c_str());
 
                wireframe_node->setPosition (mesh_node->getPosition());
            }
        }
    }
}
 
Please criticise my nooby code if you see possible improvements.
Thanks, :)
mongoose7
Posts: 1227
Joined: Wed Apr 06, 2011 12:13 pm

Re: How to create a wireframe material?

Post by mongoose7 »

I don't want to start reading code, so I'll pass on that. As regards your strategy, though, I can't undertsand why you want to move the wireframe meshes back each frame. You can get the position from the "parent" mesh, so you should simply derive the position of the wireframe mesh directly from the "parent": nodew->setPosition(node->getPosition() + nudge); and not try to return it to the position of the parent node. It certainly takes no time to do this, but it just seems to be a bad way of thinking about the problem in general.

Also, why don't *you* create the wireframe nodes. There's virtually no overhead as the meshes are already loaded, and you can remove the naming requirement. You also don't then need to specify constraints on the materials.
oringe
Posts: 41
Joined: Tue Jul 05, 2011 7:06 am
Location: San Francisco

Re: How to create a wireframe material?

Post by oringe »

mongoose7 wrote: I can't undertsand why you want to move the wireframe meshes back each frame. You can get the position from the "parent" mesh, so you should simply derive the position of the wireframe mesh directly from the "parent"
Excellent point. I must have been smoking crack when I decided it was necessary to move the wireframes back each frame?
mongoose7 wrote: Also, why don't *you* create the wireframe nodes. There's virtually no overhead as the meshes are already loaded, and you can remove the naming requirement. You also don't then need to specify constraints on the materials.
I've been trying to create the wireframes "myself" inside the scope of the nudge function, but it's proven to be quite difficult and messy.
The problem is that the only function to extract multiple nodes from the ISceneManager is ->getSceneNodesFromType, which fills an array<ISceneNode*> and the ISceneNode class doesn't have a ->getMesh() member. Thus, I can't access the IMesh from an ISceneNode* without recasting it as either an IMeshSceneNode* or IAnimatedMeshSceneNode*, and embedding the recast and initialisation into an if or switch statement causes scope problems.
Furthermore, if 'I' create the wireframes in the nudge() function, all wireframes will have the same texture and properties. By using the naming scheme, the user is given easy control over which meshes he wants to have a wireframe, and their individual colors/textures/properties, etc...
oringe
Posts: 41
Joined: Tue Jul 05, 2011 7:06 am
Location: San Francisco

Re: How to create a wireframe material?

Post by oringe »

Here is my latest version of the nudge function. Reduced the complexity a little. Now all you must do for it to work is make all wireframe nodes children to their mesh counterparts, and add "wrf" anywhere in the node's name.

Code: Select all

 
void nudge (ISceneManager* smgr) 
{
    // get nodes
    array<ISceneNode*> node_array;
    smgr->getSceneNodesFromType (ESNT_ANY, node_array);
 
    // nudge wireframes
    for (int i=0; i<node_array.size(); ++i)
    {
        string<char> node_name (node_array[i]->getName());
        
        if (node_name.find ("wrf") > -1)
        {
            ISceneNode* wireframe_node = node_array[i];
            ISceneNode* mesh_node = wireframe_node->getParent(); 
            ICameraSceneNode* cam = (ICameraSceneNode*) smgr->getSceneNodeFromName ("cam");
 
            // nudge_length is .005 of the extent of the mesh's bounding box
            float nudge_length = (wireframe_node->getBoundingBox().getExtent()).getLength();
            nudge_length *= 0.005;  
 
            vector3df nudge_vec (cam->getPosition() - mesh_node->getPosition());
            float scale_factor = ((nudge_vec.getLength() - nudge_length) / nudge_vec.getLength());
            nudge_vec.setLength (nudge_length);
 
            wireframe_node->setPosition (nudge_vec);
            wireframe_node->setScale (vector3df (scale_factor, scale_factor, scale_factor)); 
        }
    }
    smgr->drawAll();
}
 
mongoose7
Posts: 1227
Joined: Wed Apr 06, 2011 12:13 pm

Re: How to create a wireframe material?

Post by mongoose7 »

oringe wrote:
The problem is that the only function to extract multiple nodes from the ISceneManager is ->getSceneNodesFromType, which fills an array<ISceneNode*> and the ISceneNode class doesn't have a ->getMesh() member. Thus, I can't access the IMesh from an ISceneNode* without recasting it as either an IMeshSceneNode* or IAnimatedMeshSceneNode*, and embedding the recast and initialisation into an if or switch statement causes scope problems.
I don't understand this. Why would, say

Code: Select all

IMesh *mesh;
switch (node->getType) {
case XXX:
  mesh = ((TypeXXX *) node)->getMesh();
  break;
...
default:;
}
nodew->setMesh(mesh);
cause scope problems?

But I guess it doesn't matter: I had assumed that the material was constant, but if you want to allow colours then it would need a different approach.

BTW Have you run your new code? A scale fact slightly less than one seems a bit odd. And setting the position to the position of the camera relative to the node is also a bit odd. But I may have read it incorrectly.
oringe
Posts: 41
Joined: Tue Jul 05, 2011 7:06 am
Location: San Francisco

Re: How to create a wireframe material?

Post by oringe »

[EDIT= Jul 19, 2011 23:34]
Feels like I am approaching a final version for this technique. Added a couple more opimizations.

Instructions for usage:
Setup:
For every mesh you want a wireframe rendered on, simply create a child node with the same mesh and the EMF_WIREFRAME MaterialFlag set to true. After that you can set the texture however you like, to any colour. If you want a uniformly coluored wireframe, set the EMF_LIGHTING false. For example:

Code: Select all

        // get your mesh
        IMesh* sydney_m = smgr->getMesh("./sydney.md2");
 
        // mesh node
        IMeshSceneNode* sydney_mn = smgr->addMeshSceneNode( sydney_m );
            sydney_mn->setMaterialTexture(0, driver->getTexture("./textures/sydney.bmp"));
            sydney_mn->setMaterialFlag (video::EMF_LIGHTING, false);
 
        // wireframe node
        IMeshSceneNode* sydney_wrfn = smgr->addMeshSceneNode( sydney_m );
            sydney_wrfn->setMaterialFlag (video::EMF_LIGHTING, false);
            sydney_wrfn->setMaterialFlag (video::EMF_WIREFRAME, true);
            sydney_wrfn->setParent (sydney_mn);  //  Don't forget to setParent 
Then create an array of ISceneNode pointers and push_back all the wireframe nodes you want rendered into it:

Code: Select all

    array<ISceneNode*> wrf_array ();
    wrf_array.push_back (sydney_wrfn); 
Now just call the nudge function from your game loop and pass it the SceneManager and array of wireframe nodes.

Code: Select all

    nudge (smgr, wrf_array);  
All this functino does is iterate through the array, check if the node is culled, nudge wireframe slightly toward camera, and scale. It's quite simple actually and rather effective for creating that solid wireframe look. And useful as a 'prettier' debug tool.

Code: Select all

void nudge (ISceneManager* smgr, array<ISceneNode*>& wrf_array) 
{
    // nudge wireframes
    for (int i=0; i<wrf_array.size(); ++i)
    {
        ISceneNode* wireframe_node = wrf_array[i]; 
        if (!(smgr->isCulled(wireframe_node)))
        {
            ISceneNode* mesh_node = wireframe_node->getParent(); 
            ICameraSceneNode* cam = smgr->getActiveCamera ();
 
            // nudge_length is .005 of the extent of the mesh's bounding box
            f32 nudge_length = (wireframe_node->getBoundingBox().getExtent()).getLength();
            nudge_length *= 0.005;  
 
            vector3df nudge_vec (cam->getPosition() - mesh_node->getPosition());
            f32 scale_factor = ((nudge_vec.getLength() - nudge_length) / nudge_vec.getLength());
            nudge_vec.setLength (nudge_length);
 
            wireframe_node->setPosition (nudge_vec);
            wireframe_node->setScale (vector3df (scale_factor, scale_factor, scale_factor)); 
        }
    }
}
 
Image
Image
:mrgreen:
I still want to develop an even simpler version that operates on the depth bias in the z-buffer, but I'm waiting for v1.8 to try that with PolygonOffsetFactor ala hybrid's suggestion...
oringe
Posts: 41
Joined: Tue Jul 05, 2011 7:06 am
Location: San Francisco

Re: How to create a wireframe material?

Post by oringe »

Irrlicht users and admin are very nice. Nobody has pointed out how inefficient and ugly my code is : )
Anyway, I've been studying C++ and reading Scott Meyers. Improving, but I still have a long way to go.

Here is another, simpler way to render wireframes on top of a mesh using a matrix.

Code: Select all

    // build nudged matrix
    vector3df camV= SceneManager->getActiveCamera()->getAbsolutePosition() - RelativeTranslation; 
    f32 length= .01f;
    f32 scale= ( camV.getLength() - length ) / camV.getLength();
    camV.setLength( length );
    camV += RelativeTranslation;
    matrix4 mx;
    mx.setScale( scale );
    mx.setRotationDegrees( RelativeRotation );
    mx.setTranslation( camV );
 
    driver->setTransform( ETS_WORLD, mx );
    driver->setMaterial( wireMaterial );
    driver->drawVertexPrimitiveList(
        &Vertices[0], Vertices.size(), 
        &Indices[0], 
        12, // primitive count
        video::EVT_STANDARD, scene::EPT_LINES, 
        video::EIT_16BIT
    );
 
The context for this block is inside the render() method of a class derived from ISceneNode whose parent is the scene root node, thus RelativeTranslation is same as absolute.
If your context is different just sub appropriate values for RelativeTranslation, primitive count, etc.
I experimented using PolygonOffsetFactor, but could not get rid of z-fighting entirely :(

Makes a big difference:
Image
Post Reply