diff --git a/Assets/Textures/Editor/folder.png b/Assets/Textures/Editor/folder.png new file mode 100644 index 00000000..b5908bc5 Binary files /dev/null and b/Assets/Textures/Editor/folder.png differ diff --git a/Assets/Textures/Editor/shader.png b/Assets/Textures/Editor/shader.png new file mode 100644 index 00000000..7212768a Binary files /dev/null and b/Assets/Textures/Editor/shader.png differ diff --git a/CMakeLists.txt b/CMakeLists.txt index 358b7855..5088feec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,10 +22,6 @@ include_directories(includes) set(IMGUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/includes/ImGUI/imgui_impl_glfw.cpp ${CMAKE_CURRENT_SOURCE_DIR}/includes/ImGUI/imgui_impl_opengl3.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/includes/ImGUI/imgui.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/includes/ImGUI/imgui_widgets.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/includes/ImGUI/imgui_draw.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/includes/ImGUI/imgui_tables.cpp ) set(IMGUIZMO_SRC diff --git a/ICE/Core/include/ICEEngine.h b/ICE/Core/include/ICEEngine.h index a2f71f54..7ba3c3de 100644 --- a/ICE/Core/include/ICEEngine.h +++ b/ICE/Core/include/ICEEngine.h @@ -49,6 +49,8 @@ class ICEEngine { std::shared_ptr getInternalFramebuffer() const; void setRenderFramebufferInternal(bool use_internal); + std::shared_ptr getWindow() const; + private: std::shared_ptr m_graphics_factory; std::shared_ptr ctx; diff --git a/ICE/Core/src/ICEEngine.cpp b/ICE/Core/src/ICEEngine.cpp index 9b226ed2..f49aa776 100644 --- a/ICE/Core/src/ICEEngine.cpp +++ b/ICE/Core/src/ICEEngine.cpp @@ -42,12 +42,12 @@ void ICEEngine::step() { } void ICEEngine::setupScene(const std::shared_ptr &camera_) { - auto renderer = std::make_shared(api, m_graphics_factory, project->getCurrentScene()->getRegistry(), project->getAssetBank()); - auto rs = std::make_shared(); + auto renderer = std::make_shared(api, m_graphics_factory); + auto rs = std::make_shared(api, m_graphics_factory, project->getCurrentScene()->getRegistry(), project->getAssetBank()); rs->setCamera(camera_); rs->setRenderer(renderer); project->getCurrentScene()->getRegistry()->addSystem(rs); - + camera = camera_; auto [w, h] = m_window->getSize(); renderer->resize(w, h); } @@ -80,6 +80,10 @@ void ICEEngine::setRenderFramebufferInternal(bool use_internal) { } } +std::shared_ptr ICEEngine::getWindow() const { + return m_window; +} + void ICEEngine::setProject(const std::shared_ptr &project) { this->project = project; this->camera->getPosition() = project->getCameraPosition(); diff --git a/ICE/Graphics/include/ForwardRenderer.h b/ICE/Graphics/include/ForwardRenderer.h index 28f604aa..5fb71c58 100644 --- a/ICE/Graphics/include/ForwardRenderer.h +++ b/ICE/Graphics/include/ForwardRenderer.h @@ -22,46 +22,36 @@ namespace ICE { class ForwardRenderer : public Renderer { public: - ForwardRenderer(const std::shared_ptr& api, const std::shared_ptr& factory, - const std::shared_ptr& registry, const std::shared_ptr& assetBank); + ForwardRenderer(const std::shared_ptr& api, const std::shared_ptr& factory); - void submit(Entity e) override; - void remove(Entity e) override; + void submitSkybox(const Skybox& e) override; + void submitDrawable(const Drawable& e) override; + void submitLight(const Light& e) override; void prepareFrame(Camera& camera) override; - void render() override; + std::shared_ptr render() override; void endFrame() override; - void setTarget(const std::shared_ptr& fb) override; - void resize(uint32_t width, uint32_t height) override; void setClearColor(Eigen::Vector4f clearColor) override; void setViewport(int x, int y, int w, int h) override; private: - void submitModel(int node_idx, const std::vector& nodes, const std::vector>& meshes, - const std::vector& materials, const Eigen::Matrix4f& transform); - std::shared_ptr m_api; - std::shared_ptr m_registry; - std::shared_ptr m_asset_bank; - - std::shared_ptr target = nullptr; std::vector m_render_commands; - std::vector m_render_queue; - std::vector m_lights; - AssetUID m_skybox = NO_ASSET_ID; GeometryPass m_geometry_pass; - std::shared_ptr m_quad_vao; - std::shared_ptr m_camera_ubo; std::shared_ptr m_light_ubo; + std::optional m_skybox; + std::vector m_drawables; + std::vector m_lights; + RendererConfig config; }; } // namespace ICE \ No newline at end of file diff --git a/ICE/Graphics/include/Material.h b/ICE/Graphics/include/Material.h index e4ade123..ea491459 100644 --- a/ICE/Graphics/include/Material.h +++ b/ICE/Graphics/include/Material.h @@ -36,6 +36,9 @@ class Material : public Asset { } } + void renameUniform(const std::string& previous_name, const std::string& new_name); + void removeUniform(const std::string& name); + std::unordered_map getAllUniforms() const; AssetUID getShader() const; void setShader(AssetUID shader_id); diff --git a/ICE/Graphics/include/Model.h b/ICE/Graphics/include/Model.h index 28558c4a..34190164 100644 --- a/ICE/Graphics/include/Model.h +++ b/ICE/Graphics/include/Model.h @@ -37,6 +37,9 @@ class Model : public Asset { void setSkeleton(const Skeleton &skeleton) { m_skeleton = skeleton; } void setAnimations(const std::unordered_map &animations) { m_animations = animations; } + void traverse(std::vector> &meshes, std::vector &materials, std::vector &transforms, + const Eigen::Matrix4f &base_transform = Eigen::Matrix4f::Identity()); + AssetType getType() const override { return AssetType::EModel; } std::string getTypeName() const override { return "Model"; } diff --git a/ICE/Graphics/include/RenderCommand.h b/ICE/Graphics/include/RenderCommand.h index e04383dc..d3b32231 100644 --- a/ICE/Graphics/include/RenderCommand.h +++ b/ICE/Graphics/include/RenderCommand.h @@ -10,6 +10,7 @@ #include "Material.h" #include "Mesh.h" #include "Shader.h" +#include "Model.h" namespace ICE { struct RenderCommand { @@ -19,6 +20,8 @@ struct RenderCommand { std::unordered_map> textures; Eigen::Matrix4f model_matrix; + std::vector bones; + bool faceCulling = true; bool depthTest = true; }; diff --git a/ICE/Graphics/include/Renderer.h b/ICE/Graphics/include/Renderer.h index 48c00aa3..8e45f4e1 100644 --- a/ICE/Graphics/include/Renderer.h +++ b/ICE/Graphics/include/Renderer.h @@ -42,14 +42,37 @@ struct alignas(16) CameraUBO { Eigen::Matrix4f view; }; +struct Skybox { + std::shared_ptr cube_mesh; + std::shared_ptr shader; + std::unordered_map> textures; +}; + +struct Drawable { + std::shared_ptr mesh; + std::shared_ptr material; + std::shared_ptr shader; + std::unordered_map> textures; + Eigen::Matrix4f model_matrix; + Model::Skeleton skeleton; +}; + +struct Light { + Eigen::Vector3f position; + Eigen::Vector3f rotation; + Eigen::Vector3f color; + float distance_dropoff; + LightType type; +}; + class Renderer { public: - virtual void submit(Entity e) = 0; - virtual void remove(Entity e) = 0; + virtual void submitSkybox(const Skybox& e) = 0; + virtual void submitDrawable(const Drawable& e) = 0; + virtual void submitLight(const Light& e) = 0; virtual void prepareFrame(Camera &camera) = 0; - virtual void render() = 0; + virtual std::shared_ptr render() = 0; virtual void endFrame() = 0; - virtual void setTarget(const std::shared_ptr &target) = 0; virtual void resize(uint32_t width, uint32_t height) = 0; virtual void setClearColor(Eigen::Vector4f clearColor) = 0; virtual void setViewport(int x, int y, int w, int h) = 0; diff --git a/ICE/Graphics/src/ForwardRenderer.cpp b/ICE/Graphics/src/ForwardRenderer.cpp index da8fd7f3..ffefbad7 100644 --- a/ICE/Graphics/src/ForwardRenderer.cpp +++ b/ICE/Graphics/src/ForwardRenderer.cpp @@ -14,84 +14,29 @@ #include -#include "RenderData.h" - namespace ICE { -ForwardRenderer::ForwardRenderer(const std::shared_ptr& api, const std::shared_ptr& factory, - const std::shared_ptr& registry, const std::shared_ptr& assetBank) +ForwardRenderer::ForwardRenderer(const std::shared_ptr& api, const std::shared_ptr& factory) : m_api(api), - m_registry(registry), - m_asset_bank(assetBank), m_geometry_pass(api, factory, {1, 1, 1}) { - m_quad_vao = factory->createVertexArray(); - auto quad_vertex_vbo = factory->createVertexBuffer(); - quad_vertex_vbo->putData(full_quad_v.data(), full_quad_v.size() * sizeof(float)); - m_quad_vao->pushVertexBuffer(quad_vertex_vbo, 3); - auto quad_uv_vbo = factory->createVertexBuffer(); - quad_uv_vbo->putData(full_quad_tx.data(), full_quad_tx.size() * sizeof(float)); - m_quad_vao->pushVertexBuffer(quad_uv_vbo, 2); - auto quad_ibo = factory->createIndexBuffer(); - quad_ibo->putData(full_quad_idx.data(), full_quad_idx.size() * sizeof(int)); - m_quad_vao->setIndexBuffer(quad_ibo); - m_camera_ubo = factory->createUniformBuffer(sizeof(CameraUBO), 0); m_light_ubo = factory->createUniformBuffer(sizeof(SceneLightsUBO), 1); } -void ForwardRenderer::submit(Entity e) { - remove(e); - if (m_registry->entityHasComponent(e)) { - m_render_queue.emplace_back(e); - } - if (m_registry->entityHasComponent(e)) { - m_lights.emplace_back(e); - } - if (m_registry->entityHasComponent(e)) { - m_skybox = e; - } +void ForwardRenderer::submitSkybox(const Skybox& e) { + m_skybox.emplace(e); } - -void ForwardRenderer::remove(Entity e) { - auto queue_pos = std::ranges::find(m_render_queue, e); - if (queue_pos != m_render_queue.end()) { - m_render_queue.erase(queue_pos); - } - auto light_pos = std::ranges::find(m_lights, e); - if (light_pos != m_lights.end()) { - m_lights.erase(light_pos); - } - if (e == m_skybox) { - m_skybox = NO_ASSET_ID; - } +void ForwardRenderer::submitDrawable(const Drawable& e) { + m_drawables.push_back(e); +} +void ForwardRenderer::submitLight(const Light& e) { + m_lights.push_back(e); } void ForwardRenderer::prepareFrame(Camera& camera) { //TODO: Sort entities, make shader list, batch, make instances, set uniforms, etc.. - if (m_skybox != NO_ASSET_ID) { - auto shader = m_asset_bank->getAsset("__ice_skybox_shader"); - shader->bind(); - shader->loadMat4("projection", camera.getProjection()); - Eigen::Matrix4f view = camera.lookThrough(); - view(0, 3) = 0; - view(1, 3) = 0; - view(2, 3) = 0; - shader->loadMat4("view", view); - shader->loadInt("skybox", 0); - - auto skybox = m_registry->getComponent(m_skybox); - auto mesh = m_asset_bank->getAsset("cube")->getMeshes().at(0); - auto tex = m_asset_bank->getAsset(skybox->texture); - m_render_commands.push_back(RenderCommand{.mesh = mesh, - .material = nullptr, - .shader = shader, - .textures = {{skybox->texture, tex}}, - .faceCulling = false, - .depthTest = false}); - } - auto view_mat = camera.lookThrough(); auto proj_mat = camera.getProjection(); @@ -103,51 +48,35 @@ void ForwardRenderer::prepareFrame(Camera& camera) { light_ubo_data.ambient_light = Eigen::Vector4f(0.1f, 0.1f, 0.1f, 1.0f); for (int i = 0; i < m_lights.size(); i++) { auto light = m_lights[i]; - auto lc = m_registry->getComponent(light); - auto tc = m_registry->getComponent(light); - if (i >= MAX_LIGHTS) - break; - light_ubo_data.lights[i].position = tc->getPosition(); - light_ubo_data.lights[i].rotation = tc->getRotation(); - light_ubo_data.lights[i].color = lc->color; - light_ubo_data.lights[i].distance_dropoff = lc->distance_dropoff; - light_ubo_data.lights[i].type = static_cast(lc->type); + light_ubo_data.lights[i].position = light.position; + light_ubo_data.lights[i].rotation = light.rotation; + light_ubo_data.lights[i].color = light.color; + light_ubo_data.lights[i].distance_dropoff = light.distance_dropoff; + light_ubo_data.lights[i].type = static_cast(light.type); } m_light_ubo->putData(&light_ubo_data, sizeof(SceneLightsUBO)); - auto frustum = extractFrustumPlanes(proj_mat * view_mat); - for (const auto& e : m_render_queue) { - auto rc = m_registry->getComponent(e); - auto tc = m_registry->getComponent(e); - auto model = m_asset_bank->getAsset(rc->model); - if (!model) - continue; - - auto aabb = model->getBoundingBox(); - Eigen::Vector3f min = (tc->getModelMatrix() * Eigen::Vector4f(aabb.getMin().x(), aabb.getMin().y(), aabb.getMin().z(), 1.0)).head<3>(); - Eigen::Vector3f max = (tc->getModelMatrix() * Eigen::Vector4f(aabb.getMax().x(), aabb.getMax().y(), aabb.getMax().z(), 1.0)).head<3>(); - aabb = AABB(std::vector{min, max}); - if (!isAABBInFrustum(frustum, aabb)) { - continue; - } - auto meshes = model->getMeshes(); - auto materials = model->getMaterialsIDs(); - auto nodes = model->getNodes(); - - for (const auto& mtl : materials) { - auto material = m_asset_bank->getAsset(mtl); - auto shader = m_asset_bank->getAsset(material->getShader()); - - if (!shader) { - continue; - } - shader->bind(); - for (int i = 0; i < model->getSkeleton().bones.size(); i++) { - shader->loadMat4("bonesTransformMatrices[" + std::to_string(i) + "]", model->getSkeleton().bones[i].finalTransformation); - } - } + if (m_skybox.has_value()) { + RenderCommand skybox_cmd; + skybox_cmd.mesh = m_skybox->cube_mesh; + skybox_cmd.material = nullptr; + skybox_cmd.shader = m_skybox->shader; + skybox_cmd.textures = m_skybox->textures; + skybox_cmd.model_matrix = Eigen::Matrix4f::Identity(); + m_render_commands.push_back(skybox_cmd); + } - submitModel(0, nodes, meshes, materials, tc->getModelMatrix()); + for (const auto& drawable : m_drawables) { + RenderCommand cmd; + cmd.mesh = drawable.mesh; + cmd.material = drawable.material; + cmd.shader = drawable.shader; + cmd.textures = drawable.textures; + cmd.model_matrix = drawable.model_matrix; + cmd.depthTest = true; + cmd.faceCulling = true; + cmd.bones = drawable.skeleton.bones; + m_render_commands.push_back(cmd); } std::sort(m_render_commands.begin(), m_render_commands.end(), [this](const RenderCommand& a, const RenderCommand& b) { @@ -160,99 +89,25 @@ void ForwardRenderer::prepareFrame(Camera& camera) { return false; } }); - m_geometry_pass.submit(&m_render_commands); -} -void ForwardRenderer::submitModel(int node_idx, const std::vector& nodes, const std::vector>& meshes, - const std::vector& materials, const Eigen::Matrix4f& transform) { - auto node = nodes.at(node_idx); - for (const auto& i : node.meshIndices) { - if (i >= meshes.size()) { - continue; - } - auto mtl_id = materials.at(i); - auto mesh = meshes.at(i); - auto material = m_asset_bank->getAsset(mtl_id); - if (!material) { - continue; - } - auto shader = m_asset_bank->getAsset(material->getShader()); - if (!shader) { - continue; - } - - if (!mesh) { - continue; - } - - Eigen::Matrix4f node_transform; - if (mesh->usesBones()) { - node_transform = transform; - } else { - node_transform = transform * node.animatedTransform; - } - - std::unordered_map> texs; - for (const auto& [name, value] : material->getAllUniforms()) { - if (std::holds_alternative(value)) { - auto v = std::get(value); - if (auto tex = m_asset_bank->getAsset(v); tex) { - texs.try_emplace(v, tex); - } - } - } - - m_render_commands.push_back(RenderCommand{.mesh = mesh, - .material = material, - .shader = shader, - .textures = texs, - .model_matrix = node_transform, - .faceCulling = true, - .depthTest = true}); - } - - for (const auto& child_idx : node.children) { - submitModel(child_idx, nodes, meshes, materials, transform); - } + m_geometry_pass.submit(&m_render_commands); } -void ForwardRenderer::render() { +std::shared_ptr ForwardRenderer::render() { m_geometry_pass.execute(); auto result = m_geometry_pass.getResult(); - - //Final pass, render the last result to the screen - if (!this->target) { - m_api->bindDefaultFramebuffer(); - } else { - this->target->bind(); - } - m_api->clear(); - auto shader = m_asset_bank->getAsset(AssetPath::WithTypePrefix("lastpass")); - - shader->bind(); - result->bindAttachment(0); - shader->loadInt("uTexture", 0); - m_quad_vao->bind(); - m_quad_vao->getIndexBuffer()->bind(); - m_api->renderVertexArray(m_quad_vao); + return result; } void ForwardRenderer::endFrame() { + m_skybox.reset(); + m_drawables.clear(); + m_lights.clear(); m_api->checkAndLogErrors(); m_render_commands.clear(); - if (this->target != nullptr) - this->target->unbind(); -} - -void ForwardRenderer::setTarget(const std::shared_ptr& fb) { - this->target = fb; } void ForwardRenderer::resize(uint32_t width, uint32_t height) { - if (target) { - target->bind(); - target->resize(width, height); - } m_api->setViewport(0, 0, width, height); m_geometry_pass.resize(width, height); } diff --git a/ICE/Graphics/src/GeometryPass.cpp b/ICE/Graphics/src/GeometryPass.cpp index ce00e7f2..4c1472ee 100644 --- a/ICE/Graphics/src/GeometryPass.cpp +++ b/ICE/Graphics/src/GeometryPass.cpp @@ -27,9 +27,14 @@ void GeometryPass::execute() { current_shader = shader; } - if (mesh->usesBones()) { + if (mesh->usesBones()) { for (const auto& [boneID, offsetMatrix] : mesh->getSkinningData().boneOffsetMatrices) { - current_shader->loadMat4("bonesOffsetMatrices[" + std::to_string(boneID) + "]", offsetMatrix); + Eigen::Matrix4f bone_transform = Eigen::Matrix4f::Identity(); + if (boneID < command.bones.size()) { + bone_transform = command.bones[boneID].finalTransformation; + } + current_shader->loadMat4("bonesOffsetMatrices[" + std::to_string(boneID) + "]", offsetMatrix); + current_shader->loadMat4("bonesTransformMatrices[" + std::to_string(boneID) + "]", bone_transform); } } @@ -50,10 +55,12 @@ void GeometryPass::execute() { auto v = std::get(value); if (textures.contains(v)) { auto& tex = textures.at(v); - tex->bind(texture_count); - shader->loadInt(name, texture_count); + if (tex) { + tex->bind(texture_count); + shader->loadInt(name, texture_count); + texture_count++; + } } - texture_count++; } else if (std::holds_alternative(value)) { auto& v = std::get(value); shader->loadFloat2(name, v); diff --git a/ICE/Graphics/src/Material.cpp b/ICE/Graphics/src/Material.cpp index 19a5cc84..2ceb8559 100644 --- a/ICE/Graphics/src/Material.cpp +++ b/ICE/Graphics/src/Material.cpp @@ -21,6 +21,20 @@ bool Material::isTransparent() const { return m_transparent; } +void Material::renameUniform(const std::string& previous_name, const std::string& new_name) { + if (m_uniforms.contains(previous_name)) { + auto& val = m_uniforms[previous_name]; + m_uniforms.try_emplace(new_name, val); + m_uniforms.erase(previous_name); + } +} + +void Material::removeUniform(const std::string& name) { + if (m_uniforms.contains(name)) { + m_uniforms.erase(name); + } +} + std::unordered_map Material::getAllUniforms() const { return m_uniforms; } diff --git a/ICE/Graphics/src/Model.cpp b/ICE/Graphics/src/Model.cpp index 2ecaa30f..a7c67410 100644 --- a/ICE/Graphics/src/Model.cpp +++ b/ICE/Graphics/src/Model.cpp @@ -10,4 +10,35 @@ Model::Model(const std::vector &nodes, const std::vector> &meshes, std::vector &materials, std::vector &transforms, + const Eigen::Matrix4f &base_transform) { + std::function recursive_traversal = [&](int node_idx, const Eigen::Matrix4f &transform) { + auto &node = m_nodes.at(node_idx); + for (const auto &i : node.meshIndices) { + if (i >= m_meshes.size()) { + continue; + } + auto mtl_id = m_materials.at(i); + auto mesh = m_meshes.at(i); + + Eigen::Matrix4f node_transform; + if (mesh->usesBones()) { + node_transform = transform; + } else { + node_transform = transform * node.animatedTransform; + } + + meshes.push_back(mesh); + materials.push_back(mtl_id); + transforms.push_back(node_transform); + } + + for (const auto &child_idx : node.children) { + recursive_traversal(child_idx, transform); + } + }; + + recursive_traversal(0, base_transform); +} + } // namespace ICE \ No newline at end of file diff --git a/ICE/GraphicsAPI/OpenGL/src/OpenGLBuffers.cpp b/ICE/GraphicsAPI/OpenGL/src/OpenGLBuffers.cpp index 7759d889..d54faa2a 100644 --- a/ICE/GraphicsAPI/OpenGL/src/OpenGLBuffers.cpp +++ b/ICE/GraphicsAPI/OpenGL/src/OpenGLBuffers.cpp @@ -5,6 +5,7 @@ #include "OpenGLBuffers.h" #include +#include namespace ICE { @@ -82,12 +83,13 @@ uint32_t OpenGLUniformBuffer::getSize() const { void OpenGLUniformBuffer::unbind() const { glBindBuffer(GL_UNIFORM_BUFFER, 0); + glBindBufferBase(GL_UNIFORM_BUFFER, binding, id); } void OpenGLUniformBuffer::putData(const void *data, uint32_t _size, uint32_t offset) { + assert(offset + _size <= size); bind(); - glBufferSubData(GL_UNIFORM_BUFFER, offset, size, data); + glBufferSubData(GL_UNIFORM_BUFFER, offset, _size, data); unbind(); - size = _size; } } // namespace ICE diff --git a/ICE/IO/src/Project.cpp b/ICE/IO/src/Project.cpp index 5e96be8e..dce19fe6 100644 --- a/ICE/IO/src/Project.cpp +++ b/ICE/IO/src/Project.cpp @@ -36,7 +36,6 @@ Project::Project(const fs::path &base_directory, const std::string &name) bool Project::CreateDirectories() { fs::create_directories(m_scenes_directory); try { - fs::create_directories(m_base_directory / "Assets"); fs::copy("Assets", m_base_directory / "Assets", fs::copy_options::recursive); } catch (std::filesystem::filesystem_error &e) { Logger::Log(Logger::FATAL, "IO", "Could not copy default assets: %s", e.what()); @@ -53,6 +52,9 @@ bool Project::CreateDirectories() { assetBank->addAsset("cube", {m_meshes_directory / "cube.obj"}); assetBank->addAsset("sphere", {m_meshes_directory / "sphere.obj"}); + assetBank->addAsset("Editor/folder", {m_textures_directory / "Editor" / "folder.png"}); + assetBank->addAsset("Editor/shader", {m_textures_directory / "Editor" / "shader.png"}); + scenes.push_back(std::make_shared("MainScene")); setCurrentScene(getScenes()[0]); return true; diff --git a/ICE/Platform/FileUtils.cpp b/ICE/Platform/FileUtils.cpp index 13056864..0144d606 100644 --- a/ICE/Platform/FileUtils.cpp +++ b/ICE/Platform/FileUtils.cpp @@ -8,11 +8,10 @@ #include -#include "dialog.h" namespace ICE { -const std::string FileUtils::openFileDialog(const std::string &filter) { - const std::string file = open_native_dialog(filter); +const std::string FileUtils::openFileDialog(const std::vector &filters) { + const std::string file = open_native_dialog(filters); Logger::Log(Logger::DEBUG, "Platform", "User selected file: %s", file.c_str()); return file; } diff --git a/ICE/Platform/FileUtils.h b/ICE/Platform/FileUtils.h index 06f8f053..1ff210db 100644 --- a/ICE/Platform/FileUtils.h +++ b/ICE/Platform/FileUtils.h @@ -5,18 +5,16 @@ #ifndef ICE_FILEUTILS_H #define ICE_FILEUTILS_H - -#include +#include "dialog.h" namespace ICE { - class FileUtils { - - public: - static const std::string openFileDialog(const std::string& filter); - static const std::string openFolderDialog(); - static const std::string readFile(const std::string &path); - }; -} +class FileUtils { + public: + static const std::string openFileDialog(const std::vector &filters); + static const std::string openFolderDialog(); + static const std::string readFile(const std::string &path); +}; +} // namespace ICE -#endif //ICE_FILEUTILS_H +#endif //ICE_FILEUTILS_H diff --git a/ICE/Platform/Linux/dialog.cpp b/ICE/Platform/Linux/dialog.cpp index ae61b79f..816a8ae1 100644 --- a/ICE/Platform/Linux/dialog.cpp +++ b/ICE/Platform/Linux/dialog.cpp @@ -5,7 +5,7 @@ #include #include -const std::string open_native_dialog(std::string const& filter) { +const std::string open_native_dialog(const std::vector &filters) { GtkWidget *dialog; diff --git a/ICE/Platform/OSX/dialog.mm b/ICE/Platform/OSX/dialog.mm index 59f1e30c..7c755ba2 100644 --- a/ICE/Platform/OSX/dialog.mm +++ b/ICE/Platform/OSX/dialog.mm @@ -1,7 +1,7 @@ #include "dialog.h" #import -const std::string open_native_dialog(std::string const& filter) { +const std::string open_native_dialog(const std::vector& filters) { // Create the File Open Dialog class. NSOpenPanel* openDlg = [NSOpenPanel openPanel]; diff --git a/ICE/Platform/Win32/dialog.cpp b/ICE/Platform/Win32/dialog.cpp index e6ca42dd..508af06f 100644 --- a/ICE/Platform/Win32/dialog.cpp +++ b/ICE/Platform/Win32/dialog.cpp @@ -11,28 +11,42 @@ #include #include -const std::string open_native_dialog(std::string const& filter) { +const std::string open_native_dialog(const std::vector& filters) +{ + char filename[MAX_PATH] = { 0 }; - char filename[MAX_PATH]; + // Build filter string: "Description1 (*.ext1;*.ext2)\0*.ext1;*.ext2\0Description2 (*.ext3)\0*.ext3\0\0" + std::string filterStr; + for (const auto& filter : filters) + { + // Show both description and extension pattern in the filter name + filterStr += filter.description; + filterStr += " ("; + filterStr += filter.extension; + filterStr += ")"; + filterStr += '\0'; + filterStr += filter.extension; + filterStr += '\0'; + } + filterStr += '\0'; // Double null-terminated - OPENFILENAME ofn; - ZeroMemory(&filename, sizeof(filename)); - ZeroMemory(&ofn, sizeof(ofn)); - ofn.lStructSize = sizeof(ofn); - ofn.hwndOwner = NULL; // If you have a window to center over, put its HANDLE here - //"Filter\0*.PDF\0"; - std::string mfilter("Filter\0*.", 8); - ofn.lpstrFilter = (mfilter + filter + std::string("\0\0\0", 2)).c_str(); - ofn.lpstrFile = filename; - ofn.nMaxFile = MAX_PATH; - ofn.lpstrTitle = _T("Select a file"); - ofn.Flags = OFN_DONTADDTORECENT | OFN_FILEMUSTEXIST; + OPENFILENAMEA ofn; + ZeroMemory(&ofn, sizeof(ofn)); + ofn.lStructSize = sizeof(ofn); + ofn.hwndOwner = NULL; + ofn.lpstrFilter = filters.empty() ? NULL : filterStr.c_str(); + ofn.lpstrFile = filename; + ofn.nMaxFile = MAX_PATH; + ofn.lpstrTitle = "Select a file"; + ofn.Flags = OFN_DONTADDTORECENT | OFN_FILEMUSTEXIST; - if (GetOpenFileName(&ofn)) - { - return std::string(filename); - } - return std::string(); + if (GetOpenFileNameA(&ofn)) + { + // Ensure null-termination + filename[MAX_PATH - 1] = '\0'; + return std::string(filename); + } + return std::string(); } const std::string open_native_folder_dialog() { diff --git a/ICE/Platform/dialog.h b/ICE/Platform/dialog.h index b12fe94b..c9eeee4f 100644 --- a/ICE/Platform/dialog.h +++ b/ICE/Platform/dialog.h @@ -6,8 +6,14 @@ #define ICE_PLATEFORM_DIALOG_H #include +#include -const std::string open_native_dialog(const std::string& filter); +struct FileFilter { + std::string description; + std::string extension; +}; + +const std::string open_native_dialog(const std::vector& filters); const std::string open_native_folder_dialog(); #endif \ No newline at end of file diff --git a/ICE/System/include/RenderSystem.h b/ICE/System/include/RenderSystem.h index 5efba999..de8301e8 100644 --- a/ICE/System/include/RenderSystem.h +++ b/ICE/System/include/RenderSystem.h @@ -14,15 +14,19 @@ namespace ICE { class Scene; +class Registry; class RenderSystem : public System { public: - RenderSystem() {}; + RenderSystem(const std::shared_ptr &api, const std::shared_ptr &factory, const std::shared_ptr ®, + const std::shared_ptr &bank); void onEntityAdded(Entity e) override; void onEntityRemoved(Entity e) override; void update(double delta) override; + void submitModel(const std::shared_ptr &model, const Eigen::Matrix4f &transform); + std::shared_ptr getRenderer() const; void setRenderer(const std::shared_ptr &renderer); std::shared_ptr getCamera() const; @@ -46,5 +50,17 @@ class RenderSystem : public System { private: std::shared_ptr m_renderer; std::shared_ptr m_camera; + std::shared_ptr m_target; + + AssetUID m_skybox = NO_ASSET_ID; + std::vector m_render_queue; + std::vector m_lights; + + std::shared_ptr m_api; + std::shared_ptr m_factory; + std::shared_ptr m_registry; + std::shared_ptr m_asset_bank; + + std::shared_ptr m_quad_vao; }; } // namespace ICE diff --git a/ICE/System/src/AnimationSystem.cpp b/ICE/System/src/AnimationSystem.cpp index 374078ff..bb435d0c 100644 --- a/ICE/System/src/AnimationSystem.cpp +++ b/ICE/System/src/AnimationSystem.cpp @@ -74,7 +74,7 @@ Eigen::Matrix4f AnimationSystem::interpolatePosition(double timeInTicks, const B } size_t startIndex = findKeyIndex(timeInTicks, track.positions); - size_t nextIndex = startIndex + 1; + size_t nextIndex = std::min(startIndex + 1, track.positions.size() - 1); const auto& startKey = track.positions[startIndex]; const auto& nextKey = track.positions[nextIndex]; @@ -105,7 +105,7 @@ Eigen::Matrix4f AnimationSystem::interpolateScale(double timeInTicks, const Bone } size_t startIndex = findKeyIndex(timeInTicks, track.scales); - size_t nextIndex = startIndex + 1; + size_t nextIndex = std::min(startIndex + 1, track.scales.size() - 1); const auto& startKey = track.scales[startIndex]; const auto& nextKey = track.scales[nextIndex]; @@ -132,11 +132,11 @@ Eigen::Matrix4f AnimationSystem::interpolateRotation(double time, const BoneAnim return rotation_matrix; } - size_t startIdx = findKeyIndex(time, track.rotations); - size_t nextIdx = startIdx + 1; + size_t startIndex = findKeyIndex(time, track.rotations); + size_t nextIndex = std::min(startIndex + 1, track.rotations.size() - 1); - const auto& startKey = track.rotations[startIdx]; - const auto& nextKey = track.rotations[nextIdx]; + const auto& startKey = track.rotations[startIndex]; + const auto& nextKey = track.rotations[nextIndex]; double totalTime = nextKey.timeStamp - startKey.timeStamp; double factor = (time - startKey.timeStamp) / totalTime; diff --git a/ICE/System/src/RenderSystem.cpp b/ICE/System/src/RenderSystem.cpp index b28ed8bb..825d0cf9 100644 --- a/ICE/System/src/RenderSystem.cpp +++ b/ICE/System/src/RenderSystem.cpp @@ -4,22 +4,167 @@ #include "RenderSystem.h" +#include "Registry.h" +#include "RenderData.h" + namespace ICE { +RenderSystem::RenderSystem(const std::shared_ptr &api, const std::shared_ptr &factory, + const std::shared_ptr ®, const std::shared_ptr &bank) + : m_api(api), + m_factory(factory), + m_registry(reg), + m_asset_bank(bank) { + m_quad_vao = factory->createVertexArray(); + auto quad_vertex_vbo = factory->createVertexBuffer(); + quad_vertex_vbo->putData(full_quad_v.data(), full_quad_v.size() * sizeof(float)); + m_quad_vao->pushVertexBuffer(quad_vertex_vbo, 3); + auto quad_uv_vbo = factory->createVertexBuffer(); + quad_uv_vbo->putData(full_quad_tx.data(), full_quad_tx.size() * sizeof(float)); + m_quad_vao->pushVertexBuffer(quad_uv_vbo, 2); + auto quad_ibo = factory->createIndexBuffer(); + quad_ibo->putData(full_quad_idx.data(), full_quad_idx.size() * sizeof(int)); + m_quad_vao->setIndexBuffer(quad_ibo); +} + void RenderSystem::update(double delta) { + + auto view_mat = m_camera->lookThrough(); + auto proj_mat = m_camera->getProjection(); + + if (m_skybox != NO_ASSET_ID) { + auto shader = m_asset_bank->getAsset("__ice_skybox_shader"); + auto skybox = m_registry->getComponent(m_skybox); + auto mesh = m_asset_bank->getAsset("cube")->getMeshes().at(0); + auto tex = m_asset_bank->getAsset(skybox->texture); + m_renderer->submitSkybox(Skybox{ + .cube_mesh = mesh, + .shader = shader, + .textures = {{skybox->texture, tex}}, + }); + } + + auto frustum = extractFrustumPlanes(proj_mat * view_mat); + for (const auto &e : m_render_queue) { + auto rc = m_registry->getComponent(e); + auto tc = m_registry->getComponent(e); + auto model = m_asset_bank->getAsset(rc->model); + if (!model) + continue; + + auto aabb = model->getBoundingBox(); + Eigen::Vector3f min = (tc->getModelMatrix() * Eigen::Vector4f(aabb.getMin().x(), aabb.getMin().y(), aabb.getMin().z(), 1.0)).head<3>(); + Eigen::Vector3f max = (tc->getModelMatrix() * Eigen::Vector4f(aabb.getMax().x(), aabb.getMax().y(), aabb.getMax().z(), 1.0)).head<3>(); + aabb = AABB(std::vector{min, max}); + if (!isAABBInFrustum(frustum, aabb)) { + continue; + } + + submitModel(model, tc->getModelMatrix()); + } + + for (int i = 0; i < m_lights.size(); i++) { + if (i >= MAX_LIGHTS) + break; + auto light = m_lights[i]; + auto lc = m_registry->getComponent(light); + auto tc = m_registry->getComponent(light); + + m_renderer->submitLight(Light{.position = tc->getPosition(), + .rotation = tc->getRotation(), + .color = lc->color, + .distance_dropoff = lc->distance_dropoff, + .type = lc->type}); + } + m_renderer->prepareFrame(*m_camera); - m_renderer->render(); + auto rendered_fb = m_renderer->render(); m_renderer->endFrame(); + + //Final pass, render the last result to the screen + if (!m_target) { + m_api->bindDefaultFramebuffer(); + } else { + m_target->bind(); + } + + m_api->clear(); + auto shader = m_asset_bank->getAsset(AssetPath::WithTypePrefix("lastpass")); + + shader->bind(); + rendered_fb->bindAttachment(0); + shader->loadInt("uTexture", 0); + m_quad_vao->bind(); + m_quad_vao->getIndexBuffer()->bind(); + m_api->renderVertexArray(m_quad_vao); +} + +void RenderSystem::submitModel(const std::shared_ptr &model, const Eigen::Matrix4f &model_matrix) { + + std::vector> meshes; + std::vector materials; + std::vector transforms; + + model->traverse(meshes, materials, transforms, model_matrix); + + for (int i = 0; i < meshes.size(); i++) { + auto mtl_id = materials.at(i); + auto mesh = meshes.at(i); + auto material = m_asset_bank->getAsset(mtl_id); + if (!material) { + continue; + } + auto shader = m_asset_bank->getAsset(material->getShader()); + if (!shader) { + continue; + } + + if (!mesh) { + continue; + } + + std::unordered_map> texs; + for (const auto &[name, value] : material->getAllUniforms()) { + if (std::holds_alternative(value)) { + auto v = std::get(value); + if (auto tex = m_asset_bank->getAsset(v); tex) { + texs.try_emplace(v, tex); + } + } + } + + m_renderer->submitDrawable(Drawable{.mesh = mesh, + .material = material, + .shader = shader, + .textures = texs, + .model_matrix = transforms[i], + .skeleton = model->getSkeleton()}); + } } void RenderSystem::onEntityAdded(Entity e) { - if (m_renderer) { - m_renderer->submit(e); + onEntityRemoved(e); + if (m_registry->entityHasComponent(e)) { + m_render_queue.emplace_back(e); + } + if (m_registry->entityHasComponent(e)) { + m_lights.emplace_back(e); + } + if (m_registry->entityHasComponent(e)) { + m_skybox = e; } } void RenderSystem::onEntityRemoved(Entity e) { - if (m_renderer) { - m_renderer->remove(e); + auto queue_pos = std::ranges::find(m_render_queue, e); + if (queue_pos != m_render_queue.end()) { + m_render_queue.erase(queue_pos); + } + auto light_pos = std::ranges::find(m_lights, e); + if (light_pos != m_lights.end()) { + m_lights.erase(light_pos); + } + if (e == m_skybox) { + m_skybox = NO_ASSET_ID; } } @@ -29,9 +174,6 @@ std::shared_ptr RenderSystem::getRenderer() const { void RenderSystem::setRenderer(const std::shared_ptr &renderer) { m_renderer = renderer; - for (const auto &e : entities) { - m_renderer->submit(e); - } } std::shared_ptr RenderSystem::getCamera() const { @@ -43,11 +185,13 @@ void RenderSystem::setCamera(const std::shared_ptr &camera) { } void RenderSystem::setTarget(const std::shared_ptr &fb) { - m_renderer->setTarget(fb); + m_target = fb; } void RenderSystem::setViewport(int x, int y, int w, int h) { if (w > 0 && h > 0) { + if (m_target) + m_target->resize(w, h); m_renderer->resize(w, h); } } diff --git a/ICE/Util/include/GLFWWindow.h b/ICE/Util/include/GLFWWindow.h index 0f163936..ae5fdd04 100644 --- a/ICE/Util/include/GLFWWindow.h +++ b/ICE/Util/include/GLFWWindow.h @@ -13,6 +13,7 @@ class GLFWWindow : public Window { void* getHandle() const override; bool shouldClose() override; + void close() override; std::pair, std::shared_ptr> getInputHandlers() const override; void pollEvents() override; void swapBuffers() override; diff --git a/ICE/Util/include/Window.h b/ICE/Util/include/Window.h index 60a07346..39855f29 100644 --- a/ICE/Util/include/Window.h +++ b/ICE/Util/include/Window.h @@ -15,6 +15,7 @@ class Window { virtual void* getHandle() const = 0; virtual bool shouldClose() = 0; + virtual void close() = 0; virtual std::pair, std::shared_ptr> getInputHandlers() const = 0; virtual void pollEvents() = 0; virtual void swapBuffers() = 0; diff --git a/ICE/Util/src/GLFWWindow.cpp b/ICE/Util/src/GLFWWindow.cpp index 6cfa534a..193d29c6 100644 --- a/ICE/Util/src/GLFWWindow.cpp +++ b/ICE/Util/src/GLFWWindow.cpp @@ -53,6 +53,10 @@ bool GLFWWindow::shouldClose() { return glfwWindowShouldClose(m_handle); } +void GLFWWindow::close() { + glfwSetWindowShouldClose(m_handle, GLFW_TRUE); +} + void GLFWWindow::pollEvents() { glfwPollEvents(); } diff --git a/ICEBERG/CMakeLists.txt b/ICEBERG/CMakeLists.txt index 5e6c0cd4..b20a739a 100644 --- a/ICEBERG/CMakeLists.txt +++ b/ICEBERG/CMakeLists.txt @@ -11,7 +11,12 @@ add_executable(${PROJECT_NAME} src/Inspector.cpp src/Assets.cpp src/Viewport.cpp - imgui_demo.cpp ${IMGUI_SRC} ${IMGUIZMO_SRC} ${GL3W_SRC} + src/AssetsRenderer.cpp + src/Dialog.cpp + src/MaterialEditor.cpp + ${IMGUI_SRC} + ${IMGUIZMO_SRC} + ${GL3W_SRC} ) target_include_directories(${PROJECT_NAME} PUBLIC @@ -23,6 +28,7 @@ target_include_directories(${PROJECT_NAME} PUBLIC add_definitions(-DIMGUI_DEFINE_MATH_OPERATORS) -target_link_libraries(${PROJECT_NAME} PUBLIC ICE glfw) +target_link_libraries(${PROJECT_NAME} PUBLIC ICE DearImXML glfw) file(COPY ${ICE_ROOT_SOURCE_DIR}/Assets DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/XML DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/ICEBERG/Components/ComboBox.h b/ICEBERG/Components/ComboBox.h index a244b00c..fb8c6fec 100644 --- a/ICEBERG/Components/ComboBox.h +++ b/ICEBERG/Components/ComboBox.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include diff --git a/ICEBERG/Components/InputText.h b/ICEBERG/Components/InputText.h index b09eee71..29116be0 100644 --- a/ICEBERG/Components/InputText.h +++ b/ICEBERG/Components/InputText.h @@ -1,31 +1,32 @@ #pragma once -#include +#include #include #include class InputText { public: - InputText(const std::string &label, const std::string &default_text = "") : m_label(label) { setText(default_text); } - void onEdit(const std::function &f) { m_callback_edit = f; } + InputText(const std::string &label, const std::string &default_text = "") : m_label(label) { + setText(default_text); } + void onEdit(const std::function &f) { m_callback_edit = f; } void render() { - if (ImGui::InputText(m_label.c_str(), buffer, 512)) { - m_callback_edit(std::string(buffer)); + if (ImGui::InputText(m_label.c_str(), &m_text)) { + m_callback_edit(m_text_old, m_text); } - m_text = buffer; + m_text_old = m_text; } std::string getText() const { return m_text; } void setText(const std::string &text) { m_text = text; - memcpy(buffer, m_text.c_str(), m_text.size() + 1); + m_text_old = text; } private: std::string m_text; + std::string m_text_old; std::string m_label; - std::function m_callback_edit = [](const std::string &) { + std::function m_callback_edit = [](std::string, std::string) { }; - char buffer[512] = {0}; }; diff --git a/ICEBERG/Components/UniformInputs.h b/ICEBERG/Components/UniformInputs.h index 8bbd29a1..15b5a8e7 100644 --- a/ICEBERG/Components/UniformInputs.h +++ b/ICEBERG/Components/UniformInputs.h @@ -1,7 +1,7 @@ #pragma once -#include #include +#include #include #include @@ -22,6 +22,12 @@ class UniformInputs { void setValue(const ICE::UniformValue &value) { m_value = value; m_callback(value); + if (std::holds_alternative(m_value)) { + auto it = std::find(m_assets_ids.begin(), m_assets_ids.end(), std::get(m_value)); + if (it != m_assets_ids.end()) { + m_asset_combo.setSelected(std::distance(m_assets_ids.begin(), it)); + } + } } ICE::UniformValue getValue() const { return m_value; } @@ -34,9 +40,11 @@ class UniformInputs { m_asset_combo.setValues(path_with_none); m_assets_ids = {0}; m_assets_ids.insert(m_assets_ids.end(), ids.begin(), ids.end()); - auto it = std::find(m_assets_ids.begin(), m_assets_ids.end(), std::get(m_value)); - if (it != m_assets_ids.end()) { - m_asset_combo.setSelected(std::distance(m_assets_ids.begin(), it)); + if (std::holds_alternative(m_value)) { + auto it = std::find(m_assets_ids.begin(), m_assets_ids.end(), std::get(m_value)); + if (it != m_assets_ids.end()) { + m_asset_combo.setSelected(std::distance(m_assets_ids.begin(), it)); + } } m_asset_combo.onSelectionChanged( [cb = this->m_callback, id_list = this->m_assets_ids](const std::string &, int index) { cb(id_list[index]); }); @@ -52,7 +60,7 @@ class UniformInputs { } void render(ICE::AssetUID id) { m_asset_combo.render(); } void render(float &f) { - if (ImGui::InputFloat(m_label.c_str(), &f)) { + if (ImGui::InputFloat(m_label.c_str(), &f, 0.01f, 0.1f, "%.6f")) { m_callback(f); } } diff --git a/ICEBERG/UI/AddComponentPopup.h b/ICEBERG/UI/AddComponentPopup.h index 92b85627..2f8be0e8 100644 --- a/ICEBERG/UI/AddComponentPopup.h +++ b/ICEBERG/UI/AddComponentPopup.h @@ -1,13 +1,13 @@ #pragma once -#include +#include #include #include "Components/ComboBox.h" class AddComponentPopup { public: - AddComponentPopup() : m_components_combo("##add_component_combo", {"Render", "Light"}) {} + AddComponentPopup() : m_components_combo("##add_component_combo", {"Render", "Light", "Animation"}) {} void open(const std::shared_ptr ®istry, ICE::Entity entity) { m_registry = registry; @@ -21,6 +21,9 @@ class AddComponentPopup { if(!registry->entityHasComponent(entity)) { values.push_back("Light Component"); } + if (!registry->entityHasComponent(entity)) { + values.push_back("Animation Component"); + } m_components_combo.setValues(values); } @@ -39,6 +42,9 @@ class AddComponentPopup { if(m_components_combo.getSelectedItem() == "Light Component") m_registry->addComponent(m_entity, ICE::LightComponent(ICE::PointLight, Eigen::Vector3f(1, 1, 1))); + + if (m_components_combo.getSelectedItem() == "Animation Component") + m_registry->addComponent(m_entity, ICE::AnimationComponent{"", 0.0, 1.0, true, true}); ImGui::CloseCurrentPopup(); m_accepted = true; } diff --git a/ICEBERG/UI/AnimationComponentWidget.h b/ICEBERG/UI/AnimationComponentWidget.h new file mode 100644 index 00000000..a17bc62e --- /dev/null +++ b/ICEBERG/UI/AnimationComponentWidget.h @@ -0,0 +1,83 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "Components/UniformInputs.h" +#include "Widget.h" + +class AnimationComponentWidget : public Widget, ImXML::XMLEventHandler { + public: + explicit AnimationComponentWidget() : m_xml_tree(ImXML::XMLReader().read("XML/AnimationComponentWidget.xml")) { + m_xml_renderer.addDynamicBind("float_time", {&m_time, 1, ImXML::Float}); + m_xml_renderer.addDynamicBind("float_speed", {&m_speed, 1, ImXML::Float}); + m_xml_renderer.addDynamicBind("bool_playing", {&m_playing, 1, ImXML::Bool}); + m_xml_renderer.addDynamicBind("bool_loop", {&m_loop, 1, ImXML::Bool}); + } + + void onNodeBegin(ImXML::XMLNode& node) override { + if (node.arg("id") == "animation_combo") { + if (ImGui::BeginCombo("##animation_combo", m_current_animation.c_str())) { + for (const auto& [key, anim] : m_animations) { + if (ImGui::Selectable(key.c_str(), key == m_current_animation)) { + m_current_animation = key; + } + } + ImGui::EndCombo(); + } + } else if (node.arg("id") == "time_slider") { + if (m_animations.contains(m_current_animation)) + node.args["max"] = std::to_string(m_animations[m_current_animation].duration); + } + } + void onNodeEnd(ImXML::XMLNode& node) override {} + void onEvent(ImXML::XMLNode& node) override {} + + void render() override { + if (m_ac) { + m_xml_renderer.render(m_xml_tree, *this); + m_ac->currentAnimation = m_current_animation; + m_ac->currentTime = m_time; + m_ac->speed = m_speed; + m_ac->playing = m_playing; + m_ac->loop = m_loop; + } + } + + void setAnimationComponent(ICE::AnimationComponent* ac, const std::unordered_map& animations) { + if (ac && !animations.empty()) { + m_ac = ac; + m_animations = animations; + m_current_animation = m_ac->currentAnimation; + m_time = m_ac->currentTime; + m_speed = m_ac->speed; + m_playing = m_ac->playing; + m_loop = m_ac->loop; + } else { + m_ac = nullptr; + } + } + + private: + ICE::AnimationComponent* m_ac = nullptr; + std::unordered_map m_animations; + + std::string m_current_animation = ""; + + float m_max_time = 1.0; + + float m_time = 0.0; + float m_speed = 1.0; + bool m_playing; + bool m_loop; + + ImXML::XMLTree m_xml_tree; + ImXML::XMLRenderer m_xml_renderer; +}; diff --git a/ICEBERG/UI/AssetsBrowserWidget.h b/ICEBERG/UI/AssetsBrowserWidget.h new file mode 100644 index 00000000..32415fe1 --- /dev/null +++ b/ICEBERG/UI/AssetsBrowserWidget.h @@ -0,0 +1,55 @@ +#pragma once +#include +#include +#include +#include + +#include +#include +#include + +#include "AssetsCategoryWidget.h" +#include "AssetsContentWidget.h" +#include "AssetsPreviewWidget.h" + +class AssetsBrowserWidget : public Widget, ImXML::XMLEventHandler { + public: + explicit AssetsBrowserWidget(const std::vector& asset_categories, void* folder_texture) + : m_xml_tree(ImXML::XMLReader().read("XML/AssetBrowser.xml")), + m_category_widget(asset_categories), + m_content_widget(folder_texture) { + m_category_widget.registerCallback("asset_category_selected", [this](int index) { callback("asset_category_selected", index); }); + m_content_widget.registerCallback("item_clicked", [this](std::string label) { callback("item_clicked", label); }); + m_content_widget.registerCallback("item_selected", [this](std::string label) { callback("item_selected", label); }); + } + + void onNodeBegin(ImXML::XMLNode& node) override { + if (node.arg("id") == "asset_browser_category") { + m_category_widget.render(); + } else if (node.arg("id") == "asset_browser_content") { + m_content_widget.render(); + } else if (node.arg("id") == "asset_browser_preview") { + m_preview_widget.render(); + } + } + void onNodeEnd(ImXML::XMLNode& node) override {} + void onEvent(ImXML::XMLNode& node) override {} + + void render() override { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + m_xml_renderer.render(m_xml_tree, *this); + ImGui::PopStyleVar(); + } + + void setCurrentView(const AssetView& view) { m_content_widget.setCurrentView(view); } + AssetView getCurrentView() const { return m_content_widget.getCurrentView(); } + void setPreviewTexture(void* tex) { m_preview_widget.setTexture(tex); } + + private: + AssetsCategoryWidget m_category_widget; + AssetsContentWidget m_content_widget; + AssetsPreviewWidget m_preview_widget; + + ImXML::XMLTree m_xml_tree; + ImXML::XMLRenderer m_xml_renderer; +}; diff --git a/ICEBERG/UI/AssetsCategoryWidget.h b/ICEBERG/UI/AssetsCategoryWidget.h new file mode 100644 index 00000000..be12c314 --- /dev/null +++ b/ICEBERG/UI/AssetsCategoryWidget.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include +#include +#include + +#include "Widget.h" + +class AssetsCategoryWidget : public Widget { + public: + explicit AssetsCategoryWidget(const std::vector &asset_categories) : m_asset_categories(asset_categories) {} + + void render() override { + if (ImGui::BeginTable("asset_folders", 1, ImGuiTableFlags_BordersInnerH)) { + for (int i = 0; i < m_asset_categories.size(); i++) { + ImGui::TableNextColumn(); + if (ImGui::Selectable(m_asset_categories[i].c_str(), i == m_selected_index)) { + m_selected_index = i; + callback("asset_category_selected", i); + } + } + ImGui::EndTable(); + } + } + + private: + int m_selected_index = 0; + std::vector m_asset_categories; +}; diff --git a/ICEBERG/UI/AssetsContentWidget.h b/ICEBERG/UI/AssetsContentWidget.h new file mode 100644 index 00000000..28cb4748 --- /dev/null +++ b/ICEBERG/UI/AssetsContentWidget.h @@ -0,0 +1,97 @@ +#pragma once + +#include + +#include +#include +#include + +#include "Widget.h" + +struct Thumbnail { + void* ptr; + bool flip = true; +}; +struct AssetData { + std::string name; + Thumbnail thumbnail; + std::string asset_path; +}; +struct AssetView { + AssetView* parent = nullptr; + std::string folder_name; + std::vector assets; + std::vector subfolders; +}; + +class AssetsContentWidget : public Widget { + public: + AssetsContentWidget(void* folder_texture) : m_folder_texture(folder_texture) {} + + void render() override { + const float thumbnailSize = 64.0f; + const float padding = 16.0f; + float cellSize = thumbnailSize + padding; + + float panelWidth = ImGui::GetContentRegionAvail().x; + int columnCount = static_cast(panelWidth / cellSize); + if (columnCount < 1) + columnCount = 1; + + if (ImGui::BeginTable("FileBrowserTable", columnCount)) { + int itemIndex = 0; + + auto renderItem = [&](const std::string& label, const Thumbnail &tb) { + itemIndex++; + ImGui::TableNextColumn(); + ImGui::PushID(itemIndex); + + if (itemIndex != m_selected_item) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); + } + ImGui::BeginGroup(); + ImVec2 uv0 = tb.flip ? ImVec2(0, 1) : ImVec2(0, 0); + ImVec2 uv1 = tb.flip ? ImVec2(1, 0) : ImVec2(1, 1); + ImGui::ImageButton("##item", (ImTextureID) tb.ptr, ImVec2(thumbnailSize, thumbnailSize), uv0, uv1); + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + thumbnailSize); + ImGui::TextWrapped("%s", label.c_str()); + ImGui::PopTextWrapPos(); + ImGui::EndGroup(); + if (itemIndex != m_selected_item) { + ImGui::PopStyleColor(); + } + if (ImGui::IsItemClicked(0)) { + m_selected_item = itemIndex; + callback("item_selected", label); + if (ImGui::IsMouseDoubleClicked(0)) { + callback("item_clicked", label); + } + } + + ImGui::PopID(); + }; + + if (m_current_view.parent != nullptr) { + renderItem("..", {m_folder_texture, false}); + } + for (const auto& folder : m_current_view.subfolders) + renderItem(folder.folder_name, {m_folder_texture, false}); + + for (const auto& asset : m_current_view.assets) + renderItem(asset.name, asset.thumbnail); + + ImGui::EndTable(); + } + } + + void setCurrentView(const AssetView& view) { + m_current_view = view; + m_selected_item = -1; + } + AssetView getCurrentView() const { return m_current_view; } + + private: + void* m_folder_texture; + AssetView m_current_view; + int m_selected_item = -1; +}; diff --git a/ICEBERG/UI/AssetsPreviewWidget.h b/ICEBERG/UI/AssetsPreviewWidget.h new file mode 100644 index 00000000..097978f0 --- /dev/null +++ b/ICEBERG/UI/AssetsPreviewWidget.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + +#include "Widget.h" + +class AssetsPreviewWidget : public Widget { + public: + AssetsPreviewWidget() = default; + + void render() override { ImGui::Image(m_texture, {256, 256}, ImVec2(0, 1), ImVec2(1, 0)); } + + void setTexture(void* tex) { m_texture = tex; } + + private: + void* m_texture = nullptr; +}; \ No newline at end of file diff --git a/ICEBERG/UI/AssetsWidget.h b/ICEBERG/UI/AssetsWidget.h deleted file mode 100644 index ab500c93..00000000 --- a/ICEBERG/UI/AssetsWidget.h +++ /dev/null @@ -1,119 +0,0 @@ -#pragma once -#include - -#include -#include -#include - -#include "Widget.h" - -struct AssetView { - std::string folder_name; - std::vector> assets; - std::vector> subfolders; -}; - -class AssetsWidget : public Widget { - public: - AssetsWidget() = default; - - void render() override { - int flags = ImGuiWindowFlags_NoCollapse; - flags |= ImGuiWindowFlags_NoNavFocus; - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); - ImGui::Begin("Assets", 0, flags); - - if (ImGui::BeginTable("asset_layout", 3, ImGuiTableFlags_Resizable)) { - ImGui::TableSetupColumn("Folders", ImGuiTableColumnFlags_WidthStretch, 0.15); - ImGui::TableSetupColumn("Content", ImGuiTableColumnFlags_WidthStretch, 0.6); - ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthStretch, 0.25); - ImGui::TableHeadersRow(); - ImGui::TableNextColumn(); - //Left hand side: Assets folders - if (ImGui::BeginTable("asset_folders", 1, ImGuiTableFlags_BordersInnerH)) { - for (int i = 0; i < m_assets.size(); i++) { - ImGui::TableNextColumn(); - const auto& asset = m_assets[i]; - if (ImGui::Selectable(asset->folder_name.c_str(), i == m_selected_index)) { - m_selected_index = i; - m_current_view = m_assets[i]; - m_prefix = ""; - } - } - ImGui::EndTable(); - } - - //Middle: assets - ImGui::TableNextColumn(); - if (ImGui::BeginTable("assets_selection", 10, ImGuiTableFlags_Borders)) { - for (int i = 0; i < m_current_view->subfolders.size(); i++) { - const auto& folder = m_current_view->subfolders[i]; - ImGui::TableNextColumn(); - ImGui::Text(folder->folder_name.c_str()); - if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) { - m_current_view = folder; - m_prefix += folder->folder_name+"/"; - } - } - - for (int i = 0; i < m_current_view->assets.size(); i++) { - const auto& name = m_current_view->assets[i].first; - const auto& texture = m_current_view->assets[i].second; - ImGui::TableNextColumn(); - ImGui::BeginGroup(); - ImGui::Image(texture, {50, 50}); - ImGui::Text(name.c_str()); - ImGui::EndGroup(); - if (m_assets[m_selected_index]->folder_name == "Materials") { - if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) { - callback("material_edit", m_prefix+name); - } - } - if (ImGui::BeginPopupContextItem((name + "_material_context").c_str())) { - if (m_assets[m_selected_index]->folder_name == "Materials") { - if (ImGui::Button("Duplicate")) { - callback("material_duplicate", name); - } - if (ImGui::Button("Edit")) { - callback("material_edit", name); - } - } - if (ImGui::Button("Delete")) { - callback("delete_asset", m_assets[m_selected_index]->folder_name + "/" + name); - } - ImGui::EndPopup(); - } - } - ImGui::EndTable(); - } - - //Right hand side: preview - ImGui::TableNextColumn(); - ImGui::Text("Preview"); - - ImGui::EndTable(); - } - ImGui::End(); - ImGui::PopStyleVar(); - } - - void addAssets(const std::shared_ptr& assets) { - m_assets.push_back(assets); - if (m_current_view == nullptr) { - m_current_view = assets; - } - } - - void reset() { - m_assets.clear(); - m_current_view = nullptr; - m_selected_index = 0; - m_prefix = ""; - } - - private: - std::vector> m_assets; - int m_selected_index = 0; - std::shared_ptr m_current_view = nullptr; - std::string m_prefix; -}; diff --git a/ICEBERG/UI/Dialog.h b/ICEBERG/UI/Dialog.h new file mode 100644 index 00000000..04edda0e --- /dev/null +++ b/ICEBERG/UI/Dialog.h @@ -0,0 +1,43 @@ +#pragma once + +#include "Widget.h" + +enum class DialogResult { None, Pending, Ok, Cancel }; + +class Dialog : public Widget { + public: + Dialog() { m_dialog_id = DIALOG_ID++; } + virtual ~Dialog() = default; + + void open() { + m_open_request = true; + m_result = DialogResult::Pending; + m_is_open = true; + } + + void done(DialogResult result) { + m_is_open = false; + m_result = result; + } + + bool isOpenRequested() { + if (m_open_request) { + m_open_request = false; + return true; + } + return false; + } + + bool isOpen() const { return m_is_open; } + + DialogResult getResult() const { return m_result; } + + protected: + int m_dialog_id; + + private: + static int DIALOG_ID; + bool m_open_request = false; + bool m_is_open = false; + DialogResult m_result = DialogResult::None; +}; \ No newline at end of file diff --git a/ICEBERG/UI/EditorWidget.h b/ICEBERG/UI/EditorWidget.h index edbe20c4..470824a8 100644 --- a/ICEBERG/UI/EditorWidget.h +++ b/ICEBERG/UI/EditorWidget.h @@ -1,90 +1,66 @@ #pragma once -#include -#include +#include +#include +#include +#include +#include #include "Widget.h" -class EditorWidget : public Widget { +class EditorWidget : public Widget, ImXML::XMLEventHandler { public: - EditorWidget() { ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_DockingEnable; } + EditorWidget() : m_xml_tree(ImXML::XMLReader().read("XML/EditorWidget.xml")) { ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_DockingEnable; } - void render() override { - static int initialized = 0; - ImGuiWindowFlags flags = ImGuiWindowFlags_MenuBar; - flags |= ImGuiWindowFlags_NoDocking; - ImGuiViewport* viewport = ImGui::GetMainViewport(); - ImGui::SetNextWindowPos(viewport->Pos); - ImGui::SetNextWindowSize(viewport->Size); - ImGui::SetNextWindowViewport(viewport->ID); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); - flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; - flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_MenuBar; - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); - ImGui::Begin("ICE Editor", 0, flags); - ImGui::PopStyleVar(); + void onNodeBegin(ImXML::XMLNode& node) override { + if (node.arg("id") == "dockspace") { + ImGuiID dockspace_id = ImGui::GetID("MyDockspace"); - if (ImGui::BeginMainMenuBar()) { - if (ImGui::BeginMenu("File")) { - if (ImGui::BeginMenu("New")) { - if (ImGui::MenuItem("Scene")) { - callback("new_scene_clicked"); - } - if (ImGui::MenuItem("Material")) { - callback("new_material_clicked"); - } - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Import")) { - if (ImGui::MenuItem("Mesh")) { - callback("import_mesh_clicked"); - } - if (ImGui::MenuItem("Texture2D")) { - callback("import_tex2d_clicked"); - } - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("Open")) { - if (ImGui::MenuItem("Scene")) { - callback("open_scene_clicked"); - } - ImGui::EndMenu(); - } - //if (ImGui::MenuItem("Save", "Ctrl+S")) {} - //if (ImGui::MenuItem("Save as..")) {} - ImGui::EndMenu(); - } - ImGui::EndMainMenuBar(); - } - - ImGuiIO& io = ImGui::GetIO(); - ImGuiID dockspace_id = ImGui::GetID("MyDockspace"); + if (!m_docking_initialized) [[unlikely]] { + m_docking_initialized = true; + ImGui::DockBuilderRemoveNode(dockspace_id); // Clear out existing layout + ImGui::DockBuilderAddNode(dockspace_id); // Add empty node - if (initialized == 0) { - initialized = 1; - ImGui::DockBuilderRemoveNode(dockspace_id); // Clear out existing layout - ImGui::DockBuilderAddNode(dockspace_id); // Add empty node + ImGuiID dock_main_id = dockspace_id; + ImGuiID dock_top = ImGui::DockBuilderSplitNode(dock_main_id, ImGuiDir_Up, 0.80f, NULL, &dock_main_id); + ImGuiID dock_id_bottom = ImGui::DockBuilderSplitNode(dock_main_id, ImGuiDir_Down, 0.20f, NULL, &dock_main_id); - ImGuiID dock_main_id = - dockspace_id; // This variable will track the document node, however we are not using it here as we aren't docking anything into it. - ImGuiID dock_top = ImGui::DockBuilderSplitNode(dock_main_id, ImGuiDir_Up, 0.80f, NULL, &dock_main_id); - ImGuiID dock_id_bottom = ImGui::DockBuilderSplitNode(dock_main_id, ImGuiDir_Down, 0.20f, NULL, &dock_main_id); + ImGuiID dock_id_left = ImGui::DockBuilderSplitNode(dock_top, ImGuiDir_Left, 0.20f, NULL, &dock_top); + ImGuiID dock_id_right = ImGui::DockBuilderSplitNode(dock_top, ImGuiDir_Right, 0.20f, NULL, &dock_top); + ImGuiID dock_id_center = ImGui::DockBuilderSplitNode(dock_top, ImGuiDir_Left, 0.60f, NULL, &dock_top); - ImGuiID dock_id_left = ImGui::DockBuilderSplitNode(dock_top, ImGuiDir_Left, 0.20f, NULL, &dock_top); - ImGuiID dock_id_right = ImGui::DockBuilderSplitNode(dock_top, ImGuiDir_Right, 0.20f, NULL, &dock_top); - ImGuiID dock_id_center = ImGui::DockBuilderSplitNode(dock_top, ImGuiDir_Left, 0.60f, NULL, &dock_top); + ImGui::DockBuilderDockWindow("Asset Browser", dock_id_bottom); + ImGui::DockBuilderDockWindow("Hierarchy", dock_id_left); + ImGui::DockBuilderDockWindow("Inspector", dock_id_right); + ImGui::DockBuilderDockWindow("Viewport", dock_id_center); + ImGui::DockBuilderFinish(dockspace_id); + } - ImGui::DockBuilderDockWindow("Assets", dock_id_bottom); - ImGui::DockBuilderDockWindow("Hierarchy", dock_id_left); - ImGui::DockBuilderDockWindow("Inspector", dock_id_right); - ImGui::DockBuilderDockWindow("Viewport", dock_id_center); - ImGui::DockBuilderFinish(dockspace_id); + ImGui::DockSpace(dockspace_id); + } else if (node.type == ImXML::ImGuiEnum::MAINMENUBAR) { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(4, 4)); } + } + void onNodeEnd(ImXML::XMLNode& node) override { + if (node.type == ImXML::ImGuiEnum::MAINMENUBAR) { + ImGui::PopStyleVar(); + } + } + void onEvent(ImXML::XMLNode& node) override { + auto id = node.arg("id"); + callback(id); + } - ImGui::DockSpace(dockspace_id); - - ImGui::End(); - ImGui::PopStyleVar(); + void render() override { + ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->Pos); + ImGui::SetNextWindowSize(viewport->Size); + ImGui::SetNextWindowViewport(viewport->ID); + m_xml_renderer.render(m_xml_tree, *this); } private: + bool m_docking_initialized = false; + + ImXML::XMLTree m_xml_tree; + ImXML::XMLRenderer m_xml_renderer; }; diff --git a/ICEBERG/UI/HierarchyWidget.h b/ICEBERG/UI/HierarchyWidget.h index b6b1f22b..99e19c3f 100644 --- a/ICEBERG/UI/HierarchyWidget.h +++ b/ICEBERG/UI/HierarchyWidget.h @@ -1,5 +1,5 @@ #pragma once -#include +#include #include #include @@ -28,13 +28,11 @@ class HierarchyWidget : public Widget { void render() override { int flags = ImGuiWindowFlags_NoCollapse; flags |= ImGuiWindowFlags_NoNavFocus; - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); ImGui::Begin("Hierarchy", 0, flags); renderTree(m_view); ImGui::End(); - ImGui::PopStyleVar(); } private: diff --git a/ICEBERG/UI/InspectorWidget.h b/ICEBERG/UI/InspectorWidget.h index 902d27ae..058486f5 100644 --- a/ICEBERG/UI/InspectorWidget.h +++ b/ICEBERG/UI/InspectorWidget.h @@ -1,125 +1,62 @@ #pragma once -#include -#include +#include +#include "AnimationComponentWidget.h" #include "Components/InputText.h" #include "Components/UniformInputs.h" +#include "LightComponentWidget.h" +#include "RenderComponentWidget.h" +#include "TransformComponentWidget.h" #include "Widget.h" class InspectorWidget : public Widget { public: - InspectorWidget() {} + InspectorWidget() { + m_input_entity_name.onEdit([this](const std::string&, const std::string& text) { callback("entity_name_changed", text); }); + } void render() override { - m_input_entity_name.onEdit([this](const std::string& text) { callback("entity_name_changed", text); }); int flags = ImGuiWindowFlags_NoCollapse; flags |= ImGuiWindowFlags_NoNavFocus; - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); - ImGui::Begin("Inspector", 0, flags); - - ImGui::Text("Entity name"); - m_input_entity_name.render(); - - if (m_tc) { - ImGui::SeparatorText("Transform"); - ImGui::BeginGroup(); - for (auto& input : m_tc_inputs) { - ImGui::Text("%s", input.getLabel().c_str()); - input.render(); - } - ImGui::EndGroup(); - } - if (m_rc) { - ImGui::PushID("rc"); - ImGui::SeparatorText("Render"); - ImGui::BeginGroup(); - if (ImGui::Button("Remove")) { - callback("remove_render_component_clicked"); + if (ImGui::Begin("Inspector", 0, flags)) { + if (m_entity_selected) { + ImGui::Text("Entity name"); + m_input_entity_name.render(); + + m_tc_widget.render(); + m_rc_widget.render(); + m_lc_widget.render(); + m_ac_widget.render(); + + if (ImGui::Button("Add Component...")) { + callback("add_component_clicked"); + } } - for (auto& input : m_rc_inputs) { - ImGui::Text("%s", input.getLabel().c_str()); - input.render(); - } - ImGui::EndGroup(); - ImGui::PopID(); - } - if (m_lc) { - ImGui::PushID("lc"); - ImGui::SeparatorText("Light"); - ImGui::BeginGroup(); - if (ImGui::Button("Remove")) { - callback("remove_light_component_clicked"); - } - m_lc_type_combo.render(); - for (auto& input : m_lc_inputs) { - ImGui::Text("%s", input.getLabel().c_str()); - input.render(); - } - ImGui::EndGroup(); - ImGui::PopID(); - } - - if (ImGui::Button("Add Component...")) { - callback("add_component_clicked"); + ImGui::End(); } - - ImGui::End(); - ImGui::PopStyleVar(); } void setEntityName(const std::string& name) { m_input_entity_name.setText(name); } void setTransformComponent(ICE::TransformComponent* tc) { - m_tc = tc; - m_tc_inputs.clear(); - if (tc) { - m_tc_inputs.emplace_back("Position", tc->getPosition()); - m_tc_inputs.back().setForceVectorNumeric(true); - m_tc_inputs.back().onValueChanged([this](const ICE::UniformValue& v) { m_tc->setPosition(std::get(v)); }); - m_tc_inputs.emplace_back("Rotation", tc->getRotation()); - m_tc_inputs.back().setForceVectorNumeric(true); - m_tc_inputs.back().onValueChanged([this](const ICE::UniformValue& v) { m_tc->setRotation(std::get(v)); }); - m_tc_inputs.emplace_back("Scale", tc->getScale()); - m_tc_inputs.back().setForceVectorNumeric(true); - m_tc_inputs.back().onValueChanged([this](const ICE::UniformValue& v) { m_tc->setScale(std::get(v)); }); - } + m_entity_selected = (tc != nullptr); + m_tc_widget.setTransformComponent(tc); } - - void setRenderComponent(ICE::RenderComponent* rc, const std::vector& meshes_paths, const std::vector& meshes_ids) { - m_rc = rc; - m_rc_inputs.clear(); - if (rc) { - m_rc_inputs.reserve(2); - UniformInputs in_mesh("Mesh", rc->model); - in_mesh.onValueChanged([this](const ICE::UniformValue& v) { m_rc->model = std::get(v); }); - in_mesh.setAssetComboList(meshes_paths, meshes_ids); - m_rc_inputs.push_back(in_mesh); - } + void setLightComponent(ICE::LightComponent* lc) { m_lc_widget.setLightComponent(lc); } + void setAnimationComponent(ICE::AnimationComponent* ac, const std::unordered_map& animations) { + m_ac_widget.setAnimationComponent(ac, animations); } - - void setLightComponent(ICE::LightComponent* lc) { - m_lc = lc; - m_lc_inputs.clear(); - if (lc) { - m_lc_type_combo.setSelected(lc->type); - m_lc_type_combo.onSelectionChanged([this](const std::string&, int idx) { m_lc->type = static_cast(idx); }); - m_lc_inputs.emplace_back("Color", m_lc->color); - m_lc_inputs.back().onValueChanged([this](const ICE::UniformValue& v) { m_lc->color = std::get(v); }); - m_lc_inputs.emplace_back("Distance Dropoff", m_lc->distance_dropoff); - m_lc_inputs.back().onValueChanged([this](const ICE::UniformValue& v) { m_lc->distance_dropoff = std::get(v); }); - } + void setRenderComponent(ICE::RenderComponent* rc, const std::vector& meshes_paths, const std::vector& meshes_ids) { + m_rc_widget.setRenderComponent(rc, meshes_paths, meshes_ids); } private: - ICE::TransformComponent* m_tc = nullptr; - std::vector m_tc_inputs; - - ICE::RenderComponent* m_rc = nullptr; - std::vector m_rc_inputs; + TransformComponentWidget m_tc_widget; + RenderComponentWidget m_rc_widget; + LightComponentWidget m_lc_widget; + AnimationComponentWidget m_ac_widget; - ICE::LightComponent* m_lc = nullptr; - std::vector m_lc_inputs; - ComboBox m_lc_type_combo{"Light Type", {"Point Light", "Directional Light", "Spot Light"}}; + bool m_entity_selected = false; InputText m_input_entity_name{"##inspector_entity_name", ""}; }; diff --git a/ICEBERG/UI/LightComponentWidget.h b/ICEBERG/UI/LightComponentWidget.h new file mode 100644 index 00000000..9b359ec4 --- /dev/null +++ b/ICEBERG/UI/LightComponentWidget.h @@ -0,0 +1,67 @@ +#pragma once +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "Components/UniformInputs.h" +#include "Widget.h" + +class LightComponentWidget : public Widget, ImXML::XMLEventHandler { + public: + explicit LightComponentWidget() : m_xml_tree(ImXML::XMLReader().read("XML/LightComponentWidget.xml")) { + m_xml_renderer.addDynamicBind("float_distance_dropoff", {&m_distance_dropoff, 1, ImXML::Float}); + } + + void onNodeBegin(ImXML::XMLNode& node) override { + int light_type_id = static_cast(m_lc->type); + if (node.arg("id") == "light_type_combo") { + node.args["preview_value"] = m_light_types[light_type_id]; + } else if (node.arg("id") == "point_light") { + node.args["selected"] = light_type_id == 0 ? "true" : false; + } else if (node.arg("id") == "directional_light") { + node.args["selected"] = light_type_id == 1 ? "true" : false; + } else if (node.arg("id") == "spot_light") { + node.args["selected"] = light_type_id == 2 ? "true" : false; + } + } + void onNodeEnd(ImXML::XMLNode& node) override {} + void onEvent(ImXML::XMLNode& node) override { + if (node.arg("id") == "point_light") { + m_lc->type = ICE::PointLight; + } else if (node.arg("id") == "directional_light") { + m_lc->type = ICE::DirectionalLight; + } else if (node.arg("id") == "spot_light") { + m_lc->type = ICE::SpotLight; + } + } + + void render() override { + if (m_lc) { + m_xml_renderer.render(m_xml_tree, *this); + m_lc->distance_dropoff = m_distance_dropoff; + } + } + + void setLightComponent(ICE::LightComponent* lc) { + m_lc = lc; + if (lc) { + m_distance_dropoff = lc->distance_dropoff; + } + } + + private: + ICE::LightComponent* m_lc = nullptr; + + float m_distance_dropoff; + + const std::vector m_light_types = {"Point Light", "Directional Light", "Spot Light"}; + + ImXML::XMLTree m_xml_tree; + ImXML::XMLRenderer m_xml_renderer; +}; diff --git a/ICEBERG/UI/MaterialEditDialog.h b/ICEBERG/UI/MaterialEditDialog.h new file mode 100644 index 00000000..e4e232bd --- /dev/null +++ b/ICEBERG/UI/MaterialEditDialog.h @@ -0,0 +1,153 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "Components/ComboBox.h" +#include "Components/InputText.h" +#include "Components/UniformInputs.h" +#include "Dialog.h" + +class MaterialEditDialog : public Dialog, ImXML::XMLEventHandler { + public: + MaterialEditDialog() : m_xml_tree(ImXML::XMLReader().read("XML/MaterialEditPopup.xml")) { + m_xml_renderer.addDynamicBind("bool_opaque", {&m_mtl_opaque, 1, ImXML::Bool}); + m_xml_renderer.addDynamicBind("str_material_name", {m_mtl_name, 512, ImXML::Chars}); + m_shaders_combo.onSelectionChanged([this](const std::string&, int index) { callback("shader_selected", index); }); + } + + void render() override { + ImGui::PushID(m_dialog_id); + if (isOpenRequested()) + ImGui::OpenPopup("Material Editor"); + m_xml_renderer.render(m_xml_tree, *this); + ImGui::PopID(); + } + + void setPreviewTexture(void* tex) { m_preview_texture = tex; } + + void addUniformInput(const std::string& name, const ICE::UniformValue& value) { + int ctr = m_uniform_inputs.size(); + m_uniform_inputs.emplace_back(std::make_unique(std::format("##input_{}", ctr), value)); + m_uniform_names.emplace_back(std::make_unique(std::format("##name_{}", ctr), name)); + m_uniform_types.emplace_back(std::make_unique(std::format("##type_{}", ctr), m_uniform_types_list)); + m_uniform_types.back()->setSelected(value.index()); + + m_uniform_inputs.back()->onValueChanged([this, id = ctr](const auto& val) { m_material->setUniform(m_uniform_names[id]->getText(), val); }); + m_uniform_names.back()->onEdit([this](std::string prev_name, std::string new_name) { m_material->renameUniform(prev_name, new_name); }); + m_uniform_types.back()->onSelectionChanged([this, id = ctr](const std::string&, int index) { + ICE::UniformValue val; + switch (index) { + case 0: + val = ICE::AssetUID(0); + break; + case 1: + val = 0; + break; + case 2: + val = 0.0f; + break; + case 3: + val = Eigen::Vector2f(); + break; + case 4: + val = Eigen::Vector3f(); + break; + case 5: + val = Eigen::Vector4f(); + break; + case 6: + val = Eigen::Matrix4f(); + break; + default: + break; + } + m_uniform_inputs[id]->setValue(val); + }); + + std::vector tex_paths; + std::vector tex_ids; + for (const auto& [path, id] : m_textures) { + tex_paths.push_back(path); + tex_ids.push_back(id); + } + m_uniform_inputs.back()->setAssetComboList(tex_paths, tex_ids); + } + + void setShaderList(const std::vector& list, int selected = 0) { + m_shaders_combo.setValues(list); + m_shaders_combo.setSelected(selected); + } + void setTextureList(const std::unordered_map& textures) { m_textures = textures; } + void setMaterialName(const std::string& name) { strncpy(m_mtl_name, name.c_str(), 512); } + void setMaterial(std::shared_ptr mtl) { + m_material = mtl; + m_mtl_opaque = !mtl->isTransparent(); + m_uniform_inputs.clear(); + m_uniform_names.clear(); + m_uniform_types.clear(); + for (const auto& [name, value] : mtl->getAllUniforms()) { + addUniformInput(name, value); + } + } + ICE::Material getMaterial() const { return *m_material; } + std::string getName() const { return m_mtl_name; } + + void onNodeBegin(ImXML::XMLNode& node) override { + if (node.arg("id") == "combo_shader_values") { + m_shaders_combo.render(); + } else if (node.arg("id") == "uniforms_table") { + for (int i = 0; i < m_uniform_inputs.size(); i++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + m_uniform_names[i]->render(); + ImGui::TableNextColumn(); + m_uniform_inputs[i]->render(); + ImGui::TableNextColumn(); + m_uniform_types[i]->render(); + } + } else if (node.arg("id") == "material_preview") { + ImGui::Image(m_preview_texture, {256, 256}, {0, 1}, {1, 0}); + } + } + void onNodeEnd(ImXML::XMLNode& node) override {} + void onEvent(ImXML::XMLNode& node) override { + if (node.arg("id") == "btn_apply") { + ImGui::CloseCurrentPopup(); + done(DialogResult::Ok); + } else if (node.arg("id") == "btn_cancel") { + ImGui::CloseCurrentPopup(); + done(DialogResult::Cancel); + } else if (node.arg("id") == "btn_add_uniform") { + m_material->setUniform("New Uniform", 0); + addUniformInput("New Uniform", 0); + } + } + + private: + bool m_mtl_opaque; + char m_mtl_name[512] = {0}; + void* m_preview_texture = nullptr; + + ComboBox m_shaders_combo{"###shaders", {}}; + + std::vector> m_uniform_inputs; + std::vector> m_uniform_names; + std::vector> m_uniform_types; + + std::unordered_map m_textures; + + const std::vector m_uniform_types_list = {"Texture", "int", "float", "Vector2f", "Vector3f", "Vector4f", "Matrix4f"}; + + std::shared_ptr m_material; + + ImXML::XMLTree m_xml_tree; + ImXML::XMLRenderer m_xml_renderer; +}; diff --git a/ICEBERG/UI/NewMaterialWidget.h b/ICEBERG/UI/NewMaterialWidget.h deleted file mode 100644 index ece39eaa..00000000 --- a/ICEBERG/UI/NewMaterialWidget.h +++ /dev/null @@ -1,229 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include - -#include -#include - -#include "Components/ComboBox.h" -#include "Components/InputText.h" -#include "Components/UniformInputs.h" -#include "Widget.h" - -class NewMaterialWidget : public Widget { - public: - NewMaterialWidget(const std::shared_ptr& engine) : m_engine(engine) {} - - void render() override { - ImGui::PushID(m_id); - if (m_open) { - ImGui::OpenPopup("Material Editor"); - m_open = false; - } - - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); - if (ImGui::BeginPopupModal("Material Editor", 0, 0)) { - if (ImGui::BeginTable("mat_editor_layout", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) { - ImGui::TableNextColumn(); - ImGui::Text("Name:"); - ImGui::SameLine(); - ImGui::InputText("##name", m_name, 512); - ImGui::Text("Shader:"); - ImGui::SameLine(); - m_shaders_combo.render(); - ImGui::Text("Uniforms"); - ImGui::SameLine(); - if (ImGui::BeginTable("uniforms_table", 3)) { - ImGui::TableSetupColumn("Name"); - ImGui::TableSetupColumn("Value"); - ImGui::TableSetupColumn("Type"); - ImGui::TableHeadersRow(); - for (int i = 0; i < m_uniform_names.size(); i++) { - ImGui::TableNextColumn(); - m_uniform_names[i].render(); - ImGui::TableNextColumn(); - m_uniform_inputs[i].render(); - ImGui::TableNextColumn(); - m_uniform_combos[i].render(); - } - ImGui::EndTable(); - } - - if (ImGui::Button("New Uniform")) { - addUniformInput("uNew", 0); - } - - if (ImGui::Button("Cancel")) { - ImGui::CloseCurrentPopup(); - m_id = 0; - } - ImGui::SameLine(); - if (ImGui::Button("Apply")) { - auto new_name = m_engine->getAssetBank()->getName(m_id).prefix() + m_name; - auto rename_ok = m_engine->getAssetBank()->renameAsset(m_engine->getAssetBank()->getName(m_id), new_name); - if (rename_ok) { - m_accepted = true; - ImGui::CloseCurrentPopup(); - m_id = 0; - } - } - ImGui::TableNextColumn(); - ImGui::Image(renderPreview(), {256, 256}); - ImGui::EndTable(); - } - ImGui::EndPopup(); - } - ImGui::PopStyleVar(); - ImGui::PopID(); - } - - void open(ICE::AssetUID id) { - m_id = id; - m_open = true; - m_material = m_engine->getAssetBank()->getAsset(id); - auto shaders = m_engine->getAssetBank()->getAll(); - std::vector shader_names; - int shader_idx = 0; - int i = 0; - for (const auto& [id, shader] : shaders) { - shader_names.push_back(m_engine->getAssetBank()->getName(id).toString()); - if (id == m_material->getShader()) { - shader_idx = i; - } - i++; - } - m_shaders_combo.setValues(shader_names); - m_shaders_combo.setSelected(shader_idx); - auto name = m_engine->getAssetBank()->getName(id).getName(); - memcpy(m_name, name.c_str(), name.size() + 1); - - m_shaders_combo.onSelectionChanged([this](const std::string& name, int) { m_material->setShader(m_engine->getAssetBank()->getUID(name)); }); - - m_uniform_names.clear(); - m_uniform_combos.clear(); - m_uniform_inputs.clear(); - for (const auto& [uname, uniform] : m_material->getAllUniforms()) { - addUniformInput(uname, uniform); - } - } - - void addUniformInput(const std::string& uname, const ICE::UniformValue& value) { - m_uniform_names.emplace_back("##uName_" + std::to_string(m_ctr), uname); - m_uniform_combos.emplace_back("##uType_" + std::to_string(m_ctr), uniform_types_names); - m_uniform_inputs.emplace_back("##uIn_" + std::to_string(m_ctr), value); - - m_uniform_inputs.back().onValueChanged( - [this, in = m_uniform_inputs.size() - 1](const ICE::UniformValue& v) { m_material->setUniform(m_uniform_names[in].getText(), v); }); - - auto textures = m_engine->getAssetBank()->getAll(); - std::vector uids; - std::vector paths; - for (const auto& [id, ptr] : textures) { - uids.push_back(id); - paths.push_back(m_engine->getAssetBank()->getName(id).toString()); - } - if (std::holds_alternative(value)) { - m_uniform_inputs.back().setAssetComboList(paths, uids); - } - - m_uniform_combos.back().setSelected(comboIDFromValue(value)); - m_uniform_combos.back().onSelectionChanged([this, in = m_uniform_inputs.size() - 1](const std::string& selected, int i) { - if (i == 0) { - m_uniform_inputs[in].setValue(ICE::AssetUID(0)); - } else if (i == 1) { - m_uniform_inputs[in].setValue(0); - } else if (i == 2) { - m_uniform_inputs[in].setValue(0.0f); - } else if (i == 3) { - m_uniform_inputs[in].setValue(Eigen::Vector3f(0, 0, 0)); - } else if (i == 4) { - m_uniform_inputs[in].setValue(Eigen::Vector4f(0, 0, 0, 0)); - } else if (i == 4) { - m_uniform_inputs[in].setValue(Eigen::Matrix4f()); - } - }); - - m_ctr++; - } - - int comboIDFromValue(const ICE::UniformValue& value) { - if (std::holds_alternative(value)) { - return 2; - } else if (!std::holds_alternative(value) && std::holds_alternative(value)) { - return 1; - } else if (std::holds_alternative(value)) { - return 0; - } else if (std::holds_alternative(value)) { - return 3; - } else if (std::holds_alternative(value)) { - return 4; - } else if (std::holds_alternative(value)) { - return 5; - } else { - throw std::runtime_error("Uniform type not implemented"); - } - } - - void* renderPreview() { - auto model_uid = m_engine->getAssetBank()->getUID(ICE::AssetPath::WithTypePrefix("sphere")); - - auto model = m_engine->getAssetBank()->getAsset(model_uid); - auto shader = m_engine->getAssetBank()->getAsset(m_material->getShader()); - - auto camera = std::make_shared(60.0, 1.0, 0.01, 10000.0); - camera->backward(2); - camera->up(1); - camera->pitch(-30); - - shader->bind(); - shader->loadMat4("projection", camera->getProjection()); - shader->loadMat4("view", camera->lookThrough()); - - ICE::GeometryPass pass(m_engine->getApi(), m_engine->getGraphicsFactory(), {256, 256, 1}); - std::vector cmds; - std::unordered_map> textures; - for (const auto& [k, v] : m_material->getAllUniforms()) { - if (std::holds_alternative(v)) { - auto id = std::get(v); - textures.try_emplace(id, m_engine->getAssetBank()->getAsset(id)); - } - } - for (const auto& mesh : model->getMeshes()) { - cmds.push_back(ICE::RenderCommand{.mesh = mesh, - .material = m_material, - .shader = shader, - .textures = textures, - .model_matrix = Eigen::Matrix4f::Identity()}); - } - pass.submit(&cmds); - pass.execute(); - - return static_cast(0) + pass.getResult()->getTexture(); - } - - bool accepted() { - if (m_accepted) { - m_accepted = false; - return true; - } - return false; - } - - private: - bool m_open = false; - char m_name[512] = {0}; - int m_shader_index = 0; - ComboBox m_shaders_combo{"##shader_combo", {}}; - std::vector m_uniform_names; - std::vector m_uniform_combos; - std::vector m_uniform_inputs; - const std::vector uniform_types_names = {"Asset", "Int", "Float", "Vector3", "Vector4", "Matrix4"}; - int m_ctr = 0; - bool m_accepted = false; - std::shared_ptr m_engine; - std::shared_ptr m_material; - ICE::AssetUID m_id; -}; diff --git a/ICEBERG/UI/NewProjectPopup.h b/ICEBERG/UI/NewProjectPopup.h new file mode 100644 index 00000000..d9a098e0 --- /dev/null +++ b/ICEBERG/UI/NewProjectPopup.h @@ -0,0 +1,55 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +#include "Dialog.h" + +class NewProjectPopup : public Dialog, ImXML::XMLEventHandler { + public: + NewProjectPopup() : m_xml_tree(ImXML::XMLReader().read("XML/NewProjectPopup.xml")) { + m_xml_renderer.addDynamicBind("str_project_name", {m_project_name, 512, ImXML::Chars}); + m_xml_renderer.addDynamicBind("str_project_directory", {m_project_dir, 512, ImXML::Chars}); + } + + void render() override { + if (isOpenRequested()) + ImGui::OpenPopup("New Project"); + m_xml_renderer.render(m_xml_tree, *this); + } + + void onNodeBegin(ImXML::XMLNode& node) override {} + void onNodeEnd(ImXML::XMLNode& node) override {} + void onEvent(ImXML::XMLNode& node) override { + if (node.arg("id") == "btn_create") { + auto path_string = std::string(m_project_dir); + if (!path_string.empty() && std::filesystem::exists(path_string) && !std::string(m_project_name).empty()) { + auto project = std::make_shared(path_string, m_project_name); + project->CreateDirectories(); + ImGui::CloseCurrentPopup(); + done(DialogResult::Ok); + } + } else if (node.arg("id") == "btn_cancel") { + ImGui::CloseCurrentPopup(); + done(DialogResult::Cancel); + } else if (node.arg("id") == "btn_browse_directory") { + auto folder = open_native_folder_dialog(); + std::strncpy(m_project_dir, folder.c_str(), 511); + } + } + + std::string getProjectName() const { return std::string(m_project_name); } + + std::string getProjectDirectory() const { return std::string(m_project_dir); } + + private: + char m_project_name[512] = {0}; + char m_project_dir[512] = {0}; + + ImXML::XMLTree m_xml_tree; + ImXML::XMLRenderer m_xml_renderer; +}; diff --git a/ICEBERG/UI/NewSceneWidget.h b/ICEBERG/UI/NewSceneWidget.h index 162a2f3b..589862e9 100644 --- a/ICEBERG/UI/NewSceneWidget.h +++ b/ICEBERG/UI/NewSceneWidget.h @@ -1,6 +1,6 @@ #pragma once #include -#include +#include #include #include @@ -18,7 +18,6 @@ class NewSceneWidget : public Widget { m_open = false; } - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); if (ImGui::BeginPopupModal("Scene Editor", 0, ImGuiWindowFlags_AlwaysAutoResize)) { m_scene_name_in.render(); if (ImGui::Button("Accept")) { @@ -27,7 +26,6 @@ class NewSceneWidget : public Widget { } ImGui::EndPopup(); } - ImGui::PopStyleVar(); ImGui::PopID(); } diff --git a/ICEBERG/UI/OpenSceneWidget.h b/ICEBERG/UI/OpenSceneWidget.h index e2ac5919..1d5f1c12 100644 --- a/ICEBERG/UI/OpenSceneWidget.h +++ b/ICEBERG/UI/OpenSceneWidget.h @@ -1,5 +1,5 @@ #pragma once -#include +#include #include "Components/ComboBox.h" #include "Components/InputText.h" @@ -23,7 +23,6 @@ class OpenSceneWidget : public Widget { m_open = false; } - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); if (ImGui::BeginPopupModal("Scene Selection", 0, ImGuiWindowFlags_AlwaysAutoResize)) { m_scene_name_combo.render(); if (ImGui::Button("Accept")) { @@ -35,7 +34,6 @@ class OpenSceneWidget : public Widget { } ImGui::EndPopup(); } - ImGui::PopStyleVar(); ImGui::PopID(); } diff --git a/ICEBERG/UI/ProjectSelectionWidget.h b/ICEBERG/UI/ProjectSelectionWidget.h index 49910ca7..1ade7808 100644 --- a/ICEBERG/UI/ProjectSelectionWidget.h +++ b/ICEBERG/UI/ProjectSelectionWidget.h @@ -1,6 +1,12 @@ #pragma once -#include +#include +#include +#include +#include +#include +#include +#include "NewProjectPopup.h" #include "Widget.h" struct ProjectView { @@ -9,76 +15,71 @@ struct ProjectView { std::string modified_date; }; -class ProjectSelectionWidget : public Widget { +class ProjectSelectionWidget : public Widget, ImXML::XMLEventHandler { public: - ProjectSelectionWidget() = default; + ProjectSelectionWidget() : m_xml_tree(ImXML::XMLReader().read("XML/ProjectSelection.xml")) { + m_xml_renderer.addDynamicBind("str_project_search", {m_project_search, 512, ImXML::Chars}); + } void render() override { ImGuiViewport* viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(viewport->Pos); ImGui::SetNextWindowSize(viewport->Size); - ImGui::Begin("ICE Project Selection", NULL, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize); - ImGui::BeginTable("layout_split", 2, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_Resizable); - ImGui::TableNextColumn(); - renderNewProjects(); - ImGui::TableNextColumn(); - renderExistingProjects(); - ImGui::EndTable(); - ImGui::End(); - } - - int getSelectedProject() const { return m_selected_project; } + m_xml_renderer.render(m_xml_tree, *this); + m_new_project_popup.render(); - const char* getProjectName() const { return m_project_name; } + if (m_new_project_popup.getResult() == DialogResult::Ok) { + callback("create_clicked", m_new_project_popup.getProjectDirectory(), m_new_project_popup.getProjectName()); + } + } void setProjects(const std::vector& projects) { m_projects = projects; } - private: - void renderNewProjects() { - ImGui::BeginTable("layout_create", 3); - ImGui::TableNextColumn(); - ImGui::Text("Create a new Project"); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Project name: "); - ImGui::TableNextColumn(); - ImGui::InputText("##pname", m_project_name, 512); - ImGui::TableNextColumn(); - if (ImGui::Button("Create")) { - callback("create_clicked"); + void onNodeBegin(ImXML::XMLNode& node) override { + if (node.arg("id") == "list_table_body") { + renderExistingProjects(); } - ImGui::TableNextColumn(); - if (ImGui::Button("Load")) { - callback("load_clicked"); + } + void onNodeEnd(ImXML::XMLNode& node) override {} + void onEvent(ImXML::XMLNode& node) override { + if (node.arg("id") == "btn_create_project") { + m_new_project_popup.open(); + } else if (node.arg("id") == "btn_add_project") { + auto path = open_native_dialog({{"ICE Projects", "*.ice"}}); + callback("load_clicked", path); } - ImGui::EndTable(); } + private: void renderExistingProjects() { - ImGui::BeginTable("layout_projects", 1, ImGuiTableFlags_BordersH); - ImGui::TableNextColumn(); - ImGui::Text("Load existing project"); for (int i = 0; i < m_projects.size(); i++) { const auto& p = m_projects[i]; ImGui::TableNextColumn(); - ImGui::BeginGroup(); - ImGui::Text(p.name.c_str()); - ImGui::Text(p.path.c_str()); - ImGui::Text(p.modified_date.c_str()); - ImGui::EndGroup(); - if (ImGui::IsItemHovered()) { - ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ImGui::GetColorU32(ImVec4(0.2, 0.2, 0.2, 1))); + ImGui::PushID(i); + ImGui::Selectable(p.name.c_str(), false, ImGuiSelectableFlags_SpanAllColumns); + if (ImGui::IsItemClicked()) { + m_selected_project = i; + callback("project_selected", i); } + ImGui::TableNextColumn(); + ImGui::Text(p.modified_date.c_str()); + ImGui::TableNextColumn(); + ImGui::Text(p.path.c_str()); + ImGui::TableNextRow(); + if (ImGui::IsItemClicked()) { m_selected_project = i; callback("project_selected", i); } + ImGui::PopID(); } - ImGui::EndTable(); } private: - char m_project_name[512] = {0}; + char m_project_search[512] = {0}; std::vector m_projects; int m_selected_project = -1; + NewProjectPopup m_new_project_popup; + ImXML::XMLTree m_xml_tree; + ImXML::XMLRenderer m_xml_renderer; }; diff --git a/ICEBERG/UI/RenderComponentWidget.h b/ICEBERG/UI/RenderComponentWidget.h new file mode 100644 index 00000000..69a86dcf --- /dev/null +++ b/ICEBERG/UI/RenderComponentWidget.h @@ -0,0 +1,48 @@ +#pragma once +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "Components/UniformInputs.h" +#include "Widget.h" + +class RenderComponentWidget : public Widget, ImXML::XMLEventHandler { + public: + explicit RenderComponentWidget() : m_xml_tree(ImXML::XMLReader().read("XML/RenderComponentWidget.xml")) {} + + void onNodeBegin(ImXML::XMLNode& node) override { + if (node.arg("id") == "models_combo") { + m_models_combo.render(); + } + } + void onNodeEnd(ImXML::XMLNode& node) override {} + void onEvent(ImXML::XMLNode& node) override {} + + void render() override { + if (m_rc) { + m_xml_renderer.render(m_xml_tree, *this); + } + } + + void setRenderComponent(ICE::RenderComponent* rc, const std::vector& meshes_paths, const std::vector& meshes_ids) { + m_rc = rc; + if (m_rc) { + m_models_combo.setValue(rc->model); + m_models_combo.setAssetComboList(meshes_paths, meshes_ids); + m_models_combo.onValueChanged([this](const ICE::UniformValue& v) { m_rc->model = std::get(v); }); + } + } + + private: + ICE::RenderComponent* m_rc = nullptr; + UniformInputs m_models_combo{"##models_combo", 0}; + + ImXML::XMLTree m_xml_tree; + ImXML::XMLRenderer m_xml_renderer; +}; diff --git a/ICEBERG/UI/TransformComponentWidget.h b/ICEBERG/UI/TransformComponentWidget.h new file mode 100644 index 00000000..3ae14297 --- /dev/null +++ b/ICEBERG/UI/TransformComponentWidget.h @@ -0,0 +1,60 @@ +#pragma once +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "Components/UniformInputs.h" +#include "Widget.h" + +class TransformComponentWidget : public Widget, ImXML::XMLEventHandler { + public: + explicit TransformComponentWidget() : m_xml_tree(ImXML::XMLReader().read("XML/TransformComponentWidget.xml")) { + m_transform_input.setForceVectorNumeric(true); + m_rotation_input.setForceVectorNumeric(true); + m_scale_input.setForceVectorNumeric(true); + } + + void onNodeBegin(ImXML::XMLNode& node) override { + if (node.arg("id") == "position_vec") { + m_transform_input.render(); + } else if (node.arg("id") == "rotation_vec") { + m_rotation_input.render(); + } else if (node.arg("id") == "scale_vec") { + m_scale_input.render(); + } + } + void onNodeEnd(ImXML::XMLNode& node) override {} + void onEvent(ImXML::XMLNode& node) override {} + + void render() override { + if (m_tc) { + m_xml_renderer.render(m_xml_tree, *this); + } + } + + void setTransformComponent(ICE::TransformComponent* tc) { + m_tc = tc; + if (tc) { + m_transform_input.setValue(tc->getPosition()); + m_transform_input.onValueChanged([this](const ICE::UniformValue& v) { m_tc->setPosition(std::get(v)); }); + m_rotation_input.setValue(tc->getRotation()); + m_rotation_input.onValueChanged([this](const ICE::UniformValue& v) { m_tc->setRotation(std::get(v)); }); + m_scale_input.setValue(tc->getScale()); + m_scale_input.onValueChanged([this](const ICE::UniformValue& v) { m_tc->setScale(std::get(v)); }); + } + } + + private: + ICE::TransformComponent* m_tc = nullptr; + UniformInputs m_transform_input{"##transform_component_position", 0}; + UniformInputs m_rotation_input{"##transform_component_rotation", 0}; + UniformInputs m_scale_input{"##transform_component_scale", 0}; + ImXML::XMLTree m_xml_tree; + ImXML::XMLRenderer m_xml_renderer; +}; diff --git a/ICEBERG/UI/ViewportWidget.h b/ICEBERG/UI/ViewportWidget.h index 77e9d18e..77af9c12 100644 --- a/ICEBERG/UI/ViewportWidget.h +++ b/ICEBERG/UI/ViewportWidget.h @@ -1,6 +1,6 @@ #pragma once #include -#include +#include #include "Widget.h" diff --git a/ICEBERG/UI/Widget.h b/ICEBERG/UI/Widget.h index 76287952..dec01399 100644 --- a/ICEBERG/UI/Widget.h +++ b/ICEBERG/UI/Widget.h @@ -7,6 +7,8 @@ class Widget { public: + virtual ~Widget() = default; + virtual void render() = 0; template diff --git a/ICEBERG/XML/AnimationComponentWidget.xml b/ICEBERG/XML/AnimationComponentWidget.xml new file mode 100644 index 00000000..721edfdf --- /dev/null +++ b/ICEBERG/XML/AnimationComponentWidget.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + +
+
\ No newline at end of file diff --git a/ICEBERG/XML/AssetBrowser.xml b/ICEBERG/XML/AssetBrowser.xml new file mode 100644 index 00000000..a91e7ff3 --- /dev/null +++ b/ICEBERG/XML/AssetBrowser.xml @@ -0,0 +1,21 @@ + + + + + + + +
+ + + + + + + + + + + +
+
diff --git a/ICEBERG/XML/EditorWidget.xml b/ICEBERG/XML/EditorWidget.xml new file mode 100644 index 00000000..1a9021f5 --- /dev/null +++ b/ICEBERG/XML/EditorWidget.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ICEBERG/XML/LightComponentWidget.xml b/ICEBERG/XML/LightComponentWidget.xml new file mode 100644 index 00000000..c7db05f8 --- /dev/null +++ b/ICEBERG/XML/LightComponentWidget.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + +
+
\ No newline at end of file diff --git a/ICEBERG/XML/MaterialEditPopup.xml b/ICEBERG/XML/MaterialEditPopup.xml new file mode 100644 index 00000000..52c45e09 --- /dev/null +++ b/ICEBERG/XML/MaterialEditPopup.xml @@ -0,0 +1,64 @@ + + + + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + +
+ + + + + + + +