Refactoring the 'feature' setters and getters into a single method. Renaming a few things to be more consistent. Adding Documentation

This commit is contained in:
TheComet 2017-07-28 18:36:57 +02:00
parent 4a6d86804e
commit 4f96c7c29f
12 changed files with 384 additions and 335 deletions

View File

@ -144,15 +144,12 @@ void InverseKinematics::CreateScene()
solver_->SetAlgorithm(IKSolver::TWO_BONE);
// Disable auto-solving, which means we need to call Solve() manually
solver_->EnableAutoSolve(false);
solver_->SetFeature(IKSolver::AUTO_SOLVE, false);
// When this is enabled, the solver will use the current positions of the
// nodes in the skeleton as its basis every frame. If you disable this, then
// the solver will store the initial positions of the nodes once and always
// use those positions for calculating solutions.
// With animated characters you generally want to continuously update the
// initial positions.
solver_->EnableAutoUpdateInitialPose(true);
// Only enable this so the debug draw shows us the pose before solving.
// This should NOT be enabled for any other reason (it does nothing and is
// a waste of performance).
solver_->SetFeature(IKSolver::UPDATE_ORIGINAL_POSE, true);
// Create the camera.
cameraRotateNode_ = scene_->CreateChild("CameraRotate");

View File

@ -46,7 +46,7 @@ struct ik_node_t
* be set and retrieved at any time.
* @note The default value is (0, 0, 0).
*/
vec3_t initial_position;
vec3_t original_position;
/*!
* @brief The initial global rotation (in world space).

View File

@ -185,7 +185,7 @@ ik_solver_iterate_tree(ik_solver_t* solver,
* positions and rotations for every node in the tree.
*/
IK_PUBLIC_API void
ik_solver_reset_solved_data(ik_solver_t* solver);
ik_solver_reset_to_initial_pose(ik_solver_t* solver);
C_HEADER_END

View File

@ -363,8 +363,8 @@ calculate_segment_lengths_in_island(chain_t* island)
ik_node_t* parent_node =
*(ik_node_t**)ordered_vector_get_element(&island->nodes, last_idx + 1);
vec3_t diff = child_node->initial_position;
vec3_sub_vec3(diff.f, parent_node->initial_position.f);
vec3_t diff = child_node->original_position;
vec3_sub_vec3(diff.f, parent_node->original_position.f);
child_node->segment_length = vec3_length(diff.f);
}
@ -441,9 +441,9 @@ calculate_delta_rotation_of_each_segment(chain_t* chain)
ik_node_t* parent_node = *(ik_node_t**)ordered_vector_get_element(&chain->nodes, node_idx + 1);
/* calculate vectors for original and solved segments */
vec3_t segment_original = child_node->initial_position;
vec3_t segment_original = child_node->original_position;
vec3_t segment_solved = child_node->position;
vec3_sub_vec3(segment_original.f, parent_node->initial_position.f);
vec3_sub_vec3(segment_original.f, parent_node->original_position.f);
vec3_sub_vec3(segment_solved.f, parent_node->position.f);
vec3_angle(parent_node->rotation.f, segment_original.f, segment_solved.f);

View File

@ -229,7 +229,7 @@ ik_solver_iterate_tree(ik_solver_t* solver,
static void
reset_solved_data_recursive(ik_node_t* node)
{
node->position = node->initial_position;
node->position = node->original_position;
node->rotation = node->initial_rotation;
BSTV_FOR_EACH(&node->children, ik_node_t, guid, child)
@ -237,7 +237,7 @@ reset_solved_data_recursive(ik_node_t* node)
BSTV_END_EACH
}
void
ik_solver_reset_solved_data(ik_solver_t* solver)
ik_solver_reset_to_initial_pose(ik_solver_t* solver)
{
if (solver->tree == NULL)
return;

View File

@ -59,9 +59,9 @@ determine_target_data_from_effector(chain_t* chain, vec3_t* target_position)
/* lerp using effector weight to get actual target position */
*target_position = effector->target_position;
vec3_sub_vec3(target_position->f, effector_node->initial_position.f);
vec3_sub_vec3(target_position->f, effector_node->original_position.f);
vec3_mul_scalar(target_position->f, effector->weight);
vec3_add_vec3(target_position->f, effector_node->initial_position.f);
vec3_add_vec3(target_position->f, effector_node->original_position.f);
/* Fancy algorithm using nlerp, makes transitions look more natural */
if (effector->flags & EFFECTOR_WEIGHT_NLERP && effector->weight < 1.0)
@ -74,20 +74,20 @@ determine_target_data_from_effector(chain_t* chain, vec3_t* target_position)
/* Need distance from base node to target and base to effector node */
base_node = *(ik_node_t**)ordered_vector_get_element(&chain->nodes,
ordered_vector_count(&chain->nodes) - 1);
base_to_effector = effector_node->initial_position;
base_to_effector = effector_node->original_position;
base_to_target = effector->target_position;
vec3_sub_vec3(base_to_effector.f, base_node->initial_position.f);
vec3_sub_vec3(base_to_target.f, base_node->initial_position.f);
vec3_sub_vec3(base_to_effector.f, base_node->original_position.f);
vec3_sub_vec3(base_to_target.f, base_node->original_position.f);
/* The effective distance is a lerp between these two distances */
distance_to_target = vec3_length(base_to_target.f) * effector->weight;
distance_to_target += vec3_length(base_to_effector.f) * (1.0 - effector->weight);
/* nlerp the target position by pinning it to the base node */
vec3_sub_vec3(target_position->f, base_node->initial_position.f);
vec3_sub_vec3(target_position->f, base_node->original_position.f);
vec3_normalise(target_position->f);
vec3_mul_scalar(target_position->f, distance_to_target);
vec3_add_vec3(target_position->f, base_node->initial_position.f);
vec3_add_vec3(target_position->f, base_node->original_position.f);
}
}
@ -516,7 +516,7 @@ solver_FABRIK_solve(ik_solver_t* solver)
assert(ordered_vector_count(&root_chain->nodes) > 1);
root_position = (*(ik_node_t**)ordered_vector_get_element(&root_chain->nodes,
ordered_vector_count(&root_chain->nodes) - 1))->initial_position;
ordered_vector_count(&root_chain->nodes) - 1))->original_position;
if (solver->flags & SOLVER_CALCULATE_TARGET_ROTATIONS)
solve_chain_forwards_with_target_rotation(root_chain);

View File

@ -39,35 +39,33 @@ static void RegisterIKSolver(asIScriptEngine* engine)
engine->RegisterEnumValue("IKAlgorithm", "TWO_BONE", IKSolver::TWO_BONE);
engine->RegisterEnumValue("IKAlgorithm", "FABRIK", IKSolver::FABRIK);
engine->RegisterEnum("IKFeature");
engine->RegisterEnumValue("IKFeature", "JOINT_ROTATIONS", IKSolver::JOINT_ROTATIONS);
engine->RegisterEnumValue("IKFeature", "TARGET_ROTATIONS", IKSolver::TARGET_ROTATIONS);
engine->RegisterEnumValue("IKFeature", "UPDATE_ORIGINAL_POSE", IKSolver::UPDATE_ORIGINAL_POSE);
engine->RegisterEnumValue("IKFeature", "UPDATE_ACTIVE_POSE", IKSolver::UPDATE_ACTIVE_POSE);
engine->RegisterEnumValue("IKFeature", "USE_ORIGINAL_POSE", IKSolver::USE_ORIGINAL_POSE);
engine->RegisterEnumValue("IKFeature", "CONSTRAINTS", IKSolver::CONSTRAINTS);
engine->RegisterEnumValue("IKFeature", "AUTO_SOLVE", IKSolver::AUTO_SOLVE);
RegisterComponent<IKSolver>(engine, "IKSolver");
engine->RegisterObjectMethod("IKSolver", "IKAlgorithm get_algorithm() const", asMETHOD(IKSolver, GetAlgorithm), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void set_algorithm(IKAlgorithm)", asMETHOD(IKSolver, SetAlgorithm), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void SetFeature(IKFeature,bool)", asMETHOD(IKSolver, SetFeature), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "bool GetFeature(IKFeature) const", asMETHOD(IKSolver, GetFeature), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "uint get_maximumIterations() const", asMETHOD(IKSolver, GetMaximumIterations), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void set_maximumIterations(uint)", asMETHOD(IKSolver, SetMaximumIterations), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "float get_tolerance() const", asMETHOD(IKSolver, GetTolerance), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void set_tolerance(float)", asMETHOD(IKSolver, SetTolerance), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "bool get_boneRotations() const", asMETHOD(IKSolver, BoneRotationsEnabled), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void set_boneRotations(bool)", asMETHOD(IKSolver, EnableBoneRotations), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "bool get_constraints() const", asMETHOD(IKSolver, ConstraintsEnabled), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void set_constraints(bool)", asMETHOD(IKSolver, EnableConstraints), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "bool get_targetRotation() const", asMETHOD(IKSolver, TargetRotationEnabled), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void set_targetRotation(bool)", asMETHOD(IKSolver, EnableTargetRotation), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "bool get_continuousSolving() const", asMETHOD(IKSolver, ContinuousSolvingEnabled), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void set_continuousSolving(bool)", asMETHOD(IKSolver, EnableContinuousSolving), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "bool get_autoUpdateInitialPose() const", asMETHOD(IKSolver, AutoUpdateInitialPoseEnabled), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void set_autoUpdateInitialPose(bool)", asMETHOD(IKSolver, EnableAutoUpdateInitialPose), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "bool get_autoSolve() const", asMETHOD(IKSolver, AutoSolveEnabled), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void set_autoSolve(bool)", asMETHOD(IKSolver, EnableAutoSolve), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void RebuildData()", asMETHOD(IKSolver, RebuildData), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void RecalculateSegmentLengths()", asMETHOD(IKSolver, RecalculateSegmentLengths), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void CalculateJointRotations()", asMETHOD(IKSolver, CalculateJointRotations), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void Solve()", asMETHOD(IKSolver, Solve), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void ApplyInitialPoseToScene()", asMETHOD(IKSolver, ApplyInitialPoseToScene), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void ApplySceneToInitialPose()", asMETHOD(IKSolver, ApplySceneToInitialPose), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void ApplySolvedPoseToScene()", asMETHOD(IKSolver, ApplySolvedPoseToScene), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void ApplySceneToSolvedPose()", asMETHOD(IKSolver, ApplySceneToSolvedPose), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void ApplyInitialPoseToSolvedPose()", asMETHOD(IKSolver, ApplyInitialPoseToSolvedPose), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void ApplyConstraints(Node@+)", asMETHODPR(IKSolver, ApplyConstraints, (Node*), void), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void ApplyOriginalPoseToScene()", asMETHOD(IKSolver, ApplyOriginalPoseToScene), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void ApplySceneToInitialPose()", asMETHOD(IKSolver, ApplySceneToOriginalPose), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void ApplyActivePoseToScene()", asMETHOD(IKSolver, ApplyActivePoseToScene), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void ApplySceneToActivePose()", asMETHOD(IKSolver, ApplySceneToActivePose), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void ApplyOriginalPoseToActivePose()", asMETHOD(IKSolver, ApplyOriginalPoseToActivePose), asCALL_THISCALL);
engine->RegisterObjectMethod("IKSolver", "void DrawDebugGeometry(bool)", asMETHODPR(IKSolver, DrawDebugGeometry, (bool), void), asCALL_THISCALL);
}

View File

@ -74,11 +74,8 @@ IKSolver::IKSolver(Context* context) :
Component(context),
solver_(NULL),
algorithm_(FABRIK),
boneRotationsEnabled_(true),
solverTreeNeedsRebuild_(false),
continuousSolvingEnabled_(false),
updateInitialPose_(false),
autoSolveEnabled_(true)
features_(AUTO_SOLVE | JOINT_ROTATIONS | UPDATE_ACTIVE_POSE),
solverTreeNeedsRebuild_(false)
{
context_->RequireIK();
@ -121,12 +118,13 @@ void IKSolver::RegisterObject(Context* context)
URHO3D_ENUM_ACCESSOR_ATTRIBUTE("Algorithm", GetAlgorithm, SetAlgorithm, Algorithm, algorithmNames, FABRIK, AM_DEFAULT);
URHO3D_ACCESSOR_ATTRIBUTE("Max Iterations", GetMaximumIterations, SetMaximumIterations, unsigned, 20, AM_DEFAULT);
URHO3D_ACCESSOR_ATTRIBUTE("Convergence Tolerance", GetTolerance, SetTolerance, float, 0.001, AM_DEFAULT);
URHO3D_ACCESSOR_ATTRIBUTE("Bone Rotations", BoneRotationsEnabled, EnableBoneRotations, bool, true, AM_DEFAULT);
URHO3D_ACCESSOR_ATTRIBUTE("Target Rotation", TargetRotationEnabled, EnableTargetRotation, bool, false, AM_DEFAULT);
URHO3D_ACCESSOR_ATTRIBUTE("Enable Constraints", ConstraintsEnabled, EnableConstraints, bool, false, AM_DEFAULT);
URHO3D_ACCESSOR_ATTRIBUTE("Continuous Solving", ContinuousSolvingEnabled, EnableContinuousSolving, bool, false, AM_DEFAULT);
URHO3D_ACCESSOR_ATTRIBUTE("Update Initial Pose", AutoUpdateInitialPoseEnabled, EnableAutoUpdateInitialPose, bool, false, AM_DEFAULT);
URHO3D_ACCESSOR_ATTRIBUTE("Auto Solve", AutoSolveEnabled, EnableAutoSolve, bool, true, AM_DEFAULT);
URHO3D_ACCESSOR_ATTRIBUTE("Joint Rotations", GetFeature_JOINT_ROTATIONS, SetFeature_JOINT_ROTATIONS, bool, true, AM_DEFAULT);
URHO3D_ACCESSOR_ATTRIBUTE("Target Rotations", GetFeature_TARGET_ROTATIONS, SetFeature_TARGET_ROTATIONS, bool, false, AM_DEFAULT);
URHO3D_ACCESSOR_ATTRIBUTE("Update Original Pose", GetFeature_UPDATE_ORIGINAL_POSE, SetFeature_UPDATE_ORIGINAL_POSE, bool, false, AM_DEFAULT);
URHO3D_ACCESSOR_ATTRIBUTE("Update Active Pose", GetFeature_UPDATE_ACTIVE_POSE, SetFeature_UPDATE_ACTIVE_POSE, bool, true, AM_DEFAULT);
URHO3D_ACCESSOR_ATTRIBUTE("Use Original Pose", GetFeature_USE_ORIGINAL_POSE, SetFeature_USE_ORIGINAL_POSE, bool, false, AM_DEFAULT);
URHO3D_ACCESSOR_ATTRIBUTE("Enable Constraints", GetFeature_CONSTRAINTS, SetFeature_CONSTRAINTS, bool, false, AM_DEFAULT);
URHO3D_ACCESSOR_ATTRIBUTE("Auto Solve", GetFeature_AUTO_SOLVE, SetFeature_AUTO_SOLVE, bool, true, AM_DEFAULT);
}
// ----------------------------------------------------------------------------
@ -143,7 +141,7 @@ void IKSolver::SetAlgorithm(IKSolver::Algorithm algorithm)
/* We need to rebuild the tree so make sure that the scene is in the
* initial pose when this occurs.*/
if (node_ != NULL)
ApplyInitialPoseToScene();
ApplyOriginalPoseToScene();
// Initial flags for when there is no solver to destroy
uint8_t initialFlags = 0;
@ -170,6 +168,50 @@ void IKSolver::SetAlgorithm(IKSolver::Algorithm algorithm)
RebuildTree();
}
// ----------------------------------------------------------------------------
bool IKSolver::GetFeature(Feature feature) const
{
return (features_ & feature) != 0;
}
// ----------------------------------------------------------------------------
void IKSolver::SetFeature(Feature feature, bool enable)
{
switch (feature)
{
case CONSTRAINTS:
{
solver_->flags &= ~SOLVER_ENABLE_CONSTRAINTS;
if (enable)
solver_->flags |= SOLVER_ENABLE_CONSTRAINTS;
} break;
case TARGET_ROTATIONS:
{
solver_->flags &= ~SOLVER_CALCULATE_TARGET_ROTATIONS;
if (enable)
solver_->flags |= SOLVER_CALCULATE_TARGET_ROTATIONS;
} break;
case AUTO_SOLVE:
{
if (((features_ & AUTO_SOLVE) != 0) == enable)
break;
if (enable)
SubscribeToEvent(GetScene(), E_SCENEDRAWABLEUPDATEFINISHED, URHO3D_HANDLER(IKSolver, HandleSceneDrawableUpdateFinished));
else
UnsubscribeFromEvent(GetScene(), E_SCENEDRAWABLEUPDATEFINISHED);
} break;
default: break;
}
features_ &= ~feature;
if (enable)
features_ |= feature;
}
// ----------------------------------------------------------------------------
unsigned IKSolver::GetMaximumIterations() const
{
@ -196,96 +238,6 @@ void IKSolver::SetTolerance(float tolerance)
solver_->tolerance = tolerance;
}
// ----------------------------------------------------------------------------
bool IKSolver::BoneRotationsEnabled() const
{
return boneRotationsEnabled_;
}
// ----------------------------------------------------------------------------
void IKSolver::EnableBoneRotations(bool enable)
{
boneRotationsEnabled_ = enable;
}
// ----------------------------------------------------------------------------
bool IKSolver::ConstraintsEnabled() const
{
return (solver_->flags & SOLVER_ENABLE_CONSTRAINTS);
}
// ----------------------------------------------------------------------------
void IKSolver::EnableConstraints(bool enable)
{
solver_->flags &= ~SOLVER_ENABLE_CONSTRAINTS;
if (enable)
solver_->flags |= SOLVER_ENABLE_CONSTRAINTS;
}
// ----------------------------------------------------------------------------
bool IKSolver::TargetRotationEnabled() const
{
return (solver_->flags & SOLVER_CALCULATE_TARGET_ROTATIONS) != 0;
}
// ----------------------------------------------------------------------------
void IKSolver::EnableTargetRotation(bool enable)
{
solver_->flags &= ~SOLVER_CALCULATE_TARGET_ROTATIONS;
if (enable)
solver_->flags |= SOLVER_CALCULATE_TARGET_ROTATIONS;
}
// ----------------------------------------------------------------------------
bool IKSolver::ContinuousSolvingEnabled() const
{
return continuousSolvingEnabled_;
}
// ----------------------------------------------------------------------------
void IKSolver::EnableContinuousSolving(bool enable)
{
continuousSolvingEnabled_ = enable;
}
// ----------------------------------------------------------------------------
bool IKSolver::AutoUpdateInitialPoseEnabled() const
{
return updateInitialPose_;
}
// ----------------------------------------------------------------------------
void IKSolver::EnableAutoUpdateInitialPose(bool enable)
{
updateInitialPose_ = enable;
}
// ----------------------------------------------------------------------------
void IKSolver::MarkSolverTreeDirty()
{
solverTreeNeedsRebuild_ = true;
}
// ----------------------------------------------------------------------------
bool IKSolver::AutoSolveEnabled() const
{
return autoSolveEnabled_;
}
// ----------------------------------------------------------------------------
void IKSolver::EnableAutoSolve(bool enable)
{
if (autoSolveEnabled_ == enable)
return;
if (enable)
SubscribeToEvent(GetScene(), E_SCENEDRAWABLEUPDATEFINISHED, URHO3D_HANDLER(IKSolver, HandleSceneDrawableUpdateFinished));
else
UnsubscribeFromEvent(GetScene(), E_SCENEDRAWABLEUPDATEFINISHED);
autoSolveEnabled_ = enable;
}
// ----------------------------------------------------------------------------
void IKSolver::RebuildData()
{
@ -316,11 +268,14 @@ void IKSolver::Solve()
solverTreeNeedsRebuild_ = false;
}
if (updateInitialPose_)
ApplySceneToInitialPose();
if (features_ & UPDATE_ORIGINAL_POSE)
ApplySceneToOriginalPose();
if (continuousSolvingEnabled_ == false)
ApplyInitialPoseToSolvedPose();
if (features_ & UPDATE_ACTIVE_POSE)
ApplySceneToActivePose();
if (features_ & USE_ORIGINAL_POSE)
ApplyOriginalPoseToActivePose();
for (PODVector<IKEffector*>::ConstIterator it = effectorList_.Begin(); it != effectorList_.End(); ++it)
{
@ -329,10 +284,10 @@ void IKSolver::Solve()
ik_solver_solve(solver_);
if (boneRotationsEnabled_)
if (features_ & JOINT_ROTATIONS)
ik_solver_calculate_joint_rotations(solver_);
ApplySolvedPoseToScene();
ApplyActivePoseToScene();
}
// ----------------------------------------------------------------------------
@ -340,9 +295,9 @@ static void ApplyInitialPoseToSceneCallback(ik_node_t* ikNode)
{
Node* node = (Node*)ikNode->user_data;
node->SetWorldRotation(QuatIK2Urho(&ikNode->initial_rotation));
node->SetWorldPosition(Vec3IK2Urho(&ikNode->initial_position));
node->SetWorldPosition(Vec3IK2Urho(&ikNode->original_position));
}
void IKSolver::ApplyInitialPoseToScene()
void IKSolver::ApplyOriginalPoseToScene()
{
ik_solver_iterate_tree(solver_, ApplyInitialPoseToSceneCallback);
}
@ -352,9 +307,9 @@ static void ApplySceneToInitialPoseCallback(ik_node_t* ikNode)
{
Node* node = (Node*)ikNode->user_data;
ikNode->initial_rotation = QuatUrho2IK(node->GetWorldRotation());
ikNode->initial_position = Vec3Urho2IK(node->GetWorldPosition());
ikNode->original_position = Vec3Urho2IK(node->GetWorldPosition());
}
void IKSolver::ApplySceneToInitialPose()
void IKSolver::ApplySceneToOriginalPose()
{
ik_solver_iterate_tree(solver_, ApplySceneToInitialPoseCallback);
}
@ -366,7 +321,7 @@ static void ApplySolvedPoseToSceneCallback(ik_node_t* ikNode)
node->SetWorldRotation(QuatIK2Urho(&ikNode->rotation));
node->SetWorldPosition(Vec3IK2Urho(&ikNode->position));
}
void IKSolver::ApplySolvedPoseToScene()
void IKSolver::ApplyActivePoseToScene()
{
ik_solver_iterate_tree(solver_, ApplySolvedPoseToSceneCallback);
}
@ -378,15 +333,21 @@ static void ApplySceneToSolvedPoseCallback(ik_node_t* ikNode)
ikNode->rotation = QuatUrho2IK(node->GetWorldRotation());
ikNode->position = Vec3Urho2IK(node->GetWorldPosition());
}
void IKSolver::ApplySceneToSolvedPose()
void IKSolver::ApplySceneToActivePose()
{
ik_solver_iterate_tree(solver_, ApplySceneToSolvedPoseCallback);
}
// ----------------------------------------------------------------------------
void IKSolver::ApplyInitialPoseToSolvedPose()
void IKSolver::ApplyOriginalPoseToActivePose()
{
ik_solver_reset_solved_data(solver_);
ik_solver_reset_to_initial_pose(solver_);
}
// ----------------------------------------------------------------------------
void IKSolver::MarkSolverTreeDirty()
{
solverTreeNeedsRebuild_ = true;
}
// ----------------------------------------------------------------------------
@ -404,14 +365,14 @@ void IKSolver::ApplyInitialPoseToSolvedPose()
// ----------------------------------------------------------------------------
void IKSolver::OnSceneSet(Scene* scene)
{
if (autoSolveEnabled_)
if (features_ & AUTO_SOLVE)
SubscribeToEvent(scene, E_SCENEDRAWABLEUPDATEFINISHED, URHO3D_HANDLER(IKSolver, HandleSceneDrawableUpdateFinished));
}
// ----------------------------------------------------------------------------
void IKSolver::OnNodeSet(Node* node)
{
ApplyInitialPoseToScene();
ApplyOriginalPoseToScene();
DestroyTree();
if (node != NULL)
@ -424,7 +385,7 @@ ik_node_t* IKSolver::CreateIKNode(const Node* node)
ik_node_t* ikNode = ik_node_create(node->GetID());
// Set initial position/rotation and pass in Node* as user data for later
ikNode->initial_position = Vec3Urho2IK(node->GetWorldPosition());
ikNode->original_position = Vec3Urho2IK(node->GetWorldPosition());
ikNode->initial_rotation = QuatUrho2IK(node->GetWorldRotation());
ikNode->user_data = (void*)node;
@ -549,7 +510,7 @@ void IKSolver::HandleComponentRemoved(StringHash eventType, VariantMap& eventDat
effector->SetIKEffector(NULL);
effectorList_.RemoveSwap(effector);
ApplyInitialPoseToScene();
ApplyOriginalPoseToScene();
MarkSolverTreeDirty();
}
@ -635,17 +596,6 @@ void IKSolver::HandleSceneDrawableUpdateFinished(StringHash eventType, VariantMa
Solve();
}
// ----------------------------------------------------------------------------
void IKSolver::ApplyConstraints(Node* tree)
{
for (PODVector<IKConstraint*>::ConstIterator it = constraintList_.Begin(); it != constraintList_.End(); ++it)
{
IKConstraint* constraint = *it;
Node* node = constraint->GetNode();
node->SetRotation(Quaternion::IDENTITY);
}
}
// ----------------------------------------------------------------------------
void IKSolver::DrawDebugGeometry(bool depthTest)
{
@ -673,8 +623,8 @@ void IKSolver::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
unsigned numberOfSegments = 0;
while (b && chainLength-- != 0)
{
vec3_t v = a->initial_position;
vec3_sub_vec3(v.f, b->initial_position.f);
vec3_t v = a->original_position;
vec3_sub_vec3(v.f, b->original_position.f);
averageLength += vec3_length(v.f);
++numberOfSegments;
a = b;
@ -687,7 +637,7 @@ void IKSolver::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
a = *pnode;
b = a->parent;
debug->AddSphere(
Sphere(Vec3IK2Urho(&a->initial_position), averageLength * 0.1f),
Sphere(Vec3IK2Urho(&a->original_position), averageLength * 0.1f),
Color(0, 0, 255),
depthTest
);
@ -699,13 +649,13 @@ void IKSolver::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
while (b && chainLength-- != 0)
{
debug->AddLine(
Vec3IK2Urho(&a->initial_position),
Vec3IK2Urho(&b->initial_position),
Vec3IK2Urho(&a->original_position),
Vec3IK2Urho(&b->original_position),
Color(0, 255, 255),
depthTest
);
debug->AddSphere(
Sphere(Vec3IK2Urho(&b->initial_position), averageLength * 0.1f),
Sphere(Vec3IK2Urho(&b->original_position), averageLength * 0.1f),
Color(0, 0, 255),
depthTest
);
@ -726,4 +676,67 @@ void IKSolver::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
ORDERED_VECTOR_END_EACH
}
// ----------------------------------------------------------------------------
// Need these wrapper functions flags of GetFeature/SetFeature can be correctly
// exposed to the editor
// ----------------------------------------------------------------------------
bool IKSolver::GetFeature_JOINT_ROTATIONS() const
{
return (features_ & JOINT_ROTATIONS);
}
bool IKSolver::GetFeature_TARGET_ROTATIONS() const
{
return (features_ & TARGET_ROTATIONS);
}
bool IKSolver::GetFeature_UPDATE_ORIGINAL_POSE() const
{
return (features_ & UPDATE_ORIGINAL_POSE);
}
bool IKSolver::GetFeature_UPDATE_ACTIVE_POSE() const
{
return (features_ & UPDATE_ACTIVE_POSE);
}
bool IKSolver::GetFeature_USE_ORIGINAL_POSE() const
{
return (features_ & USE_ORIGINAL_POSE);
}
bool IKSolver::GetFeature_CONSTRAINTS() const
{
return (features_ & CONSTRAINTS);
}
bool IKSolver::GetFeature_AUTO_SOLVE() const
{
return (features_ & AUTO_SOLVE);
}
void IKSolver::SetFeature_JOINT_ROTATIONS(bool enable)
{
SetFeature(JOINT_ROTATIONS, enable);
}
void IKSolver::SetFeature_TARGET_ROTATIONS(bool enable)
{
SetFeature(TARGET_ROTATIONS, enable);
}
void IKSolver::SetFeature_UPDATE_ORIGINAL_POSE(bool enable)
{
SetFeature(UPDATE_ORIGINAL_POSE, enable);
}
void IKSolver::SetFeature_UPDATE_ACTIVE_POSE(bool enable)
{
SetFeature(UPDATE_ACTIVE_POSE, enable);
}
void IKSolver::SetFeature_USE_ORIGINAL_POSE(bool enable)
{
SetFeature(USE_ORIGINAL_POSE, enable);
}
void IKSolver::SetFeature_CONSTRAINTS(bool enable)
{
SetFeature(CONSTRAINTS, enable);
}
void IKSolver::SetFeature_AUTO_SOLVE(bool enable)
{
SetFeature(AUTO_SOLVE, enable);
}
} // namespace Urho3D

View File

@ -47,7 +47,7 @@ public:
enum Algorithm
{
ONE_BONE,
ONE_BONE = 0,
TWO_BONE,
FABRIK
/* not implemented yet
@ -56,6 +56,115 @@ public:
JACOBIAN_TRANSPOSE*/
};
enum Feature
{
/*!
* @brief Should be enabled if your model uses skinning or if you are
* generally interested in correct joint rotations. Has a minor
* performance impact.
*
* When enabled, final joint rotations are calculated as a post
* processing step. If you are using IK on a model with skinning, you will
* want to enable this or it will look wrong. If you disable this, then
* you will get a slight performance boost (less calculations are required)
* but only the node positions are updated. This can be useful for scene
* IK (perhaps a chain of platforms, where each platform should retain its
* initial world rotation?)
*/
JOINT_ROTATIONS = 0x01,
/*!
* @brief When enabled, the effector will try to match the target's
* rotation as well as the effectors position. When disabled, the target
* node will reach the effector with any rotation necessary.
*
* If the target position goes out of range of the effector then the
* rotation will no longer be matched. The chain will try to reach out to
* reach the target position, even if it means rotating towards it.
*/
TARGET_ROTATIONS = 0x02,
/*!
* When the solver is first initialized, it will copy the positions
* and rotations of the current Urho3D scene graph into an internal
* structure. This is referred to as the "original pose" and will by
* default never change for the duration of the solver's life cycle.
* When the solver is destroyed, the original pose is applied back to
* Urho3D's scene graph so the nodes are restored to whatever they were
* before the solver was created.
*
* By enabling UPDATE_ORIGINAL_POSE, the original pose will be updated
* right before solving to reflect the current Urho3D scene graph. As
* a consequence, there will no longer be an original pose to restore
* when the solver is destroyed.
*
* When disabled, the original pose will remain unmodified. The original
* pose is set when the solver is first created. You can manually update the
* original pose at any time by calling UpdateInitialPose().
*/
UPDATE_ORIGINAL_POSE = 0x04,
/*!
* @brief Should be enabled if you are using IK on an animated model,
* along with disabling USE_ORIGINAL_POSE.
*
* The "active pose" has two purposes: The solver uses it as the
* initial tree to derive a solution from, and at the same time uses it
* to store the solution into. Thus, the typical solving process is:
* 1) The active pose needs to be updated to reflect a preferred
* initial condition (such as the current frame of animation)
* 2) Call Solve()
* 3) The active pose now holds the solution, so it must be applied
* back to the Urho3D scene graph.
*
* When enabled, the active pose is updated right before solving to
* reflect the current state of the Urho3D scene graph.
*
* When disabled, the active pose will simply remain as it was since
* the last time Solve() was called.
*
* @note This option conflicts with USE_ORIGINAL_POSE. Make sure to
* disable USE_ORIGINAL_POSE if you enable this feature.
*/
UPDATE_ACTIVE_POSE = 0x08,
/*!
* @brief Choose between using the original pose or the active pose as
* a basis for a solution.
*
* When enabled, the solver will copy the original pose
* (see UPDATE_ORIGINAL_POSE) into the active pose before solving (and
* thus use the original pose as a basis for a solution).
*
* @note This option conflicts with UPDATE_ACTIVE_POSE. If you enable
* this feature, make sure to disable UPDATE_ACTIVE_POSE.
*
* If both UPDATE_ACTIVE_POSE and USE_ORIGINAL_POSE are disabled, then
* the solver will use the previously solved tree as a basis for the new
* calculation. The result is a more "continuous" solution that unfolds
* over time. This can be useful if you want to simulate chains or
* something similar.
*/
USE_ORIGINAL_POSE = 0x10,
/*!
* Due to the somewhat unfortunate performance impacts, the solver
* does not enable constraints by default. Enabling constraints causes
* the solver's tree to be written to and from Urho3D's scene graph every
* iteration, while calling ApplyConstraints(). Disabling constraints means
* ApplyConstraints() is never called.
*/
CONSTRAINTS = 0x20,
/*!
* Mostly exists because of the editor. When enabled, the solver
* will be invoked automatically for you. If you need to do additional
* calculations before being able to set the effector target data, you will
* want to disable this and call Solve() manually.
*/
AUTO_SOLVE = 0x40
};
/// Construct an IK root component.
IKSolver(Context* context);
/// Default destructor.
@ -67,16 +176,25 @@ public:
Algorithm GetAlgorithm() const;
/*!
* @brief Selects the solver algorithm. Default is FABRIK.
* @brief Selects the solver algorithm. Default is FABRIK. Note that this
* may not be the most efficient algorithm available. The specialized
* solvers will be a lot faster.
*
* The currently supported solvers are listed below.
* + **FABRIK**: This is a fairly new and highly efficient inverse
* kinematic solving algorithm. It requires the least iterations to
* reach its goal, it does not suffer from singularities (nearly no
* violent snapping about), and it always converges.
* + **2 Bone**: A specialized solver optimized for 2 bone problems (such
* as a human leg)
* + **1 Bone**: A specialized solver optimized for 1 bone problems (such
* as a look-at target, e.g. eyes or a head)
*/
void SetAlgorithm(Algorithm algorithm);
bool GetFeature(Feature feature) const;
void SetFeature(Feature feature, bool enable);
/// Returns the configured maximum number of iterations.
unsigned GetMaximumIterations() const;
@ -114,107 +232,38 @@ public:
*/
void SetTolerance(float tolerance);
/// Whether or not rotations should be calculated.
bool BoneRotationsEnabled() const;
/*!
* @brief When enabled, final joint rotations are calculated as a post
* processing step. If you are using IK on a model with skinning, you will
* want to enable this or it will look wrong. If you disable this, then
* you will get a slight performance boost (less calculations are required)
* but only the node positions are updated. This can be useful for scene
* IK (perhaps a chain of platforms, where each platform should retain its
* initial world rotation?)
* @brief Updates the solver's internal data structures, which is required
* whenever the tree is modified in any way (e.g. adding or removing nodes,
* adding or removing effectors, etc.).
* @note This gets called automatically for you in Solve().
*/
void EnableBoneRotations(bool enable);
/// Whether or not constraints have any effect on the result
bool ConstraintsEnabled() const;
/*!
* @brief Due to the somewhat unfortunate performance impacts, the solver
* does not enable constraints by default. Enabling constraints causes
* the solver's tree to be written to and from Urho3D's scene graph every
* iteration, while calling ApplyConstraints(). Disabling constraints means
* ApplyConstraints() is never called.
*/
void EnableConstraints(bool enable);
/// Whether or not target rotation is enabled
bool TargetRotationEnabled() const;
/*!
* @brief When enabled, the effector will try to match the target's
* rotation as well as the effectors position. When disabled, the target
* node will reach the effector with any rotation necessary.
*
* If the target position goes out of range of the effector then the
* rotation will no longer be matched. The chain will try to reach out to
* reach the target position, even if it means rotating towards it.
*/
void EnableTargetRotation(bool enable);
/// Whether or not continuous solving is enabled.
bool ContinuousSolvingEnabled() const;
/*!
* @brief When enabled, the solver will refrain from applying the initial
* pose before solving. The result is that it will use the previously
* solved tree as a basis for the new calculation instead of using the
* initial tree. This can be useful if you want to simulate chains or
* something similar. When disabled, the solver will use the initial
* positions/rotations which where set when the solver was first created.
*
* If you call UpdateInitialPose() then the initial tree will be matched to
* the current nodes in the scene graph.
*
* If you call ResetToInitialPose() then you will do the opposite of
* UpdateInitialPose() -- the initial pose is applied back to the scene
* graph.
*
* If you enable pose updating with EnableUpdatePose(), then the initial
* tree will automatically be matched to the current nodes in the scene
* graph.
*/
void EnableContinuousSolving(bool enable);
/// Whether or not the initial pose is updated when calling Solve()
bool AutoUpdateInitialPoseEnabled() const;
/*!
* @brief When enabled, the current Urho3D node positions and rotations in
* the scene graph will be copied into the solver's initial tree right
* before solving. This should generally be enabled for animated models
* so the solver refers to the current frame of animation rather than to
* the animation's initial pose.
*
* When disabled, the initial pose will remain unmodified. The initial pose
* is set when the solver is first created. You can manually update the
* initial pose at any time by calling UpdateInitialPose().
*/
void EnableAutoUpdateInitialPose(bool enable);
/// Whether or not the solver should be invoked automatically
bool AutoSolveEnabled() const;
/*!
* @brief Mostly exists because of the editor. When enabled, the solver
* will be invoked automatically for you. If you need to do additional
* calculations before being able to set the effector target data, you will
* want to disable this and call Solve() manually.
*/
void EnableAutoSolve(bool enable);
void RebuildData();
/*!
* @brief Unusual, but if you have a tree with translational motions such
* that the distances between nodes changes (perhaps a slider?), you can
* call this to recalculate the segment lengths after assigning new
* positions to the nodes.
* @note This function gets called by RebuildData() and by extension in
* Solve().
*/
void RecalculateSegmentLengths();
/*!
* @brief Skinned models require joint rotations to be calculated so
* skinning works correctly. This is automatically enabled by default with
* the feature flag JOINT_ROTATIONS.
*/
void CalculateJointRotations();
/*!
* @brief Invokes the solver. The solution is applied back to the scene
* graph automatically.
* @note You will want to register to E_SCENEDRAWABLEUPDATEFINISHED and
* call this method there. This is right after the animations have been
* applied.
* @note By default this is called automatically for you if the feature
* flag AUTO_SOLVE is set. For more complex IK problems you can disable
* that flag and call Solve() in response to E_SCENEDRAWABLEUPDATEFINISHED.
* This is right after the animations have been applied.
*/
void Solve();
@ -222,7 +271,7 @@ public:
* @brief Causes the initial tree to be applied back to Urho3D's scene
* graph. This is what gets called when continuous solving is disabled.
*/
void ApplyInitialPoseToScene();
void ApplyOriginalPoseToScene();
/*!
* @brief Causes the current scene graph data to be copied into the solvers
@ -230,13 +279,13 @@ public:
* are using IK on an animated model. If you don't update the initial pose,
* then the result will be a "continuous solution", where the solver will
* use the previously calculated tree as a basis for the new solution.
*
* @note This is
*/
void ApplySceneToInitialPose();
void ApplySolvedPoseToScene();
void ApplySceneToSolvedPose();
void ApplyInitialPoseToSolvedPose();
virtual void ApplyConstraints(Node* tree);
void ApplySceneToOriginalPose();
void ApplyActivePoseToScene();
void ApplySceneToActivePose();
void ApplyOriginalPoseToActivePose();
void DrawDebugGeometry(bool depthTest);
virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
@ -268,15 +317,29 @@ private:
/// Invokes the IK solver
void HandleSceneDrawableUpdateFinished(StringHash eventType, VariantMap& eventData);
/// Need these wrapper functions flags of GetFeature/SetFeature can be correctly exposed to the editor
bool GetFeature_JOINT_ROTATIONS() const;
bool GetFeature_TARGET_ROTATIONS() const;
bool GetFeature_UPDATE_ORIGINAL_POSE() const;
bool GetFeature_UPDATE_ACTIVE_POSE() const;
bool GetFeature_USE_ORIGINAL_POSE() const;
bool GetFeature_CONSTRAINTS() const;
bool GetFeature_AUTO_SOLVE() const;
void SetFeature_JOINT_ROTATIONS(bool enable);
void SetFeature_TARGET_ROTATIONS(bool enable);
void SetFeature_UPDATE_ORIGINAL_POSE(bool enable);
void SetFeature_UPDATE_ACTIVE_POSE(bool enable);
void SetFeature_USE_ORIGINAL_POSE(bool enable);
void SetFeature_CONSTRAINTS(bool enable);
void SetFeature_AUTO_SOLVE(bool enable);
PODVector<IKEffector*> effectorList_;
PODVector<IKConstraint*> constraintList_;
ik_solver_t* solver_;
Algorithm algorithm_;
bool boneRotationsEnabled_;
unsigned features_;
bool solverTreeNeedsRebuild_;
bool continuousSolvingEnabled_;
bool updateInitialPose_;
bool autoSolveEnabled_;
};
} // namespace Urho3D

View File

@ -4,51 +4,35 @@ class IKSolver : public Component
{
enum Algorithm
{
ONE_BONE,
ONE_BONE = 0,
TWO_BONE,
FABRIK
};
Algorithm GetAlgorithm() const;
void SetAlgorithm(Algorithm algorithm);
enum Feature
{
JOINT_ROTATIONS = 0x01,
TARGET_ROTATIONS = 0x02,
UPDATE_ORIGINAL_POSE = 0x04,
UPDATE_ACTIVE_POSE = 0x08,
USE_ORIGINAL_POSE = 0x10,
CONSTRAINTS = 0x20,
AUTO_SOLVE = 0x40
};
unsigned GetMaximumIterations() const;
void SetMaximumIterations(unsigned iterations);
float GetTolerance() const;
void SetTolerance(float tolerance);
bool BoneRotationsEnabled() const;
void EnableBoneRotations(bool enable);
bool ConstraintsEnabled() const;
void EnableConstraints(bool enable);
bool TargetRotationEnabled() const;
void EnableTargetRotation(bool enable);
bool ContinuousSolvingEnabled() const;
void EnableContinuousSolving(bool enable);
bool AutoUpdateInitialPoseEnabled() const;
void EnableAutoUpdateInitialPose(bool enable);
/// Whether or not the solver should be invoked automatically
bool AutoSolveEnabled() const;
void EnableAutoSolve(bool enable);
bool GetFeature(Feature feature) const;
void SetFeature(Feature feature, bool enable);
void RebuildData();
void RecalculateSegmentLengths();
void CalculateJointRotations();
void Solve();
void ApplyInitialPoseToScene();
void ApplySceneToInitialPose();
void ApplySolvedPoseToScene();
void ApplySceneToSolvedPose();
void ApplyInitialPoseToSolvedPose();
virtual void ApplyConstraints(Node* tree);
void ApplyOriginalPoseToScene();
void ApplySceneToOriginalPose();
void ApplyActivePoseToScene();
void ApplySceneToActivePose();
void ApplyOriginalPoseToActivePose();
void DrawDebugGeometry(bool depthTest);

View File

@ -98,18 +98,15 @@ function CreateScene()
-- Two-bone solver is more efficient and more stable than FABRIK (but only
-- works for two bones, obviously).
solver_:SetAlgorithm(IKSolver.TWO_BONE)
solver_.algorithm = IKSolver.TWO_BONE
-- Disable auto-solving, which means we can call Solve() manually.
solver_:EnableAutoSolve(false)
solver_:SetFeature(IKSolver.AUTO_SOLVE, false)
-- When this is enabled, the solver will use the current positions of the
-- nodes in the skeleton as its basis every frame. If you disable this, then
-- the solver will store the initial positions of the nodes once and always
-- use those positions for calculating solutions.
-- With animated characters you generally want to continuously update the
-- initial positions.
solver_:EnableAutoUpdateInitialPose(true)
-- Only enable this so the debug draw shows us the pose before solving.
-- This should NOT be enabled for any other reason (it does nothing and is
-- a waste of performance).
solver_:SetFeature(IKSolver.UPDATE_ORIGINAL_POSE, true)
-- Create the camera.
cameraRotateNode_ = scene_:CreateChild("CameraRotate")

View File

@ -104,15 +104,12 @@ void CreateScene()
solver_.algorithm = IKAlgorithm::TWO_BONE;
// Disable auto-solving, which means we can call Solve() manually.
solver_.autoSolve = false;
solver_.SetFeature(IKFeature::AUTO_SOLVE, false);
// When this is enabled, the solver will use the current positions of the
// nodes in the skeleton as its basis every frame. If you disable this, then
// the solver will store the initial positions of the nodes once and always
// use those positions for calculating solutions.
// With animated characters you generally want to continuously update the
// initial positions.
solver_.autoUpdateInitialPose = true;
// Only enable this so the debug draw shows us the pose before solving.
// This should NOT be enabled for any other reason (it does nothing and is
// a waste of performance).
solver_.SetFeature(IKFeature::UPDATE_ORIGINAL_POSE, true);
// Create the camera.
cameraRotateNode_ = scene_.CreateChild("CameraRotate");