Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C++
Article

Using Lua and luabind with Ogre 3d

Rate me:
Please Sign up or sign in to vote.
5.00/5 (12 votes)
3 Jan 2008CPOL7 min read 102.8K   1.1K   34   21
An article on using Lua via luabind with Ogre 3d, an open source 3d graphics engine
Image 1

Introduction

This is my first article for The Code Project. While C++ has been relegated to hobby status for me, I thought my experience in the following could shed some light for others. That and I hope other people interested in Lua, luabind or Ogre 3d might see that it's not too hard once you get the hang of it. Give it a go and create some cool stuff!

So here is my experience from integrating luabind into a very simple Ogre 3d application. I won't go in to detail on the Ogre side of things, but will concentrate on the binding of the Ogre 3d classes to Lua. For more on Ogre 3d itself, refer to its home page, Wiki and very good forum. For luabind documentation, see here. I recommend that you look at this when you see something new in the code below. I'll very briefly touch on compiling luabind and Lua. Finally, for all things Lua, this is the place to go. I'll assume you know a bit about scripting in Lua, as well as have at least done most of the Ogre 3d tutorials.

My goal (apart from learn the ins and outs of luabind) was to take one of the Ogre tutorial programs, script the creation of the scene and then duplicate the output of the original program. The challenge was that I have no control over the Ogre 3d API. I could not change the API to suit binding.

The Code

Ogre 3d

You will need either the Ogre 3d SDK or the sources.

Lua and luabind

I've included the DLLs, libraries and header files in the ZIP for your convenience. However, you may what to do things differently. You can compile Lua and luabind in several ways. The easiest is to compile everything into one static library. I made a DLL for each, which is included in the project download. However, to compile against Lua 5.1, you have to adjust some things in Lua's configuration. In the file luaconf.h, there is a section on the getn function. This needs setting for compatibility with Lua 5.0, which can be done by adding:

C++
#define LUA_COMPAT_GETN

luabind also complains about not finding two defines that ARE defined in the Lua headers, but adding them to the header just makes more errors. In the end, I just added to lua_include.hpp:

C++
#define LUA_NOREF       (-2)
#define LUA_REFNIL      (-1)

Make sure you use the same runtime for each if you have them as separate libraries and also as your program. Of course, this applies to all programs. I had std::string exploding on me after deletion for awhile. Oops!

The Lua State Manager Class

I created a small class to look after the Lua state. Just something to wrap initialization and closing. It also contains a cool function that checks if a Lua function exists. It's not mine; I think it came from the luabind site.

C++
bool LuaStateManager::FuncExist( lua_State* L, const char *name )
{
        using namespace luabind;

        object g = globals(L);
        object func = g[name];

        if( func )
        {
                if( type(func) == LUA_TFUNCTION )
                        return true;
        }

        return false;
}

You can use this before luabind has been started. Also, by looking to see if the function class has been defined, you can see if luabind has been started or not. I used this in a luabind based DLL that could be used from the interactive Lua program. In this case, class wasn't there so it started luabind or another one of my programs which had started luabind. Thus, the DLL didn't bother as luabind doesn't like starting twice.

Binding

When I started writing the code to do the binding of the Ogre classes I needed, I picked one I thought was the most useful and jumped in with both feet. This was the Vector3 class. I doubt that I'll need all those methods in the scripting. However, I wanted to see if I could completely bind a fairly complex class whole. That and I was full of caffeine. Visual Studio's IntelliSense was quite valuable here. I started with the constructors and methods. Then I added the properties and finally the operators, reading the luabind documentation [1] as I went.

Vector3::ptr() gave me some trouble. I'm not completely sure why, but I didn't need it. This is something to look at later. One problem with luabind's extensive use of templates is the number compiler errors. It's huge! The static "constants" like ZERO and UNIT_Z in Vector3 had me thinking for awhile. At first, I thought of writing a function for each to return their value to Lua. Yuck! Also, they really should be tied to the Vector3 class, as other classes have ZEROs. After reading about luabind's object class, I came up with the following:

C++
object g = globals(L);
object vector = g["Vector3"];vector["ZERO"] = Vector3::ZERO;

So the solution turned out to be quite simple. We grab the global Lua table, which has everything in it, including the table that represents our Vector3. Then we grab the Vector3 table and set a new element. This shows the power of Lua tables. They're sort of a std::vector and std::map rolled into one with Tabasco sauce. This eventually became a set of #define macros.

binding.cpp

C++
##include "stdafx.h" // Ogre, Lua and luabind headers from here too. 
#include "Application.h" 

// Prototype this before operator.hpp so it can be found for tostring() operator. 

std::ostream& operator<<( std::ostream& stream, const Ogre::Entity ent ); 

#include "luabind/operator.hpp" 

using namespace luabind; using namespace Ogre; 

// Some helpful macros for defining constants  (sort of) in Lua. Similar to this code: 
// object g = globals(L);
// object table = g["class"];
// table["constant"] = class::constant; 

#define LUA_CONST_START( class ) { object g = globals(L); object table = g[#class]; 
#define LUA_CONST( class, name ) table[#name] = class::name 
#define LUA_CONST_END } 

void bindVector3( lua_State* L ) 
{ 
        module(L)
        [ 
                class_<Vector3>( "Vector3" )
                .def_readwrite( "x", &Vector3::x )
                .def_readwrite( "y", &Vector3::y )
                .def_readwrite( "z", &Vector3::z ) 
                .def(constructor<>())
                .def(constructor<Vector3&>()) 
                .def(constructor<Real, Real, Real>()) 
                .def("absDotProduct", &Vector3::absDotProduct) 
                .def("crossProduct", &Vector3::crossProduct ) 
                .def("directionEquals", &Vector3::directionEquals )
                .def("distance", &Vector3::distance ) 
                .def("dotProduct", &Vector3::dotProduct ) 
                .def("getRotationTo", &Vector3::getRotationTo )
                .def("isZeroLength", &Vector3::isZeroLength ) 
                .def("length", &Vector3::length ) 
                .def("makeCeil", &Vector3::makeCeil ) 
                .def("makeFloor", &Vector3::makeFloor ) 
                .def("midPoint", &Vector3::midPoint ) 
                .def("normalise", &Vector3::normalise ) 
                .def("nornaliseCopy", &Vector3::normalisedCopy )
                .def("perpendicular", &Vector3::perpendicular )
                .def("positionCloses", &Vector3::positionCloses )
                .def("positionEquals", &Vector3::positionEquals ) 
                //.def("ptr", &Vector3::ptr )
                .def("randomDeviant", &Vector3::randomDeviant )
                .def("reflect", &Vector3::reflect )
                .def("squaredDistance", &Vector3::squaredDistance )
                .def("squaredLength", &Vector3::squaredLength ) 
                
                // Operators 
                
                .def( self + other<Vector3>() ) 
                .def( self - other<Vector3>() ) 
                .def( self * other<Vector3>() ) 
                .def( self * Real() ) 
                .def(tostring(self))
        ]; 
        
        LUA_CONST_START( Vector3 ) 
                LUA_CONST( Vector3, ZERO);
                LUA_CONST( Vector3, UNIT_X);
                LUA_CONST( Vector3, UNIT_Y);
                LUA_CONST( Vector3, UNIT_Z);
                LUA_CONST( Vector3, NEGATIVE_UNIT_X);
                LUA_CONST( Vector3, NEGATIVE_UNIT_Y);
                LUA_CONST( Vector3, NEGATIVE_UNIT_Z);
                LUA_CONST( Vector3, UNIT_SCALE);
        LUA_CONST_END;
}

Next it was ColourValue's turn. This time I didn't go nuts and bind every single member function. In fact, to start with, I didn't bind any at all! At about this point, I wanted to see something from the script on the graphics window rather than the console. I make all my Ogre programs console-based to help with debugging. Also, I considered the fact that not all functionally I want accessible from Lua should require binding of a complete class when just a single function will do. An example of this is setting the background color for a viewport. You can have more than one viewport; however, I only wanted one. I already had an application class that had, as members, pointers to other classes I needed. So that's where SetBackgoundColour() got put, rather than binding the Viewport class. You might notice I had a lapse in naming convention for the Lua accessible methods in the Application class. The coffee was wearing off.

binding.cpp Continued

C++
void bindColourValue( lua_State* L )
{
        module(L)
        [
                class_<ColourValue>("ColourValue")
                .def(constructor<>())
                .def(constructor<Real, Real, Real, Real>())
                .def(constructor<Real, Real, Real>())
                .def(tostring(self))
                .def_readwrite( "r", &ColourValue::r)
                .def_readwrite( "g", &ColourValue::g )
                .def_readwrite( "b", &ColourValue::b )
                .def_readwrite( "a", &ColourValue::a )
                .def( "saturate", &ColourValue::saturate )

                // Operators

                .def( self + other<ColourValue>() )
                .def( self - other<ColourValue>() )
                .def( self * other<ColourValue>() )
                .def( self * Real() )
                .def( self / Real() )
        ];

        LUA_CONST_START( ColourValue )
                LUA_CONST( ColourValue, ZERO);
                LUA_CONST( ColourValue, Black);
                LUA_CONST( ColourValue, White);
                LUA_CONST( ColourValue, Red);
                LUA_CONST( ColourValue, Green);
                LUA_CONST( ColourValue, Blue);
        LUA_CONST_END;
}

Now it's time for an entity. Ogre's entity class doesn't have a stream operator out of the box! Note that I had to define the prototype before including operator.hpp to make things work. Otherwise, there's nothing new here.

binding.cpp Continued

C++
std::ostream& operator<<( std::ostream& stream, const Entity ent )
{
        return stream << ent.getName();
}

void bindEntity( lua_State* L ) // And Movable Object for now.
{
        module(L)
        [
                class_<MovableObject>("MovableObject"),
                class_<Entity, MovableObject>("Entity")
                .def(tostring(self))
                .def("setMaterialName", Entity::setMaterialName )
                .def("setDisplaySkeleton", Entity::setDisplaySkeleton )
        ];
}

Scene nodes: I had some “fun” here. Two things: overloaded functions and optional arguments. They confuse luabind somewhat and you, too! You can handle the overloading with a nasty-looking cast. This lets the compiler know exactly which function you want. Optional arguments used with luabind aren't so known. luabind, via template wizardry, selects which function to run by matching prototypes. The entire prototype. So, you either have to use all those arguments that you never usually use or, as I did, write a proxy function. It's a pity, since Lua is very good at optional arguments, too.

binding.cpp Continued

C++
/// Fake member function for simplifing binding, as the real functions 
// have optional aguments, which I don't want to use in the Lua script.
// However luabind does not support optional arguments.
// Think of "obj" as "this"
SceneNode *createChildSceneNode( SceneNode *obj, const String name )
{
        return obj->createChildSceneNode( name );
}

void SceneNode_yaw( SceneNode *obj, const Real degrees )
{
        return obj->yaw( Degree( degrees ) );
}

void bindSceneNode( lua_State* L )
{
        module(L)
        [
                class_<SceneNode>("SceneNode")
                .def("createChildSceneNode", createChildSceneNode )
                .def("attachObject", SceneNode::attachObject )
                .def("yaw", SceneNode_yaw )
                .def("setPosition", 
                    (void( SceneNode::*)(const Vector3&))SceneNode::setPosition )
                .def("setPosition", 
                    (void( SceneNode::*)(Real,Real,Real))SceneNode::setPosition )
        ];
}

Binding applications. Sounds like some legal jargon. I was getting a bit tired by now and a bit slack. These miscellaneous functions in the Application class perhaps could have gone elsewhere.

binding.cpp Continued

C++
// Function to give lua access to the one and only applcation object.
Application& getApplication()
{
        return application;
}

// TODO: Fix the case in the names of Application's members.
void bindApplication( lua_State* L )
{
        module(L)
        [
                class_<Application>("Application")
                .def( "setBackgroundColour", Application::SetBackgroundColour )
                .def( "createEntity", Application::CreateEntity )
                .def( "getRootNode", Application::GetRootNode )
                .def( "getCamera", Application::GetCamera )
        ];

        module(L)
        [
                def( "getApplication", getApplication )
        ];
}

Now for some fun with a camera and prototypes. Here I got the hang of writing function casts, so you can see overloaded functions bound. Depending on the arguments you use in Lua, you'll get one or the other.

binding.cpp Continued

C++
void bindCamera( lua_State* L )
{
        module(L)
        [
                class_<Camera>("Camera")
                .def("setPosition", 
                    (void( Camera::*)(const Vector3&))Camera::setPosition )
                .def("setPosition", 
                    (void( Camera::*)(Real,Real,Real))Camera::setPosition )
                .def("lookAt", (void( Camera::*)(const Vector3&))Camera::lookAt )
                .def("lookAt", (void( Camera::*)(Real,Real,Real))Camera::lookAt )
                .def("setNearClipDistance", Camera::setNearClipDistance )
                .def("setFarClipDistance", Camera::setFarClipDistance )
        ];
}

Finishing up.

binding.cpp Continued

C++
// Keep this at the bottom so we don't need prototypes for other bind functions.
void bindLua( lua_State* L )
{
        bindVector3( L );
        bindColourValue( L );
        bindEntity( L );
        bindSceneNode( L );
        bindApplication( L );
        bindCamera( L );
}

So the main things that tripped me up were:

  • Overloading, and figuring out how to write those lovely looking casts.
  • Problems with optional arguments.

And now for the script!

script.lua

-- Lua script for test program.

print( 'Hello from LUA!' )

app = getApplication()

app:setBackgroundColour( ColourValue( 0, 0, .25 ) )

camera = app:getCamera()

camera:setPosition( Vector3(0,100,500) )

camera:lookAt( Vector3(0,0,0) )

camera:setNearClipDistance( 5 )

e = app:createEntity( 'Ninja', 'ninja.mesh' )

-- I added this just to see what it did. ;-)
e:setDisplaySkeleton( true )

rootNode = app:getRootNode()

child = rootNode:createChildSceneNode( 'NinjaNode' )

child:attachObject( e )

child:yaw( 180 )

child:setPosition( Vector3.ZERO )

e = app:createEntity( 'floor', 'ground' )

e:setMaterialName( 'Examples/Rockwall' )

Points of Interest / Conclusion

Things I learned:

  • std::strings deleted across a DLL boundary and different runtime libraries are not a good idea. Look at those /M switches if std::string is blowing up. ;-) But of course, you knew that at 3am in the morning like I did.
  • Look at exactly what you want to script. Binding the entire Ogre 3d API would be a phenomenal task and perhaps not the best use of your time. However, doing this small part has helped me learn just what some of the ins and outs of luabind are. Which is what I set out to do. W00t.
  • I hope reading this has helped you, sparked an idea or perhaps even entertained.

References and Links

[1] - Rasterbar luabind documentation

[2] - Lua website

[3] - Ogre 3d website

History

  • Version 1.0 - First draft. Never submitted.
  • Version 1.01 – Finished some bits and submitted some time after first draft.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Australia Australia
Started programmig when I was 8 with GW-BASIC and moved to MS Quick C when I was about 15. Learnt C++ and object oriented programming with Borland 2.0. Now after time, I'm a VB and VC++ programmer and also have moved into database admin. Just recently the maintenace managenent system I look after was upgraded to a n-tier Java based system. Now learning about and using Linux, namely SuSE and a Linux from Scratch box I've made. Other than programming, I know quite a lot about hydro electric power stations, which is the core bussiness of the company I work for. Learning Bass Guitar!

Comments and Discussions

 
GeneralProblems with luabind Pin
JokerOPR2-Jun-11 3:32
JokerOPR2-Jun-11 3:32 
GeneralRe: Problems with luabind Pin
Nigel Atkinson2-Jun-11 13:01
Nigel Atkinson2-Jun-11 13:01 
GeneralRe: Problems with luabind Pin
JokerOPR3-Jun-11 7:20
JokerOPR3-Jun-11 7:20 
GeneralRe: Problems with luabind Pin
Nigel Atkinson13-Jun-11 12:46
Nigel Atkinson13-Jun-11 12:46 
GeneralIf you having trouble with luabind - the luabind mailing list is very helpful. Pin
Nigel Atkinson2-Apr-10 18:48
Nigel Atkinson2-Apr-10 18:48 
Generalusing non-void return type functions Pin
Satchitananda20-Feb-10 23:27
Satchitananda20-Feb-10 23:27 
GeneralRe: using non-void return type functions Pin
Nigel Atkinson21-Feb-10 9:56
Nigel Atkinson21-Feb-10 9:56 
GeneralRe: using non-void return type functions Pin
Satchitananda22-Feb-10 5:52
Satchitananda22-Feb-10 5:52 
GeneralRe: using non-void return type functions Pin
Nigel Atkinson2-Apr-10 18:44
Nigel Atkinson2-Apr-10 18:44 
GeneralProblems with Luabind Pin
Dansveen2-Sep-09 8:32
Dansveen2-Sep-09 8:32 
GeneralRe: Problems with Luabind Pin
Nigel Atkinson2-Sep-09 12:59
Nigel Atkinson2-Sep-09 12:59 
GeneralRe: Problems with Luabind Pin
Dansveen3-Sep-09 7:02
Dansveen3-Sep-09 7:02 
GeneralRe: Problems with Luabind Pin
Nigel Atkinson3-Sep-09 18:25
Nigel Atkinson3-Sep-09 18:25 
GeneralRe: Problems with Luabind Pin
Dansveen4-Sep-09 11:20
Dansveen4-Sep-09 11:20 
Newsluabind has been updated. Pin
Nigel Atkinson2-Sep-09 5:23
Nigel Atkinson2-Sep-09 5:23 
GeneralRe: luabind has been updated, and now 0.9 has been released. Pin
Nigel Atkinson6-Jan-10 22:24
Nigel Atkinson6-Jan-10 22:24 
Generalcant compile Pin
imleet4-Jan-08 8:25
imleet4-Jan-08 8:25 
GeneralRe: cant compile Pin
Nigel Atkinson5-Jan-08 23:55
Nigel Atkinson5-Jan-08 23:55 
GeneralRunning in Visual Studio Pin
Nigel Atkinson4-Jan-08 3:45
Nigel Atkinson4-Jan-08 3:45 
GeneralNice work ^^ Pin
miaoux3-Jan-08 20:26
miaoux3-Jan-08 20:26 
GeneralRe: Nice work ^^ Pin
Nigel Atkinson4-Jan-08 3:30
Nigel Atkinson4-Jan-08 3:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.