imagine my shock the week-long issue predicated on scaling issues (constraint solving is now scaling aware, automatic ragdoll rigging seems to work, but need to actually bind the transforms to joint matrices)

This commit is contained in:
ecker 2026-05-27 20:54:51 -05:00
parent 94ce7be618
commit 5402aff2a8
28 changed files with 343 additions and 127 deletions

View File

@ -8,7 +8,7 @@
"useLightmaps": false,
"max": 32,
"shadows": {
"enabled": false,
"enabled": true,
"update": 4,
"max": 16,
"samples": 2
@ -351,7 +351,10 @@
},
"debug draw": {
"static": true,
"dynamic": true
"dynamic": true,
"contacts": false,
"constraints": true,
"rays": false
},
"fixed step": true,
"substeps": 4

View File

@ -14,7 +14,7 @@
"axis": [ 0, 1, 0 ],
"angle": 0
},
"scale": [ 5.0, 5.0, 5.0 ]
"scale": [ 10.0, 10.0, 10.0 ]
},
"system": {
"hot reload": {
@ -27,6 +27,9 @@
"debug": {
"print": {
"animations": true
},
"draw": {
"armature": true
}
},
"exporter": {

View File

@ -2,8 +2,8 @@
"type": "Object",
"name": "Craeture",
"ignore": false,
"import": "./craetureModel.json",
"assets": [
"./craetureModel.json",
"./scripts/craeture.lua"
],
"behaviors": [
@ -11,13 +11,10 @@
"SoundEmitterBehavior"
],
"transform": {
"position": [ -7.04676, 10.169565, -44.0822 ],
//"position": [ 0, 1.5, 21 ],
"position": [ 16.3489, 1.37972, -68.1571 ],
"rotation": {
"axis": [ 0, 1, 0 ],
"angle": 0
},
"scale": [ 1, 1, 1 ]
//"position": [ 16.3489, 1.37972, -68.1571 ],
"scale": [ 0.3, 0.3, 0.3 ]
},
"system": {
"hot reload": {
@ -26,7 +23,17 @@
},
"metadata": {
"name": "Craeture",
"graph": {
"debug": {
"draw": {
"armature": true
}
},
"physics": {
"ragdoll": true
}
}
/*
"physics": {
"gravity": [ 0, -9.81, 0 ],
"inertia": [ 0, 0, 0 ],
@ -41,5 +48,6 @@
"shared": false
}
*/
}
}

View File

@ -7,9 +7,7 @@
// "/player/bear.glb"
{ "filename": "/player/bear/graph.json" }
],
"behaviors": [
"CraetureBehavior"
],
"behaviors": [],
"transform": {
"position": [ 0, -2.5, 0 ],
// "position": [ 12.5715, 3.53811, 7.6238 ],
@ -18,7 +16,7 @@
"axis": [ 0, 1, 0 ],
"angle": 0
},
"scale": [ 0.35, 0.35, 0.35 ],
// "scale": [ 0.35, 0.35, 0.35 ],
"reference": "parent"
},
"system": {
@ -50,6 +48,7 @@
"filter": "linear",
"flip textures": false,
"invert": false,
"render": false,
"skinned": true
},
"animations": {

View File

@ -6,8 +6,9 @@
"metadata": {
"physics": {
"mass": 0,
"inertia": [0, 0, 0],
"type": "bounding box"
// "inertia": [0, 0, 0],
// "type": "bounding box"
"type": "mesh"
}
}
}

View File

@ -5,5 +5,7 @@ layout (location = 0) in vec4 inColor;
layout (location = 0) out vec4 outColor;
void main() {
outColor = inColor;
// needs to be specified like this for some unknown reason
outColor.rgb = inColor.rgb;
outColor.a = inColor.a;
}

View File

@ -150,6 +150,7 @@ namespace uf {
void UF_API override( pod::Graph& );
void UF_API animate( pod::Graph&, const uf::stl::string&, float = 1, bool = true );
uf::stl::vector<pod::Bone> collectBones( const pod::Graph& graph, const pod::Node& node );
uf::stl::vector<pod::OBB> obbFromSkin( const pod::Graph& graph, const pod::Node& node );
void rigRagdoll( pod::Graph& graph, pod::Node& node );

View File

@ -58,6 +58,11 @@ namespace pod {
uf::stl::vector<pod::Matrix4f> inverseBindMatrices;
};
struct UF_API Bone {
pod::Vector3f start;
pod::Vector3f end;
};
struct UF_API Animation {
struct Sampler {
uf::stl::string interpolator;

View File

@ -12,7 +12,9 @@ namespace impl {
pod::Transform<> getTransform( const pod::PhysicsBody& body );
pod::Vector3f getPosition( const pod::PhysicsBody& body, bool useTransform = false );
pod::Vector3f apply( const pod::Transform<>& t, const pod::Vector3f& p );
pod::Vector3f applyInverse( const pod::Transform<>& t, const pod::Vector3f& p );
pod::PhysicsBody physicsBodyHullView( const pod::PhysicsBody& body, int32_t index = -1 );
pod::PhysicsBody physicsBodyTriView( const pod::TriangleWithNormal triangle, const pod::PhysicsBody& body = {} );
pod::PhysicsBody physicsBodyTriView( const pod::PhysicsBody& body, size_t triID );

View File

@ -58,8 +58,8 @@ namespace uf {
const pod::CollisionEvent::events_t& UF_API getCollisionEvents( const pod::World& world );
pod::CollisionEvent::events_t UF_API getCollisionEvents( const pod::PhysicsBody& body );
pod::PhysicsBody& UF_API create( uf::Object&, float mass = 0.0f, const pod::Vector3f& = {} );
pod::PhysicsBody& UF_API create( pod::World&, uf::Object&, float mass = 0.0f, const pod::Vector3f& = {} );
pod::PhysicsBody& UF_API create( uf::Object&, float mass = 0.0f, const pod::Vector3f& = {}, const pod::Quaternion<>& = {0,0,0,1} );
pod::PhysicsBody& UF_API create( pod::World&, uf::Object&, float mass = 0.0f, const pod::Vector3f& = {}, const pod::Quaternion<>& = {0,0,0,1} );
void UF_API destroy( uf::Object& );
void UF_API destroy( pod::PhysicsBody& );
}

View File

@ -407,7 +407,8 @@ namespace pod {
float inverseMass = 0.0f;
int32_t viewIndex = -1; // -1 means it's not an aliased view
pod::Vector3f offset = {};
pod::Vector3f offsetPosition = {};
pod::Quaternion<> offsetOrientation = { 0, 0, 0, 1 };
pod::Vector3f velocity = {};
pod::Vector3f forceAccumulator = {};
@ -437,7 +438,6 @@ namespace pod {
bool resolveBlockContact = true; // attempts to resolve an invalid BlockNxN solve with an BlockN-1xN-1 solve
bool useGjk = false; // currently don't have a way to broadphase mesh => narrowphase tri via GJK
pod::CollisionMask debugDraw = pod::Collider::CATEGORY_NONE; // draws wireframe of collision bodies
bool async = false; // dedicated thread for physics sim
float timestep = 1.0f / 60.0f; // timestep for fixed step ticks
bool fixedStep = true; // run physics simulation with a fixed delta time (with accumulation), rather than rely on actual engine deltatime
@ -485,6 +485,13 @@ namespace pod {
};
float groundedThreshold = 0.7f; // threshold before marking a body as grounded
struct {
pod::CollisionMask mask = pod::Collider::CATEGORY_NONE; // draws wireframe of collision bodies
bool contacts = false;
bool constraints = false;
bool rays = false;
} debugDraw;
};
struct World {

View File

@ -223,17 +223,24 @@ void UF_API uf::load( ext::json::Value& json ) {
}
auto& configEnginePhysicsDebugDrawJson = configEnginePhysicsJson["debug draw"];
if ( ext::json::isObject( configEnginePhysicsDebugDrawJson ) ) {
if ( configEnginePhysicsDebugDrawJson["static"].as<bool>() ) uf::physics::settings.debugDraw |= pod::Collider::CATEGORY_STATIC;
if ( configEnginePhysicsDebugDrawJson["dynamic"].as<bool>() ) uf::physics::settings.debugDraw |= pod::Collider::CATEGORY_DYNAMIC;
if ( configEnginePhysicsDebugDrawJson["player"].as<bool>() ) uf::physics::settings.debugDraw |= pod::Collider::CATEGORY_PLAYER;
if ( configEnginePhysicsDebugDrawJson["npc"].as<bool>() ) uf::physics::settings.debugDraw |= pod::Collider::CATEGORY_NPC;
if ( configEnginePhysicsDebugDrawJson["trigger"].as<bool>() ) uf::physics::settings.debugDraw |= pod::Collider::CATEGORY_TRIGGER;
if ( configEnginePhysicsDebugDrawJson["projectile"].as<bool>() ) uf::physics::settings.debugDraw |= pod::Collider::CATEGORY_PROJECTILE;
if ( configEnginePhysicsDebugDrawJson["character"].as<bool>() ) uf::physics::settings.debugDraw |= pod::Collider::CATEGORY_CHARACTER;
if ( configEnginePhysicsDebugDrawJson["all"].as<bool>() ) uf::physics::settings.debugDraw |= pod::Collider::CATEGORY_ALL;
if ( configEnginePhysicsDebugDrawJson["static"].as<bool>() ) uf::physics::settings.debugDraw.mask |= pod::Collider::CATEGORY_STATIC;
if ( configEnginePhysicsDebugDrawJson["dynamic"].as<bool>() ) uf::physics::settings.debugDraw.mask |= pod::Collider::CATEGORY_DYNAMIC;
if ( configEnginePhysicsDebugDrawJson["player"].as<bool>() ) uf::physics::settings.debugDraw.mask |= pod::Collider::CATEGORY_PLAYER;
if ( configEnginePhysicsDebugDrawJson["npc"].as<bool>() ) uf::physics::settings.debugDraw.mask |= pod::Collider::CATEGORY_NPC;
if ( configEnginePhysicsDebugDrawJson["trigger"].as<bool>() ) uf::physics::settings.debugDraw.mask |= pod::Collider::CATEGORY_TRIGGER;
if ( configEnginePhysicsDebugDrawJson["projectile"].as<bool>() ) uf::physics::settings.debugDraw.mask |= pod::Collider::CATEGORY_PROJECTILE;
if ( configEnginePhysicsDebugDrawJson["character"].as<bool>() ) uf::physics::settings.debugDraw.mask |= pod::Collider::CATEGORY_CHARACTER;
if ( configEnginePhysicsDebugDrawJson["all"].as<bool>() ) uf::physics::settings.debugDraw.mask |= pod::Collider::CATEGORY_ALL;
uf::physics::settings.debugDraw.contacts = configEnginePhysicsDebugDrawJson["contacts"].as( uf::physics::settings.debugDraw.contacts );
uf::physics::settings.debugDraw.constraints = configEnginePhysicsDebugDrawJson["constraints"].as( uf::physics::settings.debugDraw.constraints );
uf::physics::settings.debugDraw.rays = configEnginePhysicsDebugDrawJson["rays"].as( uf::physics::settings.debugDraw.rays );
} else if ( configEnginePhysicsDebugDrawJson.is<bool>() && configEnginePhysicsDebugDrawJson.as<bool>() ) {
uf::physics::settings.debugDraw = pod::Collider::CATEGORY_ALL;
uf::physics::settings.debugDraw.mask = pod::Collider::CATEGORY_ALL;
uf::physics::settings.debugDraw.contacts = true;
uf::physics::settings.debugDraw.constraints = true;
uf::physics::settings.debugDraw.rays = true;
}
}

View File

@ -76,17 +76,18 @@ namespace {
#endif
}
pod::Matrix4f localMatrix( pod::Graph& graph, int32_t index ) {
pod::Matrix4f localMatrix( const pod::Graph& graph, int32_t index ) {
auto& node = 0 < index && index <= graph.nodes.size() ? graph.nodes[index] : graph.root;
auto& transform = node.transform;
return
uf::matrix::translate( uf::matrix::identity(), node.transform.position ) *
uf::quaternion::matrix(node.transform.orientation) *
uf::matrix::scale( uf::matrix::identity(), node.transform.scale ) *
node.transform.model;
uf::matrix::translate( uf::matrix::identity(), transform.position ) *
uf::quaternion::matrix(transform.orientation) *
uf::matrix::scale( uf::matrix::identity(), transform.scale ) *
transform.model;
}
pod::Matrix4f worldMatrix( pod::Graph& graph, int32_t index ) {
pod::Matrix4f worldMatrix( const pod::Graph& graph, int32_t index ) {
pod::Matrix4f matrix = ::localMatrix( graph, index );
auto& node = *uf::graph::find( graph, index );
auto& node = 0 < index && index <= graph.nodes.size() ? graph.nodes[index] : graph.root;
int32_t parent = node.parent;
while ( 0 < parent && parent <= graph.nodes.size() ) {
matrix = ::localMatrix( graph, parent ) * matrix;
@ -261,6 +262,25 @@ void uf::graph::updateAnimation( pod::Graph& graph, pod::Node& node ) {
}
// separate function in the event something later might need it
uf::stl::vector<pod::Bone> uf::graph::collectBones( const pod::Graph& graph, const pod::Node& node ) {
auto& storage = ::getGraphStorage(uf::scene::getCurrentScene());
auto& name = graph.skins[node.skin];
auto& skin = storage.skins[name];
uf::stl::vector<pod::Bone> bones( graph.nodes.size() );
for ( auto i = 0; i < skin.joints.size(); ++i ) {
auto nodeID = skin.joints[i];
auto& node = graph.nodes[nodeID];
if ( node.parent < 0 ) continue;
auto& parent = graph.nodes[node.parent];
auto tA = uf::transform::flatten( parent.transform );
auto tB = uf::transform::flatten( node.transform );
bones[nodeID] = pod::Bone{ tA.position, tB.position };
}
return bones;
}
uf::stl::vector<pod::OBB> uf::graph::obbFromSkin( const pod::Graph& graph, const pod::Node& node ) {
const float wThresold = 0.15f;
@ -290,11 +310,11 @@ uf::stl::vector<pod::OBB> uf::graph::obbFromSkin( const pod::Graph& graph, const
for ( auto w = 0; w < 4; ++w ) {
if ( weights[w] <= wThresold ) continue;
uint16_t boneID = joints[w];
pod::Vector3f localPos = uf::matrix::multiply( skin.inverseBindMatrices[boneID], pos );
uint16_t jointID = joints[w];
pod::Vector3f localPos = uf::matrix::multiply( skin.inverseBindMatrices[jointID], pos );
bounds[boneID].center = uf::vector::min( bounds[boneID].center, localPos );
bounds[boneID].extent = uf::vector::max( bounds[boneID].extent, localPos );
bounds[jointID].center = uf::vector::min( bounds[jointID].center, localPos );
bounds[jointID].extent = uf::vector::max( bounds[jointID].extent, localPos );
}
}
}
@ -310,5 +330,107 @@ uf::stl::vector<pod::OBB> uf::graph::obbFromSkin( const pod::Graph& graph, const
}
void uf::graph::rigRagdoll( pod::Graph& graph, pod::Node& node ) {
// scrapped for now
auto& storage = ::getGraphStorage(uf::scene::getCurrentScene());
auto& name = graph.skins[node.skin];
auto& skin = storage.skins[name];
auto bounds = uf::graph::obbFromSkin( graph, node );
auto bones = uf::graph::collectBones( graph, node );
auto armatureTransform = uf::transform::flatten( graph.root.entity->getComponent<pod::Transform<>>() );
// create physics bodies
uf::stl::vector<pod::PhysicsBody*> bodies( graph.nodes.size(), NULL );
for ( auto i = 0; i < skin.joints.size(); ++i ) {
auto nodeID = skin.joints[i];
auto& node = graph.nodes[nodeID];
if ( node.parent < 0 ) continue;
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;
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 } );
bodies[nodeID] = &body;
}
// create constraints
for ( auto i = 0; i < skin.joints.size(); ++i ) {
auto nodeID = skin.joints[i];
auto& node = graph.nodes[nodeID];
if ( !bodies[node.parent] ) continue; // no parent body, skip constraint
if ( !bodies[nodeID] ) continue; // no node body, skip constraint
auto& bodyA = *bodies[node.parent];
auto& bodyB = *bodies[nodeID];
auto tA = uf::transform::flatten( *bodyA.transform ); // bone start
auto tB = uf::transform::flatten( *bodyB.transform ); // bone end
auto pivot = tA.position;
auto axis = uf::vector::normalize( tB.position - tA.position );
auto& constraint = uf::physics::constrain( bodyA, bodyB );
uf::physics::constrainConeTwist( constraint, pivot, axis );
}
// works only pre-init and for no scaling
#if 0
for ( auto i = 0; i < skin.joints.size(); ++i ) {
auto nodeID = skin.joints[i];
auto& node = graph.nodes[nodeID];
if ( node.parent < 0 ) continue;
auto& entity = *node.entity;
auto boneStart = uf::transform::applyInverse( node.transform, pod::Vector3f{0, 0, 0} );
auto offset = uf::vector::lerp( boneStart, pod::Vector3f{}, 0.5f );
auto orientation = uf::quaternion::identity();
// float length = ...
float mass = 0.0f;
auto& body = uf::physics::create( entity, mass, offset, orientation );
uf::physics::initialize( body, pod::Sphere{ 0.5f } );
bodies[nodeID] = &body;
}
// create constraints
for ( auto i = 0; i < skin.joints.size(); ++i ) {
auto nodeID = skin.joints[i];
auto& node = graph.nodes[nodeID];
if ( node.parent < 0 ) continue;
auto& parent = graph.nodes[node.parent];
if ( bodies.count( node.parent ) == 0 ) continue;
if ( bodies.count( nodeID ) == 0 ) continue;
auto& bodyA = bodies[node.parent];
auto& bodyB = bodies[nodeID];
auto tA = uf::transform::flatten( parent.transform );
auto tB = uf::transform::flatten( node.transform );
auto pivot = tA.position;
auto axis = uf::vector::normalize( tB.position - tA.position );
auto& constraint = uf::physics::constrain( *bodyA, *bodyB );
uf::physics::constrainConeTwist( constraint, pivot, axis );
}
#endif
}

View File

@ -1113,17 +1113,6 @@ void uf::graph::process( pod::Graph& graph ) {
}
*/
UF_DEBUG_TIMER_MULTITRACE("Rigging ragdolls");
for ( auto& node : graph.nodes ) {
if ( node.skin < 0 || node.mesh < 0 ) continue;
ext::json::Value tag = ext::json::find( node.name, graphMetadataJson["tags"] );
if ( ext::json::isNull( tag ) ) tag["physics"] = graphMetadataJson["physics"];
if ( tag["physics"]["ragdoll"].as<bool>(false) ) {
uf::graph::rigRagdoll( graph, node );
}
}
UF_DEBUG_TIMER_MULTITRACE_END("Rigged ragdolls.");
UF_DEBUG_TIMER_MULTITRACE("Updating master graph");
#if UF_GRAPH_EXTENDED
uf::graph::reload( graph );
@ -1406,6 +1395,15 @@ void uf::graph::initialize( pod::Graph& graph ) {
if ( entity->getUid() == 0 ) entity->initialize();
});
auto& graphMetadataJson = graph.metadata;
for ( auto& node : graph.nodes ) {
if ( node.skin < 0 || node.mesh < 0 ) continue;
ext::json::Value tag = ext::json::find( node.name, graphMetadataJson["tags"] );
if ( ext::json::isNull( tag ) ) tag["physics"] = graphMetadataJson["physics"];
if ( tag["physics"]["ragdoll"].as<bool>(false) ) {
uf::graph::rigRagdoll( graph, node );
}
}
auto& scene = uf::scene::getCurrentScene();
scene.invalidateGraph();

View File

@ -13,6 +13,7 @@
#include <uf/utils/mesh/mesh.h>
#include <uf/utils/string/hash.h>
#include <uf/utils/io/inputs.h>
#include <uf/utils/debug/draw.h>
UF_BEHAVIOR_REGISTER_CPP(uf::GraphBehavior)
UF_BEHAVIOR_TRAITS_CPP(uf::GraphBehavior, ticks = true, renders = false, thread = "")
@ -68,13 +69,32 @@ void uf::GraphBehavior::initialize( uf::Object& self ) {
}
void uf::GraphBehavior::destroy( uf::Object& self ) {}
void uf::GraphBehavior::tick( uf::Object& self ) {
/* Test */ {
TIMER(1, uf::inputs::kbm::states::T ) {
UF_MSG_DEBUG("Regenerating graph graphics...");
auto& graph = this->getComponent<pod::Graph>();
uf::graph::reload( graph );
if ( !this->hasComponent<pod::Graph>() ) return;
auto& graph = this->getComponent<pod::Graph>();
if ( !graph.metadata["debug"]["draw"]["armature"].as<bool>(false) ) return;
auto& transform = this->getComponent<pod::Transform<>>();
for ( auto& node : graph.nodes ) {
if ( node.skin < 0 || node.mesh < 0 ) continue;
auto bones = uf::graph::collectBones( graph, node );
for ( auto& bone : bones ) {
bone.start = uf::transform::apply( transform, bone.start );
bone.end = uf::transform::apply( transform, bone.end );
uf::debug::drawLine( bone.start, bone.end, pod::Vector4f{ 0, 1, 0, 1 } );
}
}
static bool hid = false;
if ( hid ) return;
hid = true;
this->process([&](uf::Entity* entity){
if ( !entity->hasComponent<uf::Graphic>() ) return;
auto& graphic = entity->getComponent<uf::Graphic>();
if ( !graphic.initialized ) return;
auto& descriptorSet = graphic.getDescriptorSet();
descriptorSet.metadata.process = false;
uf::renderer::states::rebuild = true;
});
}
void uf::GraphBehavior::render( uf::Object& self ) {}
void uf::GraphBehavior::Metadata::serialize( uf::Object& self, uf::Serializer& serializer ) {}

View File

@ -31,7 +31,8 @@ namespace binds {
pod::Transform<> getTransform( pod::PhysicsBody& body ) {
pod::Transform<> t;
t.position = body.offset;
t.position = body.offsetPosition;
t.orientation = body.offsetOrientation;
t.reference = body.transform;
return uf::transform::flatten( t );
}

View File

@ -219,7 +219,7 @@ void uf::debug::draw( float dt ) {
graphic.material.device = &uf::renderer::device;
// to-do: bin by descriptor instead of one global set
graphic.descriptor.depth.test = true;
graphic.descriptor.depth.test = false;
graphic.descriptor.depth.write = false;
graphic.descriptor.renderTarget = 1; // "forward";
graphic.descriptor.topology = uf::renderer::enums::PrimitiveTopology::LINE_LIST;

View File

@ -65,12 +65,9 @@ void impl::updateActivity( pod::PhysicsBody& body, float dt ) {
// to-do: find a succinct way to explain this madness
pod::Transform<> impl::getTransform( const pod::PhysicsBody& body ) {
pod::Transform<> t;
t.position = body.offset;
#if UF_PHYSICS_SYNC_TRANSFORMS
t.reference = const_cast<pod::Transform<>*>( &body.flattenedTransform );
#else
t.position = body.offsetPosition;
t.orientation = body.offsetOrientation;
t.reference = body.transform;
#endif
return uf::transform::flatten( t );
}
// get position of a body, uses bounds center or transform's position
@ -78,6 +75,23 @@ pod::Vector3f impl::getPosition( const pod::PhysicsBody& body, bool useTransform
if ( !useTransform ) return impl::aabbCenter( body.bounds );
return impl::getTransform( body ).position;
}
// applies a transform
pod::Vector3f impl::apply( const pod::Transform<>& t, const pod::Vector3f& p ) {
return uf::transform::apply( t, p );
}
// applies an inverse transform
pod::Vector3f impl::applyInverse( const pod::Transform<>& t, const pod::Vector3f& p ) {
return uf::transform::applyInverse( t, p );
}
/*
// these isometrically applies a transform
pod::Vector3f impl::apply( const pod::Transform<>& t, const pod::Vector3f& p ) {
return uf::quaternion::rotate( t.orientation, p + t.position );
}
pod::Vector3f impl::applyInverse( const pod::Transform<>& t, const pod::Vector3f& p ) {
return uf::quaternion::rotate( uf::quaternion::inverse( t.orientation ), p - t.position );
}
*/
// creates a view of a hull body
pod::PhysicsBody impl::physicsBodyHullView( const pod::PhysicsBody& body, int32_t index ) {
pod::PhysicsBody view = body;
@ -94,7 +108,8 @@ pod::PhysicsBody impl::physicsBodyTriView( const pod::TriangleWithNormal triangl
view.collider.triangle.normal = impl::triangleNormal( (const pod::Triangle&) triangle );
}
// assume triangle is already transformed
view.offset = {};
view.offsetPosition = {};
view.offsetOrientation = {0,0,0,1};
view.transform = NULL;
return view;
}

View File

@ -14,8 +14,11 @@ void impl::solveBallSocketConstraint( pod::Constraint& constraint, float dt ) {
auto tA = impl::getTransform( a );
auto tB = impl::getTransform( b );
auto rA = uf::quaternion::rotate( tA.orientation, joint.localAnchorA );
auto rB = uf::quaternion::rotate( tB.orientation, joint.localAnchorB );
auto anchorA = joint.localAnchorA * tA.scale;
auto anchorB = joint.localAnchorB * tB.scale;
auto rA = uf::quaternion::rotate( tA.orientation, anchorA );
auto rB = uf::quaternion::rotate( tB.orientation, anchorB );
pod::Matrix3f K = {};
pod::Vector3f axes[3] = { {1,0,0}, {0,1,0}, {0,0,1} };
@ -63,13 +66,12 @@ pod::Constraint& uf::physics::constrainBallSocket( pod::Constraint& constraint,
void impl::drawBallSocket( const pod::Constraint& constraint ) {
if ( !constraint.a || !constraint.b ) return;
auto tA = impl::getTransform(*constraint.a);
auto tB = impl::getTransform(*constraint.b);
const auto& joint = constraint.ballSocket;
auto tA = impl::getTransform( *constraint.a );
auto tB = impl::getTransform( *constraint.b );
auto pA = tA.position + uf::quaternion::rotate(tA.orientation, joint.localAnchorA);
auto pB = tB.position + uf::quaternion::rotate(tB.orientation, joint.localAnchorB);
auto pA = uf::transform::apply( tA, joint.localAnchorA );
auto pB = uf::transform::apply( tB, joint.localAnchorB );
uf::debug::drawLine( tA.position, pA );
uf::debug::drawLine( tB.position, pB );

View File

@ -172,26 +172,26 @@ pod::Constraint& uf::physics::constrainConeTwist( pod::Constraint& constraint, c
void impl::drawConeTwist( const pod::Constraint& constraint ) {
if ( !constraint.a || !constraint.b ) return;
auto tA = impl::getTransform(*constraint.a);
auto tB = impl::getTransform(*constraint.b);
const auto& joint = constraint.coneTwist;
auto tA = impl::getTransform( *constraint.a );
auto tB = impl::getTransform( *constraint.b );
auto pA = tA.position + uf::quaternion::rotate(tA.orientation, joint.localAnchorA);
auto pB = tB.position + uf::quaternion::rotate(tB.orientation, joint.localAnchorB);
auto pA = uf::transform::apply( tA, joint.localAnchorA );
auto pB = uf::transform::apply( tB, joint.localAnchorB );
auto taA = uf::quaternion::rotate(tA.orientation, joint.localTwistAxisA);
auto taB = uf::quaternion::rotate(tB.orientation, joint.localTwistAxisB);
auto taA = uf::quaternion::rotate( tA.orientation, joint.localTwistAxisA );
auto taB = uf::quaternion::rotate( tB.orientation, joint.localTwistAxisB );
auto raA = uf::quaternion::rotate(tA.orientation, joint.localReferenceAxisA);
auto raB = uf::quaternion::rotate(tB.orientation, joint.localReferenceAxisB);
auto raA = uf::quaternion::rotate( tA.orientation, joint.localReferenceAxisA );
auto raB = uf::quaternion::rotate( tB.orientation, joint.localReferenceAxisB );
uf::debug::drawLine( pA, pB );
uf::debug::drawLine( pA, pB, pod::Vector4f{ 1, 1, 0, 1 } ); // yellow
float axisLength = 0.5f;
uf::debug::drawLine( pA, pA + taA * axisLength );
uf::debug::drawLine( pB, pB + taB * axisLength );
uf::debug::drawLine( pA, pA + taA * axisLength, pod::Vector4f{ 1, 0, 1, 1 } ); // pink
uf::debug::drawLine( pB, pB + taB * axisLength, pod::Vector4f{ 1, 0, 1, 1 } );
float refLength = 0.25f;
uf::debug::drawLine( pA, pA + raA * refLength );
uf::debug::drawLine( pB, pB + raB * refLength );
uf::debug::drawLine( pA, pA + raA * refLength, pod::Vector4f{ 0, 1, 1, 1 } ); // cyan
uf::debug::drawLine( pB, pB + raB * refLength, pod::Vector4f{ 0, 1, 1, 1 } );
}

View File

@ -11,8 +11,11 @@ void impl::solveDistanceConstraint( pod::Constraint& constraint, float dt ) {
auto tA = impl::getTransform( a );
auto tB = impl::getTransform( b );
auto rA = uf::quaternion::rotate( tA.orientation, joint.localAnchorA );
auto rB = uf::quaternion::rotate( tB.orientation, joint.localAnchorB );
auto anchorA = joint.localAnchorA * tA.scale;
auto anchorB = joint.localAnchorB * tB.scale;
auto rA = uf::quaternion::rotate( tA.orientation, anchorA );
auto rB = uf::quaternion::rotate( tB.orientation, anchorB );
auto pA = tA.position + rA;
auto pB = tB.position + rB;
@ -58,13 +61,12 @@ pod::Constraint& uf::physics::constrainDistance( pod::Constraint& constraint, co
void impl::drawDistance( const pod::Constraint& constraint ) {
if ( !constraint.a || !constraint.b ) return;
auto tA = impl::getTransform(*constraint.a);
auto tB = impl::getTransform(*constraint.b);
const auto& joint = constraint.distance;
auto tA = impl::getTransform( *constraint.a );
auto tB = impl::getTransform( *constraint.b );
auto pA = tA.position + uf::quaternion::rotate(tA.orientation, joint.localAnchorA);
auto pB = tB.position + uf::quaternion::rotate(tB.orientation, joint.localAnchorB);
auto pA = uf::transform::apply( tA, joint.localAnchorA );
auto pB = uf::transform::apply( tB, joint.localAnchorB );
uf::debug::drawLine( tA.position, pA );
uf::debug::drawLine( tB.position, pB );

View File

@ -76,16 +76,15 @@ pod::Constraint& uf::physics::constrainHinge( pod::Constraint& constraint, const
void impl::drawHinge( const pod::Constraint& constraint ) {
if ( !constraint.a || !constraint.b ) return;
auto tA = impl::getTransform(*constraint.a);
auto tB = impl::getTransform(*constraint.b);
const auto& joint = constraint.hinge;
auto tA = impl::getTransform( *constraint.a );
auto tB = impl::getTransform( *constraint.b );
auto pA = tA.position + uf::quaternion::rotate(tA.orientation, joint.localAnchorA);
auto pB = tB.position + uf::quaternion::rotate(tB.orientation, joint.localAnchorB);
auto pA = uf::transform::apply( tA, joint.localAnchorA );
auto pB = uf::transform::apply( tB, joint.localAnchorB );
auto aA = uf::quaternion::rotate(tA.orientation, joint.localAxisA);
auto aB = uf::quaternion::rotate(tB.orientation, joint.localAxisB);
auto aA = uf::quaternion::rotate( tA.orientation, joint.localAxisA );
auto aB = uf::quaternion::rotate( tB.orientation, joint.localAxisB );
uf::debug::drawLine( pA, pB );

View File

@ -12,8 +12,11 @@ void impl::solveSliderConstraint( pod::Constraint& constraint, float dt ) {
auto tA = impl::getTransform( a );
auto tB = impl::getTransform( b );
auto rA = uf::quaternion::rotate( tA.orientation, joint.localAnchorA );
auto rB = uf::quaternion::rotate( tB.orientation, joint.localAnchorB );
auto anchorA = joint.localAnchorA * tA.scale;
auto anchorB = joint.localAnchorB * tB.scale;
auto rA = uf::quaternion::rotate( tA.orientation, anchorA );
auto rB = uf::quaternion::rotate( tB.orientation, anchorB );
auto pA = tA.position + rA;
auto pB = tB.position + rB;

View File

@ -11,8 +11,11 @@ void impl::solveSpringConstraint( pod::Constraint& constraint, float dt ) {
auto tA = impl::getTransform( a );
auto tB = impl::getTransform( b );
auto rA = uf::quaternion::rotate( tA.orientation, joint.localAnchorA );
auto rB = uf::quaternion::rotate( tB.orientation, joint.localAnchorB );
auto anchorA = joint.localAnchorA * tA.scale;
auto anchorB = joint.localAnchorB * tB.scale;
auto rA = uf::quaternion::rotate( tA.orientation, anchorA );
auto rB = uf::quaternion::rotate( tB.orientation, anchorB );
auto worldAnchorA = tA.position + rA;
auto worldAnchorB = tB.position + rB;

View File

@ -7,6 +7,7 @@
void impl::drawManifold( const pod::Manifold& manifold ) {
if ( !uf::physics::settings.debugDraw.contacts ) return;
for ( auto& contact : manifold.points ) {
auto& start = contact.point;
auto end = contact.point + (contact.normal * MIN(contact.penetration, 0.1f) * 2);
@ -15,7 +16,7 @@ void impl::drawManifold( const pod::Manifold& manifold ) {
}
}
void impl::drawBody( const pod::PhysicsBody& body ) {
if ( !(body.collider.category & uf::physics::settings.debugDraw) ) return;
if ( !(body.collider.category & uf::physics::settings.debugDraw.mask) ) return;
switch( body.collider.type ) {
case pod::ShapeType::AABB:
impl::drawAabb( body );
@ -44,8 +45,9 @@ void impl::drawBody( const pod::PhysicsBody& body ) {
}
}
void impl::drawConstraint( const pod::Constraint& constraint ) {
if ( !(constraint.a->collider.category & uf::physics::settings.debugDraw) ) return;
if ( !(constraint.b->collider.category & uf::physics::settings.debugDraw) ) return;
if ( !uf::physics::settings.debugDraw.constraints ) return;
if ( !(constraint.a->collider.category & uf::physics::settings.debugDraw.mask) ) return;
if ( !(constraint.b->collider.category & uf::physics::settings.debugDraw.mask) ) return;
switch( constraint.type ) {
case pod::ConstraintType::BALL_AND_SOCKET:

View File

@ -66,7 +66,7 @@ void uf::physics::tick( pod::World& world, float dt ) {
accumulator -= timestep;
}
if ( uf::physics::settings.debugDraw != pod::Collider::CATEGORY_NONE ) impl::draw( world, dt );
if ( uf::physics::settings.debugDraw.mask != pod::Collider::CATEGORY_NONE ) impl::draw( world, dt );
}
void uf::physics::terminate() {
uf::physics::terminate( uf::scene::getCurrentScene() );
@ -241,7 +241,7 @@ void uf::physics::step( pod::World& world, float dt ) {
// dispatch collision events
impl::dispatchManifold( manifold, collisionEvents, activeCollisions, previousCollisions );
// draw collision events
if ( uf::physics::settings.debugDraw ) impl::drawManifold( manifold );
if ( uf::physics::settings.debugDraw.contacts ) impl::drawManifold( manifold );
}
// dispatch exiting collisions
for ( auto& [ key, pair ] : previousCollisions ) {
@ -534,7 +534,7 @@ pod::CollisionEvent::events_t uf::physics::getCollisionEvents( const pod::Physic
return res;
}
// body creation
pod::PhysicsBody& uf::physics::create( pod::World& world, uf::Object& object, float mass, const pod::Vector3f& offset ) {
pod::PhysicsBody& uf::physics::create( pod::World& world, uf::Object& object, float mass, const pod::Vector3f& positionOffset, const pod::Quaternion<>& orientationOffset ) {
auto& root = object.getComponent<pod::PhysicsBody>();
auto isRoot = !root.world;
auto& body = isRoot ? root : *(new pod::PhysicsBody);
@ -542,10 +542,8 @@ pod::PhysicsBody& uf::physics::create( pod::World& world, uf::Object& object, fl
body.world = &world;
body.object = &object;
body.transform = &object.getComponent<pod::Transform<>>();
#if UF_PHYSICS_SYNC_TRANSFORMS
body.flattenedTransform = uf::transform::flatten( *body.transform );
#endif
body.offset = offset;
body.offsetPosition = positionOffset;
body.offsetOrientation = orientationOffset;
body.inverseMass = mass == 0.0f ? 0.0f : 1.0f / mass;
body.collider.type = {};
@ -578,8 +576,8 @@ pod::PhysicsBody& uf::physics::create( pod::World& world, uf::Object& object, fl
return body;
}
pod::PhysicsBody& uf::physics::create( uf::Object& object, float mass, const pod::Vector3f& offset ) {
return create( getWorld(), object, mass, offset );
pod::PhysicsBody& uf::physics::create( uf::Object& object, float mass, const pod::Vector3f& positionOffset, const pod::Quaternion<>& orientationOffset ) {
return create( getWorld(), object, mass, positionOffset, orientationOffset );
}
void uf::physics::destroy( uf::Object& object ) {

View File

@ -178,7 +178,7 @@ void impl::getSupportFace( const pod::PhysicsBody& body, const pod::Vector3f& di
bool impl::generateClippingManifold( const pod::PhysicsBody& a, const pod::PhysicsBody& b, const pod::Contact& contact, pod::Manifold& manifold ) {
if ( !uf::vector::isValid(contact.point) ) return false;
auto& normal = contact.normal;
auto normal = contact.normal;
pod::Vector3f polyA[4];
pod::Vector3f polyB[4];
@ -192,18 +192,31 @@ bool impl::generateClippingManifold( const pod::PhysicsBody& a, const pod::Physi
return true;
}
// to-do: reference face is the most perpendicular face
pod::Vector3f* refPoly = polyA;
pod::Vector3f* incPoly = polyB;
int refCount = countA;
int incCount = countB;
pod::Vector3f normalA = uf::vector::normalize(uf::vector::cross(polyA[1] - polyA[0], polyA[2] - polyA[0]));
pod::Vector3f normalB = uf::vector::normalize(uf::vector::cross(polyB[1] - polyB[0], polyB[2] - polyB[0]));
float dotA = uf::vector::dot(normalA, normal);
float dotB = uf::vector::dot(normalB, -normal);
pod::Vector3f* refPoly;
pod::Vector3f* incPoly;
int refCount, incCount;
pod::Vector3f refNormal;
if ( dotA > dotB ) {
refPoly = polyA; refCount = countA; refNormal = normalA;
incPoly = polyB; incCount = countB;
} else {
refPoly = polyB; refCount = countB; refNormal = normalB;
incPoly = polyA; incCount = countA;
normal = -normal;
}
pod::Vector3f clipBuffer[8];
int clipCount = incCount;
for ( auto i = 0; i < incCount; ++i ) clipBuffer[i] = incPoly[i];
pod::Vector3f refNormal = uf::vector::normalize(uf::vector::cross(refPoly[1] - refPoly[0], refPoly[2] - refPoly[0]));
for ( auto i = 0; i < refCount; ++i ) {
pod::Vector3f edgeStart = refPoly[i];
pod::Vector3f edgeEnd = refPoly[(i + 1) % refCount];

View File

@ -342,7 +342,7 @@ pod::RayQuery uf::physics::rayCast( const pod::Ray& ray, const pod::World& world
}
}
if ( uf::physics::settings.debugDraw ) impl::drawRay( ray, rayHit );
if ( uf::physics::settings.debugDraw.rays ) impl::drawRay( ray, rayHit );
return rayHit;
}