diff --git a/src/dusk/scene/scene.c b/src/dusk/scene/scene.c index d147522e..5b2c25ce 100644 --- a/src/dusk/scene/scene.c +++ b/src/dusk/scene/scene.c @@ -18,60 +18,13 @@ #include "console/console.h" #include "util/string.h" #include "script/scriptmanager.h" +#include "script/module/scene/modulescene.h" scene_t SCENE; -// Releases the current scene class ref and rebuilds Scene.set global. -static void sceneReset(void) { - lua_State *L = SCRIPT_MANAGER.mainContext.luaState; - if(SCENE.scriptRef != LUA_NOREF) { - luaL_unref(L, LUA_REGISTRYINDEX, SCENE.scriptRef); - SCENE.scriptRef = LUA_NOREF; - } - lua_newtable(L); - lua_pushcfunction(L, sceneSetLua); - lua_setfield(L, -2, "set"); - lua_setglobal(L, "Scene"); -} - -// Calls sceneClass:method() if it exists. Returns an error if the call fails. -static errorret_t sceneCall(const char_t *method) { - lua_State *L = SCRIPT_MANAGER.mainContext.luaState; - - if(SCENE.scriptRef == LUA_NOREF || SCENE.scriptRef == LUA_REFNIL) { - errorOk(); - } - - lua_rawgeti(L, LUA_REGISTRYINDEX, SCENE.scriptRef); - - if(!lua_istable(L, -1)) { - lua_pop(L, 1); - errorThrow("Scene script ref %d is not a table", SCENE.scriptRef); - } - - lua_getfield(L, -1, method); - - if(!lua_isfunction(L, -1)) { - lua_pop(L, 2); - errorOk(); - } - - lua_pushvalue(L, -2); // self - - if(lua_pcall(L, 1, 0, 0) != LUA_OK) { - const char_t *err = lua_tostring(L, -1); - lua_pop(L, 2); // error + scene table - errorThrow("Scene:%s failed: %s", method, err); - } - - lua_pop(L, 1); // scene table - errorOk(); -} - errorret_t sceneInit(void) { memoryZero(&SCENE, sizeof(scene_t)); SCENE.scriptRef = LUA_NOREF; - sceneReset(); errorOk(); } @@ -86,8 +39,8 @@ errorret_t sceneUpdate(void) { errorChain(sceneSetImmediate(SCENE.sceneNext)); } - if(SCENE.scriptActive) { - errorChain(sceneCall("update")); + if(SCENE.sceneActive) { + errorChain(moduleSceneCall(SCRIPT_MANAGER.mainContext.luaState, "update")); } errorOk(); @@ -209,14 +162,16 @@ errorret_t sceneSetImmediate(const char_t *scene) { ); } - if(SCENE.scriptActive) { - errorChain(sceneCall("dispose")); - SCENE.scriptActive = false; + if(SCENE.sceneActive) { + errorChain(moduleSceneCall( + SCRIPT_MANAGER.mainContext.luaState, "dispose" + )); + SCENE.sceneActive = false; } // Wipe Scene back to a clean table before loading the next file so custom // methods from the previous scene (e.g. Scene.doThing) cannot bleed through. - sceneReset(); + moduleSceneReset(SCRIPT_MANAGER.mainContext.luaState); stringCopy( SCENE.sceneCurrent, @@ -240,8 +195,8 @@ errorret_t sceneSetImmediate(const char_t *scene) { SCENE.scriptRef = luaL_ref(L, LUA_REGISTRYINDEX); - errorChain(sceneCall("init")); - SCENE.scriptActive = true; + errorChain(moduleSceneCall(SCRIPT_MANAGER.mainContext.luaState, "init")); + SCENE.sceneActive = true; } errorOk(); @@ -256,18 +211,6 @@ void sceneSet(const char_t *scene) { } errorret_t sceneDispose(void) { - errorChain(sceneSetImmediate(NULL)); + errorChain(moduleSceneCall(SCRIPT_MANAGER.mainContext.luaState, "dispose")); errorOk(); } - -int sceneSetLua(lua_State *L) { - assertNotNull(L, "Lua state cannot be NULL"); - - if(!lua_isstring(L, 1)) { - luaL_error(L, "Scene.set requires a string argument"); - return 0; - } - - sceneSet(lua_tostring(L, 1)); - return 0; -} diff --git a/src/dusk/scene/scene.h b/src/dusk/scene/scene.h index 9b79e5d6..3f4eb8fe 100644 --- a/src/dusk/scene/scene.h +++ b/src/dusk/scene/scene.h @@ -13,7 +13,7 @@ #define SCENE_EVENT_UPDATE_MAX 16 typedef struct { - bool_t scriptActive; + bool_t sceneActive; int scriptRef; char_t sceneCurrent[ASSET_FILE_PATH_MAX]; char_t sceneNext[ASSET_FILE_PATH_MAX]; @@ -21,10 +21,56 @@ typedef struct { extern scene_t SCENE; +/** + * Initializes the scene manager + * + * @return Any error state that happened. + */ errorret_t sceneInit(void); + +/** + * Ticks the scene manager, may or may not call the scene's update method + * depending on time state. + * + * @return Any error state that happened. + */ errorret_t sceneUpdate(void); + +/** + * Renders the scene, happens regardless of time. + * + * @return Any error state that happened. + */ errorret_t sceneRender(void); + +/** + * Internal method to immediately switch scenes, this will also delete some of + * the lua state, which can cause problems if called during a lua callback, so + * this is managed by the scene manager. + * + * @param scene Scene to switch to (asset file path). + * @return Any error state that happened. + */ errorret_t sceneSetImmediate(const char_t *scene); + +/** + * Requests the scene manager to change scenes when it is next able to. This + * will not guarantee that the scene is valid or without errors. + * + * @param scene Which scene to set. + */ void sceneSet(const char_t *scene); + +/** + * Disposes of the current scene. + * @return Any error state that happened. + */ errorret_t sceneDispose(void); -int sceneSetLua(lua_State *L); + +/** + * Lua callback when Scene.set is invoked. + * + * @param L Lua state, expects the first argument to be the scene name (string). + * @return 0, does not return any values to Lua. + */ +int sceneSetLua(lua_State *L); \ No newline at end of file diff --git a/src/dusk/script/module/display/modulecolor.h b/src/dusk/script/module/display/modulecolor.h index de7838d3..0291dbec 100644 --- a/src/dusk/script/module/display/modulecolor.h +++ b/src/dusk/script/module/display/modulecolor.h @@ -53,7 +53,7 @@ static int moduleColorToString(lua_State *L) { return 1; } -static int moduleColorFuncColor(lua_State *L) { +static int moduleColorCreate(lua_State *L) { assertNotNull(L, "Lua state cannot be NULL."); if( @@ -114,7 +114,7 @@ static void moduleColor(lua_State *L) { } lua_pop(L, 1); - lua_register(L, "color", moduleColorFuncColor); + lua_register(L, "color", moduleColorCreate); lua_register(L, "colorRainbow", moduleColorRainbow); luaL_dostring(L, COLOR_SCRIPT); diff --git a/src/dusk/script/module/display/modulespritebatch.h b/src/dusk/script/module/display/modulespritebatch.h index b457b503..040cdfa1 100644 --- a/src/dusk/script/module/display/modulespritebatch.h +++ b/src/dusk/script/module/display/modulespritebatch.h @@ -27,8 +27,8 @@ static int moduleSpriteBatchPush(lua_State *L) { assertNotNull(L, "Lua state is null"); vec2 min, max; - luaVec2Check(L, 1, min); - luaVec2Check(L, 2, max); + moduleVec2Check(L, 1, min); + moduleVec2Check(L, 2, max); color_t *color = NULL; if(lua_gettop(L) < 3 || lua_isnil(L, 3)) { @@ -41,7 +41,7 @@ static int moduleSpriteBatchPush(lua_State *L) { float_t u0 = 0.0f, v0 = 0.0f, u1 = 1.0f, v1 = 1.0f; if(lua_gettop(L) >= 4 && !lua_isnil(L, 4)) { - vec4 uv; luaVec4Check(L, 4, uv); + vec4 uv; moduleVec4Check(L, 4, uv); u0 = uv[0]; v0 = uv[1]; u1 = uv[2]; v1 = uv[3]; } diff --git a/src/dusk/script/module/display/moduletext.h b/src/dusk/script/module/display/moduletext.h index 03b39f7e..bbcb81cb 100644 --- a/src/dusk/script/module/display/moduletext.h +++ b/src/dusk/script/module/display/moduletext.h @@ -14,7 +14,7 @@ static int moduleTextDraw(lua_State *L) { assertNotNull(L, "Lua state is null"); - vec2 pos; luaVec2Check(L, 1, pos); + vec2 pos; moduleVec2Check(L, 1, pos); if(!lua_isstring(L, 2)) { return luaL_error(L, "Text to draw must be a string"); @@ -61,7 +61,7 @@ static int moduleTextMeasure(lua_State *L) { textMeasure(text, &DEFAULT_FONT_TILESET, &w, &h); vec2 size = { (float_t)w, (float_t)h }; - luaVec2Push(L, size); + moduleVec2Push(L, size); return 1; } diff --git a/src/dusk/script/module/display/moduletileset.h b/src/dusk/script/module/display/moduletileset.h index fd9f4b08..577c62fc 100644 --- a/src/dusk/script/module/display/moduletileset.h +++ b/src/dusk/script/module/display/moduletileset.h @@ -49,7 +49,7 @@ static int moduleTilesetTileGetUV(lua_State *l) { assertNotNull(ts, "Tileset pointer cannot be NULL."); uint16_t tileIndex = (uint16_t)luaL_checknumber(l, 2); vec4 uv; tilesetTileGetUV(ts, tileIndex, uv); - luaVec4Push(l, uv); + moduleVec4Push(l, uv); return 1; } @@ -60,7 +60,7 @@ static int moduleTilesetPositionGetUV(lua_State *l) { uint16_t column = (uint16_t)luaL_checknumber(l, 2); uint16_t row = (uint16_t)luaL_checknumber(l, 3); vec4 uv; tilesetPositionGetUV(ts, column, row, uv); - luaVec4Push(l, uv); + moduleVec4Push(l, uv); return 1; } diff --git a/src/dusk/script/module/entity/component/moduleentitycamera.h b/src/dusk/script/module/entity/component/moduleentitycamera.h new file mode 100644 index 00000000..d975889f --- /dev/null +++ b/src/dusk/script/module/entity/component/moduleentitycamera.h @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/module/modulebase.h" +#include "entity/entity.h" +#include "entity/component/display/entitycamera.h" + +typedef struct { + entityid_t entityId; + componentid_t compId; +} entitycam_handle_t; + +/** + * __index metamethod for camera component userdata. + * + * @param L Lua state. Arg 1: entitycam userdata. Arg 2: key string. + * @return 1. + */ +static int moduleEntityCameraIndex(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + entitycam_handle_t *h = luaL_checkudata(L, 1, "entitycam_mt"); + assertNotNull(h, "invalid entitycam userdata"); + + const char *k = luaL_checkstring(L, 2); + assertStrLenMin(k, 1, "property key cannot be empty"); + + if(stringCompare(k, "zNear") == 0) { + lua_pushnumber(L, entityCameraGetZNear(h->entityId, h->compId)); return 1; + } else if(stringCompare(k, "zFar") == 0) { + lua_pushnumber(L, entityCameraGetZFar(h->entityId, h->compId)); return 1; + } + + lua_getmetatable(L, 1); + lua_getfield(L, -1, k); + return 1; +} + +/** + * __newindex metamethod for camera component userdata. + * + * @param L Lua state. Arg 1: entitycam userdata. Arg 2: key string. Arg 3: number. + * @return 0. + */ +static int moduleEntityCameraNewIndex(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + entitycam_handle_t *h = luaL_checkudata(L, 1, "entitycam_mt"); + assertNotNull(h, "invalid entitycam userdata"); + + const char *k = luaL_checkstring(L, 2); + assertStrLenMin(k, 1, "property key cannot be empty"); + + if(stringCompare(k, "zNear") == 0) { + entityCameraSetZNear(h->entityId, h->compId, (float_t)luaL_checknumber(L, 3)); + return 0; + } else if(stringCompare(k, "zFar") == 0) { + entityCameraSetZFar(h->entityId, h->compId, (float_t)luaL_checknumber(L, 3)); + return 0; + } + + luaL_error(L, "entitycam: unknown property '%s'", k); + return 0; +} + +/** + * Adds a camera component to an entity and returns its userdata handle. + * + * @param L Lua state. Arg 1: entity id (number). + * @return 1 (entitycam userdata). + */ +static int moduleEntityCameraAdd(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + entityid_t id = (entityid_t)luaL_checknumber(L, 1); + componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_CAMERA); + + entitycam_handle_t *h = lua_newuserdata(L, sizeof(entitycam_handle_t)); + assertNotNull(h, "failed to allocate entitycam userdata"); + + h->entityId = id; + h->compId = comp; + + luaL_getmetatable(L, "entitycam_mt"); + lua_setmetatable(L, -2); + return 1; +} + +/** + * Registers the camera component metatable and entityCameraAdd global. + * + * @param L Lua state. + */ +static void moduleEntityCamera(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + luaL_newmetatable(L, "entitycam_mt"); + lua_pushcfunction(L, moduleEntityCameraIndex); + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, moduleEntityCameraNewIndex); + lua_setfield(L, -2, "__newindex"); + lua_pop(L, 1); + + lua_register(L, "entityCameraAdd", moduleEntityCameraAdd); +} diff --git a/src/dusk/script/module/entity/component/moduleentitymaterial.h b/src/dusk/script/module/entity/component/moduleentitymaterial.h new file mode 100644 index 00000000..0da40e16 --- /dev/null +++ b/src/dusk/script/module/entity/component/moduleentitymaterial.h @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/module/modulebase.h" +#include "entity/entity.h" +#include "entity/component/display/entitymaterial.h" + +typedef struct { + entityid_t entityId; + componentid_t compId; +} entitymat_handle_t; + +/** + * __index metamethod for material component userdata. + * Delegates all lookups to the metatable (methods only, no plain properties). + * + * @param L Lua state. Arg 1: entitymat userdata. Arg 2: key string. + * @return 1. + */ +static int moduleEntityMaterialIndex(lua_State *L) { + lua_getmetatable(L, 1); + lua_getfield(L, -1, luaL_checkstring(L, 2)); + return 1; +} + +/** + * __newindex metamethod for material component userdata. + * Writes color (expects a color_mt userdata). Errors on unknown keys. + * + * @param L Lua state. Arg 1: entitymat userdata. Arg 2: key string. Arg 3: value. + * @return 0. + */ +static int moduleEntityMaterialNewIndex(lua_State *L) { + entitymat_handle_t *h = luaL_checkudata(L, 1, "entitymat_mt"); + const char *k = luaL_checkstring(L, 2); + if(stringCompare(k, "color") == 0) { + const color_t *col = (const color_t *)luaL_checkudata(L, 3, "color_mt"); + entityMaterialSetColor(h->entityId, h->compId, *col); + return 0; + } + luaL_error(L, "entitymat: unknown property '%s'", k); + return 0; +} + +/** + * Adds a material component to an entity and returns its userdata handle. + * + * @param L Lua state. Arg 1: entity id (number). + * @return 1 (entitymat userdata). + */ +static int moduleEntityMaterialAdd(lua_State *L) { + entityid_t id = (entityid_t)luaL_checknumber(L, 1); + componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_MATERIAL); + entitymat_handle_t *h = lua_newuserdata(L, sizeof(entitymat_handle_t)); + h->entityId = id; + h->compId = comp; + luaL_getmetatable(L, "entitymat_mt"); + lua_setmetatable(L, -2); + return 1; +} + +/** + * Registers the material component metatable and entityMaterialAdd global. + * + * @param L Lua state. + */ +static void moduleEntityMaterial(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + luaL_newmetatable(L, "entitymat_mt"); + lua_pushcfunction(L, moduleEntityMaterialIndex); lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, moduleEntityMaterialNewIndex); lua_setfield(L, -2, "__newindex"); + lua_pop(L, 1); + + lua_register(L, "entityMaterialAdd", moduleEntityMaterialAdd); +} diff --git a/src/dusk/script/module/entity/component/moduleentitymesh.h b/src/dusk/script/module/entity/component/moduleentitymesh.h new file mode 100644 index 00000000..32174b85 --- /dev/null +++ b/src/dusk/script/module/entity/component/moduleentitymesh.h @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/module/modulebase.h" +#include "entity/entity.h" +#include "entity/component/display/entitymesh.h" + +typedef struct { + entityid_t entityId; + componentid_t compId; +} entitymesh_handle_t; + +/** + * __index metamethod for mesh component userdata. + * Delegates all lookups to the metatable (methods only, no plain properties). + * + * @param L Lua state. Arg 1: entitymesh userdata. Arg 2: key string. + * @return 1. + */ +static int moduleEntityMeshIndex(lua_State *L) { + lua_getmetatable(L, 1); + lua_getfield(L, -1, luaL_checkstring(L, 2)); + return 1; +} + +/** + * Generates an XZ-aligned plane mesh owned by this component. + * + * @param L Lua state. Arg 1: entitymesh userdata. Arg 2: width. Arg 3: depth. + * @return 0. + */ +static int moduleEntityMeshGeneratePlane(lua_State *L) { + entitymesh_handle_t *h = luaL_checkudata(L, 1, "entitymesh_mt"); + float_t w = (float_t)luaL_checknumber(L, 2); + float_t d = (float_t)luaL_checknumber(L, 3); + errorret_t err = entityMeshGeneratePlane(h->entityId, h->compId, w, d); + if(err.code != ERROR_OK) luaL_error(L, "entityMesh: generatePlane failed"); + return 0; +} + +/** + * Generates a Y-axis capsule mesh owned by this component. + * + * @param L Lua state. Arg 1: entitymesh userdata. Arg 2: radius. Arg 3: halfHeight. + * @return 0. + */ +static int moduleEntityMeshGenerateCapsule(lua_State *L) { + entitymesh_handle_t *h = luaL_checkudata(L, 1, "entitymesh_mt"); + float_t r = (float_t)luaL_checknumber(L, 2); + float_t hh = (float_t)luaL_checknumber(L, 3); + errorret_t err = entityMeshGenerateCapsule(h->entityId, h->compId, r, hh); + if(err.code != ERROR_OK) luaL_error(L, "entityMesh: generateCapsule failed"); + return 0; +} + +/** + * Adds a mesh component to an entity and returns its userdata handle. + * + * @param L Lua state. Arg 1: entity id (number). + * @return 1 (entitymesh userdata). + */ +static int moduleEntityMeshAdd(lua_State *L) { + entityid_t id = (entityid_t)luaL_checknumber(L, 1); + componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_MESH); + entitymesh_handle_t *h = lua_newuserdata(L, sizeof(entitymesh_handle_t)); + h->entityId = id; + h->compId = comp; + luaL_getmetatable(L, "entitymesh_mt"); + lua_setmetatable(L, -2); + return 1; +} + +/** + * Registers the mesh component metatable and entityMeshAdd global. + * + * @param L Lua state. + */ +static void moduleEntityMesh(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + luaL_newmetatable(L, "entitymesh_mt"); + lua_pushcfunction(L, moduleEntityMeshIndex); lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, moduleEntityMeshGeneratePlane); lua_setfield(L, -2, "generatePlane"); + lua_pushcfunction(L, moduleEntityMeshGenerateCapsule); lua_setfield(L, -2, "generateCapsule"); + lua_pop(L, 1); + + lua_register(L, "entityMeshAdd", moduleEntityMeshAdd); +} diff --git a/src/dusk/script/module/entity/component/moduleentityphysics.h b/src/dusk/script/module/entity/component/moduleentityphysics.h new file mode 100644 index 00000000..8b9fd522 --- /dev/null +++ b/src/dusk/script/module/entity/component/moduleentityphysics.h @@ -0,0 +1,218 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/module/modulebase.h" +#include "entity/entity.h" +#include "entity/component/physics/entityphysics.h" + +typedef struct { + entityid_t entityId; + componentid_t compId; +} entityphysics_handle_t; + +/** + * __index metamethod for physics component userdata. + * Reads velX, velY, velZ, onGround (bool), bodyType; falls through to the + * metatable for method lookups. + * + * @param L Lua state. Arg 1: entityphysics userdata. Arg 2: key string. + * @return 1. + */ +static int moduleEntityPhysicsIndex(lua_State *L) { + entityphysics_handle_t *h = luaL_checkudata(L, 1, "entityphysics_mt"); + const char *k = luaL_checkstring(L, 2); + vec3 v; + if(stringCompare(k, "velX") == 0) { + entityPhysicsGetVelocity(h->entityId, h->compId, v); + lua_pushnumber(L, v[0]); return 1; + } else if(stringCompare(k, "velY") == 0) { + entityPhysicsGetVelocity(h->entityId, h->compId, v); + lua_pushnumber(L, v[1]); return 1; + } else if(stringCompare(k, "velZ") == 0) { + entityPhysicsGetVelocity(h->entityId, h->compId, v); + lua_pushnumber(L, v[2]); return 1; + } else if(stringCompare(k, "onGround") == 0) { + lua_pushboolean(L, entityPhysicsIsOnGround(h->entityId, h->compId)); + return 1; + } else if(stringCompare(k, "bodyType") == 0) { + lua_pushinteger(L, (lua_Integer)entityPhysicsGetBodyType(h->entityId, h->compId)); + return 1; + } + lua_getmetatable(L, 1); + lua_getfield(L, -1, k); + return 1; +} + +/** + * __newindex metamethod for physics component userdata. + * Writes velX, velY, velZ, bodyType. onGround is read-only. Errors on unknown keys. + * + * @param L Lua state. Arg 1: entityphysics userdata. Arg 2: key string. Arg 3: value. + * @return 0. + */ +static int moduleEntityPhysicsNewIndex(lua_State *L) { + entityphysics_handle_t *h = luaL_checkudata(L, 1, "entityphysics_mt"); + const char *k = luaL_checkstring(L, 2); + vec3 v; + if(stringCompare(k, "velX") == 0) { + entityPhysicsGetVelocity(h->entityId, h->compId, v); + v[0] = (float_t)luaL_checknumber(L, 3); + entityPhysicsSetVelocity(h->entityId, h->compId, v); return 0; + } else if(stringCompare(k, "velY") == 0) { + entityPhysicsGetVelocity(h->entityId, h->compId, v); + v[1] = (float_t)luaL_checknumber(L, 3); + entityPhysicsSetVelocity(h->entityId, h->compId, v); return 0; + } else if(stringCompare(k, "velZ") == 0) { + entityPhysicsGetVelocity(h->entityId, h->compId, v); + v[2] = (float_t)luaL_checknumber(L, 3); + entityPhysicsSetVelocity(h->entityId, h->compId, v); return 0; + } else if(stringCompare(k, "bodyType") == 0) { + entityPhysicsSetBodyType( + h->entityId, h->compId, + (physicsbodytype_t)luaL_checkinteger(L, 3) + ); + return 0; + } else if(stringCompare(k, "onGround") == 0) { + luaL_error(L, "entityphysics: onGround is read-only"); + return 0; + } + luaL_error(L, "entityphysics: unknown property '%s'", k); + return 0; +} + +/** + * Applies an immediate velocity impulse to the physics body. + * + * @param L Lua state. Arg 1: entityphysics userdata. Args 2-4: impulse x, y, z. + * @return 0. + */ +static int moduleEntityPhysicsApplyImpulse(lua_State *L) { + entityphysics_handle_t *h = luaL_checkudata(L, 1, "entityphysics_mt"); + vec3 impulse = { + (float_t)luaL_checknumber(L, 2), + (float_t)luaL_checknumber(L, 3), + (float_t)luaL_checknumber(L, 4) + }; + entityPhysicsApplyImpulse(h->entityId, h->compId, impulse); + return 0; +} + +/** + * Sets the physics shape to an axis-aligned box. + * + * @param L Lua state. Arg 1: entityphysics userdata. Args 2-4: half-extents x, y, z. + * @return 0. + */ +static int moduleEntityPhysicsSetShapeCube(lua_State *L) { + entityphysics_handle_t *h = luaL_checkudata(L, 1, "entityphysics_mt"); + physicsshape_t shape; + shape.type = PHYSICS_SHAPE_CUBE; + shape.data.cube.halfExtents[0] = (float_t)luaL_checknumber(L, 2); + shape.data.cube.halfExtents[1] = (float_t)luaL_checknumber(L, 3); + shape.data.cube.halfExtents[2] = (float_t)luaL_checknumber(L, 4); + entityPhysicsSetShape(h->entityId, h->compId, shape); + return 0; +} + +/** + * Sets the physics shape to a sphere. + * + * @param L Lua state. Arg 1: entityphysics userdata. Arg 2: radius. + * @return 0. + */ +static int moduleEntityPhysicsSetShapeSphere(lua_State *L) { + entityphysics_handle_t *h = luaL_checkudata(L, 1, "entityphysics_mt"); + physicsshape_t shape; + shape.type = PHYSICS_SHAPE_SPHERE; + shape.data.sphere.radius = (float_t)luaL_checknumber(L, 2); + entityPhysicsSetShape(h->entityId, h->compId, shape); + return 0; +} + +/** + * Sets the physics shape to a Y-axis capsule. + * + * @param L Lua state. Arg 1: entityphysics userdata. Arg 2: radius. Arg 3: halfHeight. + * @return 0. + */ +static int moduleEntityPhysicsSetShapeCapsule(lua_State *L) { + entityphysics_handle_t *h = luaL_checkudata(L, 1, "entityphysics_mt"); + physicsshape_t shape; + shape.type = PHYSICS_SHAPE_CAPSULE; + shape.data.capsule.radius = (float_t)luaL_checknumber(L, 2); + shape.data.capsule.halfHeight = (float_t)luaL_checknumber(L, 3); + entityPhysicsSetShape(h->entityId, h->compId, shape); + return 0; +} + +/** + * Sets the physics shape to an infinite plane. + * + * @param L Lua state. Arg 1: entityphysics userdata. Args 2-4: normal x, y, z. + * Arg 5: distance from origin. + * @return 0. + */ +static int moduleEntityPhysicsSetShapePlane(lua_State *L) { + entityphysics_handle_t *h = luaL_checkudata(L, 1, "entityphysics_mt"); + physicsshape_t shape; + shape.type = PHYSICS_SHAPE_PLANE; + shape.data.plane.normal[0] = (float_t)luaL_checknumber(L, 2); + shape.data.plane.normal[1] = (float_t)luaL_checknumber(L, 3); + shape.data.plane.normal[2] = (float_t)luaL_checknumber(L, 4); + shape.data.plane.distance = (float_t)luaL_checknumber(L, 5); + entityPhysicsSetShape(h->entityId, h->compId, shape); + return 0; +} + +/** + * Adds a physics component to an entity and returns its userdata handle. + * + * @param L Lua state. Arg 1: entity id (number). + * @return 1 (entityphysics userdata). + */ +static int moduleEntityPhysicsAdd(lua_State *L) { + entityid_t id = (entityid_t)luaL_checknumber(L, 1); + componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_PHYSICS); + entityphysics_handle_t *h = lua_newuserdata(L, sizeof(entityphysics_handle_t)); + h->entityId = id; + h->compId = comp; + luaL_getmetatable(L, "entityphysics_mt"); + lua_setmetatable(L, -2); + return 1; +} + +/** + * Registers the physics component metatable, entityPhysicsAdd global, and + * PHYSICS_BODY_* / PHYSICS_SHAPE_* integer constants. + * + * @param L Lua state. + */ +static void moduleEntityPhysics(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + luaL_newmetatable(L, "entityphysics_mt"); + lua_pushcfunction(L, moduleEntityPhysicsIndex); lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, moduleEntityPhysicsNewIndex); lua_setfield(L, -2, "__newindex"); + lua_pushcfunction(L, moduleEntityPhysicsApplyImpulse); lua_setfield(L, -2, "applyImpulse"); + lua_pushcfunction(L, moduleEntityPhysicsSetShapeCube); lua_setfield(L, -2, "setShapeCube"); + lua_pushcfunction(L, moduleEntityPhysicsSetShapeSphere); lua_setfield(L, -2, "setShapeSphere"); + lua_pushcfunction(L, moduleEntityPhysicsSetShapeCapsule); lua_setfield(L, -2, "setShapeCapsule"); + lua_pushcfunction(L, moduleEntityPhysicsSetShapePlane); lua_setfield(L, -2, "setShapePlane"); + lua_pop(L, 1); + + lua_register(L, "entityPhysicsAdd", moduleEntityPhysicsAdd); + + lua_pushinteger(L, PHYSICS_BODY_STATIC); lua_setglobal(L, "PHYSICS_BODY_STATIC"); + lua_pushinteger(L, PHYSICS_BODY_DYNAMIC); lua_setglobal(L, "PHYSICS_BODY_DYNAMIC"); + lua_pushinteger(L, PHYSICS_BODY_KINEMATIC); lua_setglobal(L, "PHYSICS_BODY_KINEMATIC"); + + lua_pushinteger(L, PHYSICS_SHAPE_CUBE); lua_setglobal(L, "PHYSICS_SHAPE_CUBE"); + lua_pushinteger(L, PHYSICS_SHAPE_SPHERE); lua_setglobal(L, "PHYSICS_SHAPE_SPHERE"); + lua_pushinteger(L, PHYSICS_SHAPE_CAPSULE); lua_setglobal(L, "PHYSICS_SHAPE_CAPSULE"); + lua_pushinteger(L, PHYSICS_SHAPE_PLANE); lua_setglobal(L, "PHYSICS_SHAPE_PLANE"); +} diff --git a/src/dusk/script/module/entity/component/moduleentityposition.h b/src/dusk/script/module/entity/component/moduleentityposition.h new file mode 100644 index 00000000..60e4ee37 --- /dev/null +++ b/src/dusk/script/module/entity/component/moduleentityposition.h @@ -0,0 +1,175 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/module/modulebase.h" +#include "entity/entity.h" +#include "entity/component/display/entityposition.h" + +typedef struct { + entityid_t entityId; + componentid_t compId; +} entitypos_handle_t; + +/** + * __index metamethod for position component userdata. + * Reads x, y, z, rotX, rotY, rotZ, scaleX, scaleY, scaleZ; falls through to + * the metatable for method lookups. + * + * @param L Lua state. Arg 1: entitypos userdata. Arg 2: key string. + * @return 1. + */ +static int moduleEntityPositionIndex(lua_State *L) { + entitypos_handle_t *h = luaL_checkudata(L, 1, "entitypos_mt"); + const char *k = luaL_checkstring(L, 2); + vec3 v; + if(stringCompare(k, "x") == 0) { + entityPositionGetPosition(h->entityId, h->compId, v); + lua_pushnumber(L, v[0]); return 1; + } else if(stringCompare(k, "y") == 0) { + entityPositionGetPosition(h->entityId, h->compId, v); + lua_pushnumber(L, v[1]); return 1; + } else if(stringCompare(k, "z") == 0) { + entityPositionGetPosition(h->entityId, h->compId, v); + lua_pushnumber(L, v[2]); return 1; + } else if(stringCompare(k, "rotX") == 0) { + entityPositionGetRotation(h->entityId, h->compId, v); + lua_pushnumber(L, v[0]); return 1; + } else if(stringCompare(k, "rotY") == 0) { + entityPositionGetRotation(h->entityId, h->compId, v); + lua_pushnumber(L, v[1]); return 1; + } else if(stringCompare(k, "rotZ") == 0) { + entityPositionGetRotation(h->entityId, h->compId, v); + lua_pushnumber(L, v[2]); return 1; + } else if(stringCompare(k, "scaleX") == 0) { + entityPositionGetScale(h->entityId, h->compId, v); + lua_pushnumber(L, v[0]); return 1; + } else if(stringCompare(k, "scaleY") == 0) { + entityPositionGetScale(h->entityId, h->compId, v); + lua_pushnumber(L, v[1]); return 1; + } else if(stringCompare(k, "scaleZ") == 0) { + entityPositionGetScale(h->entityId, h->compId, v); + lua_pushnumber(L, v[2]); return 1; + } + lua_getmetatable(L, 1); + lua_getfield(L, -1, k); + return 1; +} + +/** + * __newindex metamethod for position component userdata. + * Writes x, y, z, rotX, rotY, rotZ, scaleX, scaleY, scaleZ and rebuilds the + * transform. Errors on unknown keys. + * + * @param L Lua state. Arg 1: entitypos userdata. Arg 2: key string. Arg 3: number. + * @return 0. + */ +static int moduleEntityPositionNewIndex(lua_State *L) { + entitypos_handle_t *h = luaL_checkudata(L, 1, "entitypos_mt"); + const char *k = luaL_checkstring(L, 2); + vec3 v; + if(stringCompare(k, "x") == 0) { + entityPositionGetPosition(h->entityId, h->compId, v); + v[0] = (float_t)luaL_checknumber(L, 3); + entityPositionSetPosition(h->entityId, h->compId, v); return 0; + } else if(stringCompare(k, "y") == 0) { + entityPositionGetPosition(h->entityId, h->compId, v); + v[1] = (float_t)luaL_checknumber(L, 3); + entityPositionSetPosition(h->entityId, h->compId, v); return 0; + } else if(stringCompare(k, "z") == 0) { + entityPositionGetPosition(h->entityId, h->compId, v); + v[2] = (float_t)luaL_checknumber(L, 3); + entityPositionSetPosition(h->entityId, h->compId, v); return 0; + } else if(stringCompare(k, "rotX") == 0) { + entityPositionGetRotation(h->entityId, h->compId, v); + v[0] = (float_t)luaL_checknumber(L, 3); + entityPositionSetRotation(h->entityId, h->compId, v); return 0; + } else if(stringCompare(k, "rotY") == 0) { + entityPositionGetRotation(h->entityId, h->compId, v); + v[1] = (float_t)luaL_checknumber(L, 3); + entityPositionSetRotation(h->entityId, h->compId, v); return 0; + } else if(stringCompare(k, "rotZ") == 0) { + entityPositionGetRotation(h->entityId, h->compId, v); + v[2] = (float_t)luaL_checknumber(L, 3); + entityPositionSetRotation(h->entityId, h->compId, v); return 0; + } else if(stringCompare(k, "scaleX") == 0) { + entityPositionGetScale(h->entityId, h->compId, v); + v[0] = (float_t)luaL_checknumber(L, 3); + entityPositionSetScale(h->entityId, h->compId, v); return 0; + } else if(stringCompare(k, "scaleY") == 0) { + entityPositionGetScale(h->entityId, h->compId, v); + v[1] = (float_t)luaL_checknumber(L, 3); + entityPositionSetScale(h->entityId, h->compId, v); return 0; + } else if(stringCompare(k, "scaleZ") == 0) { + entityPositionGetScale(h->entityId, h->compId, v); + v[2] = (float_t)luaL_checknumber(L, 3); + entityPositionSetScale(h->entityId, h->compId, v); return 0; + } + luaL_error(L, "entitypos: unknown property '%s'", k); + return 0; +} + +/** + * Rotates the entity to face a world-space target point. + * An optional up vector may be provided (args 5-7); defaults to (0,1,0). + * + * @param L Lua state. Arg 1: entitypos userdata. Args 2-4: target x, y, z. + * Args 5-7 (optional): up x, y, z. + * @return 0. + */ +static int moduleEntityPositionLookAt(lua_State *L) { + entitypos_handle_t *h = luaL_checkudata(L, 1, "entitypos_mt"); + vec3 target = { + (float_t)luaL_checknumber(L, 2), + (float_t)luaL_checknumber(L, 3), + (float_t)luaL_checknumber(L, 4) + }; + vec3 up = { 0.0f, 1.0f, 0.0f }; + if(lua_gettop(L) >= 7) { + up[0] = (float_t)luaL_checknumber(L, 5); + up[1] = (float_t)luaL_checknumber(L, 6); + up[2] = (float_t)luaL_checknumber(L, 7); + } + vec3 eye; + entityPositionGetPosition(h->entityId, h->compId, eye); + entityPositionLookAt(h->entityId, h->compId, target, up, eye); + return 0; +} + +/** + * Adds a position component to an entity and returns its userdata handle. + * + * @param L Lua state. Arg 1: entity id (number). + * @return 1 (entitypos userdata). + */ +static int moduleEntityPositionAdd(lua_State *L) { + entityid_t id = (entityid_t)luaL_checknumber(L, 1); + componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_POSITION); + entitypos_handle_t *h = lua_newuserdata(L, sizeof(entitypos_handle_t)); + h->entityId = id; + h->compId = comp; + luaL_getmetatable(L, "entitypos_mt"); + lua_setmetatable(L, -2); + return 1; +} + +/** + * Registers the position component metatable and entityPositionAdd global. + * + * @param L Lua state. + */ +static void moduleEntityPosition(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + luaL_newmetatable(L, "entitypos_mt"); + lua_pushcfunction(L, moduleEntityPositionIndex); lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, moduleEntityPositionNewIndex); lua_setfield(L, -2, "__newindex"); + lua_pushcfunction(L, moduleEntityPositionLookAt); lua_setfield(L, -2, "lookAt"); + lua_pop(L, 1); + + lua_register(L, "entityPositionAdd", moduleEntityPositionAdd); +} diff --git a/src/dusk/script/module/entity/moduleentity.h b/src/dusk/script/module/entity/moduleentity.h index c7ecde2e..d72bb528 100644 --- a/src/dusk/script/module/entity/moduleentity.h +++ b/src/dusk/script/module/entity/moduleentity.h @@ -9,239 +9,11 @@ #include "script/module/modulebase.h" #include "entity/entity.h" #include "entity/entitymanager.h" -#include "entity/component/display/entityposition.h" -#include "entity/component/display/entitycamera.h" -#include "entity/component/display/entitymesh.h" -#include "entity/component/display/entitymaterial.h" - -// ============================================================================ -// Handles -// ============================================================================ - -typedef struct { entityid_t entityId; componentid_t compId; } entitypos_handle_t; -typedef struct { entityid_t entityId; componentid_t compId; } entitycam_handle_t; -typedef struct { entityid_t entityId; componentid_t compId; } entitymesh_handle_t; -typedef struct { entityid_t entityId; componentid_t compId; } entitymat_handle_t; - -// ============================================================================ -// entityPosition -// ============================================================================ - -static int _posIndex(lua_State *L) { - entitypos_handle_t *h = luaL_checkudata(L, 1, "entitypos_mt"); - const char *k = luaL_checkstring(L, 2); - vec3 v; - if(stringCompare(k, "x") == 0) { - entityPositionGetPosition(h->entityId, h->compId, v); - lua_pushnumber(L, v[0]); return 1; - } else if(stringCompare(k, "y") == 0) { - entityPositionGetPosition(h->entityId, h->compId, v); - lua_pushnumber(L, v[1]); return 1; - } else if(stringCompare(k, "z") == 0) { - entityPositionGetPosition(h->entityId, h->compId, v); - lua_pushnumber(L, v[2]); return 1; - } else if(stringCompare(k, "rotX") == 0) { - entityPositionGetRotation(h->entityId, h->compId, v); - lua_pushnumber(L, v[0]); return 1; - } else if(stringCompare(k, "rotY") == 0) { - entityPositionGetRotation(h->entityId, h->compId, v); - lua_pushnumber(L, v[1]); return 1; - } else if(stringCompare(k, "rotZ") == 0) { - entityPositionGetRotation(h->entityId, h->compId, v); - lua_pushnumber(L, v[2]); return 1; - } else if(stringCompare(k, "scaleX") == 0) { - entityPositionGetScale(h->entityId, h->compId, v); - lua_pushnumber(L, v[0]); return 1; - } else if(stringCompare(k, "scaleY") == 0) { - entityPositionGetScale(h->entityId, h->compId, v); - lua_pushnumber(L, v[1]); return 1; - } else if(stringCompare(k, "scaleZ") == 0) { - entityPositionGetScale(h->entityId, h->compId, v); - lua_pushnumber(L, v[2]); return 1; - } - lua_getmetatable(L, 1); - lua_getfield(L, -1, k); - return 1; -} - -static int _posNewindex(lua_State *L) { - entitypos_handle_t *h = luaL_checkudata(L, 1, "entitypos_mt"); - const char *k = luaL_checkstring(L, 2); - vec3 v; - if(stringCompare(k, "x") == 0) { - entityPositionGetPosition(h->entityId, h->compId, v); - v[0] = (float_t)luaL_checknumber(L, 3); - entityPositionSetPosition(h->entityId, h->compId, v); return 0; - } else if(stringCompare(k, "y") == 0) { - entityPositionGetPosition(h->entityId, h->compId, v); - v[1] = (float_t)luaL_checknumber(L, 3); - entityPositionSetPosition(h->entityId, h->compId, v); return 0; - } else if(stringCompare(k, "z") == 0) { - entityPositionGetPosition(h->entityId, h->compId, v); - v[2] = (float_t)luaL_checknumber(L, 3); - entityPositionSetPosition(h->entityId, h->compId, v); return 0; - } else if(stringCompare(k, "rotX") == 0) { - entityPositionGetRotation(h->entityId, h->compId, v); - v[0] = (float_t)luaL_checknumber(L, 3); - entityPositionSetRotation(h->entityId, h->compId, v); return 0; - } else if(stringCompare(k, "rotY") == 0) { - entityPositionGetRotation(h->entityId, h->compId, v); - v[1] = (float_t)luaL_checknumber(L, 3); - entityPositionSetRotation(h->entityId, h->compId, v); return 0; - } else if(stringCompare(k, "rotZ") == 0) { - entityPositionGetRotation(h->entityId, h->compId, v); - v[2] = (float_t)luaL_checknumber(L, 3); - entityPositionSetRotation(h->entityId, h->compId, v); return 0; - } else if(stringCompare(k, "scaleX") == 0) { - entityPositionGetScale(h->entityId, h->compId, v); - v[0] = (float_t)luaL_checknumber(L, 3); - entityPositionSetScale(h->entityId, h->compId, v); return 0; - } else if(stringCompare(k, "scaleY") == 0) { - entityPositionGetScale(h->entityId, h->compId, v); - v[1] = (float_t)luaL_checknumber(L, 3); - entityPositionSetScale(h->entityId, h->compId, v); return 0; - } else if(stringCompare(k, "scaleZ") == 0) { - entityPositionGetScale(h->entityId, h->compId, v); - v[2] = (float_t)luaL_checknumber(L, 3); - entityPositionSetScale(h->entityId, h->compId, v); return 0; - } - luaL_error(L, "entitypos: unknown property '%s'", k); - return 0; -} - -static int _posLookAt(lua_State *L) { - entitypos_handle_t *h = luaL_checkudata(L, 1, "entitypos_mt"); - vec3 target = { - (float_t)luaL_checknumber(L, 2), - (float_t)luaL_checknumber(L, 3), - (float_t)luaL_checknumber(L, 4) - }; - vec3 up = { 0.0f, 1.0f, 0.0f }; - if(lua_gettop(L) >= 7) { - up[0] = (float_t)luaL_checknumber(L, 5); - up[1] = (float_t)luaL_checknumber(L, 6); - up[2] = (float_t)luaL_checknumber(L, 7); - } - vec3 eye; - entityPositionGetPosition(h->entityId, h->compId, eye); - entityPositionLookAt(h->entityId, h->compId, target, up, eye); - return 0; -} - -static int _entityPositionAdd(lua_State *L) { - entityid_t id = (entityid_t)luaL_checknumber(L, 1); - componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_POSITION); - entitypos_handle_t *h = lua_newuserdata(L, sizeof(entitypos_handle_t)); - h->entityId = id; - h->compId = comp; - luaL_getmetatable(L, "entitypos_mt"); - lua_setmetatable(L, -2); - return 1; -} - -// ============================================================================ -// entityCamera -// ============================================================================ - -static int _camIndex(lua_State *L) { - entitycam_handle_t *h = luaL_checkudata(L, 1, "entitycam_mt"); - const char *k = luaL_checkstring(L, 2); - if(stringCompare(k, "zNear") == 0) { - lua_pushnumber(L, entityCameraGetZNear(h->entityId, h->compId)); return 1; - } else if(stringCompare(k, "zFar") == 0) { - lua_pushnumber(L, entityCameraGetZFar(h->entityId, h->compId)); return 1; - } - lua_getmetatable(L, 1); lua_getfield(L, -1, k); return 1; -} - -static int _camNewindex(lua_State *L) { - entitycam_handle_t *h = luaL_checkudata(L, 1, "entitycam_mt"); - const char *k = luaL_checkstring(L, 2); - if(stringCompare(k, "zNear") == 0) { - entityCameraSetZNear(h->entityId, h->compId, (float_t)luaL_checknumber(L, 3)); - return 0; - } else if(stringCompare(k, "zFar") == 0) { - entityCameraSetZFar(h->entityId, h->compId, (float_t)luaL_checknumber(L, 3)); - return 0; - } - luaL_error(L, "entitycam: unknown property '%s'", k); return 0; -} - -static int _entityCameraAdd(lua_State *L) { - entityid_t id = (entityid_t)luaL_checknumber(L, 1); - componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_CAMERA); - entitycam_handle_t *h = lua_newuserdata(L, sizeof(entitycam_handle_t)); - h->entityId = id; h->compId = comp; - luaL_getmetatable(L, "entitycam_mt"); lua_setmetatable(L, -2); - return 1; -} - -// ============================================================================ -// entityMesh -// ============================================================================ - -static int _meshIndex(lua_State *L) { - lua_getmetatable(L, 1); lua_getfield(L, -1, luaL_checkstring(L, 2)); return 1; -} - -static int _meshGeneratePlane(lua_State *L) { - entitymesh_handle_t *h = luaL_checkudata(L, 1, "entitymesh_mt"); - float_t w = (float_t)luaL_checknumber(L, 2); - float_t d = (float_t)luaL_checknumber(L, 3); - errorret_t err = entityMeshGeneratePlane(h->entityId, h->compId, w, d); - if(err.code != ERROR_OK) luaL_error(L, "entityMesh: generatePlane failed"); - return 0; -} - -static int _meshGenerateCapsule(lua_State *L) { - entitymesh_handle_t *h = luaL_checkudata(L, 1, "entitymesh_mt"); - float_t r = (float_t)luaL_checknumber(L, 2); - float_t hh = (float_t)luaL_checknumber(L, 3); - errorret_t err = entityMeshGenerateCapsule(h->entityId, h->compId, r, hh); - if(err.code != ERROR_OK) luaL_error(L, "entityMesh: generateCapsule failed"); - return 0; -} - -static int _entityMeshAdd(lua_State *L) { - entityid_t id = (entityid_t)luaL_checknumber(L, 1); - componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_MESH); - entitymesh_handle_t *h = lua_newuserdata(L, sizeof(entitymesh_handle_t)); - h->entityId = id; h->compId = comp; - luaL_getmetatable(L, "entitymesh_mt"); lua_setmetatable(L, -2); - return 1; -} - -// ============================================================================ -// entityMaterial -// ============================================================================ - -static int _matIndex(lua_State *L) { - lua_getmetatable(L, 1); lua_getfield(L, -1, luaL_checkstring(L, 2)); return 1; -} - -static int _matNewindex(lua_State *L) { - entitymat_handle_t *h = luaL_checkudata(L, 1, "entitymat_mt"); - const char *k = luaL_checkstring(L, 2); - if(stringCompare(k, "color") == 0) { - const color_t *col = (const color_t *)luaL_checkudata(L, 3, "color_mt"); - entityMaterialSetColor(h->entityId, h->compId, *col); - return 0; - } - luaL_error(L, "entitymat: unknown property '%s'", k); return 0; -} - -static int _entityMaterialAdd(lua_State *L) { - entityid_t id = (entityid_t)luaL_checknumber(L, 1); - componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_MATERIAL); - entitymat_handle_t *h = lua_newuserdata(L, sizeof(entitymat_handle_t)); - h->entityId = id; h->compId = comp; - luaL_getmetatable(L, "entitymat_mt"); lua_setmetatable(L, -2); - return 1; -} - -// ============================================================================ -// Component type Lua constants (auto-generated from componentlist.h) -// ============================================================================ +#include "component/moduleentityposition.h" +#include "component/moduleentitycamera.h" +#include "component/moduleentitymesh.h" +#include "component/moduleentitymaterial.h" +#include "component/moduleentityphysics.h" #define X(enumName, type, field, init, dispose) \ "COMPONENT_TYPE_" #enumName " = \"" #field "\"\n" @@ -250,10 +22,6 @@ static const char_t *COMPONENT_TYPE_SCRIPT = ; #undef X -// ============================================================================ -// Entity base class Lua script -// ============================================================================ - static const char_t *ENTITY_SCRIPT = "Entity = {}\n" "Entity.__index = Entity\n" @@ -269,6 +37,7 @@ static const char_t *ENTITY_SCRIPT = " [COMPONENT_TYPE_CAMERA] = entityCameraAdd,\n" " [COMPONENT_TYPE_MESH] = entityMeshAdd,\n" " [COMPONENT_TYPE_MATERIAL] = entityMaterialAdd,\n" + " [COMPONENT_TYPE_PHYSICS] = entityPhysicsAdd,\n" "}\n" "\n" "function Entity.new()\n" @@ -287,55 +56,45 @@ static const char_t *ENTITY_SCRIPT = "end\n" ; -// ============================================================================ -// entityAdd / entityRemove -// ============================================================================ - -static int _entityAdd(lua_State *L) { +/** + * Allocates a new entity and pushes its id onto the Lua stack. + * + * @param L Lua state. + * @return 1 (entity id number). + */ +static int moduleEntityAdd(lua_State *L) { lua_pushnumber(L, (lua_Number)entityManagerAdd()); return 1; } -static int _entityRemove(lua_State *L) { +/** + * Disposes the entity with the given id. + * + * @param L Lua state. Arg 1: entity id (number). + * @return 0. + */ +static int moduleEntityRemove(lua_State *L) { entityDispose((entityid_t)luaL_checknumber(L, 1)); return 0; } -// ============================================================================ -// Register -// ============================================================================ - +/** + * Registers all entity and component modules, component type constants, and + * the Entity base class into the Lua state. + * + * @param L Lua state. + */ static void moduleEntity(lua_State *L) { assertNotNull(L, "Lua state cannot be NULL"); - luaL_newmetatable(L, "entitypos_mt"); - lua_pushcfunction(L, _posIndex); lua_setfield(L, -2, "__index"); - lua_pushcfunction(L, _posNewindex); lua_setfield(L, -2, "__newindex"); - lua_pushcfunction(L, _posLookAt); lua_setfield(L, -2, "lookAt"); - lua_pop(L, 1); + moduleEntityPosition(L); + moduleEntityCamera(L); + moduleEntityMesh(L); + moduleEntityMaterial(L); + moduleEntityPhysics(L); - luaL_newmetatable(L, "entitycam_mt"); - lua_pushcfunction(L, _camIndex); lua_setfield(L, -2, "__index"); - lua_pushcfunction(L, _camNewindex); lua_setfield(L, -2, "__newindex"); - lua_pop(L, 1); - - luaL_newmetatable(L, "entitymesh_mt"); - lua_pushcfunction(L, _meshIndex); lua_setfield(L, -2, "__index"); - lua_pushcfunction(L, _meshGeneratePlane); lua_setfield(L, -2, "generatePlane"); - lua_pushcfunction(L, _meshGenerateCapsule); lua_setfield(L, -2, "generateCapsule"); - lua_pop(L, 1); - - luaL_newmetatable(L, "entitymat_mt"); - lua_pushcfunction(L, _matIndex); lua_setfield(L, -2, "__index"); - lua_pushcfunction(L, _matNewindex); lua_setfield(L, -2, "__newindex"); - lua_pop(L, 1); - - lua_register(L, "entityAdd", _entityAdd); - lua_register(L, "entityRemove", _entityRemove); - lua_register(L, "entityPositionAdd", _entityPositionAdd); - lua_register(L, "entityCameraAdd", _entityCameraAdd); - lua_register(L, "entityMeshAdd", _entityMeshAdd); - lua_register(L, "entityMaterialAdd", _entityMaterialAdd); + lua_register(L, "entityAdd", moduleEntityAdd); + lua_register(L, "entityRemove", moduleEntityRemove); luaL_dostring(L, COMPONENT_TYPE_SCRIPT); luaL_dostring(L, ENTITY_SCRIPT); diff --git a/src/dusk/script/module/math/modulemat4.h b/src/dusk/script/module/math/modulemat4.h index 07b0e3a5..b31276fa 100644 --- a/src/dusk/script/module/math/modulemat4.h +++ b/src/dusk/script/module/math/modulemat4.h @@ -10,108 +10,290 @@ #include "modulevec3.h" #include "modulevec4.h" -static void luaMat4Push(lua_State *L, mat4 m) { +/** + * Pushes a new mat4 userdata onto the Lua stack with the mat4_mt metatable. + * + * @param L Lua state. + * @param m Source matrix to copy. + */ +static void moduleMat4Push(lua_State *L, mat4 m) { + assertNotNull(L, "Lua state cannot be NULL"); + mat4 *u = (mat4 *)lua_newuserdata(L, sizeof(mat4)); glm_mat4_copy(m, *u); luaL_getmetatable(L, "mat4_mt"); lua_setmetatable(L, -2); } -static void luaMat4Check(lua_State *L, int idx, mat4 out) { +/** + * Reads a mat4 userdata from the given stack index into out. + * + * @param L Lua state. + * @param idx Stack index of the mat4 userdata. + * @param out Destination matrix. + */ +static void moduleMat4Check(lua_State *L, int idx, mat4 out) { + assertNotNull(L, "Lua state cannot be NULL"); + mat4 *m = (mat4 *)luaL_checkudata(L, idx, "mat4_mt"); glm_mat4_copy(*m, out); } -static int mat4Index(lua_State *L) { +/** + * __index metamethod for mat4 userdata. + * Delegates all lookups to the metatable (methods only). + * + * @param L Lua state. Arg 1: mat4 userdata. Arg 2: key string. + * @return 1. + */ +static int moduleMat4Index(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + lua_getmetatable(L, 1); lua_getfield(L, -1, luaL_checkstring(L, 2)); + lua_remove(L, -2); return 1; } -static int mat4OpMul(lua_State *L) { +/** + * __mul metamethod: returns a * b as a new mat4. + * + * @param L Lua state. Arg 1: mat4. Arg 2: mat4. + * @return 1 (new mat4). + */ +static int moduleMat4OpMul(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + mat4 *a = (mat4 *)luaL_checkudata(L, 1, "mat4_mt"); + assertNotNull(a, "invalid mat4 userdata"); + mat4 *b = (mat4 *)luaL_checkudata(L, 2, "mat4_mt"); - mat4 r; glm_mat4_mul(*a, *b, r); - luaMat4Push(L, r); return 1; + assertNotNull(b, "invalid mat4 userdata"); + + mat4 r; + glm_mat4_mul(*a, *b, r); + moduleMat4Push(L, r); + return 1; } -static int mat4OpToString(lua_State *L) { - lua_pushstring(L, "mat4(...)"); return 1; +/** + * __tostring metamethod: returns a placeholder string. + * + * @param L Lua state. Arg 1: mat4. + * @return 1 (string). + */ +static int moduleMat4OpToString(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + lua_pushstring(L, "mat4(...)"); + return 1; } -static int mat4Transpose(lua_State *L) { +/** + * Returns the transpose of a mat4 as a new mat4. + * + * @param L Lua state. Arg 1: mat4. + * @return 1 (new mat4). + */ +static int moduleMat4Transpose(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + mat4 *m = (mat4 *)luaL_checkudata(L, 1, "mat4_mt"); - mat4 r; glm_mat4_transpose_to(*m, r); - luaMat4Push(L, r); return 1; + assertNotNull(m, "invalid mat4 userdata"); + + mat4 r; + glm_mat4_transpose_to(*m, r); + moduleMat4Push(L, r); + return 1; } -static int mat4Inverse(lua_State *L) { +/** + * Returns the inverse of a mat4 as a new mat4. + * + * @param L Lua state. Arg 1: mat4. + * @return 1 (new mat4). + */ +static int moduleMat4Inverse(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + mat4 *m = (mat4 *)luaL_checkudata(L, 1, "mat4_mt"); - mat4 r; glm_mat4_inv(*m, r); - luaMat4Push(L, r); return 1; + assertNotNull(m, "invalid mat4 userdata"); + + mat4 r; + glm_mat4_inv(*m, r); + moduleMat4Push(L, r); + return 1; } -static int mat4MulVec3(lua_State *L) { +/** + * Multiplies a mat4 by a vec3, with an optional w component (default 1.0). + * + * @param L Lua state. Arg 1: mat4. Arg 2: vec3. Arg 3 (optional): number w. + * @return 1 (new vec3). + */ +static int moduleMat4MulVec3(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + mat4 *m = (mat4 *)luaL_checkudata(L, 1, "mat4_mt"); - vec3 v; luaVec3Check(L, 2, v); + assertNotNull(m, "invalid mat4 userdata"); + + vec3 v; + moduleVec3Check(L, 2, v); + float_t w = lua_gettop(L) >= 3 ? (float_t)luaL_checknumber(L, 3) : 1.0f; - vec3 r; glm_mat4_mulv3(*m, v, w, r); - luaVec3Push(L, r); return 1; + vec3 r; + glm_mat4_mulv3(*m, v, w, r); + moduleVec3Push(L, r); + return 1; } -static int mat4MulVec4(lua_State *L) { +/** + * Multiplies a mat4 by a vec4. + * + * @param L Lua state. Arg 1: mat4. Arg 2: vec4. + * @return 1 (new vec4). + */ +static int moduleMat4MulVec4(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + mat4 *m = (mat4 *)luaL_checkudata(L, 1, "mat4_mt"); - vec4 v; luaVec4Check(L, 2, v); - vec4 r; glm_mat4_mulv(*m, v, r); - luaVec4Push(L, r); return 1; + assertNotNull(m, "invalid mat4 userdata"); + + vec4 v; + moduleVec4Check(L, 2, v); + + vec4 r; + glm_mat4_mulv(*m, v, r); + moduleVec4Push(L, r); + return 1; } -static int mat4Translate(lua_State *L) { +/** + * Returns a copy of a mat4 translated by a vec3. + * + * @param L Lua state. Arg 1: mat4. Arg 2: vec3. + * @return 1 (new mat4). + */ +static int moduleMat4Translate(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + mat4 *m = (mat4 *)luaL_checkudata(L, 1, "mat4_mt"); - vec3 v; luaVec3Check(L, 2, v); - mat4 r; glm_mat4_copy(*m, r); + assertNotNull(m, "invalid mat4 userdata"); + + vec3 v; + moduleVec3Check(L, 2, v); + + mat4 r; + glm_mat4_copy(*m, r); glm_translate(r, v); - luaMat4Push(L, r); return 1; + moduleMat4Push(L, r); + return 1; } -static int mat4Scale(lua_State *L) { +/** + * Returns a copy of a mat4 scaled by a vec3. + * + * @param L Lua state. Arg 1: mat4. Arg 2: vec3. + * @return 1 (new mat4). + */ +static int moduleMat4Scale(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + mat4 *m = (mat4 *)luaL_checkudata(L, 1, "mat4_mt"); - vec3 v; luaVec3Check(L, 2, v); - mat4 r; glm_mat4_copy(*m, r); + assertNotNull(m, "invalid mat4 userdata"); + + vec3 v; + moduleVec3Check(L, 2, v); + + mat4 r; + glm_mat4_copy(*m, r); glm_scale(r, v); - luaMat4Push(L, r); return 1; + moduleMat4Push(L, r); + return 1; } -static int mat4Identity(lua_State *L) { - mat4 r; glm_mat4_identity(r); - luaMat4Push(L, r); return 1; +/** + * Returns a new identity mat4. + * + * @param L Lua state. + * @return 1 (new mat4). + */ +static int moduleMat4Identity(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + mat4 r; + glm_mat4_identity(r); + moduleMat4Push(L, r); + return 1; } -static int mat4Determinant(lua_State *L) { +/** + * Returns the determinant of a mat4. + * + * @param L Lua state. Arg 1: mat4. + * @return 1 (number). + */ +static int moduleMat4Determinant(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + mat4 *m = (mat4 *)luaL_checkudata(L, 1, "mat4_mt"); - lua_pushnumber(L, (lua_Number)glm_mat4_det(*m)); return 1; + assertNotNull(m, "invalid mat4 userdata"); + + lua_pushnumber(L, (lua_Number)glm_mat4_det(*m)); + return 1; } -static int mat4Create(lua_State *L) { - mat4 m; glm_mat4_identity(m); - luaMat4Push(L, m); return 1; +/** + * Constructor: creates a new identity mat4. + * + * @param L Lua state. + * @return 1 (new mat4). + */ +static int moduleMat4Create(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + mat4 m; + glm_mat4_identity(m); + moduleMat4Push(L, m); + return 1; } -static void moduleMathMat4(lua_State *L) { - if(!luaL_newmetatable(L, "mat4_mt")) { lua_pop(L, 1); return; } +/** + * Registers the mat4 metatable and mat4 constructor global. + * + * @param L Lua state. + */ +static void moduleMat4(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); - lua_pushcfunction(L, mat4Index); lua_setfield(L, -2, "__index"); - lua_pushcfunction(L, mat4OpMul); lua_setfield(L, -2, "__mul"); - lua_pushcfunction(L, mat4OpToString); lua_setfield(L, -2, "__tostring"); - lua_pushcfunction(L, mat4Transpose); lua_setfield(L, -2, "transpose"); - lua_pushcfunction(L, mat4Inverse); lua_setfield(L, -2, "inverse"); - lua_pushcfunction(L, mat4MulVec3); lua_setfield(L, -2, "mulVec3"); - lua_pushcfunction(L, mat4MulVec4); lua_setfield(L, -2, "mulVec4"); - lua_pushcfunction(L, mat4Translate); lua_setfield(L, -2, "translate"); - lua_pushcfunction(L, mat4Scale); lua_setfield(L, -2, "scale"); - lua_pushcfunction(L, mat4Identity); lua_setfield(L, -2, "identity"); - lua_pushcfunction(L, mat4Determinant); lua_setfield(L, -2, "determinant"); + if(!luaL_newmetatable(L, "mat4_mt")) { + lua_pop(L, 1); + return; + } + + lua_pushcfunction(L, moduleMat4Index); + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, moduleMat4OpMul); + lua_setfield(L, -2, "__mul"); + lua_pushcfunction(L, moduleMat4OpToString); + lua_setfield(L, -2, "__tostring"); + lua_pushcfunction(L, moduleMat4Transpose); + lua_setfield(L, -2, "transpose"); + lua_pushcfunction(L, moduleMat4Inverse); + lua_setfield(L, -2, "inverse"); + lua_pushcfunction(L, moduleMat4MulVec3); + lua_setfield(L, -2, "mulVec3"); + lua_pushcfunction(L, moduleMat4MulVec4); + lua_setfield(L, -2, "mulVec4"); + lua_pushcfunction(L, moduleMat4Translate); + lua_setfield(L, -2, "translate"); + lua_pushcfunction(L, moduleMat4Scale); + lua_setfield(L, -2, "scale"); + lua_pushcfunction(L, moduleMat4Identity); + lua_setfield(L, -2, "identity"); + lua_pushcfunction(L, moduleMat4Determinant); + lua_setfield(L, -2, "determinant"); lua_pop(L, 1); - lua_register(L, "mat4", mat4Create); + lua_register(L, "mat4", moduleMat4Create); } diff --git a/src/dusk/script/module/math/modulemath.h b/src/dusk/script/module/math/modulemath.h index f55b608c..b4828396 100644 --- a/src/dusk/script/module/math/modulemath.h +++ b/src/dusk/script/module/math/modulemath.h @@ -12,10 +12,16 @@ #include "modulevec4.h" #include "modulemat4.h" +/** + * Registers all math modules: vec2, vec3, vec4, mat4. + * + * @param L Lua state. + */ static void moduleMath(lua_State *L) { assertNotNull(L, "Lua state cannot be NULL"); - moduleMathVec2(L); - moduleMathVec3(L); - moduleMathVec4(L); - moduleMathMat4(L); + + moduleVec2(L); + moduleVec3(L); + moduleVec4(L); + moduleMat4(L); } diff --git a/src/dusk/script/module/math/modulevec2.h b/src/dusk/script/module/math/modulevec2.h index 4f7843c7..136076c8 100644 --- a/src/dusk/script/module/math/modulevec2.h +++ b/src/dusk/script/module/math/modulevec2.h @@ -8,162 +8,440 @@ #pragma once #include "script/module/modulebase.h" -static void luaVec2Push(lua_State *L, vec2 v) { +/** + * Pushes a new vec2 userdata onto the Lua stack with the vec2_mt metatable. + * + * @param L Lua state. + * @param v Source vector to copy. + */ +static void moduleVec2Push(lua_State *L, vec2 v) { + assertNotNull(L, "Lua state cannot be NULL"); + vec2 *u = (vec2 *)lua_newuserdata(L, sizeof(vec2)); glm_vec2_copy(v, *u); luaL_getmetatable(L, "vec2_mt"); lua_setmetatable(L, -2); } -static void luaVec2Check(lua_State *L, int idx, vec2 out) { +/** + * Reads a vec2 userdata from the given stack index into out. + * + * @param L Lua state. + * @param idx Stack index of the vec2 userdata. + * @param out Destination vector. + */ +static void moduleVec2Check(lua_State *L, int idx, vec2 out) { + assertNotNull(L, "Lua state cannot be NULL"); + vec2 *v = (vec2 *)luaL_checkudata(L, idx, "vec2_mt"); glm_vec2_copy(*v, out); } -static int vec2Index(lua_State *L) { +/** + * __index metamethod for vec2 userdata. + * Supports integer indices 1-2 and string keys x, y; falls through to the + * metatable for method lookups. + * + * @param L Lua state. Arg 1: vec2 userdata. Arg 2: integer or string key. + * @return 1. + */ +static int moduleVec2Index(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec2 *v = (vec2 *)luaL_checkudata(L, 1, "vec2_mt"); + assertNotNull(v, "invalid vec2 userdata"); + const char *key = luaL_checkstring(L, 2); - if(stringCompare(key, "x") == 0) { lua_pushnumber(L, (lua_Number)(*v)[0]); return 1; } - if(stringCompare(key, "y") == 0) { lua_pushnumber(L, (lua_Number)(*v)[1]); return 1; } + assertStrLenMin(key, 1, "property key cannot be empty"); + + if(lua_isnumber(L, 2)) { + lua_Integer i = lua_tointeger(L, 2); + if(i >= 1 && i <= 2) { + lua_pushnumber(L, (lua_Number)(*v)[i - 1]); + return 1; + } + return 0; + } + + if(stringCompare(key, "x") == 0) { + lua_pushnumber(L, (lua_Number)(*v)[0]); + return 1; + } + + if(stringCompare(key, "y") == 0) { + lua_pushnumber(L, (lua_Number)(*v)[1]); + return 1; + } + lua_getmetatable(L, 1); lua_getfield(L, -1, key); + lua_remove(L, -2); return 1; } -static int vec2Newindex(lua_State *L) { +/** + * __newindex metamethod for vec2 userdata. + * Writes x, y. Errors on unknown keys. + * + * @param L Lua state. Arg 1: vec2 userdata. Arg 2: key string. Arg 3: number. + * @return 0. + */ +static int moduleVec2NewIndex(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec2 *v = (vec2 *)luaL_checkudata(L, 1, "vec2_mt"); + assertNotNull(v, "invalid vec2 userdata"); + const char *key = luaL_checkstring(L, 2); - if(stringCompare(key, "x") == 0) { (*v)[0] = (float_t)luaL_checknumber(L, 3); return 0; } - if(stringCompare(key, "y") == 0) { (*v)[1] = (float_t)luaL_checknumber(L, 3); return 0; } + assertStrLenMin(key, 1, "property key cannot be empty"); + + if(stringCompare(key, "x") == 0) { + (*v)[0] = (float_t)luaL_checknumber(L, 3); + return 0; + } + + if(stringCompare(key, "y") == 0) { + (*v)[1] = (float_t)luaL_checknumber(L, 3); + return 0; + } + luaL_error(L, "vec2: unknown property '%s'", key); return 0; } -static int vec2OpAdd(lua_State *L) { +/** + * __add metamethod: returns a + b as a new vec2. + * + * @param L Lua state. Arg 1: vec2. Arg 2: vec2. + * @return 1 (new vec2). + */ +static int moduleVec2OpAdd(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec2 *a = (vec2 *)luaL_checkudata(L, 1, "vec2_mt"); + assertNotNull(a, "invalid vec2 userdata"); + vec2 *b = (vec2 *)luaL_checkudata(L, 2, "vec2_mt"); - vec2 r; glm_vec2_add(*a, *b, r); - luaVec2Push(L, r); return 1; + assertNotNull(b, "invalid vec2 userdata"); + + vec2 r; + glm_vec2_add(*a, *b, r); + moduleVec2Push(L, r); + return 1; } -static int vec2OpSub(lua_State *L) { +/** + * __sub metamethod: returns a - b as a new vec2. + * + * @param L Lua state. Arg 1: vec2. Arg 2: vec2. + * @return 1 (new vec2). + */ +static int moduleVec2OpSub(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec2 *a = (vec2 *)luaL_checkudata(L, 1, "vec2_mt"); + assertNotNull(a, "invalid vec2 userdata"); + vec2 *b = (vec2 *)luaL_checkudata(L, 2, "vec2_mt"); - vec2 r; glm_vec2_sub(*a, *b, r); - luaVec2Push(L, r); return 1; + assertNotNull(b, "invalid vec2 userdata"); + + vec2 r; + glm_vec2_sub(*a, *b, r); + moduleVec2Push(L, r); + return 1; } -static int vec2OpMul(lua_State *L) { +/** + * __mul metamethod: returns v * scalar or scalar * v as a new vec2. + * + * @param L Lua state. Arg 1: vec2 or number. Arg 2: number or vec2. + * @return 1 (new vec2). + */ +static int moduleVec2OpMul(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec2 r; if(lua_isnumber(L, 1)) { float_t s = (float_t)lua_tonumber(L, 1); vec2 *v = (vec2 *)luaL_checkudata(L, 2, "vec2_mt"); + assertNotNull(v, "invalid vec2 userdata"); glm_vec2_scale(*v, s, r); } else { vec2 *v = (vec2 *)luaL_checkudata(L, 1, "vec2_mt"); + assertNotNull(v, "invalid vec2 userdata"); float_t s = (float_t)luaL_checknumber(L, 2); glm_vec2_scale(*v, s, r); } - luaVec2Push(L, r); return 1; + + moduleVec2Push(L, r); + return 1; } -static int vec2OpDiv(lua_State *L) { +/** + * __div metamethod: returns v / scalar as a new vec2. + * + * @param L Lua state. Arg 1: vec2. Arg 2: number. + * @return 1 (new vec2). + */ +static int moduleVec2OpDiv(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec2 *v = (vec2 *)luaL_checkudata(L, 1, "vec2_mt"); + assertNotNull(v, "invalid vec2 userdata"); + float_t s = (float_t)luaL_checknumber(L, 2); - vec2 r; glm_vec2_divs(*v, s, r); - luaVec2Push(L, r); return 1; + vec2 r; + glm_vec2_divs(*v, s, r); + moduleVec2Push(L, r); + return 1; } -static int vec2OpUnm(lua_State *L) { +/** + * __unm metamethod: returns -v as a new vec2. + * + * @param L Lua state. Arg 1: vec2. + * @return 1 (new vec2). + */ +static int moduleVec2OpUnm(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec2 *v = (vec2 *)luaL_checkudata(L, 1, "vec2_mt"); - vec2 r; glm_vec2_negate_to(*v, r); - luaVec2Push(L, r); return 1; + assertNotNull(v, "invalid vec2 userdata"); + + vec2 r; + glm_vec2_negate_to(*v, r); + moduleVec2Push(L, r); + return 1; } -static int vec2OpEq(lua_State *L) { +/** + * __eq metamethod: component-wise equality test. + * + * @param L Lua state. Arg 1: vec2. Arg 2: vec2. + * @return 1 (boolean). + */ +static int moduleVec2OpEq(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec2 *a = (vec2 *)luaL_checkudata(L, 1, "vec2_mt"); + assertNotNull(a, "invalid vec2 userdata"); + vec2 *b = (vec2 *)luaL_checkudata(L, 2, "vec2_mt"); + assertNotNull(b, "invalid vec2 userdata"); + lua_pushboolean(L, (*a)[0] == (*b)[0] && (*a)[1] == (*b)[1]); return 1; } -static int vec2OpToString(lua_State *L) { +/** + * __tostring metamethod: returns a human-readable representation. + * + * @param L Lua state. Arg 1: vec2. + * @return 1 (string). + */ +static int moduleVec2OpToString(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec2 *v = (vec2 *)luaL_checkudata(L, 1, "vec2_mt"); + assertNotNull(v, "invalid vec2 userdata"); + char buf[64]; snprintf(buf, sizeof(buf), "vec2(%.3f, %.3f)", (*v)[0], (*v)[1]); - lua_pushstring(L, buf); return 1; + lua_pushstring(L, buf); + return 1; } -static int vec2Dot(lua_State *L) { +/** + * Returns the dot product of two vec2 values. + * + * @param L Lua state. Arg 1: vec2. Arg 2: vec2. + * @return 1 (number). + */ +static int moduleVec2Dot(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec2 *a = (vec2 *)luaL_checkudata(L, 1, "vec2_mt"); + assertNotNull(a, "invalid vec2 userdata"); + vec2 *b = (vec2 *)luaL_checkudata(L, 2, "vec2_mt"); - lua_pushnumber(L, (lua_Number)glm_vec2_dot(*a, *b)); return 1; + assertNotNull(b, "invalid vec2 userdata"); + + lua_pushnumber(L, (lua_Number)glm_vec2_dot(*a, *b)); + return 1; } -static int vec2Length(lua_State *L) { +/** + * Returns the length (magnitude) of a vec2. + * + * @param L Lua state. Arg 1: vec2. + * @return 1 (number). + */ +static int moduleVec2Length(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec2 *v = (vec2 *)luaL_checkudata(L, 1, "vec2_mt"); - lua_pushnumber(L, (lua_Number)glm_vec2_norm(*v)); return 1; + assertNotNull(v, "invalid vec2 userdata"); + + lua_pushnumber(L, (lua_Number)glm_vec2_norm(*v)); + return 1; } -static int vec2LengthSq(lua_State *L) { +/** + * Returns the squared length of a vec2. + * + * @param L Lua state. Arg 1: vec2. + * @return 1 (number). + */ +static int moduleVec2LengthSq(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec2 *v = (vec2 *)luaL_checkudata(L, 1, "vec2_mt"); - lua_pushnumber(L, (lua_Number)glm_vec2_norm2(*v)); return 1; + assertNotNull(v, "invalid vec2 userdata"); + + lua_pushnumber(L, (lua_Number)glm_vec2_norm2(*v)); + return 1; } -static int vec2Normalize(lua_State *L) { +/** + * Returns a normalized copy of a vec2. + * + * @param L Lua state. Arg 1: vec2. + * @return 1 (new vec2). + */ +static int moduleVec2Normalize(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec2 *v = (vec2 *)luaL_checkudata(L, 1, "vec2_mt"); - vec2 r; glm_vec2_normalize_to(*v, r); - luaVec2Push(L, r); return 1; + assertNotNull(v, "invalid vec2 userdata"); + + vec2 r; + glm_vec2_normalize_to(*v, r); + moduleVec2Push(L, r); + return 1; } -static int vec2Lerp(lua_State *L) { +/** + * Linearly interpolates between two vec2 values. + * + * @param L Lua state. Arg 1: vec2 a. Arg 2: vec2 b. Arg 3: number t. + * @return 1 (new vec2). + */ +static int moduleVec2Lerp(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec2 *a = (vec2 *)luaL_checkudata(L, 1, "vec2_mt"); + assertNotNull(a, "invalid vec2 userdata"); + vec2 *b = (vec2 *)luaL_checkudata(L, 2, "vec2_mt"); + assertNotNull(b, "invalid vec2 userdata"); + float_t t = (float_t)luaL_checknumber(L, 3); - vec2 r; glm_vec2_lerp(*a, *b, t, r); - luaVec2Push(L, r); return 1; + vec2 r; + glm_vec2_lerp(*a, *b, t, r); + moduleVec2Push(L, r); + return 1; } -static int vec2Distance(lua_State *L) { +/** + * Returns the distance between two vec2 points. + * + * @param L Lua state. Arg 1: vec2 a. Arg 2: vec2 b. + * @return 1 (number). + */ +static int moduleVec2Distance(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec2 *a = (vec2 *)luaL_checkudata(L, 1, "vec2_mt"); + assertNotNull(a, "invalid vec2 userdata"); + vec2 *b = (vec2 *)luaL_checkudata(L, 2, "vec2_mt"); - lua_pushnumber(L, (lua_Number)glm_vec2_distance(*a, *b)); return 1; + assertNotNull(b, "invalid vec2 userdata"); + + lua_pushnumber(L, (lua_Number)glm_vec2_distance(*a, *b)); + return 1; } -static int vec2Negate(lua_State *L) { +/** + * Returns a negated copy of a vec2. + * + * @param L Lua state. Arg 1: vec2. + * @return 1 (new vec2). + */ +static int moduleVec2Negate(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec2 *v = (vec2 *)luaL_checkudata(L, 1, "vec2_mt"); - vec2 r; glm_vec2_negate_to(*v, r); - luaVec2Push(L, r); return 1; + assertNotNull(v, "invalid vec2 userdata"); + + vec2 r; + glm_vec2_negate_to(*v, r); + moduleVec2Push(L, r); + return 1; } -static int vec2Create(lua_State *L) { +/** + * Constructor: creates a vec2 from two optional numbers (defaults to 0). + * + * @param L Lua state. Arg 1 (optional): x. Arg 2 (optional): y. + * @return 1 (new vec2). + */ +static int moduleVec2Create(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec2 v = {0, 0}; int top = lua_gettop(L); if(top >= 1) v[0] = (float_t)luaL_checknumber(L, 1); if(top >= 2) v[1] = (float_t)luaL_checknumber(L, 2); - luaVec2Push(L, v); return 1; + moduleVec2Push(L, v); + return 1; } -static void moduleMathVec2(lua_State *L) { - if(!luaL_newmetatable(L, "vec2_mt")) { lua_pop(L, 1); return; } +/** + * Registers the vec2 metatable and vec2 constructor global. + * + * @param L Lua state. + */ +static void moduleVec2(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); - lua_pushcfunction(L, vec2Index); lua_setfield(L, -2, "__index"); - lua_pushcfunction(L, vec2Newindex); lua_setfield(L, -2, "__newindex"); - lua_pushcfunction(L, vec2OpAdd); lua_setfield(L, -2, "__add"); - lua_pushcfunction(L, vec2OpSub); lua_setfield(L, -2, "__sub"); - lua_pushcfunction(L, vec2OpMul); lua_setfield(L, -2, "__mul"); - lua_pushcfunction(L, vec2OpDiv); lua_setfield(L, -2, "__div"); - lua_pushcfunction(L, vec2OpUnm); lua_setfield(L, -2, "__unm"); - lua_pushcfunction(L, vec2OpEq); lua_setfield(L, -2, "__eq"); - lua_pushcfunction(L, vec2OpToString); lua_setfield(L, -2, "__tostring"); - lua_pushcfunction(L, vec2Dot); lua_setfield(L, -2, "dot"); - lua_pushcfunction(L, vec2Length); lua_setfield(L, -2, "length"); - lua_pushcfunction(L, vec2LengthSq); lua_setfield(L, -2, "lengthSq"); - lua_pushcfunction(L, vec2Normalize); lua_setfield(L, -2, "normalize"); - lua_pushcfunction(L, vec2Lerp); lua_setfield(L, -2, "lerp"); - lua_pushcfunction(L, vec2Distance); lua_setfield(L, -2, "distance"); - lua_pushcfunction(L, vec2Negate); lua_setfield(L, -2, "negate"); + if(!luaL_newmetatable(L, "vec2_mt")) { + lua_pop(L, 1); + return; + } + + lua_pushcfunction(L, moduleVec2Index); + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, moduleVec2NewIndex); + lua_setfield(L, -2, "__newindex"); + lua_pushcfunction(L, moduleVec2OpAdd); + lua_setfield(L, -2, "__add"); + lua_pushcfunction(L, moduleVec2OpSub); + lua_setfield(L, -2, "__sub"); + lua_pushcfunction(L, moduleVec2OpMul); + lua_setfield(L, -2, "__mul"); + lua_pushcfunction(L, moduleVec2OpDiv); + lua_setfield(L, -2, "__div"); + lua_pushcfunction(L, moduleVec2OpUnm); + lua_setfield(L, -2, "__unm"); + lua_pushcfunction(L, moduleVec2OpEq); + lua_setfield(L, -2, "__eq"); + lua_pushcfunction(L, moduleVec2OpToString); + lua_setfield(L, -2, "__tostring"); + lua_pushcfunction(L, moduleVec2Dot); + lua_setfield(L, -2, "dot"); + lua_pushcfunction(L, moduleVec2Length); + lua_setfield(L, -2, "length"); + lua_pushcfunction(L, moduleVec2LengthSq); + lua_setfield(L, -2, "lengthSq"); + lua_pushcfunction(L, moduleVec2Normalize); + lua_setfield(L, -2, "normalize"); + lua_pushcfunction(L, moduleVec2Lerp); + lua_setfield(L, -2, "lerp"); + lua_pushcfunction(L, moduleVec2Distance); + lua_setfield(L, -2, "distance"); + lua_pushcfunction(L, moduleVec2Negate); + lua_setfield(L, -2, "negate"); lua_pop(L, 1); - lua_register(L, "vec2", vec2Create); + lua_register(L, "vec2", moduleVec2Create); } diff --git a/src/dusk/script/module/math/modulevec3.h b/src/dusk/script/module/math/modulevec3.h index 97146be7..f1081ace 100644 --- a/src/dusk/script/module/math/modulevec3.h +++ b/src/dusk/script/module/math/modulevec3.h @@ -8,173 +8,474 @@ #pragma once #include "script/module/modulebase.h" -static void luaVec3Push(lua_State *L, vec3 v) { +/** + * Pushes a new vec3 userdata onto the Lua stack with the vec3_mt metatable. + * + * @param L Lua state. + * @param v Source vector to copy. + */ +static void moduleVec3Push(lua_State *L, vec3 v) { + assertNotNull(L, "Lua state cannot be NULL"); + vec3 *u = (vec3 *)lua_newuserdata(L, sizeof(vec3)); glm_vec3_copy(v, *u); luaL_getmetatable(L, "vec3_mt"); lua_setmetatable(L, -2); } -static void luaVec3Check(lua_State *L, int idx, vec3 out) { +/** + * Reads a vec3 userdata from the given stack index into out. + * + * @param L Lua state. + * @param idx Stack index of the vec3 userdata. + * @param out Destination vector. + */ +static void moduleVec3Check(lua_State *L, int idx, vec3 out) { + assertNotNull(L, "Lua state cannot be NULL"); + vec3 *v = (vec3 *)luaL_checkudata(L, idx, "vec3_mt"); glm_vec3_copy(*v, out); } -static int vec3Index(lua_State *L) { +/** + * __index metamethod for vec3 userdata. + * Supports integer indices 1-3 and string keys x, y, z; falls through to the + * metatable for method lookups. + * + * @param L Lua state. Arg 1: vec3 userdata. Arg 2: integer or string key. + * @return 1. + */ +static int moduleVec3Index(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec3 *v = (vec3 *)luaL_checkudata(L, 1, "vec3_mt"); + assertNotNull(v, "invalid vec3 userdata"); + const char *key = luaL_checkstring(L, 2); - if(stringCompare(key, "x") == 0) { lua_pushnumber(L, (lua_Number)(*v)[0]); return 1; } - if(stringCompare(key, "y") == 0) { lua_pushnumber(L, (lua_Number)(*v)[1]); return 1; } - if(stringCompare(key, "z") == 0) { lua_pushnumber(L, (lua_Number)(*v)[2]); return 1; } + assertStrLenMin(key, 1, "property key cannot be empty"); + + if(lua_isnumber(L, 2)) { + lua_Integer i = lua_tointeger(L, 2); + if(i >= 1 && i <= 3) { + lua_pushnumber(L, (lua_Number)(*v)[i - 1]); + return 1; + } + return 0; + } + + if(stringCompare(key, "x") == 0) { + lua_pushnumber(L, (lua_Number)(*v)[0]); + return 1; + } + + if(stringCompare(key, "y") == 0) { + lua_pushnumber(L, (lua_Number)(*v)[1]); + return 1; + } + + if(stringCompare(key, "z") == 0) { + lua_pushnumber(L, (lua_Number)(*v)[2]); + return 1; + } + lua_getmetatable(L, 1); lua_getfield(L, -1, key); + lua_remove(L, -2); return 1; } -static int vec3Newindex(lua_State *L) { +/** + * __newindex metamethod for vec3 userdata. + * Writes x, y, z. Errors on unknown keys. + * + * @param L Lua state. Arg 1: vec3 userdata. Arg 2: key string. Arg 3: number. + * @return 0. + */ +static int moduleVec3NewIndex(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec3 *v = (vec3 *)luaL_checkudata(L, 1, "vec3_mt"); + assertNotNull(v, "invalid vec3 userdata"); + const char *key = luaL_checkstring(L, 2); - if(stringCompare(key, "x") == 0) { (*v)[0] = (float_t)luaL_checknumber(L, 3); return 0; } - if(stringCompare(key, "y") == 0) { (*v)[1] = (float_t)luaL_checknumber(L, 3); return 0; } - if(stringCompare(key, "z") == 0) { (*v)[2] = (float_t)luaL_checknumber(L, 3); return 0; } + assertStrLenMin(key, 1, "property key cannot be empty"); + + if(stringCompare(key, "x") == 0) { + (*v)[0] = (float_t)luaL_checknumber(L, 3); + return 0; + } + + if(stringCompare(key, "y") == 0) { + (*v)[1] = (float_t)luaL_checknumber(L, 3); + return 0; + } + + if(stringCompare(key, "z") == 0) { + (*v)[2] = (float_t)luaL_checknumber(L, 3); + return 0; + } + luaL_error(L, "vec3: unknown property '%s'", key); return 0; } -static int vec3OpAdd(lua_State *L) { +/** + * __add metamethod: returns a + b as a new vec3. + * + * @param L Lua state. Arg 1: vec3. Arg 2: vec3. + * @return 1 (new vec3). + */ +static int moduleVec3OpAdd(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec3 *a = (vec3 *)luaL_checkudata(L, 1, "vec3_mt"); + assertNotNull(a, "invalid vec3 userdata"); + vec3 *b = (vec3 *)luaL_checkudata(L, 2, "vec3_mt"); - vec3 r; glm_vec3_add(*a, *b, r); - luaVec3Push(L, r); return 1; + assertNotNull(b, "invalid vec3 userdata"); + + vec3 r; + glm_vec3_add(*a, *b, r); + moduleVec3Push(L, r); + return 1; } -static int vec3OpSub(lua_State *L) { +/** + * __sub metamethod: returns a - b as a new vec3. + * + * @param L Lua state. Arg 1: vec3. Arg 2: vec3. + * @return 1 (new vec3). + */ +static int moduleVec3OpSub(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec3 *a = (vec3 *)luaL_checkudata(L, 1, "vec3_mt"); + assertNotNull(a, "invalid vec3 userdata"); + vec3 *b = (vec3 *)luaL_checkudata(L, 2, "vec3_mt"); - vec3 r; glm_vec3_sub(*a, *b, r); - luaVec3Push(L, r); return 1; + assertNotNull(b, "invalid vec3 userdata"); + + vec3 r; + glm_vec3_sub(*a, *b, r); + moduleVec3Push(L, r); + return 1; } -static int vec3OpMul(lua_State *L) { +/** + * __mul metamethod: returns v * scalar or scalar * v as a new vec3. + * + * @param L Lua state. Arg 1: vec3 or number. Arg 2: number or vec3. + * @return 1 (new vec3). + */ +static int moduleVec3OpMul(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec3 r; if(lua_isnumber(L, 1)) { float_t s = (float_t)lua_tonumber(L, 1); vec3 *v = (vec3 *)luaL_checkudata(L, 2, "vec3_mt"); + assertNotNull(v, "invalid vec3 userdata"); glm_vec3_scale(*v, s, r); } else { vec3 *v = (vec3 *)luaL_checkudata(L, 1, "vec3_mt"); + assertNotNull(v, "invalid vec3 userdata"); float_t s = (float_t)luaL_checknumber(L, 2); glm_vec3_scale(*v, s, r); } - luaVec3Push(L, r); return 1; + + moduleVec3Push(L, r); + return 1; } -static int vec3OpDiv(lua_State *L) { +/** + * __div metamethod: returns v / scalar as a new vec3. + * + * @param L Lua state. Arg 1: vec3. Arg 2: number. + * @return 1 (new vec3). + */ +static int moduleVec3OpDiv(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec3 *v = (vec3 *)luaL_checkudata(L, 1, "vec3_mt"); + assertNotNull(v, "invalid vec3 userdata"); + float_t s = (float_t)luaL_checknumber(L, 2); - vec3 r; glm_vec3_divs(*v, s, r); - luaVec3Push(L, r); return 1; + vec3 r; + glm_vec3_divs(*v, s, r); + moduleVec3Push(L, r); + return 1; } -static int vec3OpUnm(lua_State *L) { +/** + * __unm metamethod: returns -v as a new vec3. + * + * @param L Lua state. Arg 1: vec3. + * @return 1 (new vec3). + */ +static int moduleVec3OpUnm(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec3 *v = (vec3 *)luaL_checkudata(L, 1, "vec3_mt"); - vec3 r; glm_vec3_negate_to(*v, r); - luaVec3Push(L, r); return 1; + assertNotNull(v, "invalid vec3 userdata"); + + vec3 r; + glm_vec3_negate_to(*v, r); + moduleVec3Push(L, r); + return 1; } -static int vec3OpEq(lua_State *L) { +/** + * __eq metamethod: component-wise equality test. + * + * @param L Lua state. Arg 1: vec3. Arg 2: vec3. + * @return 1 (boolean). + */ +static int moduleVec3OpEq(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec3 *a = (vec3 *)luaL_checkudata(L, 1, "vec3_mt"); + assertNotNull(a, "invalid vec3 userdata"); + vec3 *b = (vec3 *)luaL_checkudata(L, 2, "vec3_mt"); + assertNotNull(b, "invalid vec3 userdata"); + lua_pushboolean(L, (*a)[0] == (*b)[0] && (*a)[1] == (*b)[1] && (*a)[2] == (*b)[2]); return 1; } -static int vec3OpToString(lua_State *L) { +/** + * __tostring metamethod: returns a human-readable representation. + * + * @param L Lua state. Arg 1: vec3. + * @return 1 (string). + */ +static int moduleVec3OpToString(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec3 *v = (vec3 *)luaL_checkudata(L, 1, "vec3_mt"); + assertNotNull(v, "invalid vec3 userdata"); + char buf[80]; snprintf(buf, sizeof(buf), "vec3(%.3f, %.3f, %.3f)", (*v)[0], (*v)[1], (*v)[2]); - lua_pushstring(L, buf); return 1; + lua_pushstring(L, buf); + return 1; } -static int vec3Dot(lua_State *L) { +/** + * Returns the dot product of two vec3 values. + * + * @param L Lua state. Arg 1: vec3. Arg 2: vec3. + * @return 1 (number). + */ +static int moduleVec3Dot(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec3 *a = (vec3 *)luaL_checkudata(L, 1, "vec3_mt"); + assertNotNull(a, "invalid vec3 userdata"); + vec3 *b = (vec3 *)luaL_checkudata(L, 2, "vec3_mt"); - lua_pushnumber(L, (lua_Number)glm_vec3_dot(*a, *b)); return 1; + assertNotNull(b, "invalid vec3 userdata"); + + lua_pushnumber(L, (lua_Number)glm_vec3_dot(*a, *b)); + return 1; } -static int vec3Cross(lua_State *L) { +/** + * Returns the cross product of two vec3 values as a new vec3. + * + * @param L Lua state. Arg 1: vec3. Arg 2: vec3. + * @return 1 (new vec3). + */ +static int moduleVec3Cross(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec3 *a = (vec3 *)luaL_checkudata(L, 1, "vec3_mt"); + assertNotNull(a, "invalid vec3 userdata"); + vec3 *b = (vec3 *)luaL_checkudata(L, 2, "vec3_mt"); - vec3 r; glm_vec3_cross(*a, *b, r); - luaVec3Push(L, r); return 1; + assertNotNull(b, "invalid vec3 userdata"); + + vec3 r; + glm_vec3_cross(*a, *b, r); + moduleVec3Push(L, r); + return 1; } -static int vec3Length(lua_State *L) { +/** + * Returns the length (magnitude) of a vec3. + * + * @param L Lua state. Arg 1: vec3. + * @return 1 (number). + */ +static int moduleVec3Length(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec3 *v = (vec3 *)luaL_checkudata(L, 1, "vec3_mt"); - lua_pushnumber(L, (lua_Number)glm_vec3_norm(*v)); return 1; + assertNotNull(v, "invalid vec3 userdata"); + + lua_pushnumber(L, (lua_Number)glm_vec3_norm(*v)); + return 1; } -static int vec3LengthSq(lua_State *L) { +/** + * Returns the squared length of a vec3. + * + * @param L Lua state. Arg 1: vec3. + * @return 1 (number). + */ +static int moduleVec3LengthSq(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec3 *v = (vec3 *)luaL_checkudata(L, 1, "vec3_mt"); - lua_pushnumber(L, (lua_Number)glm_vec3_norm2(*v)); return 1; + assertNotNull(v, "invalid vec3 userdata"); + + lua_pushnumber(L, (lua_Number)glm_vec3_norm2(*v)); + return 1; } -static int vec3Normalize(lua_State *L) { +/** + * Returns a normalized copy of a vec3. + * + * @param L Lua state. Arg 1: vec3. + * @return 1 (new vec3). + */ +static int moduleVec3Normalize(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec3 *v = (vec3 *)luaL_checkudata(L, 1, "vec3_mt"); - vec3 r; glm_vec3_normalize_to(*v, r); - luaVec3Push(L, r); return 1; + assertNotNull(v, "invalid vec3 userdata"); + + vec3 r; + glm_vec3_normalize_to(*v, r); + moduleVec3Push(L, r); + return 1; } -static int vec3Lerp(lua_State *L) { +/** + * Linearly interpolates between two vec3 values. + * + * @param L Lua state. Arg 1: vec3 a. Arg 2: vec3 b. Arg 3: number t. + * @return 1 (new vec3). + */ +static int moduleVec3Lerp(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec3 *a = (vec3 *)luaL_checkudata(L, 1, "vec3_mt"); + assertNotNull(a, "invalid vec3 userdata"); + vec3 *b = (vec3 *)luaL_checkudata(L, 2, "vec3_mt"); + assertNotNull(b, "invalid vec3 userdata"); + float_t t = (float_t)luaL_checknumber(L, 3); - vec3 r; glm_vec3_lerp(*a, *b, t, r); - luaVec3Push(L, r); return 1; + vec3 r; + glm_vec3_lerp(*a, *b, t, r); + moduleVec3Push(L, r); + return 1; } -static int vec3Distance(lua_State *L) { +/** + * Returns the distance between two vec3 points. + * + * @param L Lua state. Arg 1: vec3 a. Arg 2: vec3 b. + * @return 1 (number). + */ +static int moduleVec3Distance(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec3 *a = (vec3 *)luaL_checkudata(L, 1, "vec3_mt"); + assertNotNull(a, "invalid vec3 userdata"); + vec3 *b = (vec3 *)luaL_checkudata(L, 2, "vec3_mt"); - lua_pushnumber(L, (lua_Number)glm_vec3_distance(*a, *b)); return 1; + assertNotNull(b, "invalid vec3 userdata"); + + lua_pushnumber(L, (lua_Number)glm_vec3_distance(*a, *b)); + return 1; } -static int vec3Negate(lua_State *L) { +/** + * Returns a negated copy of a vec3. + * + * @param L Lua state. Arg 1: vec3. + * @return 1 (new vec3). + */ +static int moduleVec3Negate(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec3 *v = (vec3 *)luaL_checkudata(L, 1, "vec3_mt"); - vec3 r; glm_vec3_negate_to(*v, r); - luaVec3Push(L, r); return 1; + assertNotNull(v, "invalid vec3 userdata"); + + vec3 r; + glm_vec3_negate_to(*v, r); + moduleVec3Push(L, r); + return 1; } -static int vec3Create(lua_State *L) { +/** + * Constructor: creates a vec3 from three optional numbers (defaults to 0). + * + * @param L Lua state. Arg 1 (optional): x. Arg 2 (optional): y. Arg 3 (optional): z. + * @return 1 (new vec3). + */ +static int moduleVec3Create(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec3 v = {0, 0, 0}; int top = lua_gettop(L); if(top >= 1) v[0] = (float_t)luaL_checknumber(L, 1); if(top >= 2) v[1] = (float_t)luaL_checknumber(L, 2); if(top >= 3) v[2] = (float_t)luaL_checknumber(L, 3); - luaVec3Push(L, v); return 1; + moduleVec3Push(L, v); + return 1; } -static void moduleMathVec3(lua_State *L) { - if(!luaL_newmetatable(L, "vec3_mt")) { lua_pop(L, 1); return; } +/** + * Registers the vec3 metatable and vec3 constructor global. + * + * @param L Lua state. + */ +static void moduleVec3(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); - lua_pushcfunction(L, vec3Index); lua_setfield(L, -2, "__index"); - lua_pushcfunction(L, vec3Newindex); lua_setfield(L, -2, "__newindex"); - lua_pushcfunction(L, vec3OpAdd); lua_setfield(L, -2, "__add"); - lua_pushcfunction(L, vec3OpSub); lua_setfield(L, -2, "__sub"); - lua_pushcfunction(L, vec3OpMul); lua_setfield(L, -2, "__mul"); - lua_pushcfunction(L, vec3OpDiv); lua_setfield(L, -2, "__div"); - lua_pushcfunction(L, vec3OpUnm); lua_setfield(L, -2, "__unm"); - lua_pushcfunction(L, vec3OpEq); lua_setfield(L, -2, "__eq"); - lua_pushcfunction(L, vec3OpToString); lua_setfield(L, -2, "__tostring"); - lua_pushcfunction(L, vec3Dot); lua_setfield(L, -2, "dot"); - lua_pushcfunction(L, vec3Cross); lua_setfield(L, -2, "cross"); - lua_pushcfunction(L, vec3Length); lua_setfield(L, -2, "length"); - lua_pushcfunction(L, vec3LengthSq); lua_setfield(L, -2, "lengthSq"); - lua_pushcfunction(L, vec3Normalize); lua_setfield(L, -2, "normalize"); - lua_pushcfunction(L, vec3Lerp); lua_setfield(L, -2, "lerp"); - lua_pushcfunction(L, vec3Distance); lua_setfield(L, -2, "distance"); - lua_pushcfunction(L, vec3Negate); lua_setfield(L, -2, "negate"); + if(!luaL_newmetatable(L, "vec3_mt")) { + lua_pop(L, 1); + return; + } + + lua_pushcfunction(L, moduleVec3Index); + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, moduleVec3NewIndex); + lua_setfield(L, -2, "__newindex"); + lua_pushcfunction(L, moduleVec3OpAdd); + lua_setfield(L, -2, "__add"); + lua_pushcfunction(L, moduleVec3OpSub); + lua_setfield(L, -2, "__sub"); + lua_pushcfunction(L, moduleVec3OpMul); + lua_setfield(L, -2, "__mul"); + lua_pushcfunction(L, moduleVec3OpDiv); + lua_setfield(L, -2, "__div"); + lua_pushcfunction(L, moduleVec3OpUnm); + lua_setfield(L, -2, "__unm"); + lua_pushcfunction(L, moduleVec3OpEq); + lua_setfield(L, -2, "__eq"); + lua_pushcfunction(L, moduleVec3OpToString); + lua_setfield(L, -2, "__tostring"); + lua_pushcfunction(L, moduleVec3Dot); + lua_setfield(L, -2, "dot"); + lua_pushcfunction(L, moduleVec3Cross); + lua_setfield(L, -2, "cross"); + lua_pushcfunction(L, moduleVec3Length); + lua_setfield(L, -2, "length"); + lua_pushcfunction(L, moduleVec3LengthSq); + lua_setfield(L, -2, "lengthSq"); + lua_pushcfunction(L, moduleVec3Normalize); + lua_setfield(L, -2, "normalize"); + lua_pushcfunction(L, moduleVec3Lerp); + lua_setfield(L, -2, "lerp"); + lua_pushcfunction(L, moduleVec3Distance); + lua_setfield(L, -2, "distance"); + lua_pushcfunction(L, moduleVec3Negate); + lua_setfield(L, -2, "negate"); lua_pop(L, 1); - lua_register(L, "vec3", vec3Create); + lua_register(L, "vec3", moduleVec3Create); } diff --git a/src/dusk/script/module/math/modulevec4.h b/src/dusk/script/module/math/modulevec4.h index cad467c2..bb17af63 100644 --- a/src/dusk/script/module/math/modulevec4.h +++ b/src/dusk/script/module/math/modulevec4.h @@ -8,161 +8,444 @@ #pragma once #include "script/module/modulebase.h" -static void luaVec4Push(lua_State *L, vec4 v) { +/** + * Pushes a new vec4 userdata onto the Lua stack with the vec4_mt metatable. + * + * @param L Lua state. + * @param v Source vector to copy. + */ +static void moduleVec4Push(lua_State *L, vec4 v) { + assertNotNull(L, "Lua state cannot be NULL"); + vec4 *u = (vec4 *)lua_newuserdata(L, sizeof(vec4)); glm_vec4_copy(v, *u); luaL_getmetatable(L, "vec4_mt"); lua_setmetatable(L, -2); } -static void luaVec4Check(lua_State *L, int idx, vec4 out) { +/** + * Reads a vec4 userdata from the given stack index into out. + * + * @param L Lua state. + * @param idx Stack index of the vec4 userdata. + * @param out Destination vector. + */ +static void moduleVec4Check(lua_State *L, int idx, vec4 out) { + assertNotNull(L, "Lua state cannot be NULL"); + vec4 *v = (vec4 *)luaL_checkudata(L, idx, "vec4_mt"); glm_vec4_copy(*v, out); } -static int vec4Index(lua_State *L) { +/** + * __index metamethod for vec4 userdata. + * Supports integer indices 1-4 and string keys x/u0, y/v0, z/u1, w/v1; + * falls through to the metatable for method lookups. + * + * @param L Lua state. Arg 1: vec4 userdata. Arg 2: integer or string key. + * @return 1. + */ +static int moduleVec4Index(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec4 *v = (vec4 *)luaL_checkudata(L, 1, "vec4_mt"); + assertNotNull(v, "invalid vec4 userdata"); + const char *key = luaL_checkstring(L, 2); - if(stringCompare(key, "x") == 0 || stringCompare(key, "u0") == 0) { lua_pushnumber(L, (lua_Number)(*v)[0]); return 1; } - if(stringCompare(key, "y") == 0 || stringCompare(key, "v0") == 0) { lua_pushnumber(L, (lua_Number)(*v)[1]); return 1; } - if(stringCompare(key, "z") == 0 || stringCompare(key, "u1") == 0) { lua_pushnumber(L, (lua_Number)(*v)[2]); return 1; } - if(stringCompare(key, "w") == 0 || stringCompare(key, "v1") == 0) { lua_pushnumber(L, (lua_Number)(*v)[3]); return 1; } + assertStrLenMin(key, 1, "property key cannot be empty"); + + if(lua_isnumber(L, 2)) { + lua_Integer i = lua_tointeger(L, 2); + if(i >= 1 && i <= 4) { + lua_pushnumber(L, (lua_Number)(*v)[i - 1]); + return 1; + } + return 0; + } + + if(stringEquals(key, "x") || stringEquals(key, "u0")) { + lua_pushnumber(L, (lua_Number)(*v)[0]); + return 1; + } + + if(stringEquals(key, "y") || stringEquals(key, "v0")) { + lua_pushnumber(L, (lua_Number)(*v)[1]); + return 1; + } + + if(stringEquals(key, "z") || stringEquals(key, "u1")) { + lua_pushnumber(L, (lua_Number)(*v)[2]); + return 1; + } + + if(stringEquals(key, "w") || stringEquals(key, "v1")) { + lua_pushnumber(L, (lua_Number)(*v)[3]); + return 1; + } + lua_getmetatable(L, 1); lua_getfield(L, -1, key); + lua_remove(L, -2); return 1; } -static int vec4Newindex(lua_State *L) { +/** + * __newindex metamethod for vec4 userdata. + * Writes x/u0, y/v0, z/u1, w/v1. Errors on unknown keys. + * + * @param L Lua state. Arg 1: vec4 userdata. Arg 2: key string. Arg 3: number. + * @return 0. + */ +static int moduleVec4NewIndex(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec4 *v = (vec4 *)luaL_checkudata(L, 1, "vec4_mt"); + assertNotNull(v, "invalid vec4 userdata"); + const char *key = luaL_checkstring(L, 2); - if(stringCompare(key, "x") == 0 || stringCompare(key, "u0") == 0) { (*v)[0] = (float_t)luaL_checknumber(L, 3); return 0; } - if(stringCompare(key, "y") == 0 || stringCompare(key, "v0") == 0) { (*v)[1] = (float_t)luaL_checknumber(L, 3); return 0; } - if(stringCompare(key, "z") == 0 || stringCompare(key, "u1") == 0) { (*v)[2] = (float_t)luaL_checknumber(L, 3); return 0; } - if(stringCompare(key, "w") == 0 || stringCompare(key, "v1") == 0) { (*v)[3] = (float_t)luaL_checknumber(L, 3); return 0; } + assertStrLenMin(key, 1, "property key cannot be empty"); + + if(stringEquals(key, "x") || stringEquals(key, "u0")) { + (*v)[0] = (float_t)luaL_checknumber(L, 3); + return 0; + } + + if(stringEquals(key, "y") || stringEquals(key, "v0")) { + (*v)[1] = (float_t)luaL_checknumber(L, 3); + return 0; + } + + if(stringEquals(key, "z") || stringEquals(key, "u1")) { + (*v)[2] = (float_t)luaL_checknumber(L, 3); + return 0; + } + + if(stringEquals(key, "w") || stringEquals(key, "v1")) { + (*v)[3] = (float_t)luaL_checknumber(L, 3); + return 0; + } + luaL_error(L, "vec4: unknown property '%s'", key); return 0; } -static int vec4OpAdd(lua_State *L) { +/** + * __add metamethod: returns a + b as a new vec4. + * + * @param L Lua state. Arg 1: vec4. Arg 2: vec4. + * @return 1 (new vec4). + */ +static int moduleVec4OpAdd(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec4 *a = (vec4 *)luaL_checkudata(L, 1, "vec4_mt"); + assertNotNull(a, "invalid vec4 userdata"); + vec4 *b = (vec4 *)luaL_checkudata(L, 2, "vec4_mt"); - vec4 r; glm_vec4_add(*a, *b, r); - luaVec4Push(L, r); return 1; + assertNotNull(b, "invalid vec4 userdata"); + + vec4 r; + glm_vec4_add(*a, *b, r); + moduleVec4Push(L, r); + return 1; } -static int vec4OpSub(lua_State *L) { +/** + * __sub metamethod: returns a - b as a new vec4. + * + * @param L Lua state. Arg 1: vec4. Arg 2: vec4. + * @return 1 (new vec4). + */ +static int moduleVec4OpSub(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec4 *a = (vec4 *)luaL_checkudata(L, 1, "vec4_mt"); + assertNotNull(a, "invalid vec4 userdata"); + vec4 *b = (vec4 *)luaL_checkudata(L, 2, "vec4_mt"); - vec4 r; glm_vec4_sub(*a, *b, r); - luaVec4Push(L, r); return 1; + assertNotNull(b, "invalid vec4 userdata"); + + vec4 r; + glm_vec4_sub(*a, *b, r); + moduleVec4Push(L, r); + return 1; } -static int vec4OpMul(lua_State *L) { +/** + * __mul metamethod: returns v * scalar or scalar * v as a new vec4. + * + * @param L Lua state. Arg 1: vec4 or number. Arg 2: number or vec4. + * @return 1 (new vec4). + */ +static int moduleVec4OpMul(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec4 r; if(lua_isnumber(L, 1)) { float_t s = (float_t)lua_tonumber(L, 1); vec4 *v = (vec4 *)luaL_checkudata(L, 2, "vec4_mt"); + assertNotNull(v, "invalid vec4 userdata"); glm_vec4_scale(*v, s, r); } else { vec4 *v = (vec4 *)luaL_checkudata(L, 1, "vec4_mt"); + assertNotNull(v, "invalid vec4 userdata"); float_t s = (float_t)luaL_checknumber(L, 2); glm_vec4_scale(*v, s, r); } - luaVec4Push(L, r); return 1; -} -static int vec4OpDiv(lua_State *L) { - vec4 *v = (vec4 *)luaL_checkudata(L, 1, "vec4_mt"); - float_t s = (float_t)luaL_checknumber(L, 2); - vec4 r; glm_vec4_divs(*v, s, r); - luaVec4Push(L, r); return 1; -} - -static int vec4OpUnm(lua_State *L) { - vec4 *v = (vec4 *)luaL_checkudata(L, 1, "vec4_mt"); - vec4 r; glm_vec4_negate_to(*v, r); - luaVec4Push(L, r); return 1; -} - -static int vec4OpEq(lua_State *L) { - vec4 *a = (vec4 *)luaL_checkudata(L, 1, "vec4_mt"); - vec4 *b = (vec4 *)luaL_checkudata(L, 2, "vec4_mt"); - lua_pushboolean(L, (*a)[0] == (*b)[0] && (*a)[1] == (*b)[1] && (*a)[2] == (*b)[2] && (*a)[3] == (*b)[3]); + moduleVec4Push(L, r); return 1; } -static int vec4OpToString(lua_State *L) { +/** + * __div metamethod: returns v / scalar as a new vec4. + * + * @param L Lua state. Arg 1: vec4. Arg 2: number. + * @return 1 (new vec4). + */ +static int moduleVec4OpDiv(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec4 *v = (vec4 *)luaL_checkudata(L, 1, "vec4_mt"); + assertNotNull(v, "invalid vec4 userdata"); + + float_t s = (float_t)luaL_checknumber(L, 2); + vec4 r; + glm_vec4_divs(*v, s, r); + moduleVec4Push(L, r); + return 1; +} + +/** + * __unm metamethod: returns -v as a new vec4. + * + * @param L Lua state. Arg 1: vec4. + * @return 1 (new vec4). + */ +static int moduleVec4OpUnm(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + vec4 *v = (vec4 *)luaL_checkudata(L, 1, "vec4_mt"); + assertNotNull(v, "invalid vec4 userdata"); + + vec4 r; + glm_vec4_negate_to(*v, r); + moduleVec4Push(L, r); + return 1; +} + +/** + * __eq metamethod: component-wise equality test. + * + * @param L Lua state. Arg 1: vec4. Arg 2: vec4. + * @return 1 (boolean). + */ +static int moduleVec4OpEq(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + vec4 *a = (vec4 *)luaL_checkudata(L, 1, "vec4_mt"); + assertNotNull(a, "invalid vec4 userdata"); + + vec4 *b = (vec4 *)luaL_checkudata(L, 2, "vec4_mt"); + assertNotNull(b, "invalid vec4 userdata"); + + lua_pushboolean(L, + (*a)[0] == (*b)[0] && (*a)[1] == (*b)[1] && + (*a)[2] == (*b)[2] && (*a)[3] == (*b)[3] + ); + return 1; +} + +/** + * __tostring metamethod: returns a human-readable representation. + * + * @param L Lua state. Arg 1: vec4. + * @return 1 (string). + */ +static int moduleVec4OpToString(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + vec4 *v = (vec4 *)luaL_checkudata(L, 1, "vec4_mt"); + assertNotNull(v, "invalid vec4 userdata"); + char buf[96]; snprintf(buf, sizeof(buf), "vec4(%.3f, %.3f, %.3f, %.3f)", (*v)[0], (*v)[1], (*v)[2], (*v)[3]); - lua_pushstring(L, buf); return 1; + lua_pushstring(L, buf); + return 1; } -static int vec4Dot(lua_State *L) { +/** + * Returns the dot product of two vec4 values. + * + * @param L Lua state. Arg 1: vec4. Arg 2: vec4. + * @return 1 (number). + */ +static int moduleVec4Dot(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec4 *a = (vec4 *)luaL_checkudata(L, 1, "vec4_mt"); + assertNotNull(a, "invalid vec4 userdata"); + vec4 *b = (vec4 *)luaL_checkudata(L, 2, "vec4_mt"); - lua_pushnumber(L, (lua_Number)glm_vec4_dot(*a, *b)); return 1; + assertNotNull(b, "invalid vec4 userdata"); + + lua_pushnumber(L, (lua_Number)glm_vec4_dot(*a, *b)); + return 1; } -static int vec4Length(lua_State *L) { +/** + * Returns the length (magnitude) of a vec4. + * + * @param L Lua state. Arg 1: vec4. + * @return 1 (number). + */ +static int moduleVec4Length(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec4 *v = (vec4 *)luaL_checkudata(L, 1, "vec4_mt"); - lua_pushnumber(L, (lua_Number)glm_vec4_norm(*v)); return 1; + assertNotNull(v, "invalid vec4 userdata"); + + lua_pushnumber(L, (lua_Number)glm_vec4_norm(*v)); + return 1; } -static int vec4LengthSq(lua_State *L) { +/** + * Returns the squared length of a vec4. + * + * @param L Lua state. Arg 1: vec4. + * @return 1 (number). + */ +static int moduleVec4LengthSq(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec4 *v = (vec4 *)luaL_checkudata(L, 1, "vec4_mt"); - lua_pushnumber(L, (lua_Number)glm_vec4_norm2(*v)); return 1; + assertNotNull(v, "invalid vec4 userdata"); + + lua_pushnumber(L, (lua_Number)glm_vec4_norm2(*v)); + return 1; } -static int vec4Normalize(lua_State *L) { +/** + * Returns a normalized copy of a vec4. + * + * @param L Lua state. Arg 1: vec4. + * @return 1 (new vec4). + */ +static int moduleVec4Normalize(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec4 *v = (vec4 *)luaL_checkudata(L, 1, "vec4_mt"); - vec4 r; glm_vec4_normalize_to(*v, r); - luaVec4Push(L, r); return 1; + assertNotNull(v, "invalid vec4 userdata"); + + vec4 r; + glm_vec4_normalize_to(*v, r); + moduleVec4Push(L, r); + return 1; } -static int vec4Lerp(lua_State *L) { +/** + * Linearly interpolates between two vec4 values. + * + * @param L Lua state. Arg 1: vec4 a. Arg 2: vec4 b. Arg 3: number t. + * @return 1 (new vec4). + */ +static int moduleVec4Lerp(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec4 *a = (vec4 *)luaL_checkudata(L, 1, "vec4_mt"); + assertNotNull(a, "invalid vec4 userdata"); + vec4 *b = (vec4 *)luaL_checkudata(L, 2, "vec4_mt"); + assertNotNull(b, "invalid vec4 userdata"); + float_t t = (float_t)luaL_checknumber(L, 3); - vec4 r; glm_vec4_lerp(*a, *b, t, r); - luaVec4Push(L, r); return 1; + vec4 r; + glm_vec4_lerp(*a, *b, t, r); + moduleVec4Push(L, r); + return 1; } -static int vec4Negate(lua_State *L) { +/** + * Returns a negated copy of a vec4. + * + * @param L Lua state. Arg 1: vec4. + * @return 1 (new vec4). + */ +static int moduleVec4Negate(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec4 *v = (vec4 *)luaL_checkudata(L, 1, "vec4_mt"); - vec4 r; glm_vec4_negate_to(*v, r); - luaVec4Push(L, r); return 1; + assertNotNull(v, "invalid vec4 userdata"); + + vec4 r; + glm_vec4_negate_to(*v, r); + moduleVec4Push(L, r); + return 1; } -static int vec4Create(lua_State *L) { +/** + * Constructor: creates a vec4 from four optional numbers (defaults to 0). + * + * @param L Lua state. Args 1-4 (optional): x, y, z, w. + * @return 1 (new vec4). + */ +static int moduleVec4Create(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + vec4 v = {0, 0, 0, 0}; int top = lua_gettop(L); if(top >= 1) v[0] = (float_t)luaL_checknumber(L, 1); if(top >= 2) v[1] = (float_t)luaL_checknumber(L, 2); if(top >= 3) v[2] = (float_t)luaL_checknumber(L, 3); if(top >= 4) v[3] = (float_t)luaL_checknumber(L, 4); - luaVec4Push(L, v); return 1; + moduleVec4Push(L, v); + return 1; } -static void moduleMathVec4(lua_State *L) { - if(!luaL_newmetatable(L, "vec4_mt")) { lua_pop(L, 1); return; } +/** + * Registers the vec4 metatable and vec4 constructor global. + * + * @param L Lua state. + */ +static void moduleVec4(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); - lua_pushcfunction(L, vec4Index); lua_setfield(L, -2, "__index"); - lua_pushcfunction(L, vec4Newindex); lua_setfield(L, -2, "__newindex"); - lua_pushcfunction(L, vec4OpAdd); lua_setfield(L, -2, "__add"); - lua_pushcfunction(L, vec4OpSub); lua_setfield(L, -2, "__sub"); - lua_pushcfunction(L, vec4OpMul); lua_setfield(L, -2, "__mul"); - lua_pushcfunction(L, vec4OpDiv); lua_setfield(L, -2, "__div"); - lua_pushcfunction(L, vec4OpUnm); lua_setfield(L, -2, "__unm"); - lua_pushcfunction(L, vec4OpEq); lua_setfield(L, -2, "__eq"); - lua_pushcfunction(L, vec4OpToString); lua_setfield(L, -2, "__tostring"); - lua_pushcfunction(L, vec4Dot); lua_setfield(L, -2, "dot"); - lua_pushcfunction(L, vec4Length); lua_setfield(L, -2, "length"); - lua_pushcfunction(L, vec4LengthSq); lua_setfield(L, -2, "lengthSq"); - lua_pushcfunction(L, vec4Normalize); lua_setfield(L, -2, "normalize"); - lua_pushcfunction(L, vec4Lerp); lua_setfield(L, -2, "lerp"); - lua_pushcfunction(L, vec4Negate); lua_setfield(L, -2, "negate"); + if(!luaL_newmetatable(L, "vec4_mt")) { + lua_pop(L, 1); + return; + } + + lua_pushcfunction(L, moduleVec4Index); + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, moduleVec4NewIndex); + lua_setfield(L, -2, "__newindex"); + lua_pushcfunction(L, moduleVec4OpAdd); + lua_setfield(L, -2, "__add"); + lua_pushcfunction(L, moduleVec4OpSub); + lua_setfield(L, -2, "__sub"); + lua_pushcfunction(L, moduleVec4OpMul); + lua_setfield(L, -2, "__mul"); + lua_pushcfunction(L, moduleVec4OpDiv); + lua_setfield(L, -2, "__div"); + lua_pushcfunction(L, moduleVec4OpUnm); + lua_setfield(L, -2, "__unm"); + lua_pushcfunction(L, moduleVec4OpEq); + lua_setfield(L, -2, "__eq"); + lua_pushcfunction(L, moduleVec4OpToString); + lua_setfield(L, -2, "__tostring"); + lua_pushcfunction(L, moduleVec4Dot); + lua_setfield(L, -2, "dot"); + lua_pushcfunction(L, moduleVec4Length); + lua_setfield(L, -2, "length"); + lua_pushcfunction(L, moduleVec4LengthSq); + lua_setfield(L, -2, "lengthSq"); + lua_pushcfunction(L, moduleVec4Normalize); + lua_setfield(L, -2, "normalize"); + lua_pushcfunction(L, moduleVec4Lerp); + lua_setfield(L, -2, "lerp"); + lua_pushcfunction(L, moduleVec4Negate); + lua_setfield(L, -2, "negate"); lua_pop(L, 1); - lua_register(L, "vec4", vec4Create); + lua_register(L, "vec4", moduleVec4Create); } diff --git a/src/dusk/script/module/module.h b/src/dusk/script/module/module.h index c059eb96..e2e5ae99 100644 --- a/src/dusk/script/module/module.h +++ b/src/dusk/script/module/module.h @@ -22,6 +22,7 @@ #include "script/module/display/modulescreen.h" #include "script/module/display/moduletexture.h" #include "script/module/display/moduletileset.h" +#include "script/module/scene/modulescene.h" void moduleRegister(lua_State *L) { assertNotNull(L, "Lua state cannot be NULL"); @@ -42,4 +43,5 @@ void moduleRegister(lua_State *L) { moduleScreen(L); moduleTexture(L); moduleTileset(L); + moduleScene(L); } \ No newline at end of file diff --git a/src/dusk/script/module/scene/modulescene.h b/src/dusk/script/module/scene/modulescene.h new file mode 100644 index 00000000..18cc233e --- /dev/null +++ b/src/dusk/script/module/scene/modulescene.h @@ -0,0 +1,126 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "script/module/modulebase.h" +#include "scene/scene.h" + +/** + * __index metamethod for the Scene table. Handles dynamic read-only properties. + * + * @param L Lua state. Arg 1: table, Arg 2: key. + * @return 1. + */ +static int moduleSceneIndex(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + const char_t *key = lua_tostring(L, 2); + assertNotNull(key, "Scene property key cannot be NULL"); + + // if(stringEquals(key, "current")) { + // if(SCENE.sceneActive) { + // lua_pushstring(L, SCENE.sceneCurrent); + // } else { + // lua_pushnil(L); + // } + // return 1; + // } + + lua_pushnil(L); + return 1; +} + +/** + * Attached Scene.set method to invoke internal C method. + * + * @param L Lua state. + * @return Number of return values on the Lua stack. + */ +static int moduleSceneSet(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + if(!lua_isstring(L, 1)) { + luaL_error(L, "Scene.set requires a string argument"); + return 0; + } + + sceneSet(lua_tostring(L, 1)); + return 0; +} + +/** + * Resets the scene back to a clean slate, this is called before loading a new + * scene to ensure that no old state bleeds through. + */ +static void moduleSceneReset(lua_State *L) { + if(SCENE.scriptRef != LUA_NOREF) { + luaL_unref(L, LUA_REGISTRYINDEX, SCENE.scriptRef); + SCENE.scriptRef = LUA_NOREF; + } + + lua_newtable(L); + + // Scene.set + lua_pushcfunction(L, moduleSceneSet); + lua_setfield(L, -2, "set"); + + // Metatable for dynamic read-only properties (e.g. Scene.current) + lua_newtable(L); + lua_pushcfunction(L, moduleSceneIndex); + lua_setfield(L, -2, "__index"); + lua_setmetatable(L, -2); + + lua_setglobal(L, "Scene"); +} + +/** + * Invokes a method on the current scene. + * + * @param method Which method to call. + * @return Any error state that happened. + */ +static errorret_t moduleSceneCall(lua_State *L, const char_t *method) { + assertNotNull(L, "Lua state cannot be NULL"); + assertStrLenMin(method, 1, "Method name cannot be empty"); + assertTrue( + SCENE.scriptRef != LUA_NOREF && SCENE.scriptRef != LUA_REFNIL, + "No active scene script to call method on" + ); + + // Get the scene table + lua_rawgeti(L, LUA_REGISTRYINDEX, SCENE.scriptRef); + if(!lua_istable(L, -1)) { + lua_pop(L, 1); + errorThrow("Scene script ref %d is not a table", SCENE.scriptRef); + } + + // Get the method from the scene table + lua_getfield(L, -1, method); + if(!lua_isfunction(L, -1)) { + lua_pop(L, 2); + errorThrow("Scene method '%s' not found", method);// TODO: Needed? + } + + // Push the scene table as the first argument (self) + lua_pushvalue(L, -2); + + // Call the method with 1 argument (the scene table) and 0 return values + if(lua_pcall(L, 1, 0, 0) != LUA_OK) { + const char_t *err = lua_tostring(L, -1); + lua_pop(L, 2);// Pops the error message and the scene table + errorThrow("Scene:%s failed: %s", method, err); + } + + lua_pop(L, 1);// Pops the scene table + errorOk(); +} + +static void moduleScene(lua_State *L) { + assertNotNull(L, "Lua state cannot be NULL"); + + moduleSceneReset(L); +} \ No newline at end of file diff --git a/src/dusk/util/string.c b/src/dusk/util/string.c index d4e108ed..a6b26bae 100644 --- a/src/dusk/util/string.c +++ b/src/dusk/util/string.c @@ -28,6 +28,10 @@ int stringCompare(const char_t *str1, const char_t *str2) { return strcmp(str1, str2); } +bool_t stringEquals(const char_t *str1, const char_t *str2) { + return stringCompare(str1, str2) == 0; +} + int stringCompareInsensitive(const char_t *str1, const char_t *str2) { assertNotNull(str1, "str1 must not be NULL"); assertNotNull(str2, "str2 must not be NULL"); diff --git a/src/dusk/util/string.h b/src/dusk/util/string.h index 8b21ae2a..b42cf795 100644 --- a/src/dusk/util/string.h +++ b/src/dusk/util/string.h @@ -36,6 +36,15 @@ void stringCopy(char_t *dest, const char_t *src, const size_t destSize); */ int stringCompare(const char_t *str1, const char_t *str2); +/** + * Compares two strings for equality. + * + * @param str1 The first string. + * @param str2 The second string. + * @return TRUE if the strings are equal, FALSE otherwise. + */ +bool_t stringEquals(const char_t *str1, const char_t *str2); + /** * Compares two strings, ignoring case. *