/** * Copyright (c) 2026 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "modulecamera.h" #include "assert/assert.h" #include "display/camera/camera.h" #include "util/memory.h" #include "util/string.h" void moduleCamera(scriptcontext_t *context) { assertNotNull(context, "Context cannot be NULL."); // Create metatable for camera structure. if(luaL_newmetatable(context->luaState, "camera_mt")) { // Metatable methods lua_pushcfunction(context->luaState, moduleCameraIndex); lua_setfield(context->luaState, -2, "__index"); lua_pushcfunction(context->luaState, moduleCameraNewIndex); lua_setfield(context->luaState, -2, "__newindex"); lua_pop(context->luaState, 1); } // Definitions #define MODULE_CAMERA_SCRIPT_LEN 64 char_t buffer[MODULE_CAMERA_SCRIPT_LEN]; #define MODULE_CAMERA_DEF(str, val) \ snprintf( \ buffer, \ MODULE_CAMERA_SCRIPT_LEN, \ "%s = %d", \ str, \ val \ ); \ scriptContextExec(context, buffer); MODULE_CAMERA_DEF( "CAMERA_PROJECTION_TYPE_PERSPECTIVE", CAMERA_PROJECTION_TYPE_PERSPECTIVE ); MODULE_CAMERA_DEF( "CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED", CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED ); MODULE_CAMERA_DEF( "CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC", CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC ); MODULE_CAMERA_DEF( "CAMERA_VIEW_TYPE_MATRIX", CAMERA_VIEW_TYPE_MATRIX ); MODULE_CAMERA_DEF( "CAMERA_VIEW_TYPE_LOOKAT", CAMERA_VIEW_TYPE_LOOKAT ); MODULE_CAMERA_DEF( "CAMERA_VIEW_TYPE_2D", CAMERA_VIEW_TYPE_2D ); MODULE_CAMERA_DEF( "CAMERA_VIEW_TYPE_LOOKAT_PIXEL_PERFECT", CAMERA_VIEW_TYPE_LOOKAT_PIXEL_PERFECT ); // Methods lua_register(context->luaState, "cameraCreate", moduleCameraCreate); lua_register(context->luaState, "cameraPushMatrix", moduleCameraPushMatrix); lua_register(context->luaState, "cameraPopMatrix", moduleCameraPopMatrix); } int moduleCameraCreate(lua_State *L) { assertNotNull(L, "Lua state cannot be NULL."); scriptcontext_t *context = *(scriptcontext_t **)lua_getextraspace(L); assertNotNull(context, "Script context cannot be NULL."); // If we are provided a projection type, use it. cameraprojectiontype_t projType = CAMERA_PROJECTION_TYPE_PERSPECTIVE; if(lua_gettop(L) >= 1) { projType = (cameraprojectiontype_t)luaL_checkinteger(L, 1); } // Create camera that Lua will own camera_t *cam = (camera_t *)lua_newuserdata(L, sizeof(camera_t)); memoryZero(cam, sizeof(camera_t)); // Set metatable luaL_getmetatable(L, "camera_mt"); lua_setmetatable(L, -2); // Init camera switch(projType) { case CAMERA_PROJECTION_TYPE_PERSPECTIVE: case CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED: cameraInitPerspective(cam); cam->projType = projType; break; case CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC: cameraInitOrthographic(cam); cam->projType = projType; break; default: luaL_error(L, "Invalid camera projection type specified."); break; } return 1; } int moduleCameraPushMatrix(lua_State *L) { assertNotNull(L, "Lua state cannot be NULL."); assertTrue(lua_gettop(L) >= 1, "cameraPushMatrix requires 1 arg."); assertTrue(lua_isuserdata(L, 1), "cameraPushMatrix arg must be userdata."); // Camera should be provided (pointer to camera_t). camera_t *cam = (camera_t *)luaL_checkudata(L, 1, "camera_mt"); assertNotNull(cam, "Camera pointer cannot be NULL."); cameraPushMatrix(cam); return 0; } int moduleCameraPopMatrix(lua_State *L) { assertNotNull(L, "Lua state cannot be NULL."); cameraPopMatrix(); return 0; } int moduleCameraIndex(lua_State *l) { assertNotNull(l, "Lua state cannot be NULL."); const char_t *key = luaL_checkstring(l, 2); assertStrLenMin(key, 1, "Key cannot be empty."); camera_t *cam = (camera_t *)luaL_checkudata(l, 1, "camera_mt"); assertNotNull(cam, "Camera pointer cannot be NULL."); if(stringCompare(key, "near") == 0) { lua_pushnumber(l, cam->nearClip); return 1; } else if(stringCompare(key, "far") == 0) { lua_pushnumber(l, cam->farClip); return 1; } else if(stringCompare(key, "nearClip") == 0) { lua_pushnumber(l, cam->nearClip); return 1; } else if(stringCompare(key, "farClip") == 0) { lua_pushnumber(l, cam->farClip); return 1; } // Perspective relative values if(cam->projType == CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) { if(stringCompare(key, "left") == 0) { lua_pushnumber(l, cam->orthographic.left); return 1; } else if(stringCompare(key, "right") == 0) { lua_pushnumber(l, cam->orthographic.right); return 1; } else if(stringCompare(key, "top") == 0) { lua_pushnumber(l, cam->orthographic.top); return 1; } else if(stringCompare(key, "bottom") == 0) { lua_pushnumber(l, cam->orthographic.bottom); return 1; } } else if( cam->projType == CAMERA_PROJECTION_TYPE_PERSPECTIVE || cam->projType == CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED ) { if(stringCompare(key, "fov") == 0) { lua_pushnumber(l, cam->perspective.fov); return 1; } } // View type relative values. if(cam->viewType == CAMERA_VIEW_TYPE_MATRIX) { } else if(cam->viewType == CAMERA_VIEW_TYPE_LOOKAT) { if(stringCompare(key, "position") == 0) { vec3 *v = (vec3 *)lua_newuserdata(l, sizeof(vec3)); memoryCopy(v, &cam->lookat.position, sizeof(vec3)); luaL_getmetatable(l, "vec3_mt"); lua_setmetatable(l, -2); return 1; } else if(stringCompare(key, "target") == 0) { vec3 *v = (vec3 *)lua_newuserdata(l, sizeof(vec3)); memoryCopy(v, &cam->lookat.target, sizeof(vec3)); luaL_getmetatable(l, "vec3_mt"); lua_setmetatable(l, -2); return 1; } else if(stringCompare(key, "up") == 0) { vec3 *v = (vec3 *)lua_newuserdata(l, sizeof(vec3)); memoryCopy(v, &cam->lookat.up, sizeof(vec3)); luaL_getmetatable(l, "vec3_mt"); lua_setmetatable(l, -2); return 1; } } else if(cam->viewType == CAMERA_VIEW_TYPE_LOOKAT_PIXEL_PERFECT) { } else if(cam->viewType == CAMERA_VIEW_TYPE_2D) { } lua_pushnil(l); return 1; } int moduleCameraNewIndex(lua_State *l) { assertNotNull(l, "Lua state cannot be NULL."); const char_t *key = luaL_checkstring(l, 2); assertStrLenMin(key, 1, "Key cannot be empty."); camera_t *cam = (camera_t *)luaL_checkudata(l, 1, "camera_mt"); assertNotNull(cam, "Camera pointer cannot be NULL."); if(stringCompare(key, "near") == 0 || stringCompare(key, "nearClip") == 0) { if(!lua_isnumber(l, 3)) { luaL_error(l, "Camera near clip must be a number."); } cam->nearClip = (float_t)lua_tonumber(l, 3); return 0; } if(stringCompare(key, "far") == 0 || stringCompare(key, "farClip") == 0) { if(!lua_isnumber(l, 3)) { luaL_error(l, "Camera far clip must be a number."); } cam->farClip = (float_t)lua_tonumber(l, 3); return 0; } // Perspective relative values if(cam->projType == CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) { if(stringCompare(key, "left") == 0) { if(!lua_isnumber(l, 3)) { luaL_error(l, "Camera orthographic left must be a number."); } cam->orthographic.left = (float_t)lua_tonumber(l, 3); return 0; } if(stringCompare(key, "right") == 0) { if(!lua_isnumber(l, 3)) { luaL_error(l, "Camera orthographic right must be a number."); } cam->orthographic.right = (float_t)lua_tonumber(l, 3); return 0; } if(stringCompare(key, "top") == 0) { if(!lua_isnumber(l, 3)) { luaL_error(l, "Camera orthographic top must be a number."); } cam->orthographic.top = (float_t)lua_tonumber(l, 3); return 0; } if(stringCompare(key, "bottom") == 0) { if(!lua_isnumber(l, 3)) { luaL_error(l, "Camera orthographic bottom must be a number."); } cam->orthographic.bottom = (float_t)lua_tonumber(l, 3); return 0; } } else if( cam->projType == CAMERA_PROJECTION_TYPE_PERSPECTIVE || cam->projType == CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED ) { if(stringCompare(key, "fov") == 0) { if(!lua_isnumber(l, 3)) { luaL_error(l, "Camera perspective FOV must be a number."); } cam->perspective.fov = (float_t)lua_tonumber(l, 3); return 0; } } if(cam->viewType == CAMERA_VIEW_TYPE_LOOKAT) { if(stringCompare(key, "position") == 0) { vec3 *v = (vec3 *)luaL_checkudata(l, 3, "vec3_mt"); assertNotNull(v, "Vec3 pointer cannot be NULL."); memoryCopy(&cam->lookat.position, v, sizeof(vec3)); return 0; } else if(stringCompare(key, "target") == 0) { vec3 *v = (vec3 *)luaL_checkudata(l, 3, "vec3_mt"); assertNotNull(v, "Vec3 pointer cannot be NULL."); memoryCopy(&cam->lookat.target, v, sizeof(vec3)); return 0; } else if(stringCompare(key, "up") == 0) { vec3 *v = (vec3 *)luaL_checkudata(l, 3, "vec3_mt"); assertNotNull(v, "Vec3 pointer cannot be NULL."); memoryCopy(&cam->lookat.up, v, sizeof(vec3)); return 0; } } return 0; }