text: #include "api.h"
  #include "physics/physics.h"
  #include "core/ref.h"
  
  StringEntry lovrShapeType[] = {
    [SHAPE_SPHERE] = ENTRY("sphere"),
    [SHAPE_BOX] = ENTRY("box"),
    [SHAPE_CAPSULE] = ENTRY("capsule"),
    [SHAPE_CYLINDER] = ENTRY("cylinder"),
    [SHAPE_MESH] = ENTRY("mesh"),
    { 0 }
  };
  
  StringEntry lovrJointType[] = {
    [JOINT_BALL] = ENTRY("ball"),
    [JOINT_DISTANCE] = ENTRY("distance"),
    [JOINT_HINGE] = ENTRY("hinge"),
    [JOINT_SLIDER] = ENTRY("slider"),
    { 0 }
  };
  
  static int l_lovrPhysicsNewWorld(lua_State* L) {
    float xg = luax_optfloat(L, 1, 0.f);
    float yg = luax_optfloat(L, 2, -9.81f);
    float zg = luax_optfloat(L, 3, 0.f);
    bool allowSleep = lua_gettop(L) < 4 || lua_toboolean(L, 4);
    const char* tags[16];
    int tagCount;
    if (lua_type(L, 5) == LUA_TTABLE) {
      tagCount = luax_len(L, 5);
      for (int i = 0; i < tagCount; i++) {
        lua_rawgeti(L, -1, i + 1);
        if (lua_isstring(L, -1)) {
          tags[i] = lua_tostring(L, -1);
        } else {
          return luaL_error(L, "World tags must be a table of strings");
        }
        lua_pop(L, 1);
      }
    } else {
      tagCount = 0;
    }
    World* world = lovrWorldCreate(xg, yg, zg, allowSleep, tags, tagCount);
    luax_pushtype(L, World, world);
    lovrRelease(World, world);
    return 1;
  }
  
  static int l_lovrPhysicsNewBallJoint(lua_State* L) {
    Collider* a = luax_checktype(L, 1, Collider);
    Collider* b = luax_checktype(L, 2, Collider);
    float anchor[4];
    luax_readvec3(L, 3, anchor, NULL);
    BallJoint* joint = lovrBallJointCreate(a, b, anchor[0], anchor[1], anchor[2]);
    luax_pushtype(L, BallJoint, joint);
    lovrRelease(Joint, joint);
    return 1;
  }
  
  static int l_lovrPhysicsNewBoxShape(lua_State* L) {
    float size[4];
    luax_readscale(L, 1, size, 1, NULL);
    BoxShape* box = lovrBoxShapeCreate(size[0], size[1], size[2]);
    luax_pushtype(L, BoxShape, box);
    lovrRelease(Shape, box);
    return 1;
  }
  
  static int l_lovrPhysicsNewCapsuleShape(lua_State* L) {
    float radius = luax_optfloat(L, 1, 1.f);
    float length = luax_optfloat(L, 2, 1.f);
    CapsuleShape* capsule = lovrCapsuleShapeCreate(radius, length);
    luax_pushtype(L, CapsuleShape, capsule);
    lovrRelease(Shape, capsule);
    return 1;
  }
  
  static int l_lovrPhysicsNewCylinderShape(lua_State* L) {
    float radius = luax_optfloat(L, 1, 1.f);
    float length = luax_optfloat(L, 2, 1.f);
    CylinderShape* cylinder = lovrCylinderShapeCreate(radius, length);
    luax_pushtype(L, CylinderShape, cylinder);
    lovrRelease(Shape, cylinder);
    return 1;
  }
  
  static int l_lovrPhysicsNewDistanceJoint(lua_State* L) {
    Collider* a = luax_checktype(L, 1, Collider);
    Collider* b = luax_checktype(L, 2, Collider);
    float anchor1[4], anchor2[4];
    int index = luax_readvec3(L, 3, anchor1, NULL);
    luax_readvec3(L, index, anchor2, NULL);
    DistanceJoint* joint = lovrDistanceJointCreate(a, b, anchor1[0], anchor1[1], anchor1[2],
      anchor2[0], anchor2[1], anchor2[2]);
    luax_pushtype(L, DistanceJoint, joint);
    lovrRelease(Joint, joint);
    return 1;
  }
  
  static int l_lovrPhysicsNewHingeJoint(lua_State* L) {
    Collider* a = luax_checktype(L, 1, Collider);
    Collider* b = luax_checktype(L, 2, Collider);
    float anchor[4], axis[4];
    int index = luax_readvec3(L, 3, anchor, NULL);
    luax_readvec3(L, index, axis, NULL);
    HingeJoint* joint = lovrHingeJointCreate(a, b, anchor[0], anchor[1], anchor[2], axis[0], axis[1], axis[2]);
    luax_pushtype(L, HingeJoint, joint);
    lovrRelease(Joint, joint);
    return 1;
  }
  
  static int l_lovrPhysicsNewSliderJoint(lua_State* L) {
    Collider* a = luax_checktype(L, 1, Collider);
    Collider* b = luax_checktype(L, 2, Collider);
    float axis[4];
    luax_readvec3(L, 3, axis, NULL);
    SliderJoint* joint = lovrSliderJointCreate(a, b, axis[0], axis[1], axis[2]);
    luax_pushtype(L, SliderJoint, joint);
    lovrRelease(Joint, joint);
    return 1;
  }
  
  static int l_lovrPhysicsNewSphereShape(lua_State* L) {
    float radius = luax_optfloat(L, 1, 1.f);
    SphereShape* sphere = lovrSphereShapeCreate(radius);
    luax_pushtype(L, SphereShape, sphere);
    lovrRelease(Shape, sphere);
    return 1;
  }
  
  static const luaL_Reg lovrPhysics[] = {
    { "newWorld", l_lovrPhysicsNewWorld },
    { "newBallJoint", l_lovrPhysicsNewBallJoint },
    { "newBoxShape", l_lovrPhysicsNewBoxShape },
    { "newCapsuleShape", l_lovrPhysicsNewCapsuleShape },
    { "newCylinderShape", l_lovrPhysicsNewCylinderShape },
    { "newDistanceJoint", l_lovrPhysicsNewDistanceJoint },
    { "newHingeJoint", l_lovrPhysicsNewHingeJoint },
    { "newSliderJoint", l_lovrPhysicsNewSliderJoint },
    { "newSphereShape", l_lovrPhysicsNewSphereShape },
    { NULL, NULL }
  };
  
  int luaopen_lovr_physics(lua_State* L) {
    lua_newtable(L);
    luax_register(L, lovrPhysics);
    luax_registertype(L, World);
    luax_registertype(L, Collider);
    luax_registertype(L, BallJoint);
    luax_registertype(L, DistanceJoint);
    luax_registertype(L, HingeJoint);
    luax_registertype(L, SliderJoint);
    luax_registertype(L, SphereShape);
    luax_registertype(L, BoxShape);
    luax_registertype(L, CapsuleShape);
    luax_registertype(L, CylinderShape);
    luax_registertype(L, MeshShape);
    if (lovrPhysicsInit()) {
      luax_atexit(L, lovrPhysicsDestroy);
    }
    return 1;
  }
  