jsantell / THREE.IK

inverse kinematics for three.js
https://jsantell.github.io/THREE.IK
MIT License
455 stars 54 forks source link

Support for other solvers? #2

Open jsantell opened 6 years ago

jsantell commented 6 years ago

Currently, THREE.IK uses a FABRIK solver. Is it worthwhile to adopt the ability to swap out solvers for different scenarios, like using CCD? Would constraints be able to be generalized across solvers?

zalo commented 6 years ago

The major issue with FABRIK is that it can not implicitly support hinge joints (since the positions of the joints cannot encode twist by themselves). Generally, I find CCDIK to be more flexible than FABRIK when it comes to hinge joints, limits, and constraints (while being roughly the same implementation complexity). The key to doing it elegantly (in 3D) is to formulate the CCDIK "rotate toward" step, the hinge constraint, and the rotation limits all as sequential rotations (which minimally rotate one vector to point along another vector).

The Unity C# code I used to accomplish this in a hierarchy looks like this. (note that all these calculations are being done in world-space). The live demo here is also a part of that git repo (if you want to take it apart in context).

While this rotation operation is typically done with Quaternions, the direct-matrix version of it is the subject of this excellent article by Inigo Quilez.

Generally FABRIK has nicer long-distance convergence properties, so what I typically do in production is run one iteration of FABRIK (without hinge/joint constraints) to get the nice convergence, and then clean up that result with CCDIK (with hinge+joint constraints). The two solvers should be able to run side-by-side, reusing all of the same data structures and math (for instance, a rotation-limit is really just a hinge joint plus the ball-socket limit applied in sequence).

jsantell commented 6 years ago

Thanks for the resources! CCD was the first implemented version, but had difficulties constraining rotations (after having done the FABRIK version, I no longer think I'd have the CCD issues). How do you imagine the configuration of these solvers? One solver per IK system, where a solver could be FABRIK, CCD, or a solver that uses both (like the FABRIK+CCD method you describe), or just only use a specific method, like FABRIK+CCD? Would joint implementations have to be solver-specific? If so, the FABRIK+CCD approach seems even more appealing.

jsantell commented 6 years ago

Similarly, I imagine each IK system should consistently use the same solver (or hybrid solver), such that an IK chain's children chains must use the same solver. Is there an advantage to allow individual chains to have their own solvers, or is this a system-wide configuration?

Separately, but related, I've been mulling over the need for the IK root class altogether, and just move 'system wide' configuration to a root chain. More here: https://github.com/jsantell/THREE.IK/issues/7

zalo commented 6 years ago

While I can't comment on the data-structure-side of the architecture, I'd vote for the "solve" method to just have arguments for which technique, how many iterations, and perhaps a bitmask/enum for which types of constraints to consider... This way, the "hybrid" technique is just calling chain.solve(Solver.FABRIK, 1, Constraints.NONE) and then calling chain.solve(Solver.CCD, 10, Constraints.ALL) right after. Additionally, the cheap "annealing" trick I mentioned can just be done by disabling constraints for some iterations.

jsantell commented 6 years ago

This is great insight, @zalo, thank you! I'll sketch up a solution when I have some time.