static void push_sweep_result (lua_State *L, const SweepResult &r)
{
        lua_pushnumber(L, r.dist);
        push_rbody(L, r.rb);
        // normal is documented as being object space but is actually world space
        Vector3 normal = /*r.rb->getOrientation()* */r.n.normalisedCopy();
        push_v3(L, normal);
        push_string(L, phys_mats.getMaterial(r.material)->name);
}
static int global_physics_get_gravity (lua_State *L)
{
TRY_START
        check_args(L, 0);
        Vector3 gravity = Vector3(physics_option(PHYSICS_GRAVITY_X), physics_option(PHYSICS_GRAVITY_Y), physics_option(PHYSICS_GRAVITY_Z));
        push_v3(L, gravity);
        return 1;
TRY_END
}
static int rbody_world_to_local (lua_State *L)
{
TRY_START
        check_args(L, 2);
        GET_UD_MACRO(RigidBody, self, 1, RBODY_TAG);
        Vector3 a = check_v3(L, 2);
        Vector3 result = self.getOrientation().inverse() * (a - self.getPosition());
        push_v3(L, result);
        return 1;
TRY_END
}
static int plot_v3_index(lua_State *L)
{
TRY_START
        typedef PlotV3::Map Map;
        typedef PlotV3::MI MI;
        check_args(L,2);
        GET_UD_MACRO(PlotV3,self,1,PLOT_V3_TAG);
        if (lua_type(L,2)==LUA_TNUMBER) {
                float k = check_float(L,2);
                push_v3(L,self[k]);
        } else {
                std::string key  = luaL_checkstring(L,2);
                if (key=="minX") {
                        lua_pushnumber(L,self.minX());
                } else if (key=="maxX") {
                        lua_pushnumber(L,self.maxX());
                } else if (key=="points") {
                        Map data = self.getPoints();
                        lua_createtable(L, data.size(), 0);
                        for (MI i=data.begin(), i_=data.end() ; i!=i_ ; ++i) {
                                lua_pushnumber(L,i->first);
                                push_v3(L,i->second);
                                lua_settable(L,-3);
                        }
                } else if (key=="tangents") {
                        Map data = self.getTangents();
                        lua_createtable(L, data.size(), 0);
                        for (MI i=data.begin(), i_=data.end() ; i!=i_ ; ++i) {
                                lua_pushnumber(L,i->first);
                                push_v3(L,i->second);
                                lua_settable(L,-3);
                        }
                } else {
                        my_lua_error(L,"Not a readable PlotV3 member: "+key);
                }
        }
        return 1;
TRY_END
}
 void pushResults (lua_State *L, int func_index, int err_handler)
 {
         for (ResultMap::iterator i=resultMap.begin(), i_=resultMap.end() ; i!=i_ ; ++i) {
                 RigidBody *body = i->first;
                 Results &results = i->second;
                 for (Results::iterator j=results.begin(), j_=results.end() ; j!=j_ ; ++j) {
                         if (body->destroyed()) continue;
                         lua_pushvalue(L, func_index);
                         push_rbody(L, body);
                         lua_pushnumber(L, results.size());
                         push_v3(L, j->pos);
                         push_v3(L, j->wpos);
                         push_v3(L, j->normal);
                         lua_pushnumber(L, j->penetration);
                         push_string(L, phys_mats.getMaterial(j->m)->name);
                         int status = lua_pcall(L, 7, 0, err_handler);
                         if (status) {
                                 lua_pop(L, 1); // error message, already printed
                                 break;
                         }
                 }
         }
 }
static int rbody_local_vel (lua_State *L)
{
TRY_START
        check_args(L, 3);
        GET_UD_MACRO(RigidBody, self, 1, RBODY_TAG);
        Vector3 pos = check_v3(L, 2);
        bool world_space = check_bool(L, 3);
        Vector3 local_pos = pos;
        if (world_space) {
                local_pos -= self.getPosition();
        }
        Vector3 local_vel = self.getLocalVelocity(local_pos);
        push_v3(L, local_vel);
        return 1;
TRY_END
}
static int rbody_index (lua_State *L)
{
TRY_START
        check_args(L, 2);
        GET_UD_MACRO(RigidBody, self, 1, RBODY_TAG);
        const char *key = luaL_checkstring(L, 2);
        if (!::strcmp(key, "force")) {
                push_cfunction(L, rbody_force);
        } else if (!::strcmp(key, "impulse")) {
                push_cfunction(L, rbody_impulse);
        } else if (!::strcmp(key, "torque")) {
                push_cfunction(L, rbody_torque);
        } else if (!::strcmp(key, "torqueImpulse")) {
                push_cfunction(L, rbody_torque_impulse);

        } else if (!::strcmp(key, "procObjMaterials")) {
                std::vector<int> mats;
                self.colMesh->getProcObjMaterials(mats);
                lua_createtable(L, mats.size(), 0);
                for (size_t j=0 ; j<mats.size(); ++j) {
                        lua_pushstring(L, phys_mats.getMaterial(mats[j])->name.c_str());
                        lua_rawseti(L, -2, j+1);
                }
        } else if (!::strcmp(key, "scatter")) {
                push_cfunction(L, rbody_scatter);
        } else if (!::strcmp(key, "rangedScatter")) {
                push_cfunction(L, rbody_ranged_scatter);

        } else if (!::strcmp(key, "activate")) {
                push_cfunction(L, rbody_activate);
        } else if (!::strcmp(key, "deactivate")) {
                push_cfunction(L, rbody_deactivate);
        } else if (!::strcmp(key, "destroy")) {
                push_cfunction(L, rbody_destroy);

        } else if (!::strcmp(key, "linearSleepThreshold")) {
                lua_pushnumber(L, self.getLinearSleepThreshold());
        } else if (!::strcmp(key, "angularSleepThreshold")) {
                lua_pushnumber(L, self.getAngularSleepThreshold());

        } else if (!::strcmp(key, "worldPosition")) {
                push_v3(L, self.getPosition());
        } else if (!::strcmp(key, "worldOrientation")) {
                push_quat(L, self.getOrientation());

        } else if (!::strcmp(key, "localToWorld")) {
                push_cfunction(L, rbody_local_to_world);
        } else if (!::strcmp(key, "worldToLocal")) {
                push_cfunction(L, rbody_world_to_local);

        } else if (!::strcmp(key, "linearVelocity")) {
                push_v3(L, self.getLinearVelocity());
        } else if (!::strcmp(key, "angularVelocity")) {
                push_v3(L, self.getAngularVelocity());
        } else if (!::strcmp(key, "getLocalVelocity")) {
                push_cfunction(L, rbody_local_vel);

        } else if (!::strcmp(key, "contactProcessingThreshold")) {
                lua_pushnumber(L, self.getContactProcessingThreshold());

        } else if (!::strcmp(key, "linearDamping")) {
                lua_pushnumber(L, self.getLinearDamping());
        } else if (!::strcmp(key, "angularDamping")) {
                lua_pushnumber(L, self.getAngularDamping());

        } else if (!::strcmp(key, "mass")) {
                lua_pushnumber(L, self.getMass());
        } else if (!::strcmp(key, "inertia")) {
                push_v3(L, self.getInertia());
        } else if (!::strcmp(key, "ghost")) {
                lua_pushboolean(L, self.getGhost());

        } else if (!::strcmp(key, "numParts")) {
                lua_pushnumber(L, self.getNumElements());
        } else if (!::strcmp(key, "getPartEnabled")) {
                push_cfunction(L, rbody_get_part_enabled);
        } else if (!::strcmp(key, "setPartEnabled")) {
                push_cfunction(L, rbody_set_part_enabled);
        } else if (!::strcmp(key, "getPartPositionInitial")) {
                push_cfunction(L, rbody_get_part_position_initial);
        } else if (!::strcmp(key, "getPartPositionOffset")) {
                push_cfunction(L, rbody_get_part_position_offset);
        } else if (!::strcmp(key, "setPartPositionOffset")) {
                push_cfunction(L, rbody_set_part_position_offset);
        } else if (!::strcmp(key, "getPartOrientationInitial")) {
                push_cfunction(L, rbody_get_part_orientation_initial);
        } else if (!::strcmp(key, "getPartOrientationOffset")) {
                push_cfunction(L, rbody_get_part_orientation_offset);
        } else if (!::strcmp(key, "setPartOrientationOffset")) {
                push_cfunction(L, rbody_set_part_orientation_offset);

        } else if (!::strcmp(key, "meshName")) {
                push_string(L, self.colMesh->getName());

        } else if (!::strcmp(key, "owner")) {
                if (self.owner.isNull()) {
                        lua_pushnil(L);
                } else {
                        push_gritobj(L, self.owner);
                }

        } else if (!::strcmp(key, "updateCallback")) {
                self.updateCallbackPtr.push(L);
        } else if (!::strcmp(key, "stepCallback")) {
                self.stepCallbackPtr.push(L);
        } else if (!::strcmp(key, "collisionCallback")) {
                self.collisionCallbackPtr.push(L);
        } else if (!::strcmp(key, "stabiliseCallback")) {
                self.stabiliseCallbackPtr.push(L);
        } else {
                my_lua_error(L, "Not a readable RigidBody member: "+std::string(key));
        }
        return 1;
TRY_END
}
static int gritobj_index (lua_State *L)
{
TRY_START
        check_args(L,2);
        GET_UD_MACRO(GritObjectPtr,self,1,GRITOBJ_TAG);
        std::string key = check_string(L,2);
        if (key=="destroy") {
                push_cfunction(L,gritobj_destroy);
        } else if (key=="activated") {
                lua_pushboolean(L,self->isActivated());
        } else if (key=="near") {
                push_gritobj(L,self->getNearObj());
        } else if (key=="far") {
                push_gritobj(L,self->getFarObj());
        } else if (key=="fade") {
                lua_pushnumber(L,self->getFade());
        } else if (key=="updateSphere") {
                push_cfunction(L,gritobj_update_sphere);
        } else if (key=="pos") {
                push_v3(L, self->getPos());
        } else if (key=="renderingDistance") {
                lua_pushnumber(L, self->getR());
        } else if (key=="deactivate") {
                push_cfunction(L,gritobj_deactivate);
        } else if (key=="activate") {
                push_cfunction(L,gritobj_activate);
        } else if (key=="instance") {
                self->pushLuaTable(L);
        } else if (key=="addDiskResource") {
                push_cfunction(L,gritobj_add_disk_resource);
        } else if (key=="reloadDiskResources") {
                push_cfunction(L,gritobj_reload_disk_resource);
/*
        } else if (key=="getAdvancePrepareHints") {
                push_cfunction(L,gritobj_get_advance_prepare_hints);
*/
        } else if (key=="destroyed") {
                lua_pushboolean(L,self->getClass()==NULL);
        } else if (key=="class") {
                GritClass *c = self->getClass();
                if (c==NULL) my_lua_error(L,"GritObject destroyed");
                push_gritcls(L,c);
        } else if (key=="className") {
                GritClass *c = self->getClass();
                if (c==NULL) my_lua_error(L,"GritObject destroyed");
                lua_pushstring(L,c->name.c_str());
        } else if (key=="name") {
                lua_pushstring(L,self->name.c_str());
        } else if (key=="needsFrameCallbacks") {
                lua_pushboolean(L,self->getNeedsFrameCallbacks());
        } else if (key=="needsStepCallbacks") {
                lua_pushboolean(L,self->getNeedsStepCallbacks());
        } else if (key=="dump") {
                self->userValues.dump(L);
        } else {
                GritClass *c = self->getClass();
                if (c==NULL) my_lua_error(L,"GritObject destroyed");
                const char *err = self->userValues.luaGet(L);
                if (err) my_lua_error(L, err);
                if (!lua_isnil(L,-1)) return 1;
                lua_pop(L,1);
                // try class instead
                c->get(L,key);
        }
        return 1;
TRY_END
}