tie joint matrices to ragdoll joints

This commit is contained in:
ecker 2026-05-27 23:06:07 -05:00
parent 5402aff2a8
commit e434f33092
4 changed files with 93 additions and 20 deletions

View File

@ -11,7 +11,7 @@
"SoundEmitterBehavior"
],
"transform": {
"position": [ -7.04676, 10.169565, -44.0822 ],
"position": [ -7, 0, -44 ],
//"position": [ 0, 1.5, 21 ],
//"position": [ 16.3489, 1.37972, -68.1571 ],
"scale": [ 0.3, 0.3, 0.3 ]
@ -23,10 +23,11 @@
},
"metadata": {
"name": "Craeture",
"holdable": true,
"graph": {
"debug": {
"draw": {
"armature": true
"armature": false
}
},
"physics": {

View File

@ -48,7 +48,7 @@
"filter": "linear",
"flip textures": false,
"invert": false,
"render": false,
"render": true,
"skinned": true
},
"animations": {

View File

@ -247,16 +247,33 @@ void uf::graph::updateAnimation( pod::Graph& graph, pod::Node& node ) {
pod::Matrix4f nodeMatrix = ::worldMatrix( graph, node.index );
pod::Matrix4f inverseTransform = uf::matrix::inverse( nodeMatrix );
pod::Matrix4f invArmatureMatrix = uf::matrix::identity();
if ( graph.root.entity && graph.root.entity->hasComponent<pod::Transform<>>() ) {
auto armTf = uf::transform::flatten( graph.root.entity->getComponent<pod::Transform<>>() );
invArmatureMatrix = uf::matrix::inverse( uf::transform::model( armTf ) );
}
auto& name = graph.skins[node.skin];
auto& skin = storage.skins[name];
auto& joints = storage.joints[name];
joints.resize( skin.joints.size() );
for ( size_t i = 0; i < skin.joints.size(); ++i ) joints[i] = uf::matrix::identity();
if ( graph.settings.animations.override.a >= 0 || !graph.sequence.empty() ) {
for ( size_t i = 0; i < skin.joints.size(); ++i ) {
joints[i] = inverseTransform * (::worldMatrix(graph, skin.joints[i]) * skin.inverseBindMatrices[i]);
auto nodeID = skin.joints[i];
auto& node = graph.nodes[nodeID];
pod::Matrix4f matrix = uf::matrix::identity();
bool ragdolled = node.entity && node.entity->hasComponent<pod::PhysicsBody>();
if ( ragdolled ) {
auto transform = uf::transform::flatten( node.entity->getComponent<pod::Transform<>>() );
matrix = invArmatureMatrix * uf::transform::model( transform );
} else if ( graph.settings.animations.override.a >= 0 || !graph.sequence.empty() ) {
matrix = ::worldMatrix(graph, nodeID);
} else {
joints[i] = matrix;
continue;
}
joints[i] = inverseTransform * matrix * skin.inverseBindMatrices[i];
}
}
}
@ -340,7 +357,10 @@ void uf::graph::rigRagdoll( pod::Graph& graph, pod::Node& node ) {
// create physics bodies
uf::stl::vector<pod::PhysicsBody*> bodies( graph.nodes.size(), NULL );
uf::stl::vector<bool> isJoint( graph.nodes.size(), false );
for ( auto i = 0; i < skin.joints.size(); ++i ) isJoint[skin.joints[i]] = true;
const float density = 1000.0f;
for ( auto i = 0; i < skin.joints.size(); ++i ) {
auto nodeID = skin.joints[i];
auto& node = graph.nodes[nodeID];
@ -348,27 +368,69 @@ void uf::graph::rigRagdoll( pod::Graph& graph, pod::Node& node ) {
auto& entity = *node.entity;
auto transform = uf::transform::flatten( entity.getComponent<pod::Transform<>>() );
// this definitely could be done better......
auto& bone = bones[nodeID];
auto& obb = bounds[i];
// if ( obb.extent.x < 0 ) continue;
// transform center into world space
/*
auto boneCenter = uf::transform::apply( armatureTransform, (bone.end + bone.start) * 0.5f );
// transform back into local space
auto offset = uf::transform::applyInverse( transform, boneCenter );
auto orientation = uf::quaternion::identity();
float length = uf::vector::norm( offset ) * 2.0f;
*/
// skip leaf bones that aren't assigned joints in the mesh
if ( obb.extent.x < 0 && node.children.empty() ) continue;
auto targetShape = pod::ShapeType::OBB;
bone.start = uf::transform::apply( armatureTransform, bone.start );
bone.end = uf::transform::apply( armatureTransform, bone.end );
auto start = uf::transform::applyInverse( transform, bone.start );
auto end = uf::transform::applyInverse( transform, bone.end );
float length = uf::vector::distance( bone.start, bone.end );
auto offset = ( start + end ) * 0.5f;
auto orientation = uf::quaternion::identity();
float mass = 100.0f;
auto& body = uf::physics::create( entity, mass, offset, orientation );
if ( obb.extent.x < 0 ) {
uf::physics::initialize( body, pod::Sphere{ 0.1f } );
} else {
uf::physics::initialize( body, pod::OBB{ pod::Vector3f{}, obb.extent * armatureTransform.scale } );
}
//uf::physics::initialize( body, pod::Capsule{ 0.5f, length * 0.5f } );
auto& body = uf::physics::create( entity, mass, offset, orientation );
bodies[nodeID] = &body;
// "root" bone will try and parent to local origin
/*
if ( !isJoint[node.parent] ) {
targetShape = pod::ShapeType::SPHERE;
// mark it as non-colliding
uf::physics::setColliderCategory( body, pod::Collider::CATEGORY_NONE );
// make it heavier
uf::physics::setMass( body, 1000.0f );
}
*/
switch ( targetShape ) {
case pod::ShapeType::CAPSULE: {
auto up = pod::Vector3f{0, 1, 0};
auto dir = uf::vector::normalize( end - start );
float dot = uf::vector::dot( up, dir );
if ( dot < -0.999f ) {
body.offsetOrientation = uf::quaternion::axisAngle( pod::Vector3f{1, 0, 0}, M_PI );
} else if ( dot < 0.999f ) {
auto cross = uf::vector::normalize( uf::vector::cross( up, dir ) );
body.offsetOrientation = uf::quaternion::axisAngle( cross, std::acos( dot ) );
}
float radius = length * 0.15f;
float halfHeight = std::max( 0.01f, length - (radius * 2.0f) ) * 0.5f;
uf::physics::initialize( body, pod::Capsule{ radius, halfHeight } );
} break;
case pod::ShapeType::AABB:
case pod::ShapeType::OBB: {
uf::physics::initialize( body, pod::OBB{ pod::Vector3f{}, obb.extent * armatureTransform.scale } );
} break;
// should probably add a NONE shape
default:
case pod::ShapeType::SPHERE: {
uf::physics::initialize( body, pod::Sphere{ 0.01f } );
} break;
}
}
// create constraints
@ -388,7 +450,7 @@ void uf::graph::rigRagdoll( pod::Graph& graph, pod::Node& node ) {
auto axis = uf::vector::normalize( tB.position - tA.position );
auto& constraint = uf::physics::constrain( bodyA, bodyB );
uf::physics::constrainConeTwist( constraint, pivot, axis );
uf::physics::constrainConeTwist( constraint, pivot, axis, M_PI, M_PI );
}
// works only pre-init and for no scaling

View File

@ -174,12 +174,22 @@ void uf::physics::step( pod::World& world, float dt ) {
// sleeping island, skip (asleep islands shouldn't ever be in here)
if ( !island.awake ) return;
// pairs of bodies that shouldn't ever collide
uf::stl::unordered_set<size_t> ignorePairs;
// mark constraining objects as non-colliding (to-do: add a flag)
for ( auto& constraint : constraints ) {
ignorePairs.insert( impl::makePairKey( *constraint->a, *constraint->b ) );
}
// iterate overlap pairs
for ( auto& [ ia, ib ] : island.pairs ) {
auto& a = *bodies[ia];
auto& b = *bodies[ib];
auto pairKey = impl::makePairKey( a, b );
pod::Manifold manifold;
// marked as ignored
if ( ignorePairs.find( pairKey ) != ignorePairs.end() ) continue;
// did not collide
if ( !impl::generateManifold( a, b, manifold, dt ) ) continue;
@ -197,7 +207,7 @@ void uf::physics::step( pod::World& world, float dt ) {
}
// retrieve accumulated impulses
if ( uf::physics::settings.warmupSolver ) {
auto it = uf::physics::settings.manifoldsCache.find( impl::makePairKey( a, b ) );
auto it = uf::physics::settings.manifoldsCache.find( pairKey );
if ( it != uf::physics::settings.manifoldsCache.end() ) impl::retrieveManifold( manifold, it->second );
}
// merge similar contacts from a mesh to ensure continuity