featherstone 0.1.0

Robotics dynamics engine — O(n) forward/inverse dynamics for kinematic trees, contact solvers, and time integration
Documentation
# API Patterns

Common patterns for using featherstone effectively.

## Building Robots Programmatically

### Serial chain (robot arm)

```rust
let mut arm = ArticulatedBody::new();
arm.set_gravity(Vector3::new(0.0, -9.81, 0.0));

let link_mass = 1.0;
let link_length = 0.3;

for i in 0..6 {
    let parent = if i == 0 { -1 } else { (i - 1) as i32 };
    arm.add_body(
        format!("link{i}"),
        parent,
        GenJoint::Revolute { axis: Vector3::z() },
        SpatialInertia::from_mass_inertia(
            link_mass,
            Vector3::new(link_length / 2.0, 0.0, 0.0),  // COM at center
            Matrix3::identity() * 0.01,
        ),
        SpatialTransform::from_translation(Vector3::new(link_length, 0.0, 0.0)),
    );
}
```

### Floating base (mobile robot)

```rust
let mut robot = ArticulatedBody::new();
robot.set_gravity(Vector3::new(0.0, -9.81, 0.0));

// Floating base: 6 DOF (translation + rotation)
robot.add_body("base", -1, GenJoint::Floating,
    SpatialInertia::sphere(10.0, 0.3),
    SpatialTransform::identity());

// Arm attached to base
robot.add_body("arm", 0, GenJoint::Revolute { axis: Vector3::z() },
    SpatialInertia::sphere(1.0, 0.1),
    SpatialTransform::from_translation(Vector3::new(0.2, 0.0, 0.0)));

// Set initial height: q = [x, y, z, qw, qx, qy, qz]
robot.set_joint_q(0, &[0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0]);
```

### Branching tree (humanoid)

```rust
let mut humanoid = ArticulatedBody::new();
humanoid.set_gravity(Vector3::new(0.0, -9.81, 0.0));

// Torso (floating base)
humanoid.add_body("torso", -1, GenJoint::Floating,
    SpatialInertia::sphere(10.0, 0.2), SpatialTransform::identity());

// Both arms branch from torso (parent = 0)
humanoid.add_body("left_arm", 0, GenJoint::Revolute { axis: Vector3::z() },
    SpatialInertia::sphere(2.0, 0.1),
    SpatialTransform::from_translation(Vector3::new(0.0, 0.3, 0.2)));

humanoid.add_body("right_arm", 0, GenJoint::Revolute { axis: Vector3::z() },
    SpatialInertia::sphere(2.0, 0.1),
    SpatialTransform::from_translation(Vector3::new(0.0, 0.3, -0.2)));
```

## Computed-Torque Control

Track a desired trajectory using inverse dynamics:

```rust
// Desired state
let q_des = vec![0.5, -0.3];
let qd_des = vec![0.0, 0.0];
let qdd_des = vec![0.0, 0.0];

// PD gains
let kp = 100.0;
let kd = 20.0;

// Compute feedforward torque via RNEA
body.qdd[0] = qdd_des[0] + kp * (q_des[0] - body.q[0]) + kd * (qd_des[0] - body.qd[0]);
body.qdd[1] = qdd_des[1] + kp * (q_des[1] - body.q[1]) + kd * (qd_des[1] - body.qd[1]);
let (tau, _) = rnea_inverse_dynamics(&body);
body.tau = tau;

// Step simulation with computed torques
aba_forward_dynamics(&mut body);
step(&mut body, &config);
```

## Gravity Compensation

Hold a pose against gravity:

```rust
let tau_grav = gravity_compensation(&body);
body.tau = tau_grav;
// Now ABA will produce zero accelerations (balanced)
```

## Forward Kinematics

Get world-frame positions and velocities:

```rust
use featherstone::kinematics::*;

let fk = forward_kinematics(&body);

// Position and orientation of each body
for i in 0..body.body_count() {
    let pos = fk.transforms[i].translation;
    let rot = fk.transforms[i].rotation;
    println!("Body {i}: pos={pos}, rot={rot}");
}

// Jacobian: maps joint velocities to body spatial velocity
let jac = body_jacobian(&body, body.body_count() - 1);  // end-effector
let v_ee = &jac * &body.qd;  // end-effector velocity

// Center of mass
let com = com_position(&body);
```

## SpatialInertia Constructors

```rust
// From mass, COM offset, and rotational inertia at COM
SpatialInertia::from_mass_inertia(mass, com_offset, inertia_at_com);

// From mass and rotational inertia at COM (COM at body frame origin)
SpatialInertia::from_mass_inertia_at_com(mass, inertia_at_com);

// Preset shapes (uniform density)
SpatialInertia::sphere(mass, radius);
SpatialInertia::cuboid(mass, half_extents);  // Vector3
SpatialInertia::cylinder(mass, radius, half_height);
```

## Thread Safety

`ArticulatedBody` is fully owned (no `Rc`, `RefCell`, or interior mutability). Each instance is independent. Safe to use from multiple threads with separate instances:

```rust
let bodies: Vec<ArticulatedBody> = (0..1000).map(|_| make_robot()).collect();

// Process in parallel (e.g., for RL vectorized environments)
bodies.par_iter_mut().for_each(|body| {
    aba_forward_dynamics(body);
    step(body, &config);
});
```