wiki:Tutorial/OpenSG1/NodeCores

Previous Chapter: Basics

Tutorial Overview

Next Chapter: Geometry


Node cores

In this chapter you will learn how to use the most important cores. The Transform NodeCore and Material are discussed in more detail and additionally I will introduce the osg::Switch, osg::DistanceLOD (level of detail) and osg::ComponentTransform cores as well. At the end of the chapter there will be our first bigger tutorial, where we will use all the cores introduced here.

The geometry core is certainly the most important one, but due to its complexity and importance the whole next chapter (Geometry) is dedicated to this core only!

Transform Core

The transform core was already briefly introduced in the last chapter (Tutorial - It's moving), but now we will have a more detailed look at it. However, about the transform class itself is not much left to say, as the most important method is the "setMatrix()" method, which lets us use any matrix as a transformation matrix. In order to create the transformation that you need, you only have to create the appropriate matrix.

Let us imagine a more complex scene using more than just one transformation like we did before. A very common example is the model of a solar system. To make it a bit easier, we only want a sun and one planet with a single moon. The sun should be stationary and the planet is orbiting in a circle around the sun whereas the moon is doing the same around the planet. We also don't want to pay any attention to real sizes and distances. There is not only one way to solve this problem. We will have a closer look at two variants: One possibility is to have a deep graph, where the planet is attached to the sun whereas the moon is attached to the planet with each having a transformation describing the rotation around their parent object. That is the moon is moving in a circle around the planet which also is moving in a circle around the sun. The next figure is illustrating what I mean.

The easy way to solve our problem...

As you can see, the rotation of the moon depends on the rotation of earth, thus describing the rotation of these both objects in an intuitive manner, but this may become inefficient if used extensively.

The other way is to have a transformation describing the complete movement, so all geometry nodes are located directly below the root node. This approach is not as intuitive than the other is, but it might be faster to compute. This figure is illustrating this variant.

The more efficient way...

On one hand this graph looks quite a bit friendlier, on the other hand the transformation for the moon will be quite a bit more difficult.

Deep Graph

Well, let us give both variants a try. Like ever here is our createScenegraph() function to begin with:

#!cpp

NodePtr createScenegraph(void)

{

    //create sun, planet & moon geometry

    GeometryPtr sun = makeSphereGeo(3, 6);

    NodePtr planet = makeSphere(3, 3);

    NodePtr moon = makeSphere(2,1);

	

    //the root node will be the sun

    NodePtr root = Node::create();

    beginEditCP(root, Node::CoreFieldMask);

		root->setCore(sun);

    endEditCP(root, Node::CoreFieldMask);

	

    NodePtr planetTransformNode = Node::create();

    NodePtr moonTransformNode = Node::create();



    // these were declared globally

    planetTransform = Transform::create();

    moonTransform = Transform::create();

	

    // Now we need to fill it with live

    // We want to have the planet some distance away from the sun, 

    // but initially with no rotation. The same applies to the moon

    Matrix m,n;

	

    m.setIdentity();

    n.setIdentity();

	

    m.setTranslate(20,0,0);

    n.setTranslate(8,0,0);

	

    beginEditCP(planetTransform, Transform::MatrixFieldMask);

        planetTransform->setMatrix(m);

    endEditCP(planetTransform, Transform::MatrixFieldMask);

	

    beginEditCP(moonTransform, Transform::MatrixFieldMask);

        moonTransform->setMatrix(n);

    endEditCP(moonTransform, Transform::MatrixFieldMask);

	

    //Insert the cores into the apropiate nodes and add the geometry

    beginEditCP(planetTransformNode, Node::CoreFieldMask | Node::ChildrenFieldMask);

        planetTransformNode->setCore(planetTransform);

        planetTransformNode->addChild(planet);

    endEditCP(planetTransformNode, Node::CoreFieldMask | Node::ChildrenFieldMask);

	

    beginEditCP(moonTransformNode, Node::CoreFieldMask | Node::ChildrenFieldMask);

        moonTransformNode->setCore(moonTransform);

	moonTransformNode->addChild(moon);

    endEditCP(moonTransformNode, Node::CoreFieldMask | Node::ChildrenFieldMask);

	

    //add the planet to the sun

    beginEditCP(root, Node::ChildrenFieldMask);

        root->addChild(planetTransformNode);

    endEditCP(root, Node::ChildrenFieldMask);

	

    //add the moon to the planet

    beginEditCP(planet, Node::ChildrenFieldMask);

        planet->addChild(moonTransformNode);

    endEditCP(planet, Node::ChildrenFieldMask);

	

    //now we are done

    return root;

}

We need to declare both TransformPtr, which will rotate the planet and moon, globally as this will make it much easier to manipulate the transform matrices during rendering.

#!cpp

TransformPtr planetTransform;

TransformPtr moonTransform;

If you compile this code now and zoom out a bit, you will see three different balls in one row. Well, it is not quite a realistic simulation of our solar system... Anyway, the first thing we add now are the rotations I am talking all the time about. Replace the display() function with the following code

#!cpp

void display(void)

{

    Real32 time = glutGet(GLUT_ELAPSED_TIME );

    

    //create the Quaternion that describes the rotation of

    //the planet around the sun

    Quaternion planetRot = Quaternion(Vec3f(0,1,0), time/float(1000));

    

    //now the rotation of the moon around the planet

    //the factor 12 slows down the rotation by 12 compared to the

    //planet rotation

    Quaternion moonRot = Quaternion(Vec3f(0,1,0), time/float(12*1000));

    

    //generate the Matrices

    Matrix p,m;

    

    p.setIdentity();

    m.setIdentity();

    

    p.setRotate(planetRot);

    m.setRotate(moonRot);

    

    beginEditCP(planetTransform, Transform::MatrixFieldMask);

        planetTransform->setMatrix(p);

    endEditCP(planetTransform, Transform::MatrixFieldMask);

    

    beginEditCP(moonTransform, Transform::MatrixFieldMask);

        moonTransform->setMatrix(m);

    endEditCP(moonTransform, Transform::MatrixFieldMask);

    

    mgr->redraw();

}

Allright, compile and execute... and what do you see? Only one planet, right? But why? Think about it, before reading any further!

Got it? Yes, we have overwritten the translation, we set up in the first place. Well, that is actually not what we desired? We could try to extract the old matrix out of our graph and then apply the rotation, or we could create a completly new matrix and assigning the translation again. That is what we will do for now, but in the next section we will learn about another possibility that solves our problem easy and efficient. Add the following code right after we set the rotation

#!cpp

// old code ------

    p.setRotate(planetRot);

    m.setRotate(moonRot);

// insert new code here:



p.setTranslate(20,0,0);

m.setTranslate(8,0,0);

The terrible thing is that it still does not work! Do you have an idea what went wrong this time? Try to put a texture onto the spheres, or replace these with cubes for example and you will see what actually is happening.

Something with the order of the transformations seem not to be correct: The rotation is applied locally to the pivot of the sphere itself, which is of course not what we desire. The reason for that is that we're just directly manipulating the matrix elements, but not doing an actual matrix multiplication for concatenating transformations.

The best solution for now is to do it simply by hand. If we multiply the seperate parts together we should get what we want. So let's remember some math from college: The matrix multiplied last is executed first, thus

    M = Rotation * Translation

yields the correct transformation matrix. So again replace some code in the display function

#!cpp

/*

locate the following lines in the display() function

and comment them out or delete them



p.setIdentity();

m.setIdentity();

    

p.setRotate(planetRot);

m.setRotate(moonRot);

    

p.setTranslate(20,0,0);

m.setTranslate(8,0,0);

*/



// add the following lines, replacing these from above





//temporary matrices for translation and rotation    

Matrix t,r;

    

t.setTransform(Vec3f(20,0,0));

r.setTransform(planetRot);



//multiplying them together 

r.mult(t);

//the usage of matrix p is just cosmetic as it is

//and will be identical to r 

p.setValue(r);

    

t.setTransform(Vec3f(8,0,0));

r.setTransform(moonRot);

r.mult(t);

m.setValue(r);

    

matrixA.mult(matrixB) multiplies matrix B from the right to matrix A, overwriting matrix A with the result (i.e. A=A*B).

Finally it works! The moon should now rotate quickly around the planet which rotates around the sun! The source file can be found here: progs/06solarsystem2.cpp.

Flat Graph

Please take the results from above (progs/06solarsystem2.cpp) as a starting point for modifying the code. First we need to rearrange the graph. I will not present the full code here again, because the changes are not so grave, but you can find the full code in the file named progs/06solarsystem3.cpp. Here are the necessary changes:

  1. Locate the piece of code where the moonTransformation is added to the planet node - remove these lines, because the moon will no longer be a child of the planet
  1. Attach the moonTransformation you just removed to the root node.

If you execute the application you will see that both the moon and planet are rotating around the sun, which makes sense, of course as they are both children of the sun. Next we need to alter the transformation of the moon. The planets transformation may stay untouched as this is still correct.

The new transformation matrix for the moon can be calculated by

    M = PlanetRotation * PlanetTranslate * MoonRotation * MoonTranslate

Matrix-Magic

As the most important thing of a transform core is the transformation itself (of course!), which is defined by a single matrix, you often have to do some magic stuff with matrices in order to create the transformation you need. Here you will find many possibilities how to create and manipulate matrices. Some basic examples can also be found here: Working with matrics. These will not be repeated here!

There are a lot of different possibilities to set up a matrix depending on what you desire. There are seperate methods to set a translation, scale, rotation or even the whole transformation. These methods often take real values, vectors or quaternions. I will show examples for all methods provided. Have a look at the matrix class documentation for additional reference.

#!cpp

Matrix m;



m.setTranslate(1,2,3);

m.setTranslate(Pnt3f(1,2,3));



m.setRotate(Quaternion(Vec3f(1,2,3),90));



//this is a uniform scale in all dimensions

m.setScale(2);

m.setScale(1,2,3);

m.setScale(Vec3f(1,2,3));

Additionally there are some methods to set up the whole transformation at once. If you would like to set up a matrix which scales everything by factor two, rotates 30 degrees around the y-axis and translates by some vector you would need at least 4 commands (including setIdentity()). This can also accomplished by just one command

#!cpp

//just to shorten the code...

//used for rotations

Quaternion r = Quaternion(Vec(1,2,3), 90);

//used for translations

Vec3f t = Vec3f(1,2,3);

//used for scalation

Vec3f s = Vec3f(1,2,3);



// the first two commands are equal to setTranslate or setRotate

m.setTransform(t);

m.setTransform(r);



// but you can set rotation and translation at once

m.setTransform(t,r);

// the first vector specifies the translation whereas 

// the second defines the rotation



// like above with an additional scalation

m.setTransform(t,r,s);



// this adds an additional scaling orientation

m.setTransform(t,r,s,Vec3f(1,1,0));



// and and additional center point for rotation

m.setTransform(t,r,s,Vec3f(1,1,0),Vec3f(10,0,0));

You also can do it the other way round: it is possible to retrieve the rotation etc. from a matrix by using getTransform. Here is an example:

#!cpp

// lets assume m is any sound matrix



Quaternion rotation;

Vec3f translation;

Vec3f scaleOrientation;

Vec3f scale;



m.getTransform(translation, rotation, scale, scaleOrientation);



// there is another variant which provides the possibility to extract 

// the center point

Vec3f center;



m.getTransform(translation, rotation, scale, scaleOrientation, center);



// the result will be written into the variables you passed to the method

// translation would be (1,2,3) if we take m from the example above

Furthermore you have some methods you need for inverting, calculating the determinant etc.

#!cpp

m.invert();        //calculates the inverse matrix;

m.transpose();      //as you expect it...

Real32 d = m.det(); //calculates the determinant

In the above example the results will overwrite the original matrix. You actually don't have to create copies by hand, because for this purpose there are ready made methods. The next code fragment gives a simple example.

#!cpp

Matrix result;



result.invertFrom(m);

m.transposed(result);

As always, you should have a look at the osg::Matrix documentation for full reference.

Component Transform

Remember the problems we had setting up the correct transformation matrices for our solar system example? Now imagine working with more complex transformations than we did, which were in fact pretty simple. Another important issue is the alteration of a matrix at runtime. In the solar system tutorial we recalculated the whole matrix by multiplying the rotation with the translation, although we only wanted to modify the rotation. In many cases it is just too complicated or even impossible to reconstruct single parts of a transformation. That is when component transforms are coming into play.

Component transform cores are able to stores translation, rotation, scales, scale orientation and a center point separate from each other. Thus every component can be edited without affecting values of other parts. There are appropriate getter and setter methods for these fields. The following little example will illustrate the usage of a component transform core

#!cpp

ComponentTransformPtr ct = ComponentTransform::create();



beginEditCP(ct, ComponentTransform::TranslationFieldMask |

                ComponentTransform::RotationFieldMask);

                

    ct->setTranslation(Vec3f(20,0,0));

    ct->setRotation(Quaternion(Vec3f(0,1,0),PI/4));

    

endEditCP(ct, ComponentTransform::TranslationFieldMask |

              ComponentTransform::RotationFieldMask);

If you now want to alter the rotation you only need to call the setRotation() method and provide a new quaternion and that's it. The bad news is that you might get the same multiplication order problems we had before, thus resulting in a undesired rotation of the object itself.

For those of you who want to know what exactly happens: This is the way the final transformation matrix is calculated:

M = Translation * Center * Rotation * ScaleOrientation * Scale * -ScaleOrientation * -Center

Materials

We already had a short look at textures here: Tutorial - Using textures. There are of course a lot more possibilities than just to map an image onto an object. Here you will find a more detailed overview about how to use materials in OpenSG.

Simple Material

The Simple Material class provides an easy to use interface for materials without textures or other advanced features. It features all basic attributes like diffuse and specular color, as well as transparency, emission, shininess and ambient color. Here is an little example of a red material that is 50% transparent and has a dark gray as ambient color

#!cpp

SimpleMaterialPtr m = SimpleMaterial::create();



beginEditCP (m, SimpleMaterial::DiffuseColorFieldMask |

                SimpleMaterial::AmbientColorFieldMask |

                SimpleMaterial::TransparencyFieldMask);

                

    m->setDiffuse(Color3f(1,0,0));

    m->setAmbient(Color3f(0.2, 0.2, 0.2));

    m->setTransparency(0.5);



endEditCP   (m, SimpleMaterial::DiffuseColorFieldMask |

                SimpleMaterial::AmbientColorFieldMask |

                SimpleMaterial::TransparencyFieldMask);

50% transparent red Material on a sphere

As you can see, there are some artifacts on the sphere. You might know that transparent surfaces quickly get highly complicated to render correctly, if some are occluding other transparent surfaces, which is of course the case here. OpenSG does sorting for transparent objects to ensure correct rendering. But, like most other scenegraphs, it only does so on a per-object level, which explains the artifacts you see. Sorting does not work within a single node.

progs/07materials.cpp shows the implementation. It might seem quite unusual that osg::SimpleMaterial

is not derived from osg::NodeCore and thus cannot be inserted as a core directly.

One possibility is to use a material group. This class is nearly identical to the usual group core but it has a setMaterial() method added with which you can assign a material to that core. Every node which is below the appropriate node containing such a core will be rendered with this material.

Another possibility is to set the material field in the geometry using setMaterial(). This next code fragment shows both variants

#!cpp

	// mat is of type osg::SimpleMaterial

	

	// This one will set the material mg for every node that is below this node

	MaterialGroupPtr mg = MaterialGroup::create();

	beginEditCP(mg);

		mg->setMaterial(mg);

	endEditCP(mg);

	

	NodePtr n = Node::create();

	beginEditCP(n);

		n->setCore(mg);

		n->addChild(someNode);

	endEditCP(n);

	

	//the node 'someNode' will be rendered

	//with the material mg

	

	//the other possiblity...

	GeometryPtr geo = Geometry::create();

	//lets assume we define a complete geometry here

	//and now we assign the material

	geo->setMaterial(mg);

	

	//This material will be used for rendering geo, but all children of geo

	//will be unaffected by this!

Simple Textured Material

You already saw simple textured material in action here: Tutorial - Using textures. I will show some more interesting features in the next tutorial!

Chunks

A scene graph system is expected to do rendering as fast as possible. As I mentioned before, OpenGL state changes can be very expensive operations, for example changing some color parameters of a material or light source. Loading and switching textures is even more expensive in most cases. Thus it is very important to handle these state changes in the most efficient way possible. It is very useful to group all primitives which use the same set of OpenGL states together. In addition to that it is necessary to minimize costs if the state is changed - i.e. changing only the diffuse color is more efficient than changing twelve states at once.

Of course that is not as easy as it sounds, unfortunately sorting all primitives in the most efficient manner is a NP-complete problem (If you are not familiar with complexity-theory: NP-complete problems are really, really bad). To simplify the problem OpenGL states that are usually changed together have been grouped in larger pieces: osg::StateChunks.

Unfortunately I was not able to complete this section in time. This part might be added in the future.

Switch

Switches are quite similar to switches in C++. You can add several children to a node containing a switch core and depending on what you want to be displayed on screen you can tell the switch to render none, one specific or all of it's children. Please note that this behaviour is not restricted to rendering only but applies to every traversal of the graph. All children which are not selected by the switch are virtually cut off the graph until you change the setting of the switch.

The usage is very easy to handle. It behaves like a normal group node with some additional functionality demonstrated in the next example

#!cpp

SwitchPtr switch = switch::create();



//insert this sw into some node and add some children



beginEditCP(sw, Switch::ChoiceFieldMask);

    //select the second child to be rendered

    sw->setChoice(2);

    //please be careful not to select a non existend child

endEditCP(sw, Switch::ChoiceFieldMask);



//Switch::ALL or Switch::NONE would have selected all or no children

Level of Detail (LOD)

A must have feature for scene graph systems is the possibility to define some, often two or three, different models representing the same object in different resolutions. The system will display one of the provided models depending on the distance from the user to the object. The advantage is obvious: If you are close to the model it is rendered in high resolution thus details are well visible, if you are far away the object is approximated by a handful polygons and thus saving memory and rendering time.

The usage of LOD cores is quite simple. Notice the class is called DistanceLOD and not just LOD! There will be other LODs in the future, but for now DistanceLOD is fine.

#!cpp

DistanceLOD lod = DistanceLOD::create();

beginEditCP(lod);

    // this is supposed to be the center of the LOD model,

    // that is, this is the point the distance is measured to

    lod->setCenter(12,1,5);

    // now we add the distances when models will change

    lod->getMFRange()->push_back(6.0);

    lod->getMFRange()->push_back(12.0);

    lod->getMFRange()->push_back(24.0);

    

endEditCP(lod);

The way we add the distance values might seem a bit unusual to you, however this is a very easy and effective way to manipulate data. If you need to refresh your knowledge about multi fields, then just jump back to Single and Multifields. The following figure shows how the LOD object we just created, works.

Example of a LOD setup

This figure shows what reasonable models for a distance LOD could look like. On the left hand side there is the full resolution model which should be rendered if the camera is very close to the model. When distance is increasing the quality will drop as each following model has only a tenth of the polygons. You have to figure out the correct values for the distance when models will swap as this is of course highly dependent on your scene and the scale you have chosen.

Big Tutorial

Now it is finally time for a more exiting tutorial as we now have a basic knowledge of the most important aspects of OpenSG. What shall we do this time?

The first thing we do is to setup a little LOD node containing three different levels of detail of a woman.

These files are provided as VRML files and can be found in the prog/data folder. As always, we choose progs/00framework.cpp as our starting point.

First of all add two new header files

#!cpp

    #include <OpenSG/OSGSceneFileHandler.h>

    #include <OpenSG/OSGDistanceLOD.h> 

Now replace the createScenegraph() method with following code

#!cpp

NodePtr createScenegraph(void)

{

    //At first we load all needed models from file

    NodePtr w_high = SceneFileHandler::the().read("data/woman_high.wrl");

    NodePtr w_medium = SceneFileHandler::the().read("data/woman_medium.wrl");

    NodePtr w_low = SceneFileHandler::the().read("data/woman_low.wrl");

    

    //we check the result

    if ((w_high == NullFC)&&(w_medium == NullFC)&&(w_low == NullFC)){

        std::cout 

            << "It was not possible to load all needed models from file" 

            << std::endl;

        return NullFC;

    }

    

    //now the LOD core

    DistanceLODPtr lod = DistanceLOD::create();

    beginEditCP(lod, DistanceLOD::CenterFieldMask | DistanceLOD::RangeFieldMask);

        lod->getSFCenter()->setValue(Pnt3f(0,0,0));

        lod->getMFRange()->push_back(200);

        lod->getMFRange()->push_back(500);

    endEditCP(lod, DistanceLOD::CenterFieldMask | DistanceLOD::RangeFieldMask);

    

    //the node containing the LOD core. The three models will be

    //added as its children

    NodePtr lodNode = Node::create();

    beginEditCP(lodNode);

        lodNode->setCore(lod);

        lodNode->addChild(w_high);

        lodNode->addChild(w_medium);

        lodNode->addChild(w_low);

    endEditCP(lodNode);

    

    NodePtr root = Node::create();

    beginEditCP(root);

        root->setCore(Group::create());

        root->addChild(lodNode);

    endEditCP(root);

    

    return root;

}

You can now compile and execute this tutorial. When the window appears, you will see the model of the woman. Right now the medium model is displayed because our distance to the center point (0,0,0) is between 200 and 500 units. If you move the camera closer (by holding right mouse button) you can see how the model flips from medium to high resolution. The same happens of course if you move far away. The parameters were chosen so that you can see the effect - in real life this is of course not what you want! Try to increase the range values by a factor of two or three and you will not be able to see the flipping effect anymore.

Now lets us extend our little application with a switch. Under certain circumstances it could be useful to turn off the LOD object, but we do not want to abandon the woman from our screen. We utilize a switch node which reacts to a specific key to switch between the LOD object and a "static" version. Add the following code after the LOD node and before the root node is created

#!cpp

    //create the node with switch core ********************

    SwitchPtr sw = Switch::create();

    beginEditCP(sw, Switch::ChoiceFieldMask);

        //Notice: the first choice is 0

        sw->setChoice(0);

    endEditCP(sw, Switch::ChoiceFieldMask);

    

    NodePtr switchNode = Node::create();

    beginEditCP(switchNode);

        switchNode->setCore(sw);

        switchNode->addChild(lodNode);

    endEditCP(switchNode);

    

    //end switch creation **********************************

Now when the root node is created replace the line that says

#!cpp

    root->addChild(lodNode);

with

#!cpp

    root->addChild(switchNode);

The compiler needs to know two more include files:

#!cpp

    #include <OpenSG/OSGSwitch.h>

    #include <OpenSG/OSGSimpleAttachments.h>

The first should be obvious, the second is later needed to read names which were eventually assigned to nodes.

Now, if executed, everything looks like it did before. Now we want to add another copy of our highres model to our switch node. We could use the scenefilehandler to load another instance of that file but that would be a waste of memory. A better aproach would be to simply reference the geometry core by a second node, that is actually what the scenegraph is for. Unfortunately we have no appropriate GeometryPtr which points to our desired women mesh as we loaded it from file and got an NodePtr in return. So it is time for some fun and retrieve that geometry from the loaded graph.

What is our concern with that? Well, if you built the graph on your own, you have knowledge about where to find what. If you load models from a file you cannot know what is all in there. This time we are lucky, because the model is not very complex. If we load it into a 3D software like Studio Max or Cinema4D we can easily figure out that the name of the mesh (not the whole object) is "FACESET_woman". You can even figure that out if you are reading the VRML file in a text editor - but do not expect that to work with huge and complex files!

As we know that our graph will consist of only a few nodes (probably not more than 20) we can be lazy when thinking about how to locate the geometry node. So we will traverse the whole graph and check the name of every node if it matches the one we are looking for. Please notice that you normally would do that by using traversals provided by OpenSG, but these will be discussed later in chapter Traversal so this time we will do it by hand:

The next function will search for a node called "FACESET_woman" starting with the node initially passed as argument

#!cpp

// this function will return the node named "FACESET_Woman"

// if there is no such node NullFC will be returned

NodePtr checkName(NodePtr n){

    UInt32 children = n->getNChildren();

    

    //make sure a name existes

    if (getName(n))

        //check if it is the name we are looking for

        if (getName(n)== std::string("FACESET_Woman"))

            // We got the node!

            return n;

    

    //check all children

    for (int i = 0; i < children; i++){

        NodePtr r = checkName(n->getChild(i));

        if (r != NullFC)

            // if it is not NullFC it is the node we are looking for

            // so just pass it through

            return r;

    }

    // no children's name matches or there are no more childs

    // so return NullFC, indicating that the node was not found yet

    return NullFC;

}

Maybe this function could use a bit more of explanation. It is a recursive function that will first check if the nodes name matches the (hardcoded) search string, "FACESET_Woman" in this case. If that happens, this node is returned, else all children are checked the same way. If a node is reached which is not the one we are looking for and it has no children then NullFC is returned, which is similar to a null-pointer. This function ends up returning either the node named "FACESET_Woman" or NullFC.

Now we have a tool for searching, now we just need to use it. Add this right before we leave the createScenegraph() function

#!cpp

    // we now want to extract the mesh geometry out of the graph

    // it is sufficent to pass the model only, as root for searching

    NodePtr womanGeometry = checkName(w_high);

It is possible, of course, to start searching from the root node, but this is not necessary here, as we know that the geometry is not to be found 'above' the nodes used by our model. Additionally we have three different versions of this model loaded into our graph where every one has one such geometry node called FACESET_Woman. As we want only the one with high resolution we pass w_high as the starting point for the search.

Well, we have now successfully stored the correct node. Now we must extract the core out of the node. The following line of code, which has to be added directly after the last piece of code, does that

#!cpp

    GeometryPtr geo = GeometryPtr::dcast(womanGeometry->getCore());

This may look a little weird, but it's really not that bad. The getCore() method returns a "NodeCorePtr", which are all other core pointer classes are derived from. As we know that this specific node will contain geometry we can safely cast it dynamically. As we will see later on it is not a big deal if you do not know which kind of core is stored in a node. OpenSG is reflective and thus you can obtain information about any core just by asking it.

Finally we can now create a new node and reference the geometry core. We also want to translate the new node a bit in order to see both of the women. Insert this directly after the last line of code you just added.

#!cpp

    //new node with "old" geometry core referenced

    NodePtr woman = Node::create();

    beginEditCP(woman);

        woman->setCore(geo);

    endEditCP(woman);

    

    //translate it a bit to see both women

    NodePtr womanTrans = Node::create();

    TransformPtr t = Transform::create();

    beginEditCP(t);

        Matrix m;

        m.setIdentity();

        m.setTranslate(Vec3f(0,0,200));

        t->setMatrix(m);

    endEditCP(t); 

    beginEditCP(womanTrans);

        womanTrans->setCore(t);

        womanTrans->addChild(woman);

    endEditCP(womanTrans);

    

    //add it to the root

    beginEditCP(root);

        root->addChild(womanTrans);

    endEditCP(root);

After compiling and executing you can see that there are two identical models on your screen, but in a different color. How can that be? Materials can be assigned directly to the geometry, but as I mentioned earlier, it is also possible to use a material group which assigns a material to all its children without using the material field of the geometry. This is always the case when loading VRML files and this is therefore the reason why we see two different materials on sceen. On the left side we have the original whereas on the right side we have the copied geometry with missing information about a material because this was stored in one of the geometries parent nodes.

The next thing we want to add is a material for our woman copy. We won't use textures as we have already done that before, but we will use an OpenSG standard material.

Just add the code somewhere after we have defined the GeometryPtr geo

#!cpp

    // generating a material *********************************

    

    SimpleMaterialPtr mat = SimpleMaterial::create();

    beginEditCP(mat);

        mat->setAmbient(Color3f(0.2,0.2,0.2));

        mat->setDiffuse(Color3f(0.6,0.3,0.1));

        mat->setSpecular(Color3f(1,1,1));

        mat->setShininess(0.8);

    endEditCP(mat);

    

    beginEditCP(geo, Geometry::MaterialFieldMask);

        geo->setMaterial(mat);

    endEditCP(geo, Geometry::MaterialFieldMask);

    

    // end material generation *******************************

Now it is time to demonstrate the usage of the component transform core. We will add rotation, scaling and a translation at once and you will see how easily these can be modified using a component transform core.

Fist create a new global variable

#!cpp

    ComponentTransformPtr ct;

Now add the following code directly before the root node is created in createScenegraph():

#!cpp

// component transform ************************************

    NodePtr ctNode = Node::create();

    

    //this one is declared globally

    ct = ComponentTransform::create();

    beginEditCP(ct, ComponentTransform::TranslationFieldMask |

                    ComponentTransform::ScaleFieldMask |

                    ComponentTransform::RotationFieldMask);

    

        ct->setTranslate(Vec3f(0,0,200));

        ct->setScale(Vec3f(1,1,1));

        ct->setRotation(Quaternion(Vec3f(0,1,0),0));

        

    endEditCP(ct,   ComponentTransform::TranslationFieldMask |

                    ComponentTransform::ScaleFieldMask |

                    ComponentTransform::RotationFieldMask);

    

    beginEditCP(ctNode);

        ctNode->setCore(ct);

        ctNode->addChild(woman);

    endEditCP(ctNode);

    // end component transform ********************************

We now need to integrate our new component transform into our scenegraph. As this one will replace the old translation you can delete the code that creates the "normal" translation node. The application will still work if you leave it as it is, but this will result in useless code in your program (We all know this problem, don't we...).

For this to compile succesfully you need to add one more inlcude file

#!cpp

    #inlcude <OpenSG/OSGComponentTransform.h>

One thing still to do: change the child of the root node from the old transform node to the new one:

#!cpp

    root->addChild(ctNode);

After executing you will see that nothing visible has changed at all. Of course not, as the new component transform describes the same transformation as the one before. Well, to make it a bit more exciting we are going to modify the transformation every frame, which will show the advantages of the component transform.

We are going to let the user decide which kind of transformation will be applied. If he presses 's' a scale will be applied, 't' is a translation whereas 'r' yields a rotation.

First we need two more global variables that will count the rendered frames so far and keeps track of the selected translation mode.

#!cpp

    UInt32 frame = 0;

    // 0 = translation

    // 1 = rotation

    // 2 = scale

    UInt8 mode = 0;

Add a new callback functions to the registration in setupGLUT

#!cpp

    glutKeyboardFunc(keyboard);

And now add the keyboard function anywhere before the setupGLUT function

#!cpp

void keyboard(unsigned char k, int x, int y){

    switch (k){

        case 't' : mode = 0; break;

        case 'r' : mode = 1; break;

        case 's' : mode = 2; break;

    }

}

Finally exchange the display function with this code:

#!cpp

void display(void)

{

    frame++;

    Real32 time = glutGet(GLUT_ELAPSED_TIME);

    

    beginEditCP(ct, ComponentTransform::TranslationFieldMask |

                    ComponentTransform::ScaleFieldMask |

                    ComponentTransform::RotationFieldMask);

    

        switch (mode){

            case 0 :

                ct->setTranslation(Vec3f(0,cos(time/2000.f)*100,200));

                break;

            case 1 :

                ct->setRotation(Quaternion(Vec3f(0,1,0), time/2000));

                break;

            case 2 :

                ct->setScale(Vec3f(cos(time/2000), sin(time/2000), tan(time/2000)));

                break;

            }

        

    endEditCP(ct,   ComponentTransform::TranslationFieldMask |

                    ComponentTransform::ScaleFieldMask |

                    ComponentTransform::RotationFieldMask);    

    

    mgr->redraw();

}   

The final code can be found in file progs/08coresdemo3.cpp

You should really try the 's' key for scaling... nice isn't it. These strange effects occur beacause we are using trigonometric functions which will return zero periodically resulting in a reduction onto a plane if used for scaling. Actually if the argument (time/2000) becomes a multiple of pi we have the scale vector (1,0,0) where the object is scaled down to a single line.

You see, that it is very easy and comfortable to edit parts of a transformation this way. If you would have used a standard transform core you would have run into big trouble or you would have to store all three transformation parts seperatly and construct the final matrix every frame by hand.

Exercises

1) Complete copy of the woman model

You might have noticed that when we were copying the geometry of the women, something was missing with the clone model. The eyes where not copied. Why? They were not copied because the are a geometry node on their own, i.e. they are not contained in FACESET_woman. Figure out how they are named and extend the search and copy function thus the eyes are correctly copied with the rest of the body.

Hint: To figure out the names, load the VRML file with a 3D modeling package or just read the file itself with a common text editor. It is really not that hard!


Previous Chapter: Basics

Tutorial Overview

Next Chapter: Geometry

Last modified 7 years ago Last modified on 01/17/10 01:11:44

Attachments (4)

Download all attachments as: .zip