-
Notifications
You must be signed in to change notification settings - Fork 75
Home
There are three parts to using this library.
- Creating and maintaining the tree structure.
- Setting up the solver and solving.
- Applying the solution back to your own tree structure.
The tree is essentially your interface to the solver. You must copy the positions and rotations that need solving into the tree, solve, and retrieve the solutions from the tree again.
The library provides a leightweight interface for building and maintaining a tree structure. Each node in the tree stores various information, such as the current transform or effector target information and chain length. The result data is also written to this tree and can be retrieved.
(see node.h for more info)
Creating a tree is quite straight forward. Every node requires a unique identifier within the tree structure. This is passed as an argument during creation and should not be changed thereafter.
struct ik_node_t* tree = ik_node_create(0);
struct ik_node_t* node = ik_node_create(1);
ik_node_add_child(tree, node);You can remove nodes with the function ik_node_destroy(). You can
retrieve node objects by uid with ik_node_find_child()
You will need to mirror the same structure present in your project (assuming your project is using a scene graph or something similar). Whether you do this continuously or whether you rebuild the whole tree from scratch before solving when relevant parts of your scene graph become "dirty" is up to you. The former approach is recommended if your scene graph is expected to change a lot. The latter is of course easier to implement, but will be slower.
Effectors are used to set the target position and rotation of a node.

In order to mark a node as an effector, you must create and attach an effector
object using ik_effector_create() and ik_node_attach_effector().
struct ik_effector_t* effector = ik_effector_create();
ik_node_attach_effector(node, effector);The most important fields that can be to set on the effector are:
effector->chain_length = 3; /* how many parent segments should be affected */
effector->target_position = getMyTargetPosition();
effector->target_rotation = getMyTargetRotation();A chain length of 1 means a single segment or "bone" is affected. Arms and
legs typically use a value of 2. The default value is 0, which means
all nodes right down to the root node are affected. Note that you must call
ik_solver_rebuild_data() after chainging the chain length (more info
later).
The target position and rotation is set in global space, where the target position is a 3-dimensional vector type and the rotation is a quaternion.
Effectors have a weight parameter (ranges from 0-1) indicating how much
influence it has on the tree to be solved. You can make use of this to
smoothly transition in and out of IK solutions.
effector->weight = splineFunction();
You may have noticed that the weight causes a linear interpolation of the target position. This can look bad on organic creatures, especially when the solved tree is far apart from the initial tree. You might consider enabling nlerping, which causes the weight to rotate around the next subbase joint (using nlerping). This feature can be enabled by setting the flag:
effector->flags |= EFFECTOR_WEIGHT_NLERP;Here you can see two transition results, with and without nlerping enabled.

Finally you can create your desired solver and set its tree.
ik_solver_t* solver = ik_solver_create(SOLVER_FABRIK);
ik_solver_set_tree(solver, tree); /* the tree we built earlier */As of this writing, the only algorithm supported is FABRIK. Additional solvers may be added in the future.
WARNING: You must rebuild the tree at least once before solving to update internal data structures. Failing to do so will result in segaults. If you change the tree in any way (add nodes or remove nodes, add effectors or remove effectors) you must also rebuild the tree.
The solver exposes a host of different features which can be enabled and disabled according to your requirements.
First and foremost, you will want to set the maximum number of iterations and tolerance:
solver->max_iterations = 20;
solver->tolerance = 1e-3;FABRIK converges extremely quickly, you can sometimes get away with just 3-5 iterations.
Next, invoking the solver. A typical solver invokation might look like this:
if (tree_was_modified_in_some_way())
ik_solver_rebuild_data(solver);
ik_solver_solve(solver);By invoking the solver every frame, we obtain a more "continuous" solution of the IK problem where each calculation uses the previous solution as a base for the next solution. Here is an example of what it might look like:

A lot of the time you may want to reset the tree to its "initial pose" (the
positions and rotations that existed before the last solve). Luckily the tree
stores this data for you. All you need to do is call
ik_solver_reset_solved_data().
if (tree_was_modified_in_some_way())
ik_solver_rebuild_data(solver);
ik_solver_reset_solved_data(solver);
ik_solver_solve(solver);
The result now looks much different. Rather than a continuous solution, the calculation is performed on the initial pose every time, causing the chain to have a tendency to snap back into its original pose if possible:

This is typically the desired mode of operation for animated characters.
There are some flags that should be set depending on your needs. By default, the solver will not calculate final rotations. This is undesired if you are solving IK for a skinned character for example. To fix this, you must add the following flag.
solver->flags |= SOLVER_CALCULATE_FINAL_ROTATIONS;The result with and without final rotations:

By default the solver will also disregard target rotations (which can be set
via effector->target_rotation). This can be enabled by setting the
following flag:
solver->flags |= SOLVER_CALCULATE_TARGET_ROTATIONS;The result:

How do you get data into and out of the tree? ik_solver_iterate_tree()
is your answer.
/* Writes transforms from an external project into the solver's tree */
void write_transforms_callback(struct ik_node_t* ik_node)
{
MyAwesomeNode* node = (MyAwesomeNode*)ik_node->user_data;
ik_node->initial_position = node->getPosition();
ik_node->initial_rotation = node->getRotation();
}
/* Reads solved transforms from the solver's tree to our external project */
void read_solved_data_callback(struct ik_node_t* ik_node)
{
MyAwesomeNode* node = (MyAwesomeNode*)ik_node->user_data;
node->setPosition(ik_node->position);
node->setRotation(ik_node->rotation);
}This code assumes we have a scene graph composed of MyAwesomeNode C++
objects. During tree mirroring, we make sure to store the pointer to the
corresponding C++ node into ik_node->user_data so it can be retrieved
later in the callback functions.
for (every node in my scene graph)
{
struct node_t* ik_node = ik_node_create(myNode->getUID());
ik_node->user_data = myNode; /* important for callbacks */
/* ..... */
}Now it is a simple matter of calling ik_solver_iterate_tree() at the
appropriate times.
if (tree_was_modified_in_some_way())
ik_solver_rebuild_data(solver);
/*
* Copy transforms from our tree into IK tree. Note that this step is only
* necessary if the initial pose has changed in some way (e.g. an animated
* object).
*/
solver->iterate_node = write_transforms_callback;
ik_solver_iterate_tree(solver);
ik_solver_reset_solved_data(solver);
ik_solver_solve(solver);
/* copy results from IK tree back into our scene graph */
solver->iterate_node = read_solved_data_callback;
ik_solver_iterate_tree(solver);
The attentive readers might have noticed the unnecessary copies being made
here. Why copy the updated initial pose into the tree's initial slots and then
copy those transforms into the current transform slots via
ik_solver_reset_solved_data()? We could instead modify the callback to
write the update initial pose into ik_node->position and
ik_node->rotation and skip calling ik_solver_reset_solved_data()
entirely!
/* Writes transforms from an external project into the solver's tree */
void write_transforms_callback(struct ik_node_t* ik_node)
{
MyAwesomeNode* node = (MyAwesomeNode*)ik_node->user_data;
/*ik_node->initial_position = node->getPosition();*/
/*ik_node->initial_rotation = node->getRotation();*/
ik_node->position = node->getPosition();
ik_node->rotation = node->getRotation();
}Then:
if (tree_was_modified_in_some_way())
ik_solver_rebuild_data(solver);
/* copy transforms from our scene graph directly into current transforms */
solver->iterate_node = write_transforms_callback;
ik_solver_iterate_tree(solver);
ik_solver_solve(solver);
/* copy results from IK tree back into our scene graph */
solver->iterate_node = read_solved_data_callback;
ik_solver_iterate_tree(solver);
The solver needs to work in global space. If you require the current transforms to be in local space instead of global space, there are two node functions that allow you to go to and from local space.
ik_solver_solve(solver);
/* want to apply results in local space instead of in global */
ik_node_global_to_local(solver->tree);
ik_solver_iterate_tree(solver);
ik_node_local_to_global(solver->tree); /* required for the next sover invokation */