www.xbdev.net
xbdev - software development
Monday April 29, 2024
home | contact | Support | Physics.... Squishy, Bouncy, Springy, Rigid, ...Bodies... | Game Physics Programming.. Bouncing Objects, Springs, Balls, Rigid Bodies...

     
 

Game Physics Programming..

Bouncing Objects, Springs, Balls, Rigid Bodies...

 

ODE with C#

by Ben Kenwright

 

The Open Dynamics Engine is an open source physics engine you can download and tinger with.  Its popular and well used with lots of example tutorials on the net.  But its written in c++!  So for those of you who use a lot of c#, you can still use it.  The way you go about doing this, is first, download the ODE source code from the website, build your ode dll. 


When your building the ODE, be aware that there are various configurations 'DebugSingleDLL', 'DebugDoubleDLL', 'DebugDoubleLib' etc.  I think the best one is 'DebugSingleDLL', as it uses floats and it has debug information, so you can still step into the c++ source code from your c# program to debug it or if your curious about what's happening inside ODE api.

 

Where going to generate the ODE linking dynamically.  You can do it manually if you want, but I did a script, as everytime I wanted a new function in the dll, I'd have to look up its function information and add it to my .cs file.

 

So to begin with, we create our accessor file 'Ode.cs', which is going to be used to load our dll, and link our c# to the ODE physics api.

 

Ode.cs

// File Ode.cs

 

using System.Runtime.InteropServices;     // DLL support

 

public class Ode_c

{

       //Location of our ode dll

       const string dllName = "C:\\PHYSICS\\ode-0.11.1\\lib\\DebugSingleDLL\\ode_singled.dll";

 

       [DllImport(dllName, CallingConvention=CallingConvention.Cdecl)]

       public static extern int dInitODE2(UInt32 uiInitFlags/*=0*/);

}

 

For the example above, I've manually created the class and added one of the first functions you'll call from your c# program when starting up your ODE physics simulator.

 

code snippet

Oded_c.dInitODE2( 0 );

 

If you wanted to, you could go through your ode source code, and look up each dll function and manually add it to your Ode.cs file.  Each time you find you need a new function you look it up, and add it.  Now this is a good way of learning the various API, and it makes you remember what each parameter is.  But where going to write a quick script which will quickly go through the ODE source code and look up all the dll export function names and then add them to a generated Ode.cs file which we can just add to our project once.

 

The API functions in the ODE source code which are for export in the dll, all start with 'ODE_API'.  So using a tiny python script, we'll go through all the .h files and generate a list of functions and there parameters and save them.

 

typical dll export syntax

ODE_API dReal dBodyGetAngularDamping (dBodyID b);

 

 

ode.py

import os

 

odeSourceDir = "C:\\PHYSICS\\ode-0.11.1\\include\\ode\\"

 

# typical format 'ODE_API dGeomID dBodyGetFirstGeom (dBodyID b);'

 

fileList = os.listdir(odeSourceDir)

for fileName in fileList:

    if len(fileName) < 3:

        continue

    if not fileName[-2:] == ".h":

        continue

    fp = open(odeSourceDir  + fileName)

    s = fp.read()

    lines = s.split("\n")

    fp.close()

 

    for line in lines:

        if line[:7] == "ODE_API":

            print line

 

If we run the above python file, it will go through all the .h files and find any lines that start with 'ODE_API'.  It then prints them out.  So what you get to your output screen is shown below.

 

output for ode.py
ODE_API void dGeomDestroy (dGeomID geom);
ODE_API void dGeomSetData (dGeomID geom, void* data);
ODE_API void *dGeomGetData (dGeomID geom);
ODE_API void dGeomSetBody (dGeomID geom, dBodyID body);
ODE_API dBodyID dGeomGetBody (dGeomID geom);

..etc etc etc

 

I've not shown all the functions, as theres loads!  But you get the idea.  I'll add a tiny bit more to the script, so it analyses the line and splits it up into the function name, and its parameters.  Then we can move onto generating the ode.cs file using this information we've extracted.

 

The following script seems a bit long compared to before, but it takes what we got earlier, the list of lines, where each line contains a function, then extracts the function name, variables, return arguments etc.  All this parsing is a bit hacky, especially when the arguments are on multiple lines.

I didn't support 'ALL' the possible exports.  As you'd also have to add in extra code to export structures and other things so its easier to work with.

 

 

ode.py

 

# ode.py

 

import os

 

odeSourceDir = "C:\\PHYSICS\\ode-0.11.1\\include\\ode\\"

 

# typical format 'ODE_API dGeomID dBodyGetFirstGeom (dBodyID b);'

 

dllfunlist = [] # Our list of functions, params and return values which

                # we'll save as we go along

 

def EquivalentArg( p ):

    # Using IntPtr for c# for pointers etc

    p = p.replace( "dJointID", "IntPtr/*dJointID*/")

    p = p.replace( "dWorldID", "IntPtr/*dWorldID*/")

    p = p.replace( "dBodyID",  "IntPtr/*dBodyID*/" )

    p = p.replace( "dGeomID",  "IntPtr/*dGeomID*/" )

    p = p.replace( "dSpaceID", "IntPtr/*dSpaceID*/" )

    p = p.replace( "dReal",     "float/*dReal*/" )

    p = p.replace( "dQuaternion", " float[]/*dQuaternion*/" )

    p = p.replace( "dBodyID", "IntPtr/*dBodyID*/" )

    p = p.replace( "unsigned int", "UInt32" )

    p = p.strip() # remove any spaces

    return p

 

fileList = os.listdir(odeSourceDir)

for fileName in fileList:

    if len(fileName) < 3:

        continue

    if not fileName[-2:] == ".h":

        continue

    fp = open(odeSourceDir  + fileName)

    s = fp.read()

    lines = s.split("\n")

    fp.close()

 

    for i in range(0,len(lines)):

        line = lines[i]

        if line[:7] == "ODE_API":

            closeB = line.find(")")

            while closeB < 0:

                line = line + lines[i+1] # parameters on multiple lines

                closeB = line.find(")")

                i = i + 1

               

            openB  = line.find("(")

            if openB<0 or closeB<0: # BUG - multiple lines

                1/0

           

            lcomment = line.find("/*");

            rcomment = line.find("*/")

            while (lcomment>=0 and rcomment>=0):

                line = line[:lcomment] + line[rcomment+2:]

                lcomment = line.find("/*");

                rcomment = line.find("*/")

            comment = line.find("//")

           

            if (comment>=0):

                line = line[:comment]

           

            line = line.strip()

            line = line.replace(" *", "* ")

            line = line.replace(" *", "*")

            line = line.replace("const", "") # ignore consts

            line = line.replace("ODE_API","").strip()

   

            openB  = line.find("(")

            closeB = line.find(")")

           

            params = line[openB:closeB+1]

            front  = line[:openB].strip()

           

            funcNameLeft  = front.rfind(" ")

            funcName = line[funcNameLeft:openB]

            returnarg = line[:funcNameLeft].strip()

            returnarg = EquivalentArg( returnarg )

           

            params = params.replace("(", "")

            params = params.replace(")", "")

            params = params.replace("\t", "")

            paramArgs = params.split(",")

           

            # tidy up the args

            outParamArgs = []

            for p in paramArgs:

                p = EquivalentArg( p )

                outParamArgs.append( p )

 

            # store them in a list for use later

            dllfunlist.append( [returnarg, funcName, outParamArgs] )

 

           

# SPIT OUT THE LIST OF FUNCTION

# redirect it to a txt file so you can use it..e.g. ode.py > myfile.txt

 

 

print "using System.Runtime.InteropServices;     // DLL support\n\n"

print "public class Ode_c\n{"

print '\tconst string dllName = "C:\\PHYSICS\\ode-0.11.1\\lib\\DebugSingleDLL\\ode_singled.dll";\n\n'

 

for func in dllfunlist:

    returnarg = func[0]

    funcname  = func[1]

    params    = func[2]

 

    out = ""

    out += "\t//[DllImport(dllName, CallingConvention=CallingConvention.Cdecl)]\n"

    out += "\t//public static extern "

    out += returnarg + " "

    out += funcname

   

    out += "("

    for i in range(0,len(params)):

        out += params[i].strip()

        if (i<len(params)-1):

            out += ","

           

    out += ");"

    out += "\n"

    print out

print "} // End Odedll_c"

 

The generated output (which I just print to the output window), has all the function delarations commented out.  So if you think you need it, you can check it and comment it in for use in your code.

 

output

using System.Runtime.InteropServices;     // DLL support

 

 

public class Ode_c

{

       const string dllName = "C:\\PHYSICS\\ode-0.11.1\\lib\\DebugSingleDLL\\ode_singled.dll";

 

       // Uncomment any that you need

 

       //[DllImport(dllName, CallingConvention=CallingConvention.Cdecl)]

       //public static extern void  dInitODE(void);

 

       [DllImport(dllName, CallingConvention=CallingConvention.Cdecl)]

       public static extern int  dInitODE2(UInt32 uiInitFlags);

 

       [DllImport(dllName, CallingConvention=CallingConvention.Cdecl)]

       public static extern IntPtr/*dWorldID*/ dWorldCreate();

 

       [DllImport(dllName, CallingConvention=CallingConvention.Cdecl)]

       public static extern void dWorldSetGravity (IntPtr/*dWorldID*/ worldId, float/*dReal*/ x, float/*dReal*/ y, float/*dReal*/ z);

 

       [DllImport(dllName, CallingConvention=CallingConvention.Cdecl)]

       public static extern IntPtr/*dSpaceID*/ dHashSpaceCreate (IntPtr/*dSpaceID*/ space);

 

       //[DllImport(dllName, CallingConvention=CallingConvention.Cdecl)]

       //public static extern IntPtr/*dBodyID*/  dGeomGetBody (dGeomID geom);

 

       ... etc etc etc..

       rest arn't included here for space reasons

 

} // End Ode_c

 

 

Once you've added the Ode.cs file to your project, you can just start coding..

 

An example of how you could start of is shown below..

 

Example of using the Ode.cs...

class OdeSimulation_c

{

       public static IntPtr m_worldID;

       public static IntPtr m_spaceID;

       public static IntPtr m_jointGroupID;

       public static IntPtr m_ground;

       public static IntPtr m_jgroup;

 

       public static

       void Create()

       {

              Ode_c.dInitODE2( 0 );

 

              m_worldID = Ode_c.dWorldCreate();

              Odedll_c.dWorldSetGravity(m_worldID, 0.0f, -9.8f, 0.0f);

              m_spaceID = Ode_c.dHashSpaceCreate(IntPtr.Zero);

              m_jointGroupID = Ode_c.dJointGroupCreate(0);

 

 

              // create the ground

              m_ground = Ode_c.dCreatePlane(IntPtr.Zero, 0, 1.0f, 0, 0);     // create a plane for the ground

              m_jgroup = Ode_c.dJointGroupCreate(0);

       }

 

       public static

       void Release()

       {

              Odedll_c.dWorldDestroy(m_worldID);

              Odedll_c.dSpaceDestroy(m_spaceID);

              Odedll_c.dCloseODE();

       }

 

       // etc etc...othere functions

}

 

 

WARNING!!!

Just a couple of things you've got to remember....your mixing managed and unmanaged code.  So in the project you've got to go into the settings and enable this.  You've also got to select the option to debug unmanaged code, this lets you step into the API and gives you loads more debug information when things start getting sticky!!

 

Errors / Problems

Runtime Error
A call to PInvoke function 'code!Ode_c::dInitODE2' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.
Your using the wrong calling convention

[DllImport(dllName)]

//change to

[DllImport(dllName, CallingConvention=CallingConvention.Cdecl)]

 

Runtime Error
Cannot marshal 'return value': Invalid managed/unmanaged type combination.

[DllImport(dllName, CallingConvention=CallingConvention.Cdecl)]

public static extern float[]/*dReal*/ dBodyGetPosition(IntPtr/*dBodyID*/ BodyID);

 

Need to copy the data that we get back into a safe array of floats before we use it.  We can't use it directly, as shown below.  Where we would use our dBodyGetPositionB(..).

 

[DllImport(dllName, CallingConvention=CallingConvention.Cdecl)]

public static extern IntPtr/*dReal*/ dBodyGetPosition(IntPtr/*dBodyID*/ BodyID);

 

public unsafe static float[] dBodyGetPositionB(IntPtr/*dBodyID*/ BodyID)

{

       IntPtr data = dBodyGetPosition(BodyID);

       float[] array = new float[3];

       float* dataPtr = (float*)data;

       for(int i = 0; i < array.Length; ++i)

       {

              array[i] = dataPtr[i];

       }

       return array;

}

 

Further Work:

As well as creating a list of dll functions, it would also be nice to generate a list of structures for data, such as collision information, quaternions etc.

 

 

References:

[Open Dynamics Engine]
http://www.ode.org/

 

 

 

 

 

s

 
Advert (Support Website)

 
 Visitor:
Copyright (c) 2002-2024 xbdev.net - All rights reserved.
Designated articles, tutorials and software are the property of their respective owners.