A 3d editor, two script languages and Ogre 3d

Using Lua as a scene definition data format

Introduction

How do you load a scene using Ogre3d? You don't. Ogre does not have any scene loading functionality and rightly so. Everyone has vastly different needs for their application, and locking everyone into one or a few methods just restricts people, and perhaps the usefullness of the entire library. So you are left with two options: Roll your own method, or use someone else's solution. The dotScene format seems popular on the Ogre forums, however if you are looking for something quite simple, it is quite easy to cook up something yourself. This article describes using a Lua script as the file format for storing scenes. Its not really tied to Ogre3d, you could use any graphics API really, however Ogre3d is what I had in mind when writing this. At the end are some suggestions on how extend this.

Assumptions

I assume you know C++, and the Lua C API. If you're a bit rough on the Lua API, a quick google or a look on www.lua.org should get you going. The main thing to get your head around is the Lua stack.

Also I'm not going to go into detail about Ogre3d at all.

Why a Lua script?

Why not? Actually if you delve into Lua's history you will find it is partially descended from Simple Object Language or SOL (witch also means sun in Portuguese, Lua is moon). SOL was a data definition language. As a result Lua is very good as a self describing data format. Personally I happen to use Lua quite often, so it is a natural choice.

Very simple Blender export script in Python

Blender uses Python, which is totally brilliant if you know Python, and not so bad if you don't. I'm in the process of learning Python, and I managed to pick up the core syntax in a few hours. I'm still figuring out what is what in the standard library, but I have figured out enough about it to write simple scripts. What you do need to get your head around is the Blender API and how it exposes it's data. Once you have that floating around in the grey matter, you're set.

Blender has a couple of templates one of which will start you off with a hiss and a roar.

Crank up Blender and switch the view to 'Scripting'. In the text window select File->Script Templates->Object Editing. There it is half written for you. Delete the example stuff, and in its place add code so you end up with:

meshdump.py

#!BPY
'''
Name: 'Nigels Mesh Export Script'
Blender: 244
Group: 'Export'
Tooltip: 'Export scene mesh data as a Lua file.'
'''

from Blender import Window, sys, Draw
import bpy
import math

def my_object_util(sce):

	# Remove these when writing your own tool
	print 'Blend object count', len(bpy.data.objects)
	print 'Scene object count', len(sce.objects)
	
	# context means its selected, in the view layer and not hidden.
	print 'Scene context count', len(sce.objects.context)
	
	# Store the current context
	context = list(sce.objects.context)
	
	f = open( '/home/nigel/Blender/meshes.lua', 'w' )
	
	f.write( 'meshes = {}\n' )
		
	for mesh in [ x for x in context if x.type == 'Mesh' ]:
		f.write( 'mesh = {}\n' )
		f.write( 'mesh.name = \'' + mesh.name + '\'\n' )
		f.write( 'mesh.filename = \'' 
                         + mesh.getData( name_only=True ) + '.mesh\'\n' )
		f.write( 'mesh.loc = { ' + str(mesh.LocX) + ', '
                         + str(mesh.LocZ) 
                         + ', ' + str(-mesh.LocY) 
                         + ' }\n' )
		q = mesh.getMatrix().toQuat()
		f.write( 'mesh.rot = { ' 
                         + str(q.w) + ', ' 
                         + str(q.x) + ', ' 
                         + str(q.z) + ', ' 
                         + str(-q.y) + ' }\n' )
		f.write( 'mesh.scale = { ' 
                         + str(mesh.SizeX) + ', ' 
                         + str(mesh.SizeZ) + ', ' 
                         + str(mesh.SizeY) + ' }\n' )
		f.write( 'meshes[mesh.name] = mesh\n' )
		print mesh.getMatrix().translationPart(), q
		
	f.close()

def main():
	
	# Gets the current scene, there can be many scenes in 1 blend file.
	sce = bpy.data.scenes.active
	
	Window.WaitCursor(1)
	t = sys.time()
	
	# Run the object editing function
	my_object_util(sce)
	
	# Timing the script is a good way to be aware on any speed hits when scripting
	print 'Script finished in %.2f seconds' % (sys.time()-t)
	Window.WaitCursor(0)
	Draw.PupMenu( "Export%t | Finished" )
	
	
# This lets you can import the script without running it
if __name__ == '__main__':
	main()

Note there is some weird ordering of Y and Z, as well as negating Y. This converts from Blenders Z up, to Y up which is the way Ogre works.

Replace the file name in the open statement with something that will work for you. Plonk the file in the temp directory, on you Desktop, or whatever you like. Making Blender ask you for a file name is left up to you.

I assume you are using the Ogre Mesh exporter for Blender or something similar to do the actual export of meshes.

This will output for each selected mesh:

Right click in the text window and select Run Script. All going well you should see a bunch of numbers for each selected mesh object. I just added the print statement so you can see it worked from the output in the console window Blender started (under windows) or in the console you started Blender from in Linux (if you started from a console). Any errors will go to the console as well. In my pictures I have changed the cube slightly added 3 more and an uvsphere.

The cubes share the same mesh. So I have 5 objects, but only 2 meshes.

You now should have a nice Lua file somewhere that when run, creates a table, containing a table for each mesh object (Entities in Ogre). Each of these has as properties, the name of the object, the file name of the mesh to load, the location, rotation and scale. The location and scale are each tables with 3 numbers representing x, y, and z. The rotation is a quaternion.

Blender Python API

meshdump.py

C++ code to load the scene

Now lets write the C++ to load this master piece of a scene. I originally started with some code that did not even use Ogre. It just displayed each objects attributes to the console. It also used an approach that did not involve binding any functions to Lua at all. In fact you did not even need to start the Lua libraries. That worked however I explored a different tack with the example linked below.

There where some problems too, the simple program did not show, hence now the export script uses a Quaternion for the rotation instead of a vector. This saves having to know which axis to rotate on first, and prevents gimbal-lock. I have no idea how they work, but since blender is supplying the numbers and Ogre takes them just fine, I don't have to know. ;-)

Full C++ code

Its based on the Ogre tutorial number 1, except for a few modifications to the frame listener, to allow me to attach a light to the camera. The overall way this works is as follows:

After running the script generated by Blender, we run another one to iterate over the data and call a supplied C++ function, for each object.

loader.lua

for _,mesh in pairs( meshes ) do
	Ogre.CreateSceneObject( mesh.name, 
		mesh.filename, 
		mesh.loc, 
		mesh.rot, 
		mesh.scale )
end
print 'All done loading scene!'

Now due to the fact that any function bound to Lua must be static, causes some jumping through hoops in C++ code. See the source code for comments on how I let the bound function 'get at' the application object. I used a very simple singleton, however there are serveral ways to the same end. Here is the main function of interest. The function that Lua calls.

int LuaSceneApp::CreateSceneObject( lua_State *L )
{
    assert( g_application );

    // Params, name, filename, location, rotation
    // location and scale are tables with 3  numbers.
    // Rotation is the same but with 4.

    const char *name;
    const char *filename;

    name = luaL_checkstring( L, 1 );
    filename = luaL_checkstring( L, 2 );

    LogManager::getSingleton().stream() << name << " " << filename;

    luaL_checktype( L, 3, LUA_TTABLE );
    Vector3 location = lua_toVector3( L, 3);

    luaL_checktype( L, 4, LUA_TTABLE );
    Quaternion rotation = lua_toQuaternion( L, 4);

    luaL_checktype( L, 5, LUA_TTABLE );
    Vector3 scale = lua_toVector3( L, 5);

    LogManager::getSingleton().stream() 
      << " Loc: " << location
      << " Rot: " << rotation
      << " Scale: " << scale;

    Entity *entity = g_application->mSceneMgr->createEntity( name, filename );
    SceneNode *node = g_application->mSceneMgr->getRootSceneNode()->createChildSceneNode( name );
    node->attachObject( entity );
    node->setPosition( location );
    node->setOrientation( rotation );
    node->setScale( scale );

    lua_pop( L, 4 );

    return 0;
}
Starting at the top, we check that our nice pointer to the application has been initialised. Grabbing the name and filename off the Lua stack is pretty standard Lua stuff, checking the passed arguments are of the correct sort. There are two helper functions in use here, and both are very similar. They both expect a table at a given index on the Lua stack, and extract either 3 or 4 numbers returning either a Vector or a Quaternion. All this data is used to create an Ogre entity and scene node, and position / rotate / scale it.

Full C++ code

Where to from here?

This is all pretty simple, but it gets you heading down the track. Here are some things you could add to this.

Properties

In Blender you can assign an object properties, under the "Logic" tab. The Python script can be easily modified to output these as well. For example add:

f.write( "mesh.properties = {}\n" )

for prop in mesh.getAllProperties():
    if prop.type == "FLOAT" or prop.type == "INT":
        f.write( "mesh.properties['" + prop.name + "'] = " + str(prop.data) + "\n" )
    elif prop.type == "STRING":
        f.write( "mesh.properties['" + prop.name + "'] = '" + prop.data + "'\n" )
    elif prop.type == "BOOL":
        f.write( "mesh.properties['" + prop.name + "'] = " + str(bool(prop.data) ) + "\n" )

Other objects: Cameras, Lights, and Empties

You could also output Camera positions, and type, Lights with all their attributes and Empties. Empties don't sound very usefull until you assign them properties, or even just give them a meaningfull name. Now you have a marked location in space. These could be way points, spawn points, a point for the camera to look at. Anything you think of really.

Hierarchy of objects

You may want to organise your scene objects in a hierarchy. In Blender you can setup parent child relationships between objects, so its just a matter of outputting this information. A simple way would to use field in the mesh table to hold the name of the parent object. You would need to change the C++ code. One method would be a two stage approach: first iterate over all objects collecting the name, the name of the parent, and index. Sort the list so that all children come after their parent. Then use this list to create the scene. The first step and the sorting/ordering could be done in pre-processing, even in the export script. Then you simply load as before except you parent the scene node to whatever scene node is named in the "parent" field.

Zones and Portals

The export script could be written to define zones and the portals between them. An idea from the Ogre3d forums is to use a material with a particular name ( "portal" perhaps? ) to mark faces as portals. For zones, one per object, or an object could be marked as a zone with a logic property.

Lua files as Ogre Resources

Here you can find an example of a custom resource type, which is simply text. Of course that's what Lua scripts are unless you have pre-compiled them. So using this example code unmodified, you can load your scripts using the resource system.

It's Resources and Resource Managers in the Ogre wiki.

 

The end

Well that's it for now. Hope you got something out of it. ;-) Any comments or suggestions can be sent to the address below.

blender

Nigel Atkinson

suprapilot+LuaCode@gmail.com

Back to Top