2002-10-18 15:46:57 +00:00
/**
* $ Id $
*
* * * * * * BEGIN GPL / BL DUAL LICENSE BLOCK * * * * *
*
* The contents of this file may be used under the terms of either the GNU
* General Public License Version 2 or later ( the " GPL " , see
* http : //www.gnu.org/licenses/gpl.html ), or the Blender License 1.0 or
* later ( the " BL " , see http : //www.blender.org/BL/ ) which has to be
* bought from the Blender Foundation to become active , in which case the
* above mentioned GPL option does not apply .
*
* The Original Code is Copyright ( C ) 2002 by NaN Holding BV .
* All rights reserved .
*
* The Original Code is : all of this file .
*
* Contributor ( s ) : none yet .
*
* * * * * * END GPL / BL DUAL LICENSE BLOCK * * * * *
*/
# include "OdePhysicsController.h"
# include "PHY_IMotionState.h"
# include <ode/ode.h>
2002-11-25 15:29:57 +00:00
# ifdef HAVE_CONFIG_H
# include <config.h>
# endif
2002-10-18 15:46:57 +00:00
///////////////////////////////////////////////////////////////////////////
//
// general to-do list for ODE physics. This is maintained in doxygen format.
//
/// \todo determine assignment time for bounding spheres.
///
/// it appears you have to select "sphere" for bounding volume AND "draw bounds"
/// in order for a bounding sphere to be generated. otherwise a box is generated.
/// determine exactly when and how the bounding volumes are generated and make
/// this consistent.
/// }
///
/// \todo bounding sphere size incorrect
///
/// it appears NOT to use the size of the shown bounding sphere (button "draw bounds").
/// it appears instead to use the size of the "size" dynamic parameter in the
/// gamebuttons but this "size" draws an incorrectly-sized circle on screen for the
/// bounding sphere (leftover skewed size calculation from sumo?) so figure out WHERE
/// its getting the radius from.
///
/// \todo ODE collisions must fire collision actuator
///
/// See OdePhysicsEnvironment::OdeNearCallback. If a sensor was created to check
/// for the presence of this collision, then in the NearCallback you need to
/// take appropriate action regarding the sensor - something like checking its
/// controller and if needed firing its actuator. Need to find similar code in
/// Fuzzics which fires collision controllers/actuators.
///
/// \todo Are ghost collisions possible?
///
/// How do ghost collisions work? Do they require collision detection through ODE
/// and NON-CREATION of contact-joint in OdeNearCallback? Currently OdeNearCallback
/// creates joints ALWAYS for collisions.
///
/// \todo Why is KX_GameObject::addLinearVelocity commented out?
///
/// Try putting this code back in.
///
/// \todo Too many non-dynamic actors bogs down ODE physics
///
/// Lots of "geoms" (ODE static geometry) probably slows down ode. Try a test file
/// with lots of static geometry - the game performance in Blender says it is
/// spending all its time in physics, and I bet all that time is in collision
/// detection. It's ode's non-hierarchical collision detection.
/// try making a separate ode test program (not within blender) with 1000 geoms and
/// see how fast it is. if it is really slow, there is the culprit.
/// isnt someone working on an improved ODE collision detector? check
/// ode mailing list.
///
///
/// \todo support collision of dynas with non-dynamic triangle meshes
///
/// ODE has trimesh-collision support but only for trimeshes without a transform
/// matrix. update ODE tricollider to support a transform matrix. this will allow
/// moving trimeshes non-dynamically (e.g. through Ipos). then collide trimeshes
/// with dynas. this allows dynamic primitives (spheres, boxes) to collide with
/// non-dynamic or kinematically controlled tri-meshes. full dynamic trimesh to
/// dynamic trimesh support is hard because it requires (a) collision and penetration
/// depth for trimesh to trimesh and (hard to compute) (b) an intertia tensor
/// (easy to compute).
///
/// a triangle mesh collision geometry should be created when the blender
/// bounding volume (F9, EDITBUTTONS) is set to "polyheder", since this is
/// currently the place where sphere/box selection is made
///
/// \todo specify ODE ERP+CFM in blender interface
///
/// when ODE physics selected, have to be able to set global cfm and erp.
/// per-joint erp/cfm could be handled in constraint window.
///
/// \todo moving infinite mass objects should impart extra impulse to objects they collide with
///
/// currently ODE's ERP pushes them apart but doesn't account for their motion.
2002-10-19 13:49:00 +00:00
/// you have to detect if one body in a collision is a non-dyna. This
/// requires adding a new accessor method to
/// KX_IPhysicsInterfaceController to access the hidden m_isDyna variable,
/// currently it can only be written, not read). If one of the bodies in a
/// collision is a non-dyna, then impart an extra impulse based on the
/// motion of the static object (using its last 2 frames as an approximation
/// of its linear and angular velocity). Linear velocity is easy to
/// approximate, but angular? you have orientation at this frame and
/// orientation at previous frame. The question is what is the angular
/// velocity which would have taken you from the previous frame's orientation
/// to this frame's orientation?
2002-10-18 15:46:57 +00:00
///
/// \todo allow tweaking bounding volume size
///
/// the scene converter currently uses the blender bounding volume of the selected
/// object as the geometry for ODE collision purposes. this is good and automatic
/// intuitive - lets you choose between cube, sphere, mesh. but you need to be able
/// to tweak this size for physics.
///
/// \todo off center meshes totally wrong for ode
///
/// ode uses x, y, z extents regradless of center. then places geom at center of object.
/// but visual geom is not necessarily at center. need to detect off-center situations.
/// then do what? treat it as an encapsulated off-center mass, or recenter it?
///
/// i.o.w. recalculate center, or recalculate mass distribution (using encapsulation)?
///
/// \todo allow off-center mass
///
/// using ode geometry encapsulators
///
/// \todo allow entering compound geoms for complex collision shapes specified as a union of simpler shapes
///
/// The collision shape for arbitrary triangle meshes can probably in general be
///well approximated by a compound ODE geometry object, which is merely a combination
///of many primitives (capsule, sphere, box). I eventually want to add the ability
///to associate compound geometry objects with Blender gameobjects. I think one
///way of doing this would be to add a new button in the GameButtons, "RigidBodyCompound".
///If the object is "Dynamic" + "RigidBody", then the object's bounding volume (sphere,
///box) is created. If an object is "Dynamic" + "RigidBodyCompound", then the object itself
///will merely create a "wrapper" compound object, with the actual geometry objects
///being created from the object's children in Blender. E.g. if I wanted to make a
///compound collision object consisting of a sphere and 2 boxes, I would create a
///parent gameobject with the actual triangle mesh, and set its GameButtons to
///"RigidBodyCompound". I would then create 3 children of this object, 1 sphere and
///2 boxes, and set the GameButtons for the children to be "RigidBody". Then at
///scene conversion time, the scene converter sees "RigidBodyCompound" for the
///top-level object, then appropriately traverses the children and creates the compound
///collision geometry consisting of 2 boxes and a sphere. In this way, arbitrary
///mesh-mesh collision becomes much less necessary - the artist can (or must,
///depending on your point of view!) approximate the collision shape for arbitrary
///meshes with a combination of one or more primitive shapes. I think using the
///parent/child relationship in Blender and a new button "RigidBodyCompound" for the
///parent object of a compound is a feasible way of doing this in Blender.
///
///See ODE demo test_boxstack and look at the code when you drop a compound object
///with the "X" key.
///
/// \todo add visual specification of constraints
///
/// extend the armature constraint system. by using empties and constraining one empty
/// to "copy location" of another, you can get a p2p constraint between the two empties.
/// by making the two empties each a parent of a blender object, you effectively have
/// a p2p constraint between 2 blender bodies. the scene converter can detect these
/// empties, detect the constraint, and generate an ODE constraint.
///
/// then add a new constraint type "hinge" and "slider" to correspond to ODE joints.
/// e.g. a slider would be a constraint which restricts the axis of its object to lie
/// along the same line as another axis of a different object. e.g. you constrain x-axis
/// of one empty to lie along the same line as the z-axis of another empty; this gives
/// a slider joint.
///
/// open questions: how to handle powered joints? to what extent should/must constraints
/// be enforced during modeling? use CCD-style algorithm in modeler to enforce constraints?
/// how about ODE powered constraints e.g. motors?
///
/// \todo enable suspension of bodies
/// ODE offers native support for suspending dynas. but what about suspending non-dynas
/// (e.g. geoms)? suspending geoms is also necessary to ease the load of ODE's (simple?)
/// collision detector. suspending dynas and geoms is important for the activity culling,
/// which apparently works at a simple level. perhaps suspension should actually
/// remove or insert geoms/dynas into the ODE space/world? is this operation (insertion/
/// removal) fast enough at run-time? test it. if fast enough, then suspension=remove from
/// ODE simulation, awakening=insertion into ODE simulation.
///
/// \todo python interface for tweaking constraints via python
///
/// \todo raytesting to support gameengine sensors that need it
2002-10-18 18:53:45 +00:00
///
/// \todo investigate compatibility issues with old Blender 2.25 physics engine (sumo/fuzzics)
/// is it possible to have compatibility? how hard is it? how important is it?
2002-10-18 15:46:57 +00:00
ODEPhysicsController : : ODEPhysicsController ( bool dyna , bool fullRigidBody ,
bool phantom , class PHY_IMotionState * motionstate , struct dxSpace * space ,
struct dxWorld * world , float mass , float friction , float restitution ,
bool implicitsphere , float center [ 3 ] , float extents [ 3 ] , float radius )
:
m_OdeDyna ( dyna ) ,
m_firstTime ( true ) ,
m_bFullRigidBody ( fullRigidBody ) ,
m_bPhantom ( phantom ) ,
m_bKinematic ( false ) ,
m_bPrevKinematic ( false ) ,
m_MotionState ( motionstate ) ,
m_OdeSuspendDynamics ( false ) ,
m_space ( space ) ,
m_world ( world ) ,
m_mass ( mass ) ,
m_friction ( friction ) ,
m_restitution ( restitution ) ,
m_bodyId ( 0 ) ,
m_geomId ( 0 ) ,
m_implicitsphere ( implicitsphere ) ,
m_radius ( radius )
{
m_center [ 0 ] = center [ 0 ] ;
m_center [ 1 ] = center [ 1 ] ;
m_center [ 2 ] = center [ 2 ] ;
m_extends [ 0 ] = extents [ 0 ] ;
m_extends [ 1 ] = extents [ 1 ] ;
m_extends [ 2 ] = extents [ 2 ] ;
} ;
ODEPhysicsController : : ~ ODEPhysicsController ( )
{
if ( m_geomId )
{
dGeomDestroy ( m_geomId ) ;
}
}
float ODEPhysicsController : : getMass ( )
{
dMass mass ;
dBodyGetMass ( m_bodyId , & mass ) ;
return mass . mass ;
}
//////////////////////////////////////////////////////////////////////
/// \todo Impart some extra impulse to dynamic objects when they collide with kinematically controlled "static" objects (ODE geoms), by using last 2 frames as 1st order approximation to the linear/angular velocity, and computing an appropriate impulse. Sumo (old physics engine) did this, see for details.
/// \todo handle scaling of static ODE geoms or fail with error message if Ipo tries to change scale of a static geom object
bool ODEPhysicsController : : SynchronizeMotionStates ( float time )
{
/**
' Late binding ' of the rigidbody , because the World Scaling is not available until the scenegraph is traversed
*/
if ( m_firstTime )
{
m_firstTime = false ;
m_MotionState - > calculateWorldTransformations ( ) ;
dQuaternion worldquat ;
float worldpos [ 3 ] ;
m_MotionState - > getWorldOrientation ( worldquat [ 1 ] , worldquat [ 2 ] , worldquat [ 3 ] , worldquat [ 0 ] ) ;
m_MotionState - > getWorldPosition ( worldpos [ 0 ] , worldpos [ 1 ] , worldpos [ 2 ] ) ;
float scaling [ 3 ] ;
m_MotionState - > getWorldScaling ( scaling [ 0 ] , scaling [ 1 ] , scaling [ 2 ] ) ;
if ( ! m_bPhantom )
{
if ( m_implicitsphere )
{
m_geomId = dCreateSphere ( m_space , m_radius * scaling [ 0 ] ) ;
} else
{
m_geomId = dCreateBox ( m_space , m_extends [ 0 ] * scaling [ 0 ] , m_extends [ 1 ] * scaling [ 1 ] , m_extends [ 2 ] * scaling [ 2 ] ) ;
}
} else
{
m_geomId = 0 ;
}
if ( m_geomId )
dGeomSetData ( m_geomId , this ) ;
if ( ! this - > m_OdeDyna )
{
if ( ! m_bPhantom )
{
dGeomSetPosition ( this - > m_geomId , worldpos [ 0 ] , worldpos [ 1 ] , worldpos [ 2 ] ) ;
dMatrix3 R ;
dQtoR ( worldquat , R ) ;
dGeomSetRotation ( this - > m_geomId , R ) ;
}
} else
{
//it's dynamic, so create a 'model'
m_bodyId = dBodyCreate ( this - > m_world ) ;
dBodySetPosition ( m_bodyId , worldpos [ 0 ] , worldpos [ 1 ] , worldpos [ 2 ] ) ;
dBodySetQuaternion ( this - > m_bodyId , worldquat ) ;
//this contains both scalar mass and inertia tensor
dMass m ;
float length = 1 , width = 1 , height = 1 ;
dMassSetBox ( & m , 1 , m_extends [ 0 ] * scaling [ 0 ] , m_extends [ 1 ] * scaling [ 1 ] , m_extends [ 2 ] * scaling [ 2 ] ) ;
dMassAdjust ( & m , this - > m_mass ) ;
dBodySetMass ( m_bodyId , & m ) ;
if ( ! m_bPhantom )
{
dGeomSetBody ( m_geomId , m_bodyId ) ;
}
}
if ( this - > m_OdeDyna & & ! m_bFullRigidBody )
{
// ?? huh? what to do here?
}
}
if ( m_OdeDyna )
{
if ( this - > m_OdeSuspendDynamics )
{
return false ;
}
const float * worldPos = dBodyGetPosition ( m_bodyId ) ;
m_MotionState - > setWorldPosition ( worldPos [ 0 ] , worldPos [ 1 ] , worldPos [ 2 ] ) ;
const float * worldquat = dBodyGetQuaternion ( m_bodyId ) ;
m_MotionState - > setWorldOrientation ( worldquat [ 1 ] , worldquat [ 2 ] , worldquat [ 3 ] , worldquat [ 0 ] ) ;
}
else {
// not a dyna, so dynamics (i.e. this controller) has not updated
// anything. BUT! an Ipo or something else might have changed the
// position/orientation of this geometry.
// so update the static geom position
/// \todo impart some extra impulse to colliding objects!
dQuaternion worldquat ;
float worldpos [ 3 ] ;
m_MotionState - > getWorldOrientation ( worldquat [ 1 ] , worldquat [ 2 ] , worldquat [ 3 ] , worldquat [ 0 ] ) ;
m_MotionState - > getWorldPosition ( worldpos [ 0 ] , worldpos [ 1 ] , worldpos [ 2 ] ) ;
float scaling [ 3 ] ;
m_MotionState - > getWorldScaling ( scaling [ 0 ] , scaling [ 1 ] , scaling [ 2 ] ) ;
/// \todo handle scaling! what if Ipo changes scale of object?
// Must propagate to geom... is scaling geoms possible with ODE? Also
// what about scaling trimeshes, that is certainly difficult...
dGeomSetPosition ( this - > m_geomId , worldpos [ 0 ] , worldpos [ 1 ] , worldpos [ 2 ] ) ;
dMatrix3 R ;
dQtoR ( worldquat , R ) ;
dGeomSetRotation ( this - > m_geomId , R ) ;
}
return false ; //it update the worldpos
}
// kinematic methods
void ODEPhysicsController : : RelativeTranslate ( float dlocX , float dlocY , float dlocZ , bool local )
{
}
void ODEPhysicsController : : RelativeRotate ( const float drot [ 9 ] , bool local )
{
}
void ODEPhysicsController : : setOrientation ( float quatImag0 , float quatImag1 , float quatImag2 , float quatReal )
{
dQuaternion worldquat ;
worldquat [ 0 ] = quatReal ;
worldquat [ 1 ] = quatImag0 ;
worldquat [ 2 ] = quatImag1 ;
worldquat [ 3 ] = quatImag2 ;
if ( ! this - > m_OdeDyna )
{
dMatrix3 R ;
dQtoR ( worldquat , R ) ;
dGeomSetRotation ( this - > m_geomId , R ) ;
} else
{
dBodySetQuaternion ( m_bodyId , worldquat ) ;
this - > m_MotionState - > setWorldOrientation ( quatImag0 , quatImag1 , quatImag2 , quatReal ) ;
}
}
void ODEPhysicsController : : getOrientation ( float & quatImag0 , float & quatImag1 , float & quatImag2 , float & quatReal )
{
float q [ 4 ] ;
this - > m_MotionState - > getWorldOrientation ( q [ 0 ] , q [ 1 ] , q [ 2 ] , q [ 3 ] ) ;
quatImag0 = q [ 0 ] ;
quatImag1 = q [ 1 ] ;
quatImag2 = q [ 2 ] ;
quatReal = q [ 3 ] ;
}
void ODEPhysicsController : : setPosition ( float posX , float posY , float posZ )
{
if ( ! m_bPhantom )
{
if ( ! this - > m_OdeDyna )
{
dGeomSetPosition ( m_geomId , posX , posY , posZ ) ;
} else
{
dBodySetPosition ( m_bodyId , posX , posY , posZ ) ;
}
}
}
void ODEPhysicsController : : setScaling ( float scaleX , float scaleY , float scaleZ )
{
}
// physics methods
void ODEPhysicsController : : ApplyTorque ( float torqueX , float torqueY , float torqueZ , bool local )
{
if ( m_OdeDyna ) {
if ( local ) {
dBodyAddRelTorque ( m_bodyId , torqueX , torqueY , torqueZ ) ;
} else {
dBodyAddTorque ( m_bodyId , torqueX , torqueY , torqueZ ) ;
}
}
}
void ODEPhysicsController : : ApplyForce ( float forceX , float forceY , float forceZ , bool local )
{
if ( m_OdeDyna ) {
if ( local ) {
dBodyAddRelForce ( m_bodyId , forceX , forceY , forceZ ) ;
} else {
dBodyAddForce ( m_bodyId , forceX , forceY , forceZ ) ;
}
}
}
void ODEPhysicsController : : SetAngularVelocity ( float ang_velX , float ang_velY , float ang_velZ , bool local )
{
if ( m_OdeDyna ) {
if ( local ) {
// TODO: translate angular vel into local frame, then apply
} else {
dBodySetAngularVel ( m_bodyId , ang_velX , ang_velY , ang_velZ ) ;
}
}
}
void ODEPhysicsController : : SetLinearVelocity ( float lin_velX , float lin_velY , float lin_velZ , bool local )
{
if ( m_OdeDyna )
{
dVector3 vel = { lin_velX , lin_velY , lin_velZ } ;
if ( local )
{
dMatrix3 worldmat ;
dVector3 localvel ;
dQuaternion worldquat ;
m_MotionState - > getWorldOrientation ( worldquat [ 1 ] , worldquat [ 2 ] , worldquat [ 3 ] , worldquat [ 0 ] ) ;
dQtoR ( worldquat , worldmat ) ;
dMULTIPLY0_331 ( localvel , worldmat , vel ) ;
dBodySetLinearVel ( m_bodyId , localvel [ 0 ] , localvel [ 1 ] , localvel [ 2 ] ) ;
} else
{
dBodySetLinearVel ( m_bodyId , lin_velX , lin_velY , lin_velZ ) ;
}
}
}
void ODEPhysicsController : : applyImpulse ( float attachX , float attachY , float attachZ , float impulseX , float impulseY , float impulseZ )
{
if ( m_OdeDyna )
{
//apply linear and angular effect
const dReal * linvel = dBodyGetLinearVel ( m_bodyId ) ;
float mass = getMass ( ) ;
if ( mass > = 0.00001f )
{
float massinv = 1.f / mass ;
float newvel [ 3 ] ;
newvel [ 0 ] = linvel [ 0 ] + impulseX * massinv ;
newvel [ 1 ] = linvel [ 1 ] + impulseY * massinv ;
newvel [ 2 ] = linvel [ 2 ] + impulseZ * massinv ;
dBodySetLinearVel ( m_bodyId , newvel [ 0 ] , newvel [ 1 ] , newvel [ 2 ] ) ;
const float * worldPos = dBodyGetPosition ( m_bodyId ) ;
const float * angvelc = dBodyGetAngularVel ( m_bodyId ) ;
float angvel [ 3 ] ;
angvel [ 0 ] = angvelc [ 0 ] ;
angvel [ 1 ] = angvelc [ 1 ] ;
angvel [ 2 ] = angvelc [ 2 ] ;
dVector3 impulse ;
impulse [ 0 ] = impulseX ;
impulse [ 1 ] = impulseY ;
impulse [ 2 ] = impulseZ ;
dVector3 ap ;
ap [ 0 ] = attachX - worldPos [ 0 ] ;
ap [ 1 ] = attachY - worldPos [ 1 ] ;
ap [ 2 ] = attachZ - worldPos [ 2 ] ;
dCROSS ( angvel , + = , ap , impulse ) ;
dBodySetAngularVel ( m_bodyId , angvel [ 0 ] , angvel [ 1 ] , angvel [ 2 ] ) ;
}
}
}
void ODEPhysicsController : : SuspendDynamics ( )
{
}
void ODEPhysicsController : : RestoreDynamics ( )
{
}
/**
reading out information from physics
*/
void ODEPhysicsController : : GetLinearVelocity ( float & linvX , float & linvY , float & linvZ )
{
if ( m_OdeDyna )
{
const float * vel = dBodyGetLinearVel ( m_bodyId ) ;
linvX = vel [ 0 ] ;
linvY = vel [ 1 ] ;
linvZ = vel [ 2 ] ;
} else
{
linvX = 0.f ;
linvY = 0.f ;
linvZ = 0.f ;
}
}
/**
GetVelocity parameters are in geometric coordinates ( Origin is not center of mass ! ) .
*/
void ODEPhysicsController : : GetVelocity ( const float posX , const float posY , const float posZ , float & linvX , float & linvY , float & linvZ )
{
}
void ODEPhysicsController : : getReactionForce ( float & forceX , float & forceY , float & forceZ )
{
}
void ODEPhysicsController : : setRigidBody ( bool rigid )
{
}
void ODEPhysicsController : : PostProcessReplica ( class PHY_IMotionState * motionstate , class PHY_IPhysicsController * parentctrl )
{
m_MotionState = motionstate ;
m_bKinematic = false ;
m_bPrevKinematic = false ;
m_firstTime = true ;
}
void ODEPhysicsController : : SetSimulatedTime ( float time )
{
}
void ODEPhysicsController : : WriteMotionStateToDynamics ( bool nondynaonly )
{
}