Browse Source

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

TheComet 8 years ago
parent
commit
4f96c7c29f

+ 6 - 9
Source/Samples/45_InverseKinematics/InverseKinematics.cpp

@@ -144,15 +144,12 @@ void InverseKinematics::CreateScene()
     solver_->SetAlgorithm(IKSolver::TWO_BONE);
     solver_->SetAlgorithm(IKSolver::TWO_BONE);
 
 
     // Disable auto-solving, which means we need to call Solve() manually
     // Disable auto-solving, which means we need to call Solve() manually
-    solver_->EnableAutoSolve(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);
+    solver_->SetFeature(IKSolver::AUTO_SOLVE, false);
+
+    // 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.
     // Create the camera.
     cameraRotateNode_ = scene_->CreateChild("CameraRotate");
     cameraRotateNode_ = scene_->CreateChild("CameraRotate");

+ 1 - 1
Source/ThirdParty/ik/include/ik/node.h

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

+ 1 - 1
Source/ThirdParty/ik/include/ik/solver.h

@@ -185,7 +185,7 @@ ik_solver_iterate_tree(ik_solver_t* solver,
  * positions and rotations for every node in the tree.
  * positions and rotations for every node in the tree.
  */
  */
 IK_PUBLIC_API void
 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
 C_HEADER_END
 
 

+ 4 - 4
Source/ThirdParty/ik/src/chain_tree.c

@@ -363,8 +363,8 @@ calculate_segment_lengths_in_island(chain_t* island)
         ik_node_t* parent_node =
         ik_node_t* parent_node =
             *(ik_node_t**)ordered_vector_get_element(&island->nodes, last_idx + 1);
             *(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);
         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);
         ik_node_t* parent_node = *(ik_node_t**)ordered_vector_get_element(&chain->nodes, node_idx + 1);
 
 
         /* calculate vectors for original and solved segments */
         /* 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_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_sub_vec3(segment_solved.f, parent_node->position.f);
 
 
         vec3_angle(parent_node->rotation.f, segment_original.f, segment_solved.f);
         vec3_angle(parent_node->rotation.f, segment_original.f, segment_solved.f);

+ 2 - 2
Source/ThirdParty/ik/src/solver.c

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

+ 8 - 8
Source/ThirdParty/ik/src/solver_FABRIK.c

@@ -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 */
     /* lerp using effector weight to get actual target position */
     *target_position = effector->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_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 */
     /* Fancy algorithm using nlerp, makes transitions look more natural */
     if (effector->flags & EFFECTOR_WEIGHT_NLERP && effector->weight < 1.0)
     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 */
         /* Need distance from base node to target and base to effector node */
         base_node = *(ik_node_t**)ordered_vector_get_element(&chain->nodes,
         base_node = *(ik_node_t**)ordered_vector_get_element(&chain->nodes,
                 ordered_vector_count(&chain->nodes) - 1);
                 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;
         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 */
         /* 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_target.f) * effector->weight;
         distance_to_target += vec3_length(base_to_effector.f) * (1.0 - 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 */
         /* 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_normalise(target_position->f);
         vec3_mul_scalar(target_position->f, distance_to_target);
         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);
             assert(ordered_vector_count(&root_chain->nodes) > 1);
 
 
             root_position = (*(ik_node_t**)ordered_vector_get_element(&root_chain->nodes,
             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)
             if (solver->flags & SOLVER_CALCULATE_TARGET_ROTATIONS)
                 solve_chain_forwards_with_target_rotation(root_chain);
                 solve_chain_forwards_with_target_rotation(root_chain);

+ 16 - 18
Source/Urho3D/AngelScript/IKAPI.cpp

@@ -39,35 +39,33 @@ static void RegisterIKSolver(asIScriptEngine* engine)
     engine->RegisterEnumValue("IKAlgorithm", "TWO_BONE", IKSolver::TWO_BONE);
     engine->RegisterEnumValue("IKAlgorithm", "TWO_BONE", IKSolver::TWO_BONE);
     engine->RegisterEnumValue("IKAlgorithm", "FABRIK", IKSolver::FABRIK);
     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");
     RegisterComponent<IKSolver>(engine, "IKSolver");
     engine->RegisterObjectMethod("IKSolver", "IKAlgorithm get_algorithm() const", asMETHOD(IKSolver, GetAlgorithm), asCALL_THISCALL);
     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 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", "uint get_maximumIterations() const", asMETHOD(IKSolver, GetMaximumIterations), asCALL_THISCALL);
     engine->RegisterObjectMethod("IKSolver", "void set_maximumIterations(uint)", asMETHOD(IKSolver, SetMaximumIterations), 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", "float get_tolerance() const", asMETHOD(IKSolver, GetTolerance), asCALL_THISCALL);
     engine->RegisterObjectMethod("IKSolver", "void set_tolerance(float)", asMETHOD(IKSolver, SetTolerance), 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 RebuildData()", asMETHOD(IKSolver, RebuildData), asCALL_THISCALL);
     engine->RegisterObjectMethod("IKSolver", "void RecalculateSegmentLengths()", asMETHOD(IKSolver, RecalculateSegmentLengths), 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 CalculateJointRotations()", asMETHOD(IKSolver, CalculateJointRotations), asCALL_THISCALL);
     engine->RegisterObjectMethod("IKSolver", "void Solve()", asMETHOD(IKSolver, Solve), 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);
     engine->RegisterObjectMethod("IKSolver", "void DrawDebugGeometry(bool)", asMETHODPR(IKSolver, DrawDebugGeometry, (bool), void), asCALL_THISCALL);
 }
 }
 
 

+ 146 - 133
Source/Urho3D/IK/IKSolver.cpp

@@ -74,11 +74,8 @@ IKSolver::IKSolver(Context* context) :
     Component(context),
     Component(context),
     solver_(NULL),
     solver_(NULL),
     algorithm_(FABRIK),
     algorithm_(FABRIK),
-    boneRotationsEnabled_(true),
-    solverTreeNeedsRebuild_(false),
-    continuousSolvingEnabled_(false),
-    updateInitialPose_(false),
-    autoSolveEnabled_(true)
+    features_(AUTO_SOLVE | JOINT_ROTATIONS | UPDATE_ACTIVE_POSE),
+    solverTreeNeedsRebuild_(false)
 {
 {
     context_->RequireIK();
     context_->RequireIK();
 
 
@@ -121,12 +118,13 @@ void IKSolver::RegisterObject(Context* context)
     URHO3D_ENUM_ACCESSOR_ATTRIBUTE("Algorithm", GetAlgorithm, SetAlgorithm, Algorithm, algorithmNames, FABRIK, AM_DEFAULT);
     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("Max Iterations", GetMaximumIterations, SetMaximumIterations, unsigned, 20, AM_DEFAULT);
     URHO3D_ACCESSOR_ATTRIBUTE("Convergence Tolerance", GetTolerance, SetTolerance, float, 0.001, 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
     /* We need to rebuild the tree so make sure that the scene is in the
      * initial pose when this occurs.*/
      * initial pose when this occurs.*/
     if (node_ != NULL)
     if (node_ != NULL)
-        ApplyInitialPoseToScene();
+        ApplyOriginalPoseToScene();
 
 
     // Initial flags for when there is no solver to destroy
     // Initial flags for when there is no solver to destroy
     uint8_t initialFlags = 0;
     uint8_t initialFlags = 0;
@@ -171,119 +169,73 @@ void IKSolver::SetAlgorithm(IKSolver::Algorithm algorithm)
 }
 }
 
 
 // ----------------------------------------------------------------------------
 // ----------------------------------------------------------------------------
-unsigned IKSolver::GetMaximumIterations() const
-{
-    return solver_->max_iterations;
-}
-
-// ----------------------------------------------------------------------------
-void IKSolver::SetMaximumIterations(unsigned iterations)
-{
-    solver_->max_iterations = iterations;
-}
-
-// ----------------------------------------------------------------------------
-float IKSolver::GetTolerance() const
-{
-    return solver_->tolerance;
-}
-
-// ----------------------------------------------------------------------------
-void IKSolver::SetTolerance(float tolerance)
+bool IKSolver::GetFeature(Feature feature) const
 {
 {
-    if (tolerance < M_EPSILON)
-        tolerance = M_EPSILON;
-    solver_->tolerance = tolerance;
+    return (features_ & feature) != 0;
 }
 }
 
 
 // ----------------------------------------------------------------------------
 // ----------------------------------------------------------------------------
-bool IKSolver::BoneRotationsEnabled() const
+void IKSolver::SetFeature(Feature feature, bool enable)
 {
 {
-    return boneRotationsEnabled_;
-}
+    switch (feature)
+    {
+        case CONSTRAINTS:
+        {
+            solver_->flags &= ~SOLVER_ENABLE_CONSTRAINTS;
+            if (enable)
+                solver_->flags |= SOLVER_ENABLE_CONSTRAINTS;
+        } break;
 
 
-// ----------------------------------------------------------------------------
-void IKSolver::EnableBoneRotations(bool enable)
-{
-    boneRotationsEnabled_ = enable;
-}
+        case TARGET_ROTATIONS:
+        {
+            solver_->flags &= ~SOLVER_CALCULATE_TARGET_ROTATIONS;
+            if (enable)
+                solver_->flags |= SOLVER_CALCULATE_TARGET_ROTATIONS;
+        } break;
 
 
-// ----------------------------------------------------------------------------
-bool IKSolver::ConstraintsEnabled() const
-{
-    return (solver_->flags & SOLVER_ENABLE_CONSTRAINTS);
-}
+        case AUTO_SOLVE:
+        {
+            if (((features_ & AUTO_SOLVE) != 0) == enable)
+                break;
 
 
-// ----------------------------------------------------------------------------
-void IKSolver::EnableConstraints(bool enable)
-{
-    solver_->flags &= ~SOLVER_ENABLE_CONSTRAINTS;
-    if (enable)
-        solver_->flags |= SOLVER_ENABLE_CONSTRAINTS;
-}
+            if (enable)
+                SubscribeToEvent(GetScene(), E_SCENEDRAWABLEUPDATEFINISHED, URHO3D_HANDLER(IKSolver, HandleSceneDrawableUpdateFinished));
+            else
+                UnsubscribeFromEvent(GetScene(), E_SCENEDRAWABLEUPDATEFINISHED);
+        } break;
 
 
-// ----------------------------------------------------------------------------
-bool IKSolver::TargetRotationEnabled() const
-{
-    return (solver_->flags & SOLVER_CALCULATE_TARGET_ROTATIONS) != 0;
-}
+        default: break;
+    }
 
 
-// ----------------------------------------------------------------------------
-void IKSolver::EnableTargetRotation(bool enable)
-{
-    solver_->flags &= ~SOLVER_CALCULATE_TARGET_ROTATIONS;
+    features_ &= ~feature;
     if (enable)
     if (enable)
-        solver_->flags |= SOLVER_CALCULATE_TARGET_ROTATIONS;
+        features_ |= feature;
 }
 }
 
 
 // ----------------------------------------------------------------------------
 // ----------------------------------------------------------------------------
-bool IKSolver::ContinuousSolvingEnabled() const
-{
-    return continuousSolvingEnabled_;
-}
-
-// ----------------------------------------------------------------------------
-void IKSolver::EnableContinuousSolving(bool enable)
-{
-    continuousSolvingEnabled_ = enable;
-}
-
-// ----------------------------------------------------------------------------
-bool IKSolver::AutoUpdateInitialPoseEnabled() const
-{
-    return updateInitialPose_;
-}
-
-// ----------------------------------------------------------------------------
-void IKSolver::EnableAutoUpdateInitialPose(bool enable)
+unsigned IKSolver::GetMaximumIterations() const
 {
 {
-    updateInitialPose_ = enable;
+    return solver_->max_iterations;
 }
 }
 
 
 // ----------------------------------------------------------------------------
 // ----------------------------------------------------------------------------
-void IKSolver::MarkSolverTreeDirty()
+void IKSolver::SetMaximumIterations(unsigned iterations)
 {
 {
-    solverTreeNeedsRebuild_ = true;
+    solver_->max_iterations = iterations;
 }
 }
 
 
 // ----------------------------------------------------------------------------
 // ----------------------------------------------------------------------------
-bool IKSolver::AutoSolveEnabled() const
+float IKSolver::GetTolerance() const
 {
 {
-    return autoSolveEnabled_;
+    return solver_->tolerance;
 }
 }
 
 
 // ----------------------------------------------------------------------------
 // ----------------------------------------------------------------------------
-void IKSolver::EnableAutoSolve(bool enable)
+void IKSolver::SetTolerance(float tolerance)
 {
 {
-    if (autoSolveEnabled_ == enable)
-        return;
-
-    if (enable)
-        SubscribeToEvent(GetScene(), E_SCENEDRAWABLEUPDATEFINISHED, URHO3D_HANDLER(IKSolver, HandleSceneDrawableUpdateFinished));
-    else
-        UnsubscribeFromEvent(GetScene(), E_SCENEDRAWABLEUPDATEFINISHED);
-
-    autoSolveEnabled_ = enable;
+    if (tolerance < M_EPSILON)
+        tolerance = M_EPSILON;
+    solver_->tolerance = tolerance;
 }
 }
 
 
 // ----------------------------------------------------------------------------
 // ----------------------------------------------------------------------------
@@ -316,11 +268,14 @@ void IKSolver::Solve()
         solverTreeNeedsRebuild_ = false;
         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)
     for (PODVector<IKEffector*>::ConstIterator it = effectorList_.Begin(); it != effectorList_.End(); ++it)
     {
     {
@@ -329,10 +284,10 @@ void IKSolver::Solve()
 
 
     ik_solver_solve(solver_);
     ik_solver_solve(solver_);
 
 
-    if (boneRotationsEnabled_)
+    if (features_ & JOINT_ROTATIONS)
         ik_solver_calculate_joint_rotations(solver_);
         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* node = (Node*)ikNode->user_data;
     node->SetWorldRotation(QuatIK2Urho(&ikNode->initial_rotation));
     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);
     ik_solver_iterate_tree(solver_, ApplyInitialPoseToSceneCallback);
 }
 }
@@ -352,9 +307,9 @@ static void ApplySceneToInitialPoseCallback(ik_node_t* ikNode)
 {
 {
     Node* node = (Node*)ikNode->user_data;
     Node* node = (Node*)ikNode->user_data;
     ikNode->initial_rotation = QuatUrho2IK(node->GetWorldRotation());
     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);
     ik_solver_iterate_tree(solver_, ApplySceneToInitialPoseCallback);
 }
 }
@@ -366,7 +321,7 @@ static void ApplySolvedPoseToSceneCallback(ik_node_t* ikNode)
     node->SetWorldRotation(QuatIK2Urho(&ikNode->rotation));
     node->SetWorldRotation(QuatIK2Urho(&ikNode->rotation));
     node->SetWorldPosition(Vec3IK2Urho(&ikNode->position));
     node->SetWorldPosition(Vec3IK2Urho(&ikNode->position));
 }
 }
-void IKSolver::ApplySolvedPoseToScene()
+void IKSolver::ApplyActivePoseToScene()
 {
 {
     ik_solver_iterate_tree(solver_, ApplySolvedPoseToSceneCallback);
     ik_solver_iterate_tree(solver_, ApplySolvedPoseToSceneCallback);
 }
 }
@@ -378,15 +333,21 @@ static void ApplySceneToSolvedPoseCallback(ik_node_t* ikNode)
     ikNode->rotation = QuatUrho2IK(node->GetWorldRotation());
     ikNode->rotation = QuatUrho2IK(node->GetWorldRotation());
     ikNode->position = Vec3Urho2IK(node->GetWorldPosition());
     ikNode->position = Vec3Urho2IK(node->GetWorldPosition());
 }
 }
-void IKSolver::ApplySceneToSolvedPose()
+void IKSolver::ApplySceneToActivePose()
 {
 {
     ik_solver_iterate_tree(solver_, ApplySceneToSolvedPoseCallback);
     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)
 void IKSolver::OnSceneSet(Scene* scene)
 {
 {
-    if (autoSolveEnabled_)
+    if (features_ & AUTO_SOLVE)
         SubscribeToEvent(scene, E_SCENEDRAWABLEUPDATEFINISHED, URHO3D_HANDLER(IKSolver, HandleSceneDrawableUpdateFinished));
         SubscribeToEvent(scene, E_SCENEDRAWABLEUPDATEFINISHED, URHO3D_HANDLER(IKSolver, HandleSceneDrawableUpdateFinished));
 }
 }
 
 
 // ----------------------------------------------------------------------------
 // ----------------------------------------------------------------------------
 void IKSolver::OnNodeSet(Node* node)
 void IKSolver::OnNodeSet(Node* node)
 {
 {
-    ApplyInitialPoseToScene();
+    ApplyOriginalPoseToScene();
     DestroyTree();
     DestroyTree();
 
 
     if (node != NULL)
     if (node != NULL)
@@ -424,7 +385,7 @@ ik_node_t* IKSolver::CreateIKNode(const Node* node)
     ik_node_t* ikNode = ik_node_create(node->GetID());
     ik_node_t* ikNode = ik_node_create(node->GetID());
 
 
     // Set initial position/rotation and pass in Node* as user data for later
     // 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->initial_rotation = QuatUrho2IK(node->GetWorldRotation());
     ikNode->user_data = (void*)node;
     ikNode->user_data = (void*)node;
 
 
@@ -549,7 +510,7 @@ void IKSolver::HandleComponentRemoved(StringHash eventType, VariantMap& eventDat
         effector->SetIKEffector(NULL);
         effector->SetIKEffector(NULL);
         effectorList_.RemoveSwap(effector);
         effectorList_.RemoveSwap(effector);
 
 
-        ApplyInitialPoseToScene();
+        ApplyOriginalPoseToScene();
         MarkSolverTreeDirty();
         MarkSolverTreeDirty();
     }
     }
 
 
@@ -635,17 +596,6 @@ void IKSolver::HandleSceneDrawableUpdateFinished(StringHash eventType, VariantMa
     Solve();
     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)
 void IKSolver::DrawDebugGeometry(bool depthTest)
 {
 {
@@ -673,8 +623,8 @@ void IKSolver::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
         unsigned numberOfSegments = 0;
         unsigned numberOfSegments = 0;
         while (b && chainLength-- != 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);
             averageLength += vec3_length(v.f);
             ++numberOfSegments;
             ++numberOfSegments;
             a = b;
             a = b;
@@ -687,7 +637,7 @@ void IKSolver::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
         a = *pnode;
         a = *pnode;
         b = a->parent;
         b = a->parent;
         debug->AddSphere(
         debug->AddSphere(
-            Sphere(Vec3IK2Urho(&a->initial_position), averageLength * 0.1f),
+            Sphere(Vec3IK2Urho(&a->original_position), averageLength * 0.1f),
             Color(0, 0, 255),
             Color(0, 0, 255),
             depthTest
             depthTest
         );
         );
@@ -699,13 +649,13 @@ void IKSolver::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
         while (b && chainLength-- != 0)
         while (b && chainLength-- != 0)
         {
         {
             debug->AddLine(
             debug->AddLine(
-                Vec3IK2Urho(&a->initial_position),
-                Vec3IK2Urho(&b->initial_position),
+                Vec3IK2Urho(&a->original_position),
+                Vec3IK2Urho(&b->original_position),
                 Color(0, 255, 255),
                 Color(0, 255, 255),
                 depthTest
                 depthTest
             );
             );
             debug->AddSphere(
             debug->AddSphere(
-                Sphere(Vec3IK2Urho(&b->initial_position), averageLength * 0.1f),
+                Sphere(Vec3IK2Urho(&b->original_position), averageLength * 0.1f),
                 Color(0, 0, 255),
                 Color(0, 0, 255),
                 depthTest
                 depthTest
             );
             );
@@ -726,4 +676,67 @@ void IKSolver::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
     ORDERED_VECTOR_END_EACH
     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
 } // namespace Urho3D

+ 164 - 101
Source/Urho3D/IK/IKSolver.h

@@ -47,7 +47,7 @@ public:
 
 
     enum Algorithm
     enum Algorithm
     {
     {
-        ONE_BONE,
+        ONE_BONE = 0,
         TWO_BONE,
         TWO_BONE,
         FABRIK
         FABRIK
         /* not implemented yet
         /* not implemented yet
@@ -56,6 +56,115 @@ public:
         JACOBIAN_TRANSPOSE*/
         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.
     /// Construct an IK root component.
     IKSolver(Context* context);
     IKSolver(Context* context);
     /// Default destructor.
     /// Default destructor.
@@ -67,16 +176,25 @@ public:
     Algorithm GetAlgorithm() const;
     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.
      * The currently supported solvers are listed below.
      *   + **FABRIK**: This is a fairly new and highly efficient inverse
      *   + **FABRIK**: This is a fairly new and highly efficient inverse
      *     kinematic solving algorithm. It requires the least iterations to
      *     kinematic solving algorithm. It requires the least iterations to
      *     reach its goal, it does not suffer from singularities (nearly no
      *     reach its goal, it does not suffer from singularities (nearly no
      *     violent snapping about), and it always converges.
      *     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);
     void SetAlgorithm(Algorithm algorithm);
 
 
+    bool GetFeature(Feature feature) const;
+    void SetFeature(Feature feature, bool enable);
+
     /// Returns the configured maximum number of iterations.
     /// Returns the configured maximum number of iterations.
     unsigned GetMaximumIterations() const;
     unsigned GetMaximumIterations() const;
 
 
@@ -114,107 +232,38 @@ public:
      */
      */
     void SetTolerance(float tolerance);
     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?)
-     */
-    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.
+     * @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 EnableContinuousSolving(bool enable);
-
-    /// Whether or not the initial pose is updated when calling Solve()
-    bool AutoUpdateInitialPoseEnabled() const;
+    void RebuildData();
 
 
     /*!
     /*!
-     * @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().
+     * @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 EnableAutoUpdateInitialPose(bool enable);
-
-    /// Whether or not the solver should be invoked automatically
-    bool AutoSolveEnabled() const;
+    void RecalculateSegmentLengths();
 
 
     /*!
     /*!
-     * @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.
+     * @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 EnableAutoSolve(bool enable);
-
-    void RebuildData();
-    void RecalculateSegmentLengths();
     void CalculateJointRotations();
     void CalculateJointRotations();
 
 
     /*!
     /*!
      * @brief Invokes the solver. The solution is applied back to the scene
      * @brief Invokes the solver. The solution is applied back to the scene
      * graph automatically.
      * 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();
     void Solve();
 
 
@@ -222,7 +271,7 @@ public:
      * @brief Causes the initial tree to be applied back to Urho3D's scene
      * @brief Causes the initial tree to be applied back to Urho3D's scene
      * graph. This is what gets called when continuous solving is disabled.
      * 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
      * @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,
      * 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
      * then the result will be a "continuous solution", where the solver will
      * use the previously calculated tree as a basis for the new solution.
      * 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);
     void DrawDebugGeometry(bool depthTest);
     virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
     virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
@@ -268,15 +317,29 @@ private:
     /// Invokes the IK solver
     /// Invokes the IK solver
     void HandleSceneDrawableUpdateFinished(StringHash eventType, VariantMap& eventData);
     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<IKEffector*> effectorList_;
     PODVector<IKConstraint*> constraintList_;
     PODVector<IKConstraint*> constraintList_;
     ik_solver_t* solver_;
     ik_solver_t* solver_;
     Algorithm algorithm_;
     Algorithm algorithm_;
-    bool boneRotationsEnabled_;
+    unsigned features_;
     bool solverTreeNeedsRebuild_;
     bool solverTreeNeedsRebuild_;
-    bool continuousSolvingEnabled_;
-    bool updateInitialPose_;
-    bool autoSolveEnabled_;
 };
 };
 
 
 } // namespace Urho3D
 } // namespace Urho3D

+ 18 - 34
Source/Urho3D/LuaScript/pkgs/IK/IKSolver.pkg

@@ -4,51 +4,35 @@ class IKSolver : public Component
 {
 {
     enum Algorithm
     enum Algorithm
     {
     {
-        ONE_BONE,
+        ONE_BONE = 0,
         TWO_BONE,
         TWO_BONE,
         FABRIK
         FABRIK
     };
     };
 
 
-    Algorithm GetAlgorithm() const;
-    void SetAlgorithm(Algorithm algorithm);
-
-    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);
+    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
+    };
 
 
-    /// 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 RebuildData();
     void RecalculateSegmentLengths();
     void RecalculateSegmentLengths();
     void CalculateJointRotations();
     void CalculateJointRotations();
     void Solve();
     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);
     void DrawDebugGeometry(bool depthTest);
 
 

+ 7 - 10
bin/Data/LuaScripts/45_InverseKinematics.lua

@@ -98,18 +98,15 @@ function CreateScene()
 
 
     -- Two-bone solver is more efficient and more stable than FABRIK (but only
     -- Two-bone solver is more efficient and more stable than FABRIK (but only
     -- works for two bones, obviously).
     -- 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.
     -- Disable auto-solving, which means we can call Solve() manually.
-    solver_:EnableAutoSolve(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)
+    solver_:SetFeature(IKSolver.AUTO_SOLVE, false)
+
+    -- 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.
     -- Create the camera.
     cameraRotateNode_ = scene_:CreateChild("CameraRotate")
     cameraRotateNode_ = scene_:CreateChild("CameraRotate")

+ 6 - 9
bin/Data/Scripts/45_InverseKinematics.as

@@ -104,15 +104,12 @@ void CreateScene()
     solver_.algorithm = IKAlgorithm::TWO_BONE;
     solver_.algorithm = IKAlgorithm::TWO_BONE;
 
 
     // Disable auto-solving, which means we can call Solve() manually.
     // Disable auto-solving, which means we can call Solve() manually.
-    solver_.autoSolve = 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;
+    solver_.SetFeature(IKFeature::AUTO_SOLVE, false);
+
+    // 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.
     // Create the camera.
     cameraRotateNode_ = scene_.CreateChild("CameraRotate");
     cameraRotateNode_ = scene_.CreateChild("CameraRotate");