XEngine Programming Guide

Home About XEngine Engine API SceneGraph Changelog Programming Guide

Prerequisites

A basic undestanding of how XEngine is structured in terms of API calls is suggested. Please have a look at the Engine API, SceneGraph and the About page.

Getting Started

A Visual Studio 2010 example project is provided with the XEngine DLL files in the XEngineDist folder. The window management toolkit used is FreeGLUT. Its including folders are:

Class Description

The following cpp files are included in the XEngine_GLUT project:

Initializing XEngine

Before rendering anything, we need to initialize XEngine, as well as any external data we need to use. The InitializeApplication function is responsible for this. The basic code structure is as follows:

Initializing SceneGraph

After calling XE_engine_create, the scene graph contains only the root node. The most basic initialization is done using the Configuration API. This setups a default camera, a default user and a default input node, as well as a default lighting scheme.

// Create a default camera
XE_configuration_create_default_camera();

// Create a default lighting configuration. Two settings are currently available.
// An indoor light setting, and an outdoor light setting.
// These can also be set to follow the user as he navigates in the scene.
//XE_configuration_create_default_light_rig(false);
XE_configuration_create_default_sun_light_rig(false);

Alternatively, we need to perform this setup ourselves. This requires to create each item separately, and attach them to the appropriate nodes. The following example shows how to setup a default light.

// Retrieve the root node id. Alternatively, using -1 has the same effect.
uint32 root_id = XE_scene_get_node_id(SN_ROOT, NULL);

// Create a light node, which is attached to the root node. Set all other light parameters here.
uint32 light1_id = XE_scene_create_node(SN_LIGHT, "point1", root_id);

XE_light_set_shadows_status(light1_id, true);
XE_light_set_rsm_status(light1_id, true);
XE_light_set_color(light1_id, 0.8f, 0.2f, 0.2f);
XE_light_set_light_type(light1_id, LT_POINT_SPOT);
XE_light_set_attenuation_status(light1_id, true);
XE_light_set_position(light1_id, 120.0f, 180.0f, 0.0f);
XE_light_set_target(light1_id, 0.0f, 0.0f, 0.0f);
XE_light_set_shadow_resolution(light1_id, 1024);
XE_light_set_conical_status(light1_id, true);
XE_light_set_cone_apperture(light1_id, 50.0f);
XE_light_set_soft_shadow_size(light1_id, 7.0f);
XE_light_set_near_range(light1_id, 10.0f);
XE_light_set_far_range(light1_id, 400.0f);

To setup a basic camera, user and input node, we perform the following steps:

// Create an input node, which is attached to the root node.
uint32 input_id = XE_scene_create_node(SN_INPUT, "MouseKeyboard", root_id);

// Create a user node, which is attached to the root node. The user needs to be attached to an input node, so that it is controlled by input events. Also, set all other user parameters here.
uint32 user_id = XE_scene_create_node(SN_USER, "Myself", root_id);
XE_user_attach_to_input_node(user_id, input_id);
XE_user_set_linear_speed(user_id, 10.0f);
XE_user_set_angular_speed(user_id, 1.0f);
XE_user_set_position(user_id, 0.0f, 0.0f, 80.0f);
XE_user_set_look_at(user_id, 0.0f, 0.0f, 0.0f);

// Create a camera node, which is attached to the root node. The camera can follow a node. It's most common use is to follow the user, which in turn is attached to an input node. Set all other camera parameters here.
uint32 camera_id = XE_scene_create_node(SN_CAMERA, "first_person", root_id);
XE_camera_set_apperture(camera_id, 60.0f);
XE_camera_set_near_range(camera_id, 1.0f);
XE_camera_set_far_range(camera_id, 1400.0f);
XE_camera_set_primary(camera_id, true);
XE_camera_set_follow(camera_id, user_id);


To load a static geometry file, we need to:

// Add the directory that contains the obj file
XE_scene_append_directory("DoraEmbrasure_Reassembled");

// Add a transformation node, so that we can apply transformations to the object
float scale = 0.01f;
unsigned int scene_transformation1_id = XE_scene_create_node(SN_TRANSFORM, "environment1", root_id);
XE_transform_set_uniform_scale(scene_transformation1_id, scale);
XE_transform_set_rotation(scene_transformation1_id, 180.0f, 0.0f, 1.0f, 0.0f);

// Add a geometry node
unsigned int geometry1_id = XE_scene_create_node(SN_GEOMETRY, "geom1", scene_transformation1_id);

// Register the mesh. If the mesh is not loaded, a log message will be generated later on
XE_geometry_register_static_mesh(geometry1_id, "DoraEmbrasure_Reassembled_s.obj");

Render and Resize

Rendering happens within the Render function:

// Update the engine timer. This is set explicitly, so that the user can perform time-based calculations in the correct time intervals
XE_timer_update();

// Update any external data (such as updating the engine's input device information)
UpdateApplication();

// Update the engine (the graph, any new provided parameters, primitives, etc)
XE_engine_update();

// Render
XE_engine_render();

// Swap Buffers
glutSwapBuffers();

Resize happens within the Resize function:

// Prevent a divide by zero by making height equal to one
if (height == 0)
    height = 1;

// Inform the engine a resize has occurred
XE_engine_resize(width, height);

Handling Input

To handle input events we need to do the following (the following should be performed before XE_engine_initialize if we wish to navigate within the scene when the application starts:

// Retrieve the input node id
int input_id = XE_scene_get_node_id("MouseKeyboard");

// Attach the default device to the input (simply send 0 for single input applications)
XE_input_attach_device(input_id, s_input.m_mouse_keyboard_device.id);

// For mouse and keyboard navigation, we need to set a maximum number of buttons and axes the engine will interact with.
// In this example, 5 buttons and 5 axes are being set up.
// The buttons are used for mouse click events, (button 0 is left click, button 2 is right click),
// while the axes are used for translation on the XYZ axes and rotation on the XY axes based on mouse movement.
// These are configurable by the external user, as you can see in the KeyDownOperations, KeyDownSpecialOperations and Mouse functions of the Input class
XE_device_set_max_values(s_input.m_mouse_keyboard_device.id, s_input.m_mouse_keyboard_device.num_buttons, s_input.m_mouse_keyboard_device.num_axes);


The Input class handles all FreeGLUT input events. This includes mouse motion, regular and special keys with multi-key support and generation of events when a key is in an up, down or pressed state and when a mouse button is in an up or down state. Four functions are provided for this purpose:

For example, if we wish to use the Settings API and zoom to the extents of the scene's bounding box, we add the following to the KeyPressedSpecialOperations function.

void Input::KeyPressedSpecialOperations()
{
    if (KEYSPECIALPRESSED(GLUT_KEY_F2))
    {
        XE_settings_zoom_to_extents(-1);
    }
}

To update input events with XEngine, we do the following in the UpdateApplication function:

void UpdateApplication()
{
    
// Update key events and exit if the ESC key has been pressed
    if (s_input.Update()) ReleaseGLUT();

    
// Update XEngine with the current values
    for (int i = 0; i < s_input.m_mouse_keyboard_device.num_axes; ++i)
    {
        XE_device_set_axis(s_input.m_mouse_keyboard_device.id, i,         s_input.m_mouse_keyboard_device.axes[i]);
        s_input.m_mouse_keyboard_device.axes[i] = 0;
    }
    for (int i = 0; i < s_input.m_mouse_keyboard_device.num_buttons; ++i)
    {
        bool state = s_input.m_mouse_keyboard_device.buttons[i] == 1;
        XE_device_set_button_pressed(s_input.m_mouse_keyboard_device.id, i, state);
    }

    
// We are done updating the input
    s_input.PostUpdate();
}

Handling Primitives

In order to send primitive data to XEngine using the Primitives API a special class called TestData is provided. This contains a structure called GroupData which can contains all possible information that can be sent to XEngine. As a test case, it builds list of primitives per data type. A box made of triangles, a box bade of lines and a box made of points.

Two functions are important here:

During the UpdateApplication, the AddPrimitives function is called which iterates through the TestData containers and sends them to XEngine using the Primitives API.

The actual AddPrimitives in the example project creates the lists twice, one with lighting enabled and one with lighting disabled. The lists with lighting disabled are rendered through a separate rendering pass, while the lists with the lighting enabled are merged with the default XEngine rendering pipeline.

Note that the list begin-end procedure needs to be repeated each frame the primitives are to be rendered.

This example performs the following actions:

XML File Loading

Instead of using direct function calls to load the graph's nodes, an external XML file can be used according to the specifications of the Scene Description Language. The following example, performs the basic operations of loading a scene file, as well as setting up all basic nodes.

<?xml version="1.0" encoding="utf-8"?>

<world background="0.3 0.4 0.7" ambient="0.26 0.31 0.35">

<directory path="knossos"></directory>

<user name="default_user" linear_speed="10.0" angular_speed = "1.0" position = "-3.3 32.9 88.9" lookat = "3.6 9.3 33.2" input="default_input">
</user>

<camera name="first_person" apperture="60.0" focal_range="100.0" near="1" far="400" follow="default_user" primary="true">
</camera>

<input name="default_input" devicename="device0">
</input>

<transformation name="environment" rotation="0,1,0,0" scale="1.4,1.4,1.4" translation="0,0,0">
<object name="geom2" file="knossos.obj"></object>
</transformation>

<light name="point1" shadows="on" color="0.43, 0.42, 0.36" active="true" type="spotlight" rsm="true"
attenuation="on" far_range="400" near_range="100" position="120,180,0" target="0,0,0"
resolution="1024" conical="true" aperture="50" soft_shadow_size="1">
</light>

</world>

This file can be loaded either from the configuration file, or explicitly by calling the following Engine API functions.

// Explicitly set a scene file
XE_scene_set_file("knossos.scene");

// Read it back for verification
char scene_name[256];
bool has_scene_name = XE_util_read_scene_name(scene_name), 256);

// Explicitly parse the file (otherwise it will be parsed on XE_engine_initialize
// Parsing the file before init allows to create additional nodes or attach an input device
// to an input node

XE_scene_parse_file();

Last updated: 27 May 2013