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:
parent
4a6d86804e
commit
4f96c7c29f
@ -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");
|
||||
|
2
Source/ThirdParty/ik/include/ik/node.h
vendored
2
Source/ThirdParty/ik/include/ik/node.h
vendored
@ -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).
|
||||
|
2
Source/ThirdParty/ik/include/ik/solver.h
vendored
2
Source/ThirdParty/ik/include/ik/solver.h
vendored
@ -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
|
||||
|
||||
|
8
Source/ThirdParty/ik/src/chain_tree.c
vendored
8
Source/ThirdParty/ik/src/chain_tree.c
vendored
@ -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);
|
||||
|
4
Source/ThirdParty/ik/src/solver.c
vendored
4
Source/ThirdParty/ik/src/solver.c
vendored
@ -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;
|
||||
|
16
Source/ThirdParty/ik/src/solver_FABRIK.c
vendored
16
Source/ThirdParty/ik/src/solver_FABRIK.c
vendored
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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");
|
||||
|
Loading…
Reference in New Issue
Block a user