dotScene Importer + physics [Blender]

mcaden

04-10-2008 20:01:14

I'm not quite ready yet on the blender importer but here's the bulk of the physics stuff:



void DotSceneLoader::parsePhysicsProperties(SceneNode* pNode, Entity* mesh, vector<NodeProperty> props)
{
char* sizeValue = new char[5];

String size = itoa( props.size(), sizeValue, 10 );
LogManager::getSingleton().logMessage( "object " + pNode->getName() + " has " + size + " properties" );

NodeRenderableParams renderParams;
renderParams.setToDefault();
renderParams.mGraphicsModel = mesh->getName();
renderParams.mGraphicsModelScale = NxVec3(1, 1, 1);
renderParams.mIdentifier = pNode->getName();
renderParams.mIdentifierUsage = renderParams.IU_Use;
renderParams.mMode = RenderableSource::RM_Interpolate;

ActorParams actorParams;
actorParams.setToDefault();
actorParams.mMass = 10;
actorParams.mDensity = 0;
actorParams.mLinearDamping = 5;
actorParams.mAngularDamping = 5;
//actorParams.mGroup = "default";

vector<NodeProperty> classProperties;
DOTSCENE_OBJECT_TYPE objectType = DS_NXOBTYPE_UNSET;
DOTSCENE_COLLISION_TYPE collisionType = DS_NXCOLTYPE_UNSET;
Real collisionSize = 1;

while( props.size() > 0 )
{
NodeProperty prop = props.back();
props.pop_back();
if( prop.propertyNm == "ObType" )
{
if( prop.valueName == "Player" )
objectType = DS_NXOBTYPE_PLAYER;
else if( prop.valueName == "Terrain" )
{
objectType = DS_NXOBTYPE_TERRAIN;
actorParams.mMass = 0;
actorParams.Static( true );
collisionType = DS_NXCOLTYPE_TRIANGLE;
}
else if( prop.valueName == "Generic_Dynamic" )
objectType = DS_NXOBTYPE_GENERIC_DYNAMIC;
else if( prop.valueName == "Generic_Static" )
objectType = DS_NXOBTYPE_GENERIC_STATIC;
}
else if( prop.propertyNm == "ColType" )
{
if( prop.valueName == "Sphere" )
collisionType = DS_NXCOLTYPE_SPHERE;
else if( prop.valueName == "Triangle" )
collisionType = DS_NXCOLTYPE_TRIANGLE;
else if( prop.valueName == "Convex" )
collisionType = DS_NXCOLTYPE_CONVEX;
else
collisionType = DS_NXCOLTYPE_CUBE;
}
else if( prop.propertyNm == "ColSize" )
{
collisionSize = atof( prop.valueName.c_str() );
}
else if( prop.propertyNm == "LinearDamping" )
{
actorParams.mLinearDamping = atof( prop.valueName.c_str() );
}
else if( prop.propertyNm == "AngularDamping" )
{
actorParams.mAngularDamping = atof( prop.valueName.c_str() );
}
else if( prop.propertyNm == "Mass" )
{
actorParams.mMass = atof( prop.valueName.c_str() );
}
else if( prop.propertyNm == "Group" )
{
actorParams.mGroup = prop.valueName;
}
else if( prop.propertyNm == "FreezeRotX" )
{
actorParams.mFreeze.mRotation.x = true;
}
else if( prop.propertyNm == "FreezeRotY" )
{
actorParams.mFreeze.mRotation.y = true;
}
else if( prop.propertyNm == "FreezeRotZ" )
{
actorParams.mFreeze.mRotation.z = true;
}
else
{
classProperties.push_back( prop );
}
}

Shape* collisionShape = NULL;
if( collisionType == DS_NXCOLTYPE_TRIANGLE || collisionType == DS_NXCOLTYPE_CONVEX )
{
Ogre::String collisionMesh;
collisionMesh = mesh->getMesh()->getName() + ".nxs"; // ____.mesh.nxs is the file format..easily changeable
NxOgre::Resources::ResourceSystem::getSingleton()->addMesh("file://" + collisionMesh); //set the path here
if( collisionType == DS_NXCOLTYPE_TRIANGLE )
collisionShape = new TriangleMesh(NxOgre::Resources::ResourceSystem::getSingleton()->getMesh( collisionMesh ));
else
collisionShape = new Convex( NxOgre::Resources::ResourceSystem::getSingleton()->getMesh( collisionMesh ) );
}
else if( collisionType == DS_NXCOLTYPE_SPHERE )
{
collisionShape = new NxOgre::Sphere( collisionSize );
}
else //If shape isn't specified, or there's an error - default to ( collisionType == DS_NXCOLTYPE_CUBE )
{
Ogre::AxisAlignedBox meshBounds = pNode->getAttachedObject( mesh->getName() )->getBoundingBox();
collisionShape = new Cube( meshBounds.getSize() );
}


createPhysXObject( pNode, classProperties, objectType, collisionShape,
actorParams, renderParams );
}



That sets all the physics properties...then you react accordingly to use the correct class:


void DotSceneLoader::createPhysXObject( SceneNode* node, vector<NodeProperty> props, DOTSCENE_OBJECT_TYPE objectType,
Shape* collisionShape, ActorParams actorParams,
NodeRenderableParams renderParams )
{
vector<NodeProperty> unusedClassProperties;
NxOgre::Pose actorPose( node->getPosition(), node->getOrientation() );

switch( objectType )
{
case( DS_NXOBTYPE_TERRAIN ):
try
{
Actor* myActor = mScene->createBody<Body>(renderParams.mIdentifier,
collisionShape, actorPose, renderParams, actorParams);
myActor->setGroup( mScene->getActorGroup( "terrain" ) );
LogManager::getSingleton().logMessage( "-= Terrain Object - '" + myActor->getName() + "' created successfully =-" );
}
catch(...)
{
LogManager::getSingleton().logMessage( "-= Terrain Creation Error - Not applying physics to terrain object '" + node->getName() + "' =-" );
}
break;
case( DS_NXOBTYPE_PLAYER ):
try
{
mPlayer = (Player*)mScene->createBody<Player>(renderParams.mIdentifier,
collisionShape, actorPose, renderParams, actorParams);
mPlayer->setGroup( mScene->getActorGroup( "player" ) );
LogManager::getSingleton().logMessage( "-= Player Object - '" + mPlayer->getName() + "' created successfully =-" );
mPlayer->initialize( mSceneMgr, (Entity*)node->getAttachedObject( node->getName() ), 200, 50.0f, mSceneMgr->getCameraIterator().getNext() );

while( props.size() > 0 )
{
NodeProperty prop = props.back();
props.pop_back();
if( StringUtil::startsWith( prop.propertyNm, "attack" ) )
{
vector<String>params = StringUtil::split( prop.valueName, "," );
if( params.size() == 4 )
{
mPlayer->addAttack( params.at(0), params.at(1),
StringConverter::parseInt( params.at( 2 )),
StringConverter::parseVector3( params.at( 3 )) );
}
else if( params.size() == 3 )
{
mPlayer->addAttack( params.at(0), params.at(1),
StringConverter::parseInt( params.at( 2 )));
}
}
else if( StringUtil::startsWith( prop.propertyNm, "particle" ) )
{
vector<String>params = StringUtil::split( prop.valueName, "," );
if( params.size() == 3 )
{
mPlayer->attachParticles( params.at(0), params.at(1), params.at(2) );
}
}
else
{
unusedClassProperties.push_back( prop );
}
}
LogManager::getSingleton().logMessage( "-= Player Creation Completed with '" + StringConverter::toString( unusedClassProperties.size() ) + "' unused properties =-" );

Light* mLight = mSceneMgr->createLight("Light2");
mLight->setType(Ogre::Light::LT_POINT);
mLight->setPosition( 0, 0, 1 );
mLight->setAttenuation( 32, 0.00000, 0.100000, 0.070000 );
mLight->setDiffuseColour( 0.39, 1.0, 0.39 );
mLight->setSpecularColour( 0.0, 1.0, 0.0 );
mPlayer->attachObject( "Torso", mLight );
mPlayer->cameraPitch( Ogre::Degree(40) );
}
catch(...)
{
LogManager::getSingleton().logMessage( "-= Player Creation Error - Exception thrown on player object '" + node->getName() + "' =-" );
}
break;
case( DS_NXOBTYPE_GENERIC_STATIC ):
case( DS_NXOBTYPE_GENERIC_DYNAMIC ):
case( DS_NXOBTYPE_UNSET ):
if( collisionShape != NULL )
{
try
{
Actor* myActor = mScene->createBody<Body>(renderParams.mIdentifier, collisionShape,
actorPose, renderParams, actorParams);
myActor->setGroup( mScene->getActorGroup( "enemy" ) );
LogManager::getSingleton().logMessage( "-= Generic Physics-Enabled Object - '" + myActor->getName() + "' created successfully =-" );
}
catch(...)
{
LogManager::getSingleton().logMessage( "-= Generic Physics-Enabled Object Creation Error - Not applying physics to object '" + node->getName() + "' =-" );
}
}

break;
}
}



Right now my XML has a player node:

<node name="Garm">
<position x="6.337165" y="0" z="-9.414388"/>
<quaternion x="0.000000" y="-0.325688" z="0.000000" w="0.945477"/>
<scale x="1.000000" y="1.000000" z="1.000000"/>
<entity name="Garm" meshFile="Garm.mesh"/>
<userData>
<property type="FLOAT" name="ColSize" data="1.0"/>
<property type="STRING" name="ColType" data="Cube"/>
<property type="STRING" name="ObType" data="Player"/>
<property type="STRING" name="FreezeRotX" data="true"/>
<property type="STRING" name="FreezeRotZ" data="true"/>
<property type="STRING" name="Mass" data="15"/>
<property type="FLOAT" name="LinearDamping" data="0.0"/>
<property type="FLOAT" name="AngularDamping" data="12.0"/>
<property type="STRING" name="Group" data="player"/>
<property type="STRING" name="Attack1" data="Bite,Bite,30"/>
<property type="STRING" name="Particle1" data="backParticles1,Player/Garm_back,Torso"/>
<property type="STRING" name="Particle2" data="backParticles2,Player/Garm_back2,Torso"/>
<property type="STRING" name="Particle3" data="eyeParticles_L,Player/Garm_eye,Eye_L"/>
<property type="STRING" name="Particle4" data="eyeParticles_R,Player/Garm_eye,Eye_R"/>
</userData>
</node>


And it reads in both the physics properties, AND the extra player properties I specified in the second function and applies them accordingly.

For a player class this basically means I don't have to recompile each time I edit or add a new attack or if I change the name of a bone or something for a particle emitter. Not as big a deal...but when I start adding enemies all with their own attacks and particles along with adding AI properties for aggression, sight range, movement speed, etc...the time saved and ease of editing will really add up.

Right now my terrain and enemy are using the default Body class...later on they'll use <Terrain> and <Enemy> through the new body templating system available in '22.



Eventually I soon hope to release full code to the community for a dotScene importer with physics capabilities, but I wanted to get some feedback first.

Prophet

05-10-2008 15:13:44

I must ask, does this convert a .mesh to a .nxs on the fly? From what I gathered, it looks like it don't, but if it does, we will certainly use your loader!
I'll add this to the Unofficial FAQ right away.

mcaden

05-10-2008 16:38:10

Nope, it assumes you've already converted it to .nxs and placed it where it needs to go.

It simply uses the latest blender dotscene exporter format - and on reading the .scene file applies physics based on userData provided. If the .scene file format is the same, it should work with any file no matter where it was exported from.

It's not for importing into blender, it's for importing into the game. It's biggest problem at the moment I believe is that it 'assumes' certain things - like the name of the .nxs file - but that can be easily edited.

I've tried to make it in 2 phases...the physics phase which uses raw physics data, properties that are pretty much global to any object that would be created, and class-specific properties. Class-specific properties are intended to be coded based on your usage. I have an example above of my "Player" class - using properties that are specific to the player like attacks and particles...I've since expanded it to include things like max HP, movement speed, and idle animations.

betajaen

05-10-2008 16:47:15

I know this is slightly off-topic, but related to to the subject in whole.

I've always a wanted flour to support the "obj" file-format; but instead of that, would people prefer if I just allowed you to dump a load of vertex and index data directly into a flour command line?

Literally:

flour make type: convex, vertices: 0.5 0.5 0.5; 1.0 1.0 1.0, indexes: 1 2

Then it just spits out the appropriate NXS? At least then, you don't have to save each part of your scene then convert them, someone can create a exporter and work with flour with each mesh as needed.

[Edit]

Oh, and it can do this now. Inside a window, or directly into a windows control.

mcaden

05-10-2008 17:03:32

I know this is slightly off-topic, but related to to the subject in whole.

I've always a wanted flour to support the "obj" file-format; but instead of that, would people prefer if I just allowed you to dump a load of vertex and index data directly into a flour command line?

Literally:

flour make type: convex, vertices: 0.5 0.5 0.5; 1.0 1.0 1.0, indexes: 1 2

Then it just spits out the appropriate NXS? At least then, you don't have to save each part of your scene then convert them, someone can create a exporter and work with flour with each mesh as needed.



I guess that would make it more possible to create a direct exporter from blender/3ds/maya into nxs. Personally most of my objects are fine with using a cube based on their bounding box so converting the few meshes than need it isn't a hassle but I suppose if somebody wants to be precise with a lot of different meshes they might feel differently than I do.


[Edit]

Oh, and it can do this now. Inside a window, or directly into a windows control.
...

I was thinking it wouldn't be too difficult to make a GUI for flour.

Prophet

05-10-2008 17:03:55

It's not for importing into blender, it's for importing into the game. It's biggest problem at the moment I believe is that it 'assumes' certain things - like the name of the .nxs file - but that can be easily edited.That the problems I'm facing as well. That's why I'm thinking of integrating it directly in 3ds Max through a plug-in. That way I can, for example, cook meshes directly inside 3ds Max, which will be quite handy. Is this possible with Blender as well?

@betajaen:
To continue your slightly off-topic track; is it possible to cook meshes on the fly, when your app is running? Something tells me it should be possible, but something tells me I read that it wasn't.
About the flour-thing; I can see how this can be really handy! I will definitely look into this.

mcaden

05-10-2008 17:06:42

I was thinking it be really easy to just code in an extra command-line call to flour from within the exporters already made for blender/3ds/maya.

They already convert the mesh to .xml then throw that through the ogre xml converter...why not take the resulting .mesh and throw it through flour.

Kill 2 birds with one stone.

betajaen

05-10-2008 17:14:09

[Edit]

Oh, and it can do this now. Inside a window, or directly into a windows control.
...

I was thinking it wouldn't be too difficult to make a GUI for flour.


Oh it was. I had to invent a new image compression format and an entirely new GUI for it. There are literally no dependencies (apart from the two PhysX dlls), including resources and so on. It's all procedural.

@Prophet

Not when the GUI is running, it just shows the meshes on the window. But you could always run a second instance of flour, then just click the reload button on the main one.

Prophet

05-10-2008 17:32:25

I was more thinking of inside your own app. Something like (very roughly) NxOgre::Convex = Flour::Cook("Ninja.mesh");. Preferably without command-line. (Me no like it)
I'm probably off wandering, I've taken a pause from it for quite some time now and I'm quite tired, so I'm most likely missing something.

betajaen

05-10-2008 18:37:16

No. NxOgre itself can cook meshes (which is how Flour does it), except flour has the nice functions to convert the Ogre mesh into a NxOgre ManualMesh.

Prophet

05-10-2008 20:15:54

That's the bell that's ringing. Thanks!

betajaen

06-10-2008 16:47:11

I've just killed two birds with one stone.

N:\Flour\installer>flour make skeleton indices 0 1 3 0 3 2 3 7 6 3 6 2 1 5 7 1 7 3 4 6 7 4 7 5 1 0 4 5 1 4 4 0 2 4 2 6 vertices 0.8 -0.8 -0.8 0.8 -0.8 0.8 0.8 0.8 -0.8 0.8 0.8 0.8 -0.8 -0.8 -0.8 -0.8 -0.8 0.8 -0.8 0.8 -0.8 -0.8 0.8 0.8

Generates a 800 byte CCD Skeleton mesh, now I have to write the code load the thing back in. :)