Chapter Overview
| >Transform Core >Component Transform >Materials >Switch >Level of Detail (LOD) >Big Tutorial >Exercises |
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 for sure the most important one, but due to its complexity and importance the whole next chapter (Geometry) is dedicated to this core only!
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 is dependant on the rotation of earth, thus describing the rotation of these both objects in an intuitive manner, but this may become unefficient 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.
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.
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
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
// 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
/* 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
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
2. 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
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, scalation, 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.
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
//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 retrive the rotation etc. from a matrix by using getTransform. Here is an example:
// lets assume m is any sound matrix Quaternion rotation; Vec3f translation; Vec3f scaleOrientation; Vec3f scalation; m.getTransform(translation, rotation, scalation, scaleOrientation); // there is another variant which provides the possibility to extract // the center point Vec3f center; m.getTransform(translation, rotation, scalation, 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.
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.
Matrix result; result.invertFrom(m); m.transposed(result);
As always, you should have a look at the osg::Matrix documentation class for full reference.
Component transform cores are able to stores translation, rotation, scales, scale orientation and a center point seperate 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
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
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 possibiliy is to set the material field in the geometry using setMaterial(). This next code fragment shows a possible usage of both variants
// 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!
Of course that is not as easy as it sounds, unfortunately sorting all primitives in the most efficent manner is a NP-complete problem (If you are not familiar with complexity-therory: NP-complete problems are really, really bad). To simplify the problem OpenGL states that are usually changed together have been grouped in larger pices: osg::StateChunks
Unfortunately I was not able to complete this section in time. This part might be added in the future.
The usage is very easy to handle. It behaves like a normal group node with some additional functionallity demonstrated in the next example
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
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.
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 unsual to you, however this is a very easy and effective way to manipulate data. If you need to refresh your knowledge about multifields, 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 dependant on your scene and the scale you have choosen.
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
#include <OpenSG/OSGSceneFileHandler.h> #include <OpenSG/OSGDistanceLOD.h>
Now replace the createScenegraph() method with following code
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
//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
root->addChild(lodNode);
root->addChild(switchNode);
The compiler needs to know two more include files:
#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 of the Graph 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
// 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
// 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);
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
GeometryPtr geo = GeometryPtr::dcast(womanGeometry->getCore());
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.
//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
// 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
ComponentTransformPtr ct;
Now add the following code directly before the root node is created in createScenegraph():
// 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
#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:
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.
Add a new callback functions to the registration in setupGLUT
glutKeyboardFunc(keyboard);
And now add the keyboard function anywhere before the setupGLUT function
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:
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(); }
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.
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!
Next Chapter: Geometry
1.5.5