All SDK docs

Animation

Skeletal animation with keyframed clips and two-bone IK, soft bodies (cloth, blobs, jelly meshes), and ropes.

vpybone — skeletal animation

vpybone is a tree of rigid bones, posed by keyframed clips:

  • Each bone is a joint placed at an offset from its parent's joint and turned by its own rotation (a Q14 quaternion).
  • Each bone carries its own small mesh. The parts are rigid, so nothing stretches. There is no skinning.

To use it, add $(VPY_C_SDK)/vpybone.c and vpyik.c to UVM2_SRCS.

vpyb_quat vpyb_quat_axis(int32_t ax,int32_t ay,int32_t az,int angle);   /* 4096 per turn */
void vpyb_init(vpyb_skeleton *s);                          /* declare it static: about 2 KB */
int  vpyb_add(vpyb_skeleton *s,int parent,int32_t ox,int32_t oy,int32_t oz,const vpy_mesh *m);
void vpyb_pose(vpyb_skeleton *s,const int32_t pos[3],vpyb_quat rot);  /* forward kinematics */
vpy_xf vpyb_xf(const vpyb_skeleton *s,int bone);           /* a bone's frame: attach things */
int  vpyb_draw(const vpyb_skeleton *s,int br,int what);    /* VPYB_DRAW_MESH | VPYB_DRAW_LINES */
void vpyb_apply(vpyb_skeleton *s,const vpyb_clip *c,int32_t t_q8);    /* time in frames, Q8 */
void vpyb_apply_blend(vpyb_skeleton *s,const vpyb_clip *a,int32_t ta,
                      const vpyb_clip *b,int32_t tb,int w_q8);         /* walk into run */
int  vpyb_ik(vpyb_skeleton *s,int upper,const int32_t target[3],const int32_t pole[3]);
const vpyb_stats_t *vpyb_stats(void);                      /* refused, ik_refused, ik_stretched */

Clips

A clip has one track of keys (frame, rotation) per bone. The keys are sampled with normalised linear interpolation, and a clip either loops or holds at its ends. Two clips blend with a weight, so a walk can turn smoothly into a run. bone_demo drives that weight from the stick and shares the phase between the two clips.

IK

vpyb_ik takes a limb (a bone, its child and its grandchild, such as hip, knee and ankle) and hands it to two-bone IK so that the end lands on a target. Then it poses the skeleton again. That is how feet stay on uneven ground.

Quaternions are Q14, so an IK end lands within about 3 units on bones about 450 units long.

Attaching things

vpyb_xf(s, bone) returns a bone's frame, so a sword or a lamp can follow the hand.

sdk/vpy-c/tools/bone_check.c checks it against trigonometry.

vpyik — two-bone IK on its own

int vpyik_two_bone(const int32_t root[3],const int32_t target[3],int32_t l1,int32_t l2,
                   const int32_t pole[3],int32_t joint[3],int32_t end[3]);   /* 1 = reached */

This works out where the elbow (or knee) goes so that the hand (or foot) lands on a target, bending towards a pole point. When the target is out of reach, the limb stretches straight towards it.


vpysoft — soft bodies

vpysoft is points joined by springs, which sag, wobble and flap. It uses the same Verlet core as the ropes, with a stiffness per spring in Q8: 256 means pulled fully back to length on every pass, and less gives.

You can build:

  • a cloth or a flag;
  • a blob that keeps its area;
  • a jelly version of any vpy3d mesh.

To use it, add $(VPY_C_SDK)/vpysoft.c to UVM2_SRCS.

int  vpysoft_cloth(int32_t x,int32_t y,int32_t z,int w,int h,int32_t cell,int stiff_q8);
int  vpysoft_blob(int32_t x,int32_t y,int32_t z,int n,int32_t radius,int stiff_q8,int pressure_q8);
int  vpysoft_mesh(const vpy_mesh *m,int32_t x,int32_t y,int32_t z,int stiff_q8);
void vpysoft_pin(int body,int i,int32_t x,int32_t y,int32_t z);   /* every frame, to move it */
void vpysoft_push(int body,int32_t vx,int32_t vy,int32_t vz);     /* a hit, a gust */
void vpysoft_set_budget(int strokes);                              /* per draw call; default 160 */
void vpysoft_step(void);                                          /* once per frame */
void vpysoft_draw(int body,int br,int occlude);  void vpysoft_draw2d(int body,int br);
const vpysoft_stats_t *vpysoft_stats(void);      /* stretch, area_error_q8, drawn, shed, refused */

How the shapes hold together:

  • Blobs keep their area by pushing their rim outwards along the area's gradient on every pass. Without that pressure, a dropped ring kept 85% of its area. With it, 99%.
  • Meshes get a hidden centre point tied to every vertex, because edges alone fold flat.

Drawing and collisions:

  • Only the structural springs are drawn, one stroke each, at VPY_PRI_LOW and within a budget. What the budget leaves out is counted.
  • Soft bodies collide with the floor only, not with each other and not with vpyphys bodies.

sdk/vpy-c/tools/soft_check.c checks it. soft_demo is the worked example.


vpyrope — ropes and chains

void vpyrope_reset(void);
void vpyrope_set_gravity(int32_t gx,int32_t gy,int32_t gz);   /* units/s² */
void vpyrope_set_floor(int on,int32_t y);
void vpyrope_set_iterations(int n);                           /* default 8 */
int  vpyrope_new(int32_t ax,int32_t ay,int32_t az, int32_t bx,int32_t by,int32_t bz, int links);
void vpyrope_free(int rope);
void vpyrope_pin(int rope,int i,int32_t x,int32_t y,int32_t z);   /* every frame, to move it */
void vpyrope_unpin(int rope,int i);
int  vpyrope_points(int rope);                                /* links + 1, or 0 */
void vpyrope_point(int rope,int i,int32_t *x,int32_t *y,int32_t *z);
void vpyrope_step(void);                                      /* once per frame at 50 Hz */
void vpyrope_draw(int rope,int br,int occlude);   void vpyrope_draw2d(int rope,int br);
const vpyrope_stats_t *vpyrope_stats(void);

A rope is a chain of Verlet points held to their link length, with pins the game moves. It draws as one chained polyline, which is the cheapest shape there is on a vector beam. vpyrope_stats()->stretch reports how far the worst link was pulled.

sdk/vpy-c/tools/rope_check.c checks a rope's hang and a pendulum's period.