For my current project I needed a way to define the contents of a scene in a quick and easy fashion. Some years ago I try and built an entire UI for building scenes (aka the ‘Sandbox’) that, albeit powerful, it was very hard to extend and maintain and ended up being discarded.
This time I went back to the roots and built a small set of classes for parsing LUA files, generating an entire scene based on its contents. And that includes components too.
On one hand, the new set of LUA parsers are extremely powerful. Based on this excellent article which cleverly uses C++ Type Traits pattern (and other tricks), the parsers evaluate of different functions and statements in order to grab values from the scripts.
Consider the following LUA content:
scene = {
nodes = {
{
type = 'geometry',
transformation = {
translate = { 1.0, 2.0, 3.0 }
}
}
},
camera = {
frustum = {
fov = 90.0
}
}
}
Getting the camera FOV angle is as simple as evaluating a statement using the ‘dot’ notation:
float fov = script.eval< float >( "camera.frustum.fov" );
Iterating over a collection of objects is also straightforward:
Pointer< Group > group( new Group() );
script.foreach( "nodes", [&]( ScriptContext &c, ScriptContext::Iterable &childIt ) {
group->attachNode( buildNode( childIt );
});
By employing these set of tools I define a SceneBuilder class that loads a LUA file, parse it using a predefined structure and generate nodes and components.
The trick for building components is to register ‘builder’ functions into a SceneBuilder instance that will instantiate and configure a new component based on the input. If a component defines a constructor that accepts a ScriptContext::Iterable object, then it can be registered in the SceneBuilder like this:
SceneBuilder builder;
builder.registerComponent< MyCustomComponent >();
By default, the SceneBuilder class contains builders for most of the components include in the Core package so they are ready to be used. For these components that don’t declare the required constructor, the SceneBuilder class provides a way to register factory methods that will fulfill the same purpose. This is ensures backward compatibility.
As a final note, I also improved the Simulation framework to accept settings from a file or from the command line in a similar way as explained above. For example, you can define a crimild.lua file that specifies the video resolution like this:
video = {
width = 1136,
height = 640
}
… or you can override the default settings by using the command line:
./MyCrimildBasedProgram video.width=1440 video.height=900
That’s it for today. I still need to create a demo that uses these new set of classes, but they are already available in the ‘master’ branch in GitHub if you want to take a look.