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_LOWand 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.