Hi, and welcome to the second part of ‘Building an engine’. In the first part we learned something about the languages we’re going to use and got to download the 3d engine as it stands now. Also, the engine was built and maybe even deployed to your iPhone.
In this part I’ll discuss one of the most basic building blocks of the engine: the scenegraph.
Almost all 3d engines have a scene graph of some sort at their core and the Qixis engine is no exception. The scene graph is built up from one or more so called ‘nodes’. The root of the world is also a scenegraph node and is the data member m_root on the engine object. Since the engine object is a singleton, the engine, in its current incarnation, can handle one scene graph.
The basic building block of the scene graph is the ‘node’, a simple class mainly consisting of a position and an orientation in the 3d world. Take a look at this picture:
Here we see a simple scenegraph. In the picture, for simplicity, it’s two dimensional. In the engine, of course, all coordinates are tree dimensional. This scene contains three nodes, named ‘A’, ‘B’ and ‘C’. As you can see in the picture, all nodes have a position and an orientation (called ‘rotation’ in the picture). These two attributes determine their positions in the world.
Every node has a parent. For most nodes, the world’s root node will be their parent, but it’s possible for a node to have child nodes. The position and orientation for child nodes is based on their parent nodes. In the picture, node B has node A as its parent. Its position is calculated by adding node B’s position and orientation to that of node A. Moving node A moves node B along with it. Rotating node A rotates node B too. This is very useful for 3d objects that are made up of multiple parts, like a plane consisting of a body part and a propellor. The propellor has to move and rotate with the plane while at the same time having its own rotation…
In the ‘Init’ function of the engine, we find the following code:
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | bool Engine::Init() { std::cout <<"Engine::Init(): " <<this <<std::endl; //// Construct a test node // Node *nd = new Node("square"); iRenderable *rd = new Square(); nd->position = Vector3<math_type>(0.0f, 0.0f, 0.0f); nd->orientation = Quaternion<math_type>(Vector3<math_type>(0.0f, 1.0f, 0.0f), 0.0f); Node::RenderableInfo ri; ri.renderable = rd; nd->renderables.push_back(ri); m_root->AddChild(nd); // //// return true; } |
These few lines of code actually fire up a large part of the engine. Let’s take a look at the code a few lines at a time.
bool Engine::Init() { //// Construct a test node // Node *nd = new Node("square"); iRenderable *rd = new Square();
This code allocated and initializes two objects. The first is a scenegraph node (we’ll get to that later), the second an ‘iRenderable’-derived class Square. We’ll get to the renderables in the next tutorial.
nd->position = Vector3<math_type>(0.0f, 0.0f, 0.0f); nd->orientation = Quaternion<math_type>(Vector3<math_type>(0.0f, 1.0f, 0.0f), 0.0f);
Every scenegraph node has a few attributes that it must have set. The position and orientation are mandatory and are initialized here to be at the coordinates (0, 0, 0) and have a rotation around the ‘y’-axis (0, 1, 0) of 0 degrees. In short, a square is placed at the center of our virtual universe.
Node::RenderableInfo ri; ri.renderable = rd;
In order for the square to be visible, we need to attach it to the scenegraph node. This is done using a container object, ‘RenderableInfo’. RenderableInfo has two member fields: the renderable object and a material description. Because ‘Square’ is a test object and handles its own material settings, we can leave the material definition empty. Easy
nd->renderables.push_back(ri); m_root->AddChild(nd); // ////
And the last step, were we attach the renderable square to the node, and add the node to our universe, the root of which is conveniently called ‘m_root’, on the engine.
Now that we have set up something to render, let’s try to make it visible on the screen. To do so, all we have to do is to tell the engine where the camera is in our universe and in which direction it is pointing. Since the square we just created is in the center of our universe, it seems only logical that the camera has to point there. Also, we need some distance from the square so we can admire it in full.
All of the work to achieve this is done in the engine’s Render function. Most of it is plumbing and not important right now. The interesting part is where we set up the camera.
129 130 131 132 133 134 135 136 137 138 | //// Temporary camera code // m_camera.Eye(Vector3<math_type>(1.0f, 1.0f, -3.0f)); m_camera.Target(Vector3<math_type>(0.0f, 0.0f, 0.0f)); m_camera.Up(Vector3<math_type>(0.0f, 1.0f, 0.0f)); m_camera.UpdateRenderer(); m_camera.SetFoV(60.0f, 1.0f, 0.1f, 10.0f); m_camera.UpdateFrustumCull(); m_renderer->Clear(true, true, Qixis::Color::Color::DarkBlue()); // //// |
The camera is another member of the engine, just like the universe’s root node. It is controlled by three vectors: Eye – where the camera itself is located, Target – where the camera is looking at and Up – a vector pointing up in the world. After setting up the camera position, we tell the engine to update its internal state so it actually uses the new camera position. Also, we tell it to use a view angle of about 60 degrees, an angle comparable to what a human eye sees.
That’s it for now. You now know the basics of the scene graph and how nodes determine the position of objects in the 3d world. In the next part we’ll add an animator to the node to make the square spin.


