diff --git a/CMakeLists.txt b/CMakeLists.txt index 625591d..65e5b85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.5) set (CMAKE_EXPORT_COMPILE_COMMANDS ON) # C++ -set (CMAKE_CXX_STANDARD 20) +set (CMAKE_CXX_STANDARD 23) set (CMAKE_CXX_STANDARD_REQUIRED ON) # Project declaration diff --git a/README.md b/README.md index e0b087d..37cfc0b 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,9 @@ Come back later, sorry! Jenjin is not ready for real usage. - [x] Editor - [x] Serialization/Deserialization of scenes - [x] Texture loading +- [x] Scripting (Lua) - [ ] Transparency on sprites -- [ ] Scripting (Lua) +- [ ] Icons everywhere - [ ] Code editor in the editor - [ ] Dynamic data attached to game objects at runtime - [ ] Hierarchy and `.Parent` in Lua along with `workspace` (e.g. `Workspace.MyGameObject.Parent` == `Workspace`) diff --git a/engine/include/jenjin/editor/editor.h b/engine/include/jenjin/editor/editor.h index d8455aa..8a61ba8 100644 --- a/engine/include/jenjin/editor/editor.h +++ b/engine/include/jenjin/editor/editor.h @@ -40,6 +40,8 @@ class Manager { bool selectedCamera = false; char renameGameObjectBuffer[256] = {0}; + + bool running = false; }; } // namespace Editor } // namespace Jenjin diff --git a/engine/include/jenjin/editor/utils.h b/engine/include/jenjin/editor/utils.h index 3f62588..5ab2983 100644 --- a/engine/include/jenjin/editor/utils.h +++ b/engine/include/jenjin/editor/utils.h @@ -1,5 +1,6 @@ #pragma once +#define GLFW_INCLUDE_NONE #include #include diff --git a/engine/include/jenjin/editor/widgets.h b/engine/include/jenjin/editor/widgets.h index c538d28..d2bd720 100644 --- a/engine/include/jenjin/editor/widgets.h +++ b/engine/include/jenjin/editor/widgets.h @@ -1,8 +1,9 @@ #pragma once #include "jenjin/gameobject.h" + namespace Jenjin::Editor::Widgets { bool transformWidget( Jenjin::GameObject::Transform - *transform); // Returns true if the transform was changed + *transform); }; diff --git a/engine/include/jenjin/engine.h b/engine/include/jenjin/engine.h index 166d8a8..95892c8 100644 --- a/engine/include/jenjin/engine.h +++ b/engine/include/jenjin/engine.h @@ -1,5 +1,6 @@ #pragma once +#include "jenjin/luamanager.h" #include "jenjin/scene.h" #include "jenjin/target.h" @@ -23,6 +24,8 @@ class Engine { private: std::vector> scenes = {}; Scene *currentScene = nullptr; + + LuaManager luaManager; }; extern Engine *EngineRef; diff --git a/engine/include/jenjin/luamanager.h b/engine/include/jenjin/luamanager.h new file mode 100644 index 0000000..5ab131e --- /dev/null +++ b/engine/include/jenjin/luamanager.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include + +namespace Jenjin { +class LuaManager { +public: + LuaManager(); + + // Used to manage loading and executing Lua scripts + // from files and directories + void LoadDirectory(const std::string &directory); + void LoadFile(const std::string &file); + void ReloadScripts(const std::string &directory); + + // One-shot Lua script execution + void Execute(const std::string &script); + void ExecuteFile(const std::string &file); + + // Utilities for calling Lua functions collected from files + // NOTE: This is not related to one-shot `Execute`/`ExecuteFile` + // calls. This is for `LoadFile`, `LoadDirectory`, etc. + void Ready(); + void Update(); + + // Getters + sol::state *GetLuaState() { return &lua; } + +private: + sol::state lua; + + std::unordered_map> + functions = {}; +}; +} // namespace Jenjin diff --git a/engine/include/jenjin/scene.h b/engine/include/jenjin/scene.h index 8fb9921..d66bd59 100644 --- a/engine/include/jenjin/scene.h +++ b/engine/include/jenjin/scene.h @@ -2,6 +2,7 @@ #include "jenjin/camera.h" #include "jenjin/gameobject.h" +#include "jenjin/luamanager.h" #include "jenjin/shader.h" #include "jenjin/target.h" #include "jenjin/texture.h" @@ -36,11 +37,14 @@ class Scene { void Update(); void Render(); + std::shared_ptr GetGameObject(const std::string &name); + Camera *GetCamera() { return &camera; } std::vector> *GetGameObjects() { return &gameObjects; } Target *GetTarget() { return target; } + LuaManager *GetLuaManager() { return &luaManager; } void Save(const std::string &path); void Save(std::ofstream &file); @@ -60,5 +64,7 @@ class Scene { Target *target = nullptr; Camera camera = Camera(&shader, glm::vec2(800, 600)); + + LuaManager luaManager; }; } // namespace Jenjin diff --git a/engine/include/jenjin/targets/default.h b/engine/include/jenjin/targets/runtime.h similarity index 80% rename from engine/include/jenjin/targets/default.h rename to engine/include/jenjin/targets/runtime.h index de220cf..1698009 100644 --- a/engine/include/jenjin/targets/default.h +++ b/engine/include/jenjin/targets/runtime.h @@ -4,9 +4,9 @@ namespace Jenjin { namespace Targets { -class DefaultTarget : public Jenjin::Target { +class RuntimeTarget : public Jenjin::Target { public: - DefaultTarget() = default; + RuntimeTarget() = default; virtual void PreRender() override; diff --git a/engine/src/camera.cpp b/engine/src/camera.cpp index bfd8877..c339dfc 100644 --- a/engine/src/camera.cpp +++ b/engine/src/camera.cpp @@ -5,8 +5,8 @@ #include #include -#include #include +#include using namespace Jenjin; diff --git a/engine/src/editor/editor.cpp b/engine/src/editor/editor.cpp index 46919cb..2710681 100644 --- a/engine/src/editor/editor.cpp +++ b/engine/src/editor/editor.cpp @@ -53,15 +53,34 @@ void Manager::menu() { if (!this->paths.openScenePath.empty()) { if (ImGui::BeginMenu("Scripts")) { if (ImGui::MenuItem("Reload")) { - spdlog::warn("Unimplemented: Reload Scripts"); + auto luaManager = + Jenjin::EngineRef->GetCurrentScene()->GetLuaManager(); + + luaManager->ReloadScripts(this->paths.projectPath + "/scripts/"); + luaManager->Ready(); } ImGui::EndMenu(); } if (ImGui::BeginMenu("Game")) { - if (ImGui::MenuItem("Play")) { - spdlog::warn("Unimplemented: Play"); + if (ImGui::MenuItem(this->running ? "Stop" : "Play")) { + this->running = !this->running; + + auto scene = Jenjin::EngineRef->GetCurrentScene(); + + if (this->running) { + scene->Save(this->paths.liveScenePath); + + Jenjin::EngineRef->GetCurrentScene() + ->GetLuaManager() + ->LoadDirectory( + (this->paths.projectPath + "/scripts/").c_str()); + + scene->GetLuaManager()->Ready(); + } else { + scene->Load(this->paths.liveScenePath); + } } ImGui::EndMenu(); @@ -305,7 +324,12 @@ void Manager::inspector(Jenjin::Scene *scene) { selectedObject->texturePath == texture.path().string(); if (ImGui::Selectable(texture.path().filename().string().c_str(), isSelected)) { - scene->SetGameObjectTexture(selectedObject, texture.path().string()); + if (isSelected) { + scene->SetGameObjectTexture(selectedObject, ""); + } else { + scene->SetGameObjectTexture(selectedObject, + texture.path().string()); + } } } } @@ -337,64 +361,6 @@ void Manager::inspector(Jenjin::Scene *scene) { ImGui::Unindent(); } - /* ImGui::Text("Colours"); */ - /* ImGui::Separator(); */ - /* ImGui::Indent(); */ - /* ImGui::Spacing(); */ - /* ImGui::ColorEdit3("Color", glm::value_ptr(selectedObject->color)); */ - /* ImGui::Unindent(); */ - - /* ImGui::Text("Textures"); */ - /* ImGui::Separator(); */ - /* ImGui::Indent(); */ - /* ImGui::Spacing(); */ - /* auto diriter = std::filesystem::directory_iterator(this->paths.projectPath - * + "/textures/"); */ - - /* if (diriter == std::filesystem::directory_iterator()) { */ - /* ImGui::Text("No textures found"); */ - /* } */ - - /* for (auto& texture : diriter) { */ - /* if (texture.is_regular_file() && texture.path().extension() == ".png" || - * texture.path().extension() == ".jpg") { */ - /* bool isSelected = selectedObject->texturePath == - * texture.path().string(); */ - /* if - * (ImGui::Selectable(texture.path().filename().string().c_str(), isSelected)) - * { */ - /* scene->SetGameObjectTexture(selectedObject, - * texture.path().string()); */ - /* } */ - /* } */ - /* } */ - - /* if (!selectedObject->texturePath.empty()) { */ - /* ImGui::Spacing(); */ - /* ImGui::Checkbox("Mix Color", &selectedObject->mixColor); */ - /* } */ - - /* ImGui::Unindent(); */ - - /* ImGui::Spacing(); */ - - /* ImGui::Text("Manage"); */ - /* ImGui::Separator(); */ - /* ImGui::Indent(); */ - /* ImGui::Spacing(); */ - - /* ImGui::InputText("##RenameInput", renameGameObjectBuffer, - * sizeof(renameGameObjectBuffer)); */ - /* ImGui::SameLine(); */ - /* if (ImGui::Button("Rename")) { */ - /* selectedObject->SetName(renameGameObjectBuffer); */ - /* } */ - - /* if (ImGui::Button("Delete")) { */ - /* scene->RemoveGameObject(selectedObject); */ - /* selectedObject = nullptr; */ - /* } */ - ImGui::Unindent(); ImGui::End(); @@ -465,6 +431,19 @@ void Manager::backup_prompts(Jenjin::Scene *scene) { void Manager::code(Jenjin::Scene *scene) {} void Manager::show_all(Jenjin::Scene *scene) { + bool isRunning = this->running; + if (isRunning) { + scene->Update(); + + auto bg = ImGui::GetStyle().Colors[ImGuiCol_WindowBg]; + auto red_bg = ImVec4((bg.x + 0.01) * 1.5f, bg.y, bg.z, bg.w); + auto red_bg_dark = + ImVec4((bg.x + 0.01) * 1.5, bg.y * 0.9, bg.z * 0.9, bg.w); + ImGui::PushStyleColor(ImGuiCol_WindowBg, red_bg); + ImGui::PushStyleColor(ImGuiCol_TitleBg, red_bg_dark); + ImGui::PushStyleColor(ImGuiCol_TitleBgActive, red_bg); + } + if (this->paths.projectPath.empty()) { menu(); dockspace(); @@ -475,6 +454,8 @@ void Manager::show_all(Jenjin::Scene *scene) { menu(); dockspace(); + if (isRunning) + ImGui::PopStyleColor(3); hierarchy(scene); inspector(scene); @@ -493,6 +474,7 @@ void Manager::welcome() { ImGui::GetFrameHeightWithSpacing(); int spad = ImGui::GetStyle().WindowPadding.y * 2 + ImGui::GetStyle().ItemSpacing.y * 2; + ImGui::BeginChild( "WelcomeChild", ImVec2(ImGui::GetWindowWidth() - ImGui::GetStyle().WindowPadding.x * 2, @@ -509,13 +491,11 @@ void Manager::welcome() { Jenjin::EngineRef->GetCurrentScene()->GetGameObjects()->clear(); std::ifstream ifile(this->paths.openScenePath); - /* Jenjin::EngineRef->GetCurrentScene()->load(ifile); */ Jenjin::EngineRef->GetCurrentScene()->Load(this->paths.openScenePath); // Load all the lua files - spdlog::warn("Unimplemented: Load Lua files"); - /* Jenjin::EngineRef->GetCurrentScene()->reload_lua((this->paths.projectPath - * + "/scripts/").c_str()); */ + Jenjin::EngineRef->GetCurrentScene()->GetLuaManager()->LoadDirectory( + (this->paths.projectPath + "/scripts/").c_str()); }; for (auto file : std::filesystem::directory_iterator( diff --git a/engine/src/editor/utils.cpp b/engine/src/editor/utils.cpp index fec561d..8436ccb 100644 --- a/engine/src/editor/utils.cpp +++ b/engine/src/editor/utils.cpp @@ -4,8 +4,8 @@ #include -#include #include +#include void Jenjin::Editor::ensure_dir(std::string path) { if (!std::filesystem::exists(path)) { diff --git a/engine/src/engine.cpp b/engine/src/engine.cpp index 2201036..2550df0 100644 --- a/engine/src/engine.cpp +++ b/engine/src/engine.cpp @@ -22,6 +22,9 @@ Engine::Engine(GLFWwindow *window) { spdlog::debug("Initializing Jenjin {}", VERSION); glfwSetErrorCallback([](int code, const char *error) { + if (code == 65540 || code == 65539) + return; + spdlog::error("GLFW Error: {} ({})", error, code); }); diff --git a/engine/src/luamanager.cpp b/engine/src/luamanager.cpp new file mode 100644 index 0000000..e415c73 --- /dev/null +++ b/engine/src/luamanager.cpp @@ -0,0 +1,534 @@ +#include +#define GLFW_INCLUDE_NONE + +#include "jenjin/engine.h" +#include "jenjin/luamanager.h" + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Jenjin; + +void Bindings(LuaManager *lm, sol::state &lua) { + // ======================================================================== + // Logging + // ======================================================================== + + // This is a lambda function that takes a variadic number of arguments + // and constructs a string from them to be used in the logging functions + // It takes in basic types and handles some bound userdata types> + + auto construct_string = [&](sol::variadic_args va) { + std::stringstream ss; + + int i = 0; + for (auto v : va) { + if (v.get_type() == sol::type::number) { + ss << v.get(); + } else if (v.get_type() == sol::type::string) { + ss << v.get(); + } else if (v.get_type() == sol::type::boolean) { + ss << (v.get() ? "true" : "false"); + } else if (v.get_type() == sol::type::nil) { + ss << "nil"; + } else if (v.get_type() == sol::type::userdata) { + auto ud = v.get(); + if (ud.is()) { + auto vec = ud.as(); + ss << "vec2(" << vec.x << ", " << vec.y << ")"; + } else if (ud.is()) { + auto vec = ud.as(); + ss << "vec3(" << vec.x << ", " << vec.y << ", " << vec.z << ")"; + } else { + ss << "unknown userdata"; + } + } + + // Add a comma if there are more arguments + if (i++ < va.size() - 1) { + ss << ", "; + } + } + + return ss.str(); + }; + + lua.set_function("info", [&](sol::variadic_args va) { + spdlog::info("[LUA] {}", construct_string(va)); + }); + + lua.set_function("warn", [&](sol::variadic_args va) { + spdlog::warn("[LUA] {}", construct_string(va)); + }); + + lua.set_function("error", [&](sol::variadic_args va) { + spdlog::error("[LUA] {}", construct_string(va)); + }); + + lua.set_function("debug", [&](sol::variadic_args va) { + spdlog::debug("[LUA] {}", construct_string(va)); + }); + + lua.set_function("trace", [&](sol::variadic_args va) { + spdlog::trace("[LUA] {}", construct_string(va)); + }); + + lua.set_function("critical", [&](sol::variadic_args va) { + spdlog::critical("[LUA] {}", construct_string(va)); + }); + + lua.set_function("print", [&](sol::variadic_args va) { + spdlog::info("[LUA] {}", construct_string(va)); + }); + + // ======================================================================== + // Userdata + // ======================================================================== + + lua.new_usertype( + "vec2", sol::constructors(), "x", + &glm::vec2::x, "y", &glm::vec2::y, + + sol::meta_function::addition, + sol::overload( + [](const glm::vec2 &lhs, const glm::vec2 &rhs) { return lhs + rhs; }, + [](const glm::vec2 &lhs, float rhs) { return lhs + rhs; }, + [](float lhs, const glm::vec2 &rhs) { return lhs + rhs; }), + + sol::meta_function::subtraction, + sol::overload( + [](const glm::vec2 &lhs, const glm::vec2 &rhs) { return lhs - rhs; }, + [](const glm::vec2 &lhs, float rhs) { return lhs - rhs; }, + [](float lhs, const glm::vec2 &rhs) { return lhs - rhs; }), + + sol::meta_function::multiplication, + sol::overload( + [](const glm::vec2 &lhs, const glm::vec2 &rhs) { return lhs * rhs; }, + [](const glm::vec2 &lhs, float rhs) { return lhs * rhs; }, + [](float lhs, const glm::vec2 &rhs) { return lhs * rhs; }), + + sol::meta_function::division, + sol::overload( + [](const glm::vec2 &lhs, const glm::vec2 &rhs) { return lhs / rhs; }, + [](const glm::vec2 &lhs, float rhs) { return lhs / rhs; }, + [](float lhs, const glm::vec2 &rhs) { return lhs / rhs; }), + + sol::meta_function::equal_to, + sol::overload( + [](const glm::vec2 &lhs, const glm::vec2 &rhs) { return lhs == rhs; }, + [](const glm::vec2 &lhs, float rhs) { return lhs == glm::vec2(rhs); }, + [](float lhs, const glm::vec2 &rhs) { + return glm::vec2(lhs) == rhs; + }), + + sol::meta_function::less_than, + sol::overload( + [](const glm::vec2 &lhs, const glm::vec2 &rhs) { + return (lhs.x < rhs.x) && (lhs.y < rhs.y); + }, + [](const glm::vec2 &lhs, float rhs) { + return (lhs.x < rhs) && (lhs.y < rhs); + }, + [](float lhs, const glm::vec2 &rhs) { + return (lhs < rhs.x) && (lhs < rhs.y); + }), + + sol::meta_function::less_than_or_equal_to, + sol::overload( + [](const glm::vec2 &lhs, const glm::vec2 &rhs) { + return (lhs.x <= rhs.x) && (lhs.y <= rhs.y); + }, + [](const glm::vec2 &lhs, float rhs) { + return (lhs.x <= rhs) && (lhs.y <= rhs); + }, + [](float lhs, const glm::vec2 &rhs) { + return (lhs <= rhs.x) && (lhs <= rhs.y); + }), + + sol::meta_function::to_string, + [](const glm::vec2 &vec) { + std::stringstream ss; + ss << "vec2(" << vec.x << ", " << vec.y << ")"; + return ss.str(); + }, + + sol::meta_function::index, + [](const glm::vec2 &vec, const std::string &key) { + if (key == "x") { + return vec.x; + } else if (key == "y") { + return vec.y; + } else if (key == "r") { + return vec.x; + } else if (key == "g") { + return vec.y; + } else if (key == "s") { + return vec.x; + } else if (key == "t") { + return vec.y; + } else { + return -1.0f; + } + }); + + // glm::vec3 + lua.new_usertype( + "vec3", sol::constructors(), + "x", &glm::vec3::x, "y", &glm::vec3::y, "z", &glm::vec3::z, + + // Operators + // +, -, *, /, ==, <, >, <=, >= are our targets + sol::meta_function::addition, + sol::overload( + [](const glm::vec3 &lhs, const glm::vec3 &rhs) { return lhs + rhs; }, + [](const glm::vec3 &lhs, float rhs) { return lhs + rhs; }, + [](float lhs, const glm::vec3 &rhs) { return lhs + rhs; }), + + sol::meta_function::subtraction, + sol::overload( + [](const glm::vec3 &lhs, const glm::vec3 &rhs) { return lhs - rhs; }, + [](const glm::vec3 &lhs, float rhs) { return lhs - rhs; }, + [](float lhs, const glm::vec3 &rhs) { return lhs - rhs; }), + + sol::meta_function::multiplication, + sol::overload( + [](const glm::vec3 &lhs, const glm::vec3 &rhs) { return lhs * rhs; }, + [](const glm::vec3 &lhs, float rhs) { return lhs * rhs; }, + [](float lhs, const glm::vec3 &rhs) { return lhs * rhs; }), + + sol::meta_function::division, + sol::overload( + [](const glm::vec3 &lhs, const glm::vec3 &rhs) { return lhs / rhs; }, + [](const glm::vec3 &lhs, float rhs) { return lhs / rhs; }, + [](float lhs, const glm::vec3 &rhs) { return lhs / rhs; }), + + sol::meta_function::equal_to, + sol::overload( + [](const glm::vec3 &lhs, const glm::vec3 &rhs) { return lhs == rhs; }, + [](const glm::vec3 &lhs, float rhs) { return lhs == glm::vec3(rhs); }, + [](float lhs, const glm::vec3 &rhs) { + return glm::vec3(lhs) == rhs; + }), + + sol::meta_function::less_than, + sol::overload( + [](const glm::vec3 &lhs, const glm::vec3 &rhs) { + return (lhs.x < rhs.x) && (lhs.y < rhs.y) && (lhs.z < rhs.z); + }, + [](const glm::vec3 &lhs, float rhs) { + return (lhs.x < rhs) && (lhs.y < rhs) && (lhs.z < rhs); + }, + [](float lhs, const glm::vec3 &rhs) { + return (lhs < rhs.x) && (lhs < rhs.y) && (lhs < rhs.z); + }), + + sol::meta_function::less_than_or_equal_to, + sol::overload( + [](const glm::vec3 &lhs, const glm::vec3 &rhs) { + return (lhs.x <= rhs.x) && (lhs.y <= rhs.y) && (lhs.z <= rhs.z); + }, + [](const glm::vec3 &lhs, float rhs) { + return (lhs.x <= rhs) && (lhs.y <= rhs) && (lhs.z <= rhs); + }, + [](float lhs, const glm::vec3 &rhs) { + return (lhs <= rhs.x) && (lhs <= rhs.y) && (lhs <= rhs.z); + }), + + sol::meta_function::to_string, + [](const glm::vec3 &vec) { + std::stringstream ss; + ss << "vec3(" << vec.x << ", " << vec.y << ", " << vec.z << ")"; + return ss.str(); + }, + + sol::meta_function::index, + [](const glm::vec3 &vec, const std::string &key) { + if (key == "x") { + return vec.x; + } else if (key == "y") { + return vec.y; + } else if (key == "z") { + return vec.z; + } else if (key == "r") { + return vec.x; + } else if (key == "g") { + return vec.y; + } else if (key == "b") { + return vec.z; + } else if (key == "s") { + return vec.x; + } else if (key == "t") { + return vec.y; + } else if (key == "p") { + return vec.z; + } else { + return -1.0f; + } + }); + + // ======================================================================== + // Math + // ======================================================================== + lua.set_function("dot", [](const glm::vec2 &lhs, const glm::vec2 &rhs) { + return glm::dot(lhs, rhs); + }); + + lua.set_function("dot", [](const glm::vec3 &lhs, const glm::vec3 &rhs) { + return glm::dot(lhs, rhs); + }); + + lua.set_function("cross", [](const glm::vec3 &lhs, const glm::vec3 &rhs) { + return glm::cross(lhs, rhs); + }); + + lua.set_function("normalize", + [](const glm::vec2 &vec) { return glm::normalize(vec); }); + + lua.set_function("normalize", + [](const glm::vec3 &vec) { return glm::normalize(vec); }); + + lua.set_function("length", + [](const glm::vec2 &vec) { return glm::length(vec); }); + + lua.set_function("length", + [](const glm::vec3 &vec) { return glm::length(vec); }); + + lua.set_function("distance", [](const glm::vec2 &lhs, const glm::vec2 &rhs) { + return glm::distance(lhs, rhs); + }); + + lua.set_function("distance", [](const glm::vec3 &lhs, const glm::vec3 &rhs) { + return glm::distance(lhs, rhs); + }); + + // ======================================================================== + // Jenjin userdata + // ======================================================================== + + lua.new_usertype( + "Scene", sol::no_constructor, "AddGameObject", &Scene::AddGameObject, + "RemoveGameObject", + sol::overload((void(Scene::*)(GameObject *)) & Scene::RemoveGameObject, + (void(Scene::*)(std::shared_ptr)) & + Scene::RemoveGameObject), + "SetGameObjectTexture", + sol::overload( + (void(Scene::*)(GameObject *, const std::string &)) & + Scene::SetGameObjectTexture, + (void(Scene::*)(std::shared_ptr, const std::string &)) & + Scene::SetGameObjectTexture), + //"Build", &Scene::Build, "Update", &Scene::Update, "Render", + // &Scene::Render, + "GetGameObject", &Scene::GetGameObject, "GetCamera", // + // Useless for Lua + &Scene::GetCamera, "GetGameObjects", &Scene::GetGameObjects, "GetTarget", + &Scene::GetTarget, "GetLuaManager", &Scene::GetLuaManager, "Save", + sol::overload((void(Scene::*)(const std::string &)) & Scene::Save, + (void(Scene::*)(std::ofstream &)) & Scene::Save), + "Load", + sol::overload((void(Scene::*)(const std::string &)) & Scene::Load, + (void(Scene::*)(std::ifstream &)) & Scene::Load)); + + lua.new_usertype( + "GameObject", sol::factories([](std::string name, Mesh mesh) { + return std::make_shared(name, mesh); + }), + "name", &GameObject::name, "transform", &GameObject::transform, "color", + &GameObject::color, "mesh", &GameObject::mesh, "texturePath", + &GameObject::texturePath, "meshReferenceID", &GameObject::meshReferenceID, + "mixColor", &GameObject::mixColor, "GetName", &GameObject::GetName, + "GetPosition", &GameObject::GetPosition, "GetScale", + &GameObject::GetScale, "GetRotation", &GameObject::GetRotation, "SetName", + &GameObject::SetName, "SetPosition", &GameObject::SetPosition, "SetScale", + &GameObject::SetScale, "SetRotation", &GameObject::SetRotation, + "GetNamePointer", &GameObject::GetNamePointer, "GetPositionPointer", + &GameObject::GetPositionPointer, "GetScalePointer", + &GameObject::GetScalePointer, "GetRotationPointer", + &GameObject::GetRotationPointer, "Translate", &GameObject::Translate, + "Scale", &GameObject::Scale, "Rotate", &GameObject::Rotate); + + lua.new_usertype( + "Transform", + sol::constructors(), + "position", &GameObject::Transform::position, "scale", + &GameObject::Transform::scale, "rotation", + &GameObject::Transform::rotation); + + lua.new_usertype("Engine", sol::no_constructor, "GetCurrentScene", + &Engine::GetCurrentScene); + + // ======================================================================== + // Utility tables + // ======================================================================== + auto input_tb = lua.create_named_table("input"); + input_tb.set_function("GetMousePosition", [&]() { + if (!EngineRef) { + spdlog::error("[LUA] No Jenjin instance initialized while calling " + "`GetMousePosition`"); + return glm::vec2(-1, -1); + } + + auto scene = EngineRef->GetCurrentScene(); + if (!scene) { + spdlog::error("[LUA] No current scene while calling `GetMousePosition`"); + return glm::vec2(-1, -1); + } + + auto target = scene->GetTarget(); + if (!scene) { + spdlog::error("[LUA] No target while calling `get_key_down`"); + return glm::vec2(-1, -1); + } + + return target->GetMousePosition(); + }); + + input_tb.set_function("IsKeyDown", [&](const std::string &key) { + static std::unordered_map keys = {}; + + if (keys.empty()) { + spdlog::info("[LUA] Initializing key map"); + + for (int i = 0; i < GLFW_KEY_LAST; i++) { + auto keyName = glfwGetKeyName(i, 0); + if (keyName) { + keys[keyName] = i; + } + } + } + + auto context = glfwGetCurrentContext(); + + if (context) { + char *key_lower = (char *)key.c_str(); + std::transform(key_lower, key_lower + strlen(key_lower), key_lower, + ::tolower); + return glfwGetKey(context, keys[key_lower]) == GLFW_PRESS; + } else { + spdlog::error("[LUA] No current context while calling `GetKeyDown`"); + return false; + } + }); +} + +LuaManager::LuaManager() { + lua.open_libraries(sol::lib::base, sol::lib::string, sol::lib::table, + sol::lib::math); + + Bindings(this, lua); +} + +void LuaManager::LoadDirectory(const std::string &directory) { + for (const auto &entry : std::filesystem::directory_iterator(directory)) { + if (entry.is_regular_file() && entry.path().extension() == ".lua") { + LoadFile(entry.path().string()); + } + } +} + +void LuaManager::LoadFile(const std::string &file) { + spdlog::info("[LUA] Loading file {}", file); + + auto res = lua.safe_script_file(file, sol::script_pass_on_error); + + if (!res.valid()) { + spdlog::error("[LUA] {}", sol::error(res).what()); + } + + sol::optional readyt = + lua.get>("READY"); + sol::optional updatet = + lua.get>("UPDATE"); + + if (!readyt || !updatet) { + spdlog::error("[LUA] `READY` or `UPDATE` function not found in {}", file); + return; + } + + auto ready = readyt.value(); + auto update = updatet.value(); + + if (ready.valid() && ready.get_type() == sol::type::function && + update.valid() && update.get_type() == sol::type::function) { + functions[file] = {ready, update}; + } +} + +void LuaManager::ReloadScripts(const std::string &directory) { + /*std::vector keys(functions | std::views::keys |*/ + /* std::ranges::to>());*/ + + functions.clear(); + this->LoadDirectory(directory); + + /*for (auto &path : keys) {*/ + /* LoadFile(path);*/ + /*}*/ +} + +void LuaManager::Execute(const std::string &script) { + auto res = lua.safe_script(script, sol::script_pass_on_error); + + if (!res.valid()) { + spdlog::error("[LUA] {}", sol::error(res).what()); + } +} + +void LuaManager::ExecuteFile(const std::string &file) { + auto res = lua.safe_script_file(file, sol::script_pass_on_error); + + if (!res.valid()) { + spdlog::error("[LUA] {}", sol::error(res).what()); + } +} + +void LuaManager::Ready() { + lua["scene"] = EngineRef->GetCurrentScene(); + lua["target"] = EngineRef->GetCurrentScene()->GetTarget(); + lua["engine"] = EngineRef; + + for (auto &func : functions) { + auto &ready = func.second.first; + auto &update = func.second.second; + + if (ready) { + auto res = ready(); + + if (!res.valid()) { + spdlog::error("[LUA] {}", sol::error(res).what()); + } + } + } +} + +void LuaManager::Update() { + for (auto &func : functions) { + auto &ready = func.second.first; + auto &update = func.second.second; + + if (update) { + auto res = update(); + + if (!res.valid()) { + spdlog::error("[LUA] {}", sol::error(res).what()); + } + } + } +} diff --git a/engine/src/scene.cpp b/engine/src/scene.cpp index fa7119c..5c4e055 100644 --- a/engine/src/scene.cpp +++ b/engine/src/scene.cpp @@ -43,6 +43,12 @@ void Scene::RemoveGameObject(GameObject *gameObject) { void Scene::SetGameObjectTexture(GameObject *gameObject, const std::string &texturePath) { + + if (texturePath.empty()) { + gameObject->texturePath = ""; + return; + } + if (this->textures.find(texturePath) == this->textures.end()) { auto alpha = texturePath.find(".png") != std::string::npos; this->textures[texturePath] = @@ -112,7 +118,7 @@ void Scene::Build() { spdlog::debug("Generated buffers (vao: {}, vbo: {}, ebo: {})", vao, vbo, ebo); } -void Scene::Update() { spdlog::trace("Scene::Update()"); } +void Scene::Update() { this->luaManager.Update(); } void Scene::Render() { this->shader.use(); @@ -154,6 +160,16 @@ void Scene::Render() { } } +std::shared_ptr Scene::GetGameObject(const std::string &name) { + for (auto &go : this->gameObjects) { + if (go->name == name) { + return go; + } + } + + return nullptr; +} + struct GOBJSAVABLE { Jenjin::GameObject::Transform transform; glm::vec3 color; diff --git a/engine/src/targets/editor.cpp b/engine/src/targets/editor.cpp index bd8a527..3d887d4 100644 --- a/engine/src/targets/editor.cpp +++ b/engine/src/targets/editor.cpp @@ -17,10 +17,11 @@ using namespace Jenjin::Targets; EditorTarget::EditorTarget() {} void EditorTarget::PreRender() { - static int i = 0; - if (i < 5) { - i++; - + // HACK: This is a hack to set the camera position to 0,0,0.. + // This needs to be done properly. + static bool done = false; + if (!done) { + done = true; Jenjin::EngineRef->GetCurrentScene()->GetCamera()->SetPosition( glm::vec3(0, 0, 0)); } diff --git a/engine/src/targets/default.cpp b/engine/src/targets/runtime.cpp similarity index 59% rename from engine/src/targets/default.cpp rename to engine/src/targets/runtime.cpp index 7cbb49e..794e9b0 100644 --- a/engine/src/targets/default.cpp +++ b/engine/src/targets/runtime.cpp @@ -1,6 +1,6 @@ #define GLFW_INCLUDE_NONE -#include "jenjin/targets/default.h" +#include "jenjin/targets/runtime.h" #include "jenjin/engine.h" #include @@ -8,20 +8,23 @@ using namespace Jenjin::Targets; -void DefaultTarget::PreRender() { +void RuntimeTarget::PreRender() { + auto scene = Jenjin::EngineRef->GetCurrentScene(); + scene->Update(); + glClearColor(0.1f, 0.1f, 0.1f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); - static int i = 0; - if (i < 5) { - i++; - - Jenjin::EngineRef->GetCurrentScene()->GetCamera()->SetPosition( - glm::vec3(0, 0, 0)); + // HACK: This is a hack to set the camera position to 0,0,0.. + // This needs to be done properly. + static bool done = false; + if (!done) { + done = true; + scene->GetCamera()->SetPosition(glm::vec3(0, 0, 0)); } } -glm::vec2 DefaultTarget::GetSize() { +glm::vec2 RuntimeTarget::GetSize() { int width, height; static auto ctx = glfwGetCurrentContext(); glfwGetWindowSize(ctx, &width, &height); @@ -29,14 +32,14 @@ glm::vec2 DefaultTarget::GetSize() { return glm::vec2(width, height); } -void DefaultTarget::Resize(glm::vec2 size) { +void RuntimeTarget::Resize(glm::vec2 size) { static auto ctx = glfwGetCurrentContext(); Jenjin::EngineRef->GetCurrentScene()->GetCamera()->Resize(size); glfwSetWindowSize(ctx, (int)size.x, (int)size.y); glViewport(0, 0, (int)size.x, (int)size.y); } -glm::vec2 DefaultTarget::GetMousePosition() { +glm::vec2 RuntimeTarget::GetMousePosition() { static auto ctx = glfwGetCurrentContext(); static double x, y; glfwGetCursorPos(ctx, &x, &y); diff --git a/jenjin/game b/jenjin/game new file mode 100755 index 0000000..0d333db Binary files /dev/null and b/jenjin/game differ diff --git a/jenjin/src/main.cpp b/jenjin/src/main.cpp index dbc0fca..233a11d 100644 --- a/jenjin/src/main.cpp +++ b/jenjin/src/main.cpp @@ -1,8 +1,8 @@ #define GLFW_INCLUDE_NONE #include "jenjin/editor/utils.h" -#include "jenjin/targets/default.h" #include "jenjin/targets/editor.h" +#include "jenjin/targets/runtime.h" #include "jenjin/engine.h" #include "jenjin/helpers.h" @@ -38,6 +38,7 @@ int main(int argc, char *argv[]) { auto scene = std::make_shared(); engine.AddScene(scene, true); + spdlog::info("Launching Jenjin {}", editor ? "Editor" : "Runtime"); (editor ? launchEditor : launchRuntime)(engine, window, scene); } @@ -56,9 +57,16 @@ void launchEditor(LAUNCH_ARGS) { } void launchRuntime(LAUNCH_ARGS) { - auto runtime = Jenjin::Targets::DefaultTarget(); + auto runtime = Jenjin::Targets::RuntimeTarget(); scene->SetTarget(&runtime); + if (!std::filesystem::exists("scripts")) { + spdlog::error("Could not find `scripts` directory"); + exit(1); + } + + scene->GetLuaManager()->LoadDirectory("scripts"); + // Look for `main.jenscene` in the current directory if (!std::filesystem::exists("main.jenscene")) { spdlog::error("Could not find `main.jenscene` in the current directory"); @@ -66,6 +74,7 @@ void launchRuntime(LAUNCH_ARGS) { } scene->Load("main.jenscene"); + scene->GetLuaManager()->Ready(); while (!glfwWindowShouldClose(window)) { engine.Render(&runtime);