# `BB.IK.FABRIK`
[🔗](https://github.com/beam-bots/bb_ik_fabrik/blob/main/lib/bb/ik/fabrik.ex#L5)

FABRIK-based inverse kinematics solver for serial chains.

FABRIK (Forward And Backward Reaching Inverse Kinematics) is an iterative
solver that works by alternately reaching from the end-effector toward the
target, then from the base back to maintain segment lengths. This implementation
extends classic FABRIK with orientation tracking at each joint frame.

## Features

- Works with `BB.Robot.State` or plain position maps
- Position and orientation solving (quaternion or axis constraints)
- Handles co-located joints by fitting each about its own axis
- Respects joint limits by clamping solved values
- Uses Nx tensors for efficient computation
- Returns best-effort positions even on failure

## Usage

    robot = MyRobot.robot()
    {:ok, state} = BB.Robot.State.new(robot)

    # Solve for end-effector to reach target position
    target = Vec3.new(0.4, 0.2, 0.1)

    case BB.IK.FABRIK.solve(robot, state, :end_effector, target) do
      {:ok, positions, meta} ->
        BB.Robot.State.set_configurations(state, configurations)
        IO.puts("Solved in #{meta.iterations} iterations")

      {:error, %BB.Error.Kinematics.Unreachable{residual: residual}} ->
        IO.puts("Target unreachable, residual: #{residual}m")
    end

## Target Formats

- `Vec3.t()` - Position-only target
- `Transform.t()` - Position + full orientation from transform
- `{Vec3.t(), {:quaternion, Quaternion.t()}}` - Position + explicit quaternion
- `{Vec3.t(), {:axis, Vec3.t()}}` - Position + tool axis direction constraint

## Options

- `:max_iterations` - Maximum solver iterations (default: 50)
- `:tolerance` - Position convergence tolerance in metres (default: 1.0e-4)
- `:orientation_tolerance` - Orientation convergence tolerance in radians (default: 0.01)
- `:respect_limits` - Whether to clamp to joint limits (default: true)

## Limitations

- Serial chains only (no branching topologies)
- Revolute and prismatic joints (fixed joints are skipped)
- Only position targets respect joint axes. An orientation target still solves
  in point space and fits joint values afterwards, so its result may not
  correspond to a pose the robot can hold — `meta.reached` says which
- Constraining costs iterations, so a solve takes tens rather than a handful.
  `BB.IK.FABRIK.Math.solve_constrained/4` vectorises over a batch axis when
  many chains need solving at once

# `solve_and_update`

```elixir
@spec solve_and_update(
  BB.Robot.t(),
  BB.Robot.State.t(),
  atom(),
  atom(),
  BB.IK.Solver.target(),
  keyword()
) :: BB.IK.Solver.solve_result()
```

Solve IK and update the state in-place.

Convenience function that calls `solve/6` and applies the result
to the given `BB.Robot.State`.

Meant for a state of your own - one from `BB.Robot.State.new/1`, stepped
through a planned motion. A running robot's state belongs to its sensors,
which write it from `BB.Message.Sensor.JointState` messages, and writing a
solved configuration into it claims the joints have arrived somewhere they
have only been asked to go.

## Returns

Same as `solve/6`, but on success the state's ETS table is updated.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
