A brief introduction to the Murl Engine development
The Murl Engine is a native, platform-independent, time-based scene graph framework for developing games and multimedia applications. The framework has been designed for maximum performance and flexibility and it allows the cross platform development of native applications with only a single code base.
Native and Cross Platform
The framework development package consists of a platform-independent core module (“framework code”) and a platform-specific adaption module (“platform code”) for each supported platform. The framework code was developed in C++ and the platform code in C, C++, ObjectiveC and Java depending on the actual target.
Application development (“user code”) is performed in C++ or LUA based on the framework's API (Application Programming Interface). It is possible to create a native application for each supported target platform from these three components: user code, framework code and platform code.
Application = User Code + Framework Code + Platform Code
Using C++ for native application development allows efficient programming at a high abstraction level with optimum run-time performance.
In this context, the term “native” means that the created applications can be directly run on the selected target devices with their actual operating systems. There is no need for additional plugins, emulators, run-time environments or similar external components, which helps to reach optimum performance and system integration.
Basically, application development can be performed by using any available C++ IDE. However, the framework's support for project creation and maintenance is currently only provided for Microsoft Visual Studio (Express) and Apple XCode, which are therefore recommended for application development.
In order to build applications for iOS and/or Mac OS X, a computer running Mac OS X and XCode is necessary, whereas a computer running Microsoft Windows and Visual Studio is required for the development of Windows applications. Applications for Android can be built on both of these systems.
OS dependency for building application
Time Based
Applications are executed in a time-based manner. The framework uses a rendering thread and a logic thread, which are always processed simultaneously in order to create a sequence of individual display frames.
During the process of each loop, the logic thread calls a user code method (e.g. OnProcessTick()
), which can be used to update the current state of the application. Typically, the implementation of this method should check how much time has passed since the last call (e.g. GetCurrentTickDuration()
) and which user input has been carried out. Based on this information, the application is “simulated” and relevant objects are updated accordingly.
After finishing this procedure one or more times, the rendering thread starts to actually render all visible objects, while the logic thread already begins to evaluate the subsequent step.
The logic thread's actual step size (i.e. the time between two steps) can be defined via specific items in the main configuration of the framework. It is possible to either select a fixed or variable step size. Furthermore, it is also possible to define a minimum and maximum time value. Additionally, the configuration allows defining the actual number of steps which are carried out for each frame, again with possible minimum and maximum values.
Scene Graph
In order to describe the virtual world, the Murl Engine uses a scene graph. A scene graph is an object-oriented data structure, specifically a directed acyclic graph (DAG). Within a scene graph individual objects can be stored, transformed and/or grouped together in a user-defined way.
The scene graph represents a tree-like structure made up from individual graph nodes with exactly one root node and any number of child nodes. Each graph node contains specific information about the virtual scene such as geometry, color, light sources, audio sources or transformation depending on its specialization.
Scene Graph Example
Usually, every graph node affects its child nodes. For instance, a Transform
node can be used to hierarchically modify e.g. the position and rotation of the sub-graph below the node, i.e. all of its children, grand-children etc. Generic nodes may be e.g. made invisible, which also affects all child nodes in their sub-graphs.
State nodes, such as MaterialState
, ParametersState
or CameraState
, form an exception. State nodes cause a change in the current traversal context and affect therefore not only the child nodes, but also all subsequent nodes. If, for instance, a MaterialState
node is used to select a certain material, every subsequent node is rendered with this material until a different material is chosen with another MaterialState
node.
The graph node SubState
can be used to locally restrict state changes. That means that any context changes within a child of a SubState
node do not affect any subsequent nodes. After processing its child nodes, the SubState
node resets all changes made to the traversal context.
All types of graph nodes have some basic properties in common:
- unique
id
active
property
visible
property
The id
property is used to clearly identify a specific node in the graph. The visible
property allows controlling the visibility of the node and its child graphs. All nodes, which are set invisible, are skipped during the rendering of a frame (the Output Traversal is skipped). The active
property controls, if logic operations are carried out or not (the Logic Traversal is skipped). To completely deactivate a node, both the active
and visible
properties must be set to false. This can either be done separately or via the combined activeAndVisible
property.
- Note
- Caution, only alphanumeric characters and the characters point and underscore are allowed as any character of the id property. In addition, id properties may not start with a numeric character. Other characters (e.g. -, +, :, etc.) are not allowed.
During the process of each loop, the framework performs a number of traversals on the specified scene graph. Starting at the root, the graph is processed in a depth-first manner. All children are visited recursively in the order they are defined. For the sample graph above, the order, in which the graph nodes are traversed, is as follows:
- Root
- View
- Camera
- FixedProgram
- Material
- MaterialState
- Transform
- CubeGeometry
- PlaneGeometry
- CameraTransform
Basically, there are two different traversals which are carried out during each loop - Logic Traversals and Output Traversals:
- A logic traversal always happens directly after the calls in the
OnProcessTick()
methods of the user code are finished. Nodes, whose properties were modified through the user code, have their internal state updated, and a physics simulation step is performed, if the graph contains nodes which require such an action.
- After all logic traversals for a frame are finished, a single output traversal is carried out. All relevant information for output generation is gathered and all visible nodes are prepared for rendering.
XML Files
One way to build a scene graph or parts of a scene graph for the Murl Engine is to create one or more text documents using XML notation. These XML files can be loaded into the system memory by the application. Afterwards, the individual nodes can be accessed and modified as required. The following paragraphs give a brief overview of the structure of such a file.
<?xml version="1.0" ?>
<Graph>
<View id="view" />
<Camera id="camera"
viewId="view"
fieldOfViewX="400"
nearPlane="400" farPlane="2500"
clearColorBuffer="1" >
<!-- comment -->
<FixedProgram id="prg_white" />
<Material id="mat_white"
programId="prg_white" />
<MaterialState materialId="mat_white"
slot="0" />
<Transform id="transform">
<CubeGeometry
id="myCube01"
scaleFactor="200"
posX="0" posY="0" posZ="0" />
<PlaneGeometry
id="myPlane01"
scaleFactorX="42"
scaleFactorY="100"
posX="0" posY="0" posZ="0" />
</Transform>
<CameraTransform cameraId="camera"
posX="0" posY="0" posZ="800" />
</Camera>
</Graph>
Generally, XML documents start with an optional XML declaration, e.g. <?xml version="1.0" ?>
Elements within an XML document are indicated by < >
. Every element consists of a start tag <element>
and an end tag </element>
. Child elements are defined in between the start and end tags. For elements, which do not contain any child elements, the empty element tag <element/>
can be used optionally. Defining an element via <element></element>
is equivalent to <element/>
.
Attributes of an element are specified in the start tag (or in the empty element tag) as a pair of attribute name and attribute value: attributeName="attributeValue"
Comments within an XML file start with <!--
and end with -->
.
- Note
- Caution! Individual attributes cannot be put within comment markers.
There are several basic rules for an XML document to be well-formed:
- A well-formed XML document has to contain exactly one root element, which encloses all other (child-) elements.
- For every start tag there has to be exactly one matching end tag at the same nesting level and vice-versa.
- All attribute names of an element must be unique (there cannot be two or more attributes with the same name within an element).
Naming Conventions
CLASSES, NAMESPACES, FUNCTIONS, METHODS
Class, namespace, method and function names start with an upper-case letter
e.g. MyApp
or LoadData()
INTERFACES
Interface names begin with an upper-case I
e.g. IAppConfiguration
VARIABLES
Variables start with a lower-case letter
e.g. rotationAngleX
MEMBER VARIABLES
Member variables start with a lower-case m
e.g. mVar
FILE NAMES
File names are composed of namespace and class name and consist only of lower-case letters, digits and/or underscore (“_”) as separator.
e.g. murl_my_app.cpp
/ murl_my_app.h
for class MyApp
inside the namespace Murl
Primitive Data Types
C++ compilers are free to define the standard C++ data types with e.g. different bit depths and value ranges. For this reason, it should be considered to always use the platform-independent data types defined in the framework (Note the upper-case, e.g. Char
instead of char
.). These data types are defined in the file murl_types.h
:
murl/base/include/engine/murl_types.h
UInt64 Unsigned 64 bit integer (0 to 18.446.744.073.709.551.615)
UInt32 Unsigned 32 bit integer (0 to 4.294.967.295)
UInt16 Unsigned 16 bit integer (0 to 65.535)
UInt8 Unsigned 8 bit integer (0 to 255)
SInt64 Signed 64 bit integer (−9.223.372.036.854.775.808 to 9.223.372.036.854.775.807)
SInt32 Signed 32 bit integer (−2.147.483.648 to 2.147.483.647)
SInt16 Signed 16 bit integer (−32.768 to 32.767)
SInt8 Signed 8 bit integer (-128 to 127)
Double 64bit IEEE floating point (52 Bit Mantisse, 11 Bit Exponent)
Float 32bit IEEE floating point (23 Bit Mantisse, 8 Bit Exponent)
Real platform-specific - either 32 bit or 64 bit IEEE floating point
Bool Boolean
true or
false (XML File:
"true",
"on",
"yes",
"1" or
"false",
"off",
"no",
"0")
char Char
Character data type.
Definition: murl_types.h:179
bool Bool
Boolean data type This typedef represents a boolean value (true or false).
Definition: murl_types.h:174
MurlSInt64 SInt64
Signed 64 bit integer data type.
Definition: murl_types.h:164
MurlUInt16 UInt16
Unsigned 16 bit integer data type.
Definition: murl_types.h:144
MurlUInt8 UInt8
Unsigned 8 bit integer data type.
Definition: murl_types.h:136
MurlUInt32 UInt32
Unsigned 32 bit integer data type.
Definition: murl_types.h:152
MurlSInt8 SInt8
Signed 8 bit integer data type.
Definition: murl_types.h:140
MurlSInt32 SInt32
Signed 32 bit integer data type.
Definition: murl_types.h:156
double Double
Explicit 64bit IEEE floating point data type.
Definition: murl_types.h:193
float Float
Explicit 32bit IEEE floating point data type.
Definition: murl_types.h:189
MurlSInt16 SInt16
Signed 16 bit integer data type.
Definition: murl_types.h:148
MurlReal Real
Generic floating point data type.
Definition: murl_types.h:200
MurlUInt64 UInt64
Unsigned 64 bit integer data type.
Definition: murl_types.h:160
Matching container classes from murl_types.h
:
Array< Bool > BoolArray
An array of boolean values.
Definition: murl_types.h:318
Array< SInt64 > SInt64Array
A signed 64 bit integer array.
Definition: murl_types.h:298
Array< UInt64 > UInt64Array
An unsigned 64 bit integer array.
Definition: murl_types.h:294
Array< UInt16 > UInt16Array
An unsigned 16 bit integer array.
Definition: murl_types.h:278
Array< UInt8 > UInt8Array
An unsigned 8 bit integer array.
Definition: murl_types.h:270
Array< SInt16 > SInt16Array
A signed 16 bit integer array.
Definition: murl_types.h:282
Array< SInt8 > SInt8Array
A signed 8 bit integer array.
Definition: murl_types.h:274
Array< Float > FloatArray
Explicit 32bit IEEE floating point array.
Definition: murl_types.h:309
Array< String > StringArray
A string array.
Definition: murl_types.h:261
Array< Real > RealArray
Generic floating point array.
Definition: murl_types.h:305
Array< Double > DoubleArray
Explicit 64bit IEEE floating point array.
Definition: murl_types.h:313
Index< String, StdHash< String > > StringIndex
String index container, with default hashing function.
Definition: murl_types.h:210
Array< SInt32 > SInt32Array
A signed 32 bit integer array.
Definition: murl_types.h:290
Array< UInt32 > UInt32Array
An unsigned 32 bit integer array.
Definition: murl_types.h:286
Mathematical constants from murl_math_types.h
:
constexpr Double MM_TO_CM
Definition of millimeters to centimeters factor.
Definition: murl_math_types.h:56
constexpr Double TWO_PI
Definition of two pi.
Definition: murl_math_types.h:26
constexpr Double MM_TO_INCHES
Definition of millimeters to inches factor.
Definition: murl_math_types.h:65
constexpr Double E
Definition of e.
Definition: murl_math_types.h:17
constexpr Double HALF_PI
Definition of halve pi.
Definition: murl_math_types.h:30
constexpr Double PI
Definition of pi.
Definition: murl_math_types.h:22
constexpr Double INCHES_TO_MM
Definition of inches to millimeters factor.
Definition: murl_math_types.h:74
constexpr Double CM_TO_INCHES
Definition of centimeters to inches factor.
Definition: murl_math_types.h:69
constexpr Double INV_PI
Definition of inverse pi.
Definition: murl_math_types.h:34
constexpr Double INV_TWO_PI
Definition of inverse 2*pi.
Definition: murl_math_types.h:38
constexpr Double CM_TO_MM
Definition of centimeters to millimeters factor.
Definition: murl_math_types.h:60
constexpr Double RAD_TO_DEG
Definition of radians to degrees factor.
Definition: murl_math_types.h:51
constexpr Double DEG_TO_RAD
Definition of degrees to radians factor.
Definition: murl_math_types.h:47
constexpr Double INCHES_TO_CM
Definition of inches to centimeters factor.
Definition: murl_math_types.h:78
constexpr Double INV_HALF_PI
Definition of inverse pi/2.
Definition: murl_math_types.h:42
Corresponding mathematic functions
are located in the namespace Murl::Math
:
Abs,
Clamp,
IsEqual,
Sgn,
Min,
Max,
IsNaN,
IsInfinite,
IsFinite,
Exp,
Log,
Log2,
Log10,
Sqrt,
Pow,
Fmod,
ModF,
Sin,
Cos,
Tan,
ArcSin,
ArcCos,
ArcTan,
ArcTan2,
SinHyp,
CosHyp,
TanHyp,
ArcSinHyp,
ArcCosHyp,
ArcTanHyp,
Interpolation
Predefined interpolation curves (easing functions).
Definition: murl_i_enums_animation.h:21
DataType Pow(DataType base, DataType exponent)
Get a base raised to the power of an exponent.
DataType Sgn(DataType value)
Get the sign of a value.
Definition: murl_math.h:37
DataType ArcTanHyp(DataType value)
Get the hyperbolic arc tangent of an value.
DataType ModF(DataType value, DataType &intPart)
Get the integer part and the fractional part of a value.
DataType SinHyp(DataType value)
Get the hyperbolic sine of an value.
Bool IsNaN(DataType value)
Check if a value is not a number (NaN).
DataType Cos(DataType radAngle)
Get the cosine of an angle value.
DataType RadToDeg(DataType radiants)
Convert radiants into degrees.
Definition: murl_math.h:423
DataType ArcTan2(DataType y, DataType x)
Get the arc tangent of y divided by x.
DataType Abs(DataType value)
Get the absolute value.
Definition: murl_math.h:25
DataType Sin(DataType radAngle)
Get the sine of an angle value.
DataType ArcCos(DataType value)
Get the arc cosine of a value.
DataType Ceil(DataType value)
Round up to an integral value.
DataType Log(DataType value)
Get the natural logarithm of a value.
Bool IsEqual(const DataType &a, const DataType &b, const DataType &epsilon=Limits< DataType >::Epsilon())
Check if two values are equal within an epsilon range.
Definition: murl_math.h:400
DataType TanHyp(DataType value)
Get the hyperbolic tangent of an value.
DataType Log2(DataType value)
Get the base 2 logarithm of a value.
DataType SubAngle(DataType angle1, DataType angle2)
Calculate the difference between two angles.
Definition: murl_math.h:467
DataType AddAngle(DataType angle1, DataType angle2)
Calculate the sum of two angles.
Definition: murl_math.h:455
const DataType & Max(const DataType &x, const DataType &y)
Get the maximum of two values.
Definition: murl_math.h:100
DataType Tan(DataType radAngle)
Get the tangent of an angle value.
DataType ArcSinHyp(DataType value)
Get the hyperbolic arc sine of an value.
DataType MapAngle(DataType angle)
Map an angle into range [-PI .
Definition: murl_math.h:434
Bool IsInfinite(DataType value)
Check if a value is infinite (either positive infinity or negative infinity).
DataType CosHyp(DataType value)
Get the hyperbolic cosine of an value.
Bool IsFinite(DataType value)
Check if a value is finite.
DataType Exp(DataType value)
Get the the e number raised to the power of a value.
const DataType & Clamp(const DataType &val, const DataType &min, const DataType &max)
Clamp a value.
Definition: murl_math.h:142
DataType Fmod(DataType numerator, DataType denominator)
Get the remainder of a numerator divided by a denominator.
DataType Round(DataType value)
Round to an integral value, regardless of the rounding direction.
Definition: murl_math.h:380
DataType Sqrt(DataType value)
Get the square root of a value.
DataType ArcTan(DataType value)
Get the arc tangent of a value.
DataType ArcCosHyp(DataType value)
Get the hyperbolic arc cosine of an value.
const DataType & Min(const DataType &x, const DataType &y)
Get the minimum of two values.
Definition: murl_math.h:58
DataType Floor(DataType value)
Round down to an integral value.
DataType DegToRad(DataType degrees)
Convert degrees into radiants.
Definition: murl_math.h:412
DataType ArcSin(DataType value)
Get the arc sine of a value.
DataType Log10(DataType value)
Get the base 10 logarithm of a value.
Further util functions
can be found in the namespace Murl::Util
e.g.:
DataType RoundToNextPowerOfTwo(DataType value)
Round a value to the next power of two.
Definition: murl_util.h:270
Bool IsPowerOfTwo(DataType value)
Check if a given value equals a power of two.
Definition: murl_util.h:294
UInt64 SwapBytes(UInt64 value)
Swap all bytes of a 64 bit value.
UInt32 GetNumberOfSetBits(DataType value)
Compute the number of set bits.
Definition: murl_util.h:375
DataType RoundToNextSixteenByteBoundary(DataType value)
Round a value to the next 16 byte boundary.
Definition: murl_util.h:327
DataType RoundToRaster(DataType value, DataType raster, DataType &diff)
Round a value to the next raster and return the rounded value and the difference to the previous rast...
Definition: murl_util.h:341
void Swap(DataType &a, DataType &b)
Swap two values.
Definition: murl_util.h:257
UInt32 GetNumberOfDigits(DataType value, DataType base=10)
Compute the number of digits.
Definition: murl_util.h:407
DataType RoundToNextEightByteBoundary(DataType value)
Round a value to the next 8 byte boundary.
Definition: murl_util.h:316
DataType RoundToNextFourByteBoundary(DataType value)
Round a value to the next 4 byte boundary.
Definition: murl_util.h:305
UInt32 GetNumberOfClearedBits(DataType value)
Compute the number of cleared bits.
Definition: murl_util.h:395
Strings
Furthermore, the framework provides its own string class
, together with a number of conversion functions
and useful auxiliary functions
.
murl/base/include/engine/murl_string.h
murl/base/include/engine/util/murl_util_string.h
…
void SortStringArray(StringArray &array, Bool ascending)
Sort a String array.
String UInt32ToString(UInt32 inputValue)
Convert a UInt32 value to a string.
Bool StringToBool(const String &inputString, Bool &value)
Convert a decimal string to a bool value.
Bool StringToUInt32(const String &inputString, UInt32 &value)
Convert a decimal string to a UInt32 value.
Bool StringToFloat(const String &inputString, Float &value)
Convert a string to a Float value.
Bool StringToDouble(const String &inputString, Double &value)
Convert a string to a Double value.
String SInt32ToString(SInt32 inputValue)
Convert a SInt32 value to a string.
Bool StringToColor(const String &inputString, Color &value, ColorStringFormat &format)
Convert a string to a color object.
Bool StringToSInt32(const String &inputString, SInt32 &value)
Convert a decimal string to a SInt32 value.
String DoubleToString(Double inputValue)
Convert a Double value to a string.
Bool AngleStringToDouble(const String &inputString, Double &value, Bool &hasUnit)
Convert an angle string to a Double value.
Bool HexStringToUInt32(const String &inputString, UInt32 &value)
Convert a hexadecimal string to a UInt32 value.
Bool StringToColorComponent(const String &inputString, Float &value, ColorStringFormat &format)
Convert a string to a Float color component.
void TrimStringArray(StringArray &inputString)
Trim all strings in a string array.
UInt32 SplitString(const String &inputString, Char delimiter, StringArray &pieces, Bool acceptEmpty=false)
Split a string into pieces using a given delimiter, the pieces are stored in a string array.
Debug Messages
The header file murl_debug_trace.h
contains methods to print debug messages
.
murl/base/include/engine/debug/murl_debug_trace.h
DEBUG::TRACE, DEBUG::ERROR
The function Debug::Trace
can be used to print simple status information to the console. On some platforms, the output also includes time stamp information. Just as for the function printf
, formatting parameters can be used optionally.
Debug::Trace("Hello World!");
Debug::Trace("The result is %u:", result);
Any output of Debug::Trace
calls will only be generated in the debug mode and will automatically be omitted during the release build of an application. If a message should be visible in a release build as well, Debug::Error
can be used instead.
MURL_TRACE
The command MURL_TRACE
prints in addition to the status information also the method name and the line number. Optional formatting parameters can also be used.
first-line:63;
MURL_TRACE(0, "Debug Hello World");
The first parameter (0) defines the log level for this messages. Debug messages with a log level greater than the global log level will be suppressed. If for example the global log level is set to 1, only debug messages with a log level smaller than or equal to 1 are printetd. You can use the method SetLogLevel
and GetLogLevel
to set or read the global log level. The printed status information would look like this:
Murl::App::ContainerLogic::PrintDemo(), line 63: Debug Hello World
The output of MURL_TRACE
calls will also only be generated in the debug mode.
SYSTEM::CONSOLE::PRINT
Another way to print information to the console is the Murl::System::Console::Print
function. By using this function, the output will be retained even in a release build. In addition, it prints the given text “as is”, with no extra information such as time stamp, new lines etc.
murl/base/include/engine/system/murl_system_console.h
It also accepts optional formatting parameters:
System::Console::Print("Print with System Console");
System::Console::Print("Current Time %.10f", state->GetCurrentTickTime());
PRINTTREE
By using the PrintTree
method, it is possible to print the structure of the current scene graph to the console:
Graph::IRoot* root = state->GetGraphRoot();
root->PrintTree();
Depending on the given start node, it is possible to print the whole scene graph or only a sub-graph.
Graph::INode* node = mBallTransform->GetNodeInterface();
node->PrintTree();
SETUSERDEBUGMESSAGE
As soon as the “debug” package is loaded by the application, the method SetUserDebugMessage
of the Murl::Logic::IState
interface can be used to show a simple status message on the screen:
state->SetUserDebugMessage("Package Loading succeeded!");