My only gripe is with Vec3_new() function in "Memory Ownership" section.
It assumes you want a single malloc of Vec3. It tries to behave as if you are doing a 'new' in an OOP language.
Let the programmer decide the size of it.
Mock example (not tested)
struct Vec3* Vec3_new(size_t size)
{
if(size <= 0) {
// todo: handle properly
return NULL;
}
struct Vec3 *v = malloc(sizeof(struct Vec3) * size);
size_t i;
for(i = 0; i < size; i++) {
v[i].x = 0.0F;
v[i].y = 0.0F;
v[i].z = 0.0F;
}
return v;
}