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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,5 +5,7 @@ layout (location = 0) in vec4 inColor;
layout (location = 0) out vec4 outColor; layout (location = 0) out vec4 outColor;
void main() { 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 override( pod::Graph& );
void UF_API animate( pod::Graph&, const uf::stl::string&, float = 1, bool = true ); 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 ); uf::stl::vector<pod::OBB> obbFromSkin( const pod::Graph& graph, const pod::Node& node );
void rigRagdoll( pod::Graph& graph, 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; uf::stl::vector<pod::Matrix4f> inverseBindMatrices;
}; };
struct UF_API Bone {
pod::Vector3f start;
pod::Vector3f end;
};
struct UF_API Animation { struct UF_API Animation {
struct Sampler { struct Sampler {
uf::stl::string interpolator; uf::stl::string interpolator;

View File

@ -12,6 +12,8 @@ namespace impl {
pod::Transform<> getTransform( const pod::PhysicsBody& body ); pod::Transform<> getTransform( const pod::PhysicsBody& body );
pod::Vector3f getPosition( const pod::PhysicsBody& body, bool useTransform = false ); 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 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::TriangleWithNormal triangle, const pod::PhysicsBody& body = {} );

View File

@ -58,8 +58,8 @@ namespace uf {
const pod::CollisionEvent::events_t& UF_API getCollisionEvents( const pod::World& world ); const pod::CollisionEvent::events_t& UF_API getCollisionEvents( const pod::World& world );
pod::CollisionEvent::events_t UF_API getCollisionEvents( const pod::PhysicsBody& body ); 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( 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& = {} ); 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( uf::Object& );
void UF_API destroy( pod::PhysicsBody& ); void UF_API destroy( pod::PhysicsBody& );
} }

View File

@ -407,7 +407,8 @@ namespace pod {
float inverseMass = 0.0f; float inverseMass = 0.0f;
int32_t viewIndex = -1; // -1 means it's not an aliased view 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 velocity = {};
pod::Vector3f forceAccumulator = {}; 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 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 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 bool async = false; // dedicated thread for physics sim
float timestep = 1.0f / 60.0f; // timestep for fixed step ticks 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 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 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 { struct World {

View File

@ -223,17 +223,24 @@ void UF_API uf::load( ext::json::Value& json ) {
} }
auto& configEnginePhysicsDebugDrawJson = configEnginePhysicsJson["debug draw"]; auto& configEnginePhysicsDebugDrawJson = configEnginePhysicsJson["debug draw"];
if ( ext::json::isObject( configEnginePhysicsDebugDrawJson ) ) { if ( ext::json::isObject( configEnginePhysicsDebugDrawJson ) ) {
if ( configEnginePhysicsDebugDrawJson["static"].as<bool>() ) uf::physics::settings.debugDraw |= pod::Collider::CATEGORY_STATIC; if ( configEnginePhysicsDebugDrawJson["static"].as<bool>() ) uf::physics::settings.debugDraw.mask |= pod::Collider::CATEGORY_STATIC;
if ( configEnginePhysicsDebugDrawJson["dynamic"].as<bool>() ) uf::physics::settings.debugDraw |= pod::Collider::CATEGORY_DYNAMIC; if ( configEnginePhysicsDebugDrawJson["dynamic"].as<bool>() ) uf::physics::settings.debugDraw.mask |= pod::Collider::CATEGORY_DYNAMIC;
if ( configEnginePhysicsDebugDrawJson["player"].as<bool>() ) uf::physics::settings.debugDraw |= pod::Collider::CATEGORY_PLAYER; if ( configEnginePhysicsDebugDrawJson["player"].as<bool>() ) uf::physics::settings.debugDraw.mask |= pod::Collider::CATEGORY_PLAYER;
if ( configEnginePhysicsDebugDrawJson["npc"].as<bool>() ) uf::physics::settings.debugDraw |= pod::Collider::CATEGORY_NPC; if ( configEnginePhysicsDebugDrawJson["npc"].as<bool>() ) uf::physics::settings.debugDraw.mask |= pod::Collider::CATEGORY_NPC;
if ( configEnginePhysicsDebugDrawJson["trigger"].as<bool>() ) uf::physics::settings.debugDraw |= pod::Collider::CATEGORY_TRIGGER; if ( configEnginePhysicsDebugDrawJson["trigger"].as<bool>() ) uf::physics::settings.debugDraw.mask |= pod::Collider::CATEGORY_TRIGGER;
if ( configEnginePhysicsDebugDrawJson["projectile"].as<bool>() ) uf::physics::settings.debugDraw |= pod::Collider::CATEGORY_PROJECTILE; if ( configEnginePhysicsDebugDrawJson["projectile"].as<bool>() ) uf::physics::settings.debugDraw.mask |= pod::Collider::CATEGORY_PROJECTILE;
if ( configEnginePhysicsDebugDrawJson["character"].as<bool>() ) uf::physics::settings.debugDraw |= pod::Collider::CATEGORY_CHARACTER; if ( configEnginePhysicsDebugDrawJson["character"].as<bool>() ) uf::physics::settings.debugDraw.mask |= pod::Collider::CATEGORY_CHARACTER;
if ( configEnginePhysicsDebugDrawJson["all"].as<bool>() ) uf::physics::settings.debugDraw |= pod::Collider::CATEGORY_ALL; 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>() ) { } 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 #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& node = 0 < index && index <= graph.nodes.size() ? graph.nodes[index] : graph.root;
auto& transform = node.transform;
return return
uf::matrix::translate( uf::matrix::identity(), node.transform.position ) * uf::matrix::translate( uf::matrix::identity(), transform.position ) *
uf::quaternion::matrix(node.transform.orientation) * uf::quaternion::matrix(transform.orientation) *
uf::matrix::scale( uf::matrix::identity(), node.transform.scale ) * uf::matrix::scale( uf::matrix::identity(), transform.scale ) *
node.transform.model; 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 ); 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; int32_t parent = node.parent;
while ( 0 < parent && parent <= graph.nodes.size() ) { while ( 0 < parent && parent <= graph.nodes.size() ) {
matrix = ::localMatrix( graph, parent ) * matrix; 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 // 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 ) { uf::stl::vector<pod::OBB> uf::graph::obbFromSkin( const pod::Graph& graph, const pod::Node& node ) {
const float wThresold = 0.15f; 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 ) { for ( auto w = 0; w < 4; ++w ) {
if ( weights[w] <= wThresold ) continue; if ( weights[w] <= wThresold ) continue;
uint16_t boneID = joints[w]; uint16_t jointID = joints[w];
pod::Vector3f localPos = uf::matrix::multiply( skin.inverseBindMatrices[boneID], pos ); pod::Vector3f localPos = uf::matrix::multiply( skin.inverseBindMatrices[jointID], pos );
bounds[boneID].center = uf::vector::min( bounds[boneID].center, localPos ); bounds[jointID].center = uf::vector::min( bounds[jointID].center, localPos );
bounds[boneID].extent = uf::vector::max( bounds[boneID].extent, 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 ) { 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"); UF_DEBUG_TIMER_MULTITRACE("Updating master graph");
#if UF_GRAPH_EXTENDED #if UF_GRAPH_EXTENDED
uf::graph::reload( graph ); uf::graph::reload( graph );
@ -1406,6 +1395,15 @@ void uf::graph::initialize( pod::Graph& graph ) {
if ( entity->getUid() == 0 ) entity->initialize(); 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(); auto& scene = uf::scene::getCurrentScene();
scene.invalidateGraph(); scene.invalidateGraph();

View File

@ -13,6 +13,7 @@
#include <uf/utils/mesh/mesh.h> #include <uf/utils/mesh/mesh.h>
#include <uf/utils/string/hash.h> #include <uf/utils/string/hash.h>
#include <uf/utils/io/inputs.h> #include <uf/utils/io/inputs.h>
#include <uf/utils/debug/draw.h>
UF_BEHAVIOR_REGISTER_CPP(uf::GraphBehavior) UF_BEHAVIOR_REGISTER_CPP(uf::GraphBehavior)
UF_BEHAVIOR_TRAITS_CPP(uf::GraphBehavior, ticks = true, renders = false, thread = "") 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::destroy( uf::Object& self ) {}
void uf::GraphBehavior::tick( uf::Object& self ) { void uf::GraphBehavior::tick( uf::Object& self ) {
/* Test */ { if ( !this->hasComponent<pod::Graph>() ) return;
TIMER(1, uf::inputs::kbm::states::T ) { auto& graph = this->getComponent<pod::Graph>();
UF_MSG_DEBUG("Regenerating graph graphics..."); if ( !graph.metadata["debug"]["draw"]["armature"].as<bool>(false) ) return;
auto& graph = this->getComponent<pod::Graph>(); auto& transform = this->getComponent<pod::Transform<>>();
uf::graph::reload( graph ); 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::render( uf::Object& self ) {}
void uf::GraphBehavior::Metadata::serialize( uf::Object& self, uf::Serializer& serializer ) {} 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<> getTransform( pod::PhysicsBody& body ) {
pod::Transform<> t; pod::Transform<> t;
t.position = body.offset; t.position = body.offsetPosition;
t.orientation = body.offsetOrientation;
t.reference = body.transform; t.reference = body.transform;
return uf::transform::flatten( t ); return uf::transform::flatten( t );
} }

View File

@ -219,7 +219,7 @@ void uf::debug::draw( float dt ) {
graphic.material.device = &uf::renderer::device; graphic.material.device = &uf::renderer::device;
// to-do: bin by descriptor instead of one global set // 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.depth.write = false;
graphic.descriptor.renderTarget = 1; // "forward"; graphic.descriptor.renderTarget = 1; // "forward";
graphic.descriptor.topology = uf::renderer::enums::PrimitiveTopology::LINE_LIST; 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 // to-do: find a succinct way to explain this madness
pod::Transform<> impl::getTransform( const pod::PhysicsBody& body ) { pod::Transform<> impl::getTransform( const pod::PhysicsBody& body ) {
pod::Transform<> t; pod::Transform<> t;
t.position = body.offset; t.position = body.offsetPosition;
#if UF_PHYSICS_SYNC_TRANSFORMS t.orientation = body.offsetOrientation;
t.reference = const_cast<pod::Transform<>*>( &body.flattenedTransform );
#else
t.reference = body.transform; t.reference = body.transform;
#endif
return uf::transform::flatten( t ); return uf::transform::flatten( t );
} }
// get position of a body, uses bounds center or transform's position // 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 ); if ( !useTransform ) return impl::aabbCenter( body.bounds );
return impl::getTransform( body ).position; 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 // creates a view of a hull body
pod::PhysicsBody impl::physicsBodyHullView( const pod::PhysicsBody& body, int32_t index ) { pod::PhysicsBody impl::physicsBodyHullView( const pod::PhysicsBody& body, int32_t index ) {
pod::PhysicsBody view = body; 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 ); view.collider.triangle.normal = impl::triangleNormal( (const pod::Triangle&) triangle );
} }
// assume triangle is already transformed // assume triangle is already transformed
view.offset = {}; view.offsetPosition = {};
view.offsetOrientation = {0,0,0,1};
view.transform = NULL; view.transform = NULL;
return view; return view;
} }

View File

@ -14,8 +14,11 @@ void impl::solveBallSocketConstraint( pod::Constraint& constraint, float dt ) {
auto tA = impl::getTransform( a ); auto tA = impl::getTransform( a );
auto tB = impl::getTransform( b ); auto tB = impl::getTransform( b );
auto rA = uf::quaternion::rotate( tA.orientation, joint.localAnchorA ); auto anchorA = joint.localAnchorA * tA.scale;
auto rB = uf::quaternion::rotate( tB.orientation, joint.localAnchorB ); 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::Matrix3f K = {};
pod::Vector3f axes[3] = { {1,0,0}, {0,1,0}, {0,0,1} }; 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 ) { void impl::drawBallSocket( const pod::Constraint& constraint ) {
if ( !constraint.a || !constraint.b ) return; if ( !constraint.a || !constraint.b ) return;
auto tA = impl::getTransform(*constraint.a);
auto tB = impl::getTransform(*constraint.b);
const auto& joint = constraint.ballSocket; 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 pA = uf::transform::apply( tA, joint.localAnchorA );
auto pB = tB.position + uf::quaternion::rotate(tB.orientation, joint.localAnchorB); auto pB = uf::transform::apply( tB, joint.localAnchorB );
uf::debug::drawLine( tA.position, pA ); uf::debug::drawLine( tA.position, pA );
uf::debug::drawLine( tB.position, pB ); 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 ) { void impl::drawConeTwist( const pod::Constraint& constraint ) {
if ( !constraint.a || !constraint.b ) return; if ( !constraint.a || !constraint.b ) return;
auto tA = impl::getTransform(*constraint.a);
auto tB = impl::getTransform(*constraint.b);
const auto& joint = constraint.coneTwist; 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 pA = uf::transform::apply( tA, joint.localAnchorA );
auto pB = tB.position + uf::quaternion::rotate(tB.orientation, joint.localAnchorB); auto pB = uf::transform::apply( tB, joint.localAnchorB );
auto taA = uf::quaternion::rotate(tA.orientation, joint.localTwistAxisA); auto taA = uf::quaternion::rotate( tA.orientation, joint.localTwistAxisA );
auto taB = uf::quaternion::rotate(tB.orientation, joint.localTwistAxisB); auto taB = uf::quaternion::rotate( tB.orientation, joint.localTwistAxisB );
auto raA = uf::quaternion::rotate(tA.orientation, joint.localReferenceAxisA); auto raA = uf::quaternion::rotate( tA.orientation, joint.localReferenceAxisA );
auto raB = uf::quaternion::rotate(tB.orientation, joint.localReferenceAxisB); 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; float axisLength = 0.5f;
uf::debug::drawLine( pA, pA + taA * axisLength ); uf::debug::drawLine( pA, pA + taA * axisLength, pod::Vector4f{ 1, 0, 1, 1 } ); // pink
uf::debug::drawLine( pB, pB + taB * axisLength ); uf::debug::drawLine( pB, pB + taB * axisLength, pod::Vector4f{ 1, 0, 1, 1 } );
float refLength = 0.25f; float refLength = 0.25f;
uf::debug::drawLine( pA, pA + raA * refLength ); uf::debug::drawLine( pA, pA + raA * refLength, pod::Vector4f{ 0, 1, 1, 1 } ); // cyan
uf::debug::drawLine( pB, pB + raB * refLength ); 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 tA = impl::getTransform( a );
auto tB = impl::getTransform( b ); auto tB = impl::getTransform( b );
auto rA = uf::quaternion::rotate( tA.orientation, joint.localAnchorA ); auto anchorA = joint.localAnchorA * tA.scale;
auto rB = uf::quaternion::rotate( tB.orientation, joint.localAnchorB ); 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 pA = tA.position + rA;
auto pB = tB.position + rB; 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 ) { void impl::drawDistance( const pod::Constraint& constraint ) {
if ( !constraint.a || !constraint.b ) return; if ( !constraint.a || !constraint.b ) return;
auto tA = impl::getTransform(*constraint.a);
auto tB = impl::getTransform(*constraint.b);
const auto& joint = constraint.distance; 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 pA = uf::transform::apply( tA, joint.localAnchorA );
auto pB = tB.position + uf::quaternion::rotate(tB.orientation, joint.localAnchorB); auto pB = uf::transform::apply( tB, joint.localAnchorB );
uf::debug::drawLine( tA.position, pA ); uf::debug::drawLine( tA.position, pA );
uf::debug::drawLine( tB.position, pB ); 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 ) { void impl::drawHinge( const pod::Constraint& constraint ) {
if ( !constraint.a || !constraint.b ) return; if ( !constraint.a || !constraint.b ) return;
auto tA = impl::getTransform(*constraint.a);
auto tB = impl::getTransform(*constraint.b);
const auto& joint = constraint.hinge; 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 pA = uf::transform::apply( tA, joint.localAnchorA );
auto pB = tB.position + uf::quaternion::rotate(tB.orientation, joint.localAnchorB); auto pB = uf::transform::apply( tB, joint.localAnchorB );
auto aA = uf::quaternion::rotate(tA.orientation, joint.localAxisA); auto aA = uf::quaternion::rotate( tA.orientation, joint.localAxisA );
auto aB = uf::quaternion::rotate(tB.orientation, joint.localAxisB); auto aB = uf::quaternion::rotate( tB.orientation, joint.localAxisB );
uf::debug::drawLine( pA, pB ); 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 tA = impl::getTransform( a );
auto tB = impl::getTransform( b ); auto tB = impl::getTransform( b );
auto rA = uf::quaternion::rotate( tA.orientation, joint.localAnchorA ); auto anchorA = joint.localAnchorA * tA.scale;
auto rB = uf::quaternion::rotate( tB.orientation, joint.localAnchorB ); 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 pA = tA.position + rA;
auto pB = tB.position + rB; 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 tA = impl::getTransform( a );
auto tB = impl::getTransform( b ); auto tB = impl::getTransform( b );
auto rA = uf::quaternion::rotate( tA.orientation, joint.localAnchorA ); auto anchorA = joint.localAnchorA * tA.scale;
auto rB = uf::quaternion::rotate( tB.orientation, joint.localAnchorB ); 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 worldAnchorA = tA.position + rA;
auto worldAnchorB = tB.position + rB; auto worldAnchorB = tB.position + rB;

View File

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

View File

@ -66,7 +66,7 @@ void uf::physics::tick( pod::World& world, float dt ) {
accumulator -= timestep; 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() { void uf::physics::terminate() {
uf::physics::terminate( uf::scene::getCurrentScene() ); uf::physics::terminate( uf::scene::getCurrentScene() );
@ -241,7 +241,7 @@ void uf::physics::step( pod::World& world, float dt ) {
// dispatch collision events // dispatch collision events
impl::dispatchManifold( manifold, collisionEvents, activeCollisions, previousCollisions ); impl::dispatchManifold( manifold, collisionEvents, activeCollisions, previousCollisions );
// draw collision events // draw collision events
if ( uf::physics::settings.debugDraw ) impl::drawManifold( manifold ); if ( uf::physics::settings.debugDraw.contacts ) impl::drawManifold( manifold );
} }
// dispatch exiting collisions // dispatch exiting collisions
for ( auto& [ key, pair ] : previousCollisions ) { for ( auto& [ key, pair ] : previousCollisions ) {
@ -534,7 +534,7 @@ pod::CollisionEvent::events_t uf::physics::getCollisionEvents( const pod::Physic
return res; return res;
} }
// body creation // 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& root = object.getComponent<pod::PhysicsBody>();
auto isRoot = !root.world; auto isRoot = !root.world;
auto& body = isRoot ? root : *(new pod::PhysicsBody); 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.world = &world;
body.object = &object; body.object = &object;
body.transform = &object.getComponent<pod::Transform<>>(); body.transform = &object.getComponent<pod::Transform<>>();
#if UF_PHYSICS_SYNC_TRANSFORMS body.offsetPosition = positionOffset;
body.flattenedTransform = uf::transform::flatten( *body.transform ); body.offsetOrientation = orientationOffset;
#endif
body.offset = offset;
body.inverseMass = mass == 0.0f ? 0.0f : 1.0f / mass; body.inverseMass = mass == 0.0f ? 0.0f : 1.0f / mass;
body.collider.type = {}; body.collider.type = {};
@ -578,8 +576,8 @@ pod::PhysicsBody& uf::physics::create( pod::World& world, uf::Object& object, fl
return body; return body;
} }
pod::PhysicsBody& uf::physics::create( uf::Object& object, float mass, const pod::Vector3f& offset ) { pod::PhysicsBody& uf::physics::create( uf::Object& object, float mass, const pod::Vector3f& positionOffset, const pod::Quaternion<>& orientationOffset ) {
return create( getWorld(), object, mass, offset ); return create( getWorld(), object, mass, positionOffset, orientationOffset );
} }
void uf::physics::destroy( uf::Object& object ) { 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 ) { 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; if ( !uf::vector::isValid(contact.point) ) return false;
auto& normal = contact.normal; auto normal = contact.normal;
pod::Vector3f polyA[4]; pod::Vector3f polyA[4];
pod::Vector3f polyB[4]; pod::Vector3f polyB[4];
@ -192,18 +192,31 @@ bool impl::generateClippingManifold( const pod::PhysicsBody& a, const pod::Physi
return true; return true;
} }
// to-do: reference face is the most perpendicular face pod::Vector3f normalA = uf::vector::normalize(uf::vector::cross(polyA[1] - polyA[0], polyA[2] - polyA[0]));
pod::Vector3f* refPoly = polyA; pod::Vector3f normalB = uf::vector::normalize(uf::vector::cross(polyB[1] - polyB[0], polyB[2] - polyB[0]));
pod::Vector3f* incPoly = polyB;
int refCount = countA; float dotA = uf::vector::dot(normalA, normal);
int incCount = countB; 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]; pod::Vector3f clipBuffer[8];
int clipCount = incCount; int clipCount = incCount;
for ( auto i = 0; i < incCount; ++i ) clipBuffer[i] = incPoly[i]; 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 ) { for ( auto i = 0; i < refCount; ++i ) {
pod::Vector3f edgeStart = refPoly[i]; pod::Vector3f edgeStart = refPoly[i];
pod::Vector3f edgeEnd = refPoly[(i + 1) % refCount]; 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; return rayHit;
} }