diff --git a/3DEditor/CMakeLists.txt b/3DEditor/CMakeLists.txt index 4e22009..1a410b4 100644 --- a/3DEditor/CMakeLists.txt +++ b/3DEditor/CMakeLists.txt @@ -19,5 +19,6 @@ file(GLOB APP_SOURCES "./applications/*.cpp") add_executable(3DEditorApp ${APP_SOURCES}) target_link_libraries(3DEditorApp ${PROJECT_NAME}) -file(COPY "rsc" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/") -file(COPY "rsc/Font.ttf" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/") +file(COPY "../RasterRender/rsc" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/") +file(COPY "../Graphics/rsc" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/") +file(COPY "../3DScene/rsc" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/") diff --git a/3DEditor/private/Denoise.cpp b/3DEditor/private/Denoise.cpp index 7c4955a..a20672a 100644 --- a/3DEditor/private/Denoise.cpp +++ b/3DEditor/private/Denoise.cpp @@ -37,4 +37,4 @@ void Editor::denoise() { if (device.getError(errorMessage) != oidn::Error::None) { std::cerr << "Error: " << errorMessage << std::endl; } -} \ No newline at end of file +} diff --git a/3DEditor/private/Editor.cpp b/3DEditor/private/Editor.cpp index 59facdf..9641bf8 100644 --- a/3DEditor/private/Editor.cpp +++ b/3DEditor/private/Editor.cpp @@ -24,7 +24,10 @@ Editor::Editor() { void Editor::renderViewport() { switch (mRenderType) { case RenderType::RASTER: - mRasterRenderer.render(mScene, mScene.mRenderSettings.size); + mRasterRenderer.beginRender(mScene, mScene.mRenderSettings.size); + mRasterRenderer.renderDefault(mScene); + if (mActive) mRasterRenderer.renderOutline(mScene.mCamera, *mActive); + mRasterRenderer.endRender(); break; case RenderType::PATH_TRACER: @@ -54,6 +57,7 @@ uint4 Editor::getViewportTexID() { void Editor::loadDefaults() { mScene.load("rsc/scene/script.lua"); + mResetCamera = mScene.mCamera; } void Editor::setViewportSize(const Vec2F& size) { @@ -73,6 +77,7 @@ Editor::~Editor() { } void Editor::renderPathFrame() { + mScene.updateCache(); mPathRenderer.render(mScene, mPathTracerBuffers, mScene.mRenderSettings); sendBuffersToGPU(); } @@ -116,5 +121,20 @@ void Editor::navigationZoom(halnf factor) { } void Editor::navigationReset() { - mScene.mCamera.lookAtPoint({ 0, 0, 0 }, { 1, 5, 1 }, { 0, 0, 1 }); -} \ No newline at end of file + mScene.mCamera = mResetCamera; + // mScene.mCamera.lookAtPoint({ 0, 0, 0 }, { 1, 5, 1 }, { 0, 0, 1 }); +} + +void Editor::selectObject(const Vec2F& screenPos) { + mScene.updateCache(); + + auto dir = (mScene.mCamera.project(screenPos) - mScene.mCamera.getPos()).normalize(); + Ray ray = Ray( dir, mScene.mCamera.getPos() ); + + auto rayCastData = mScene.castRay(ray, 1000); + mActive = rayCastData.obj; +} + +Object* Editor::getActiveObject() { return mActive; } + +Scene* Editor::getScene() { return &mScene; } \ No newline at end of file diff --git a/3DEditor/public/Editor.hpp b/3DEditor/public/Editor.hpp index ee16122..a4c3b0f 100644 --- a/3DEditor/public/Editor.hpp +++ b/3DEditor/public/Editor.hpp @@ -28,6 +28,10 @@ namespace tp { void navigationZoom(halnf factor); void navigationReset(); + void selectObject(const Vec2F& screenPos); + Object* getActiveObject(); + + Scene* getScene(); private: void sendBuffersToGPU(); @@ -36,6 +40,8 @@ namespace tp { private: Scene mScene; + Camera mResetCamera; + RenderType mRenderType = RenderType::RASTER; RasterRender mRasterRenderer; @@ -43,5 +49,7 @@ namespace tp { RayTracer::OutputBuffers mPathTracerBuffers; uint4 mPathRenderTexture = 0; + + Object* mActive = nullptr; }; } \ No newline at end of file diff --git a/3DEditor/public/EditorWidget.hpp b/3DEditor/public/EditorWidget.hpp index 0914937..7b4e748 100644 --- a/3DEditor/public/EditorWidget.hpp +++ b/3DEditor/public/EditorWidget.hpp @@ -66,11 +66,13 @@ namespace tp { mNavigationOrbit.setText("Orbit"); mNavigationZoom.setText("Zoom"); mNavigationReset.setText("Reset"); + mNavigationSelect.setText("Select"); mNavigationMenu.addToMenu(&mNavigationPan); mNavigationMenu.addToMenu(&mNavigationOrbit); mNavigationMenu.addToMenu(&mNavigationZoom); mNavigationMenu.addToMenu(&mNavigationReset); + mNavigationMenu.addToMenu(&mNavigationSelect); mNavigationMenu.setText("Navigation"); } @@ -85,30 +87,39 @@ namespace tp { mNavigationOrbit.setAction( [this]() { mNavigationType = ORBIT; }); mNavigationPan.setAction( [this]() { mNavigationType = PAN; }); mNavigationZoom.setAction( [this](){ mNavigationType = ZOOM; }); + mNavigationSelect.setAction( [this](){ mNavigationType = SELECT; }); mNavigationReset.setAction( [this]() { mEditor->navigationReset(); }); } void process(const EventHandler& events) override { DockWidget::process(events); - auto pointer = events.getPointer(); - auto pointerPrev = events.getPointerPrev(); + if (auto obj = mEditor->getActiveObject()) { // dummy rotate active object + auto rotator = obj->mTopology.Basis.rotatorDir({1, 1, 1}, 0.1); + obj->mTopology.Basis = rotator * obj->mTopology.Basis; + } + + mEditor->getScene()->updateCache(); const auto& activeArea = mViewport.getArea(); + auto pointer = events.getPointer(); + auto pointerPrev = events.getPointerPrev(); + auto pointerRelative = (((pointer - activeArea.pos) / activeArea.size) - 0.5f) * 2; + if (activeArea.isInside(pointer) && events.isDown(InputID::MOUSE1)) { switch (mNavigationType) { case ORBIT: mEditor->navigationOrbit(events.getPointerDelta() / activeArea.size * 3); break; case PAN: { - auto pointerRelative = (((pointer - activeArea.pos) / activeArea.size) - 0.5f) * 2; auto prevPointerRelative = (((pointerPrev - activeArea.pos) / activeArea.size) - 0.5f) * 2; mEditor->navigationPan(prevPointerRelative, pointerRelative); } break; case ZOOM: mEditor->navigationZoom(1 + (events.getPointerDelta().y / activeArea.size.y)); break; + case SELECT: mEditor->selectObject(pointerRelative * -1); break; } } } @@ -129,13 +140,14 @@ namespace tp { ButtonWidget mRenderDeNoise; // Navigation - enum NavigationType { ORBIT, PAN, ZOOM } mNavigationType = ORBIT; + enum NavigationType { SELECT, ORBIT, PAN, ZOOM } mNavigationType = ORBIT; FloatingMenu mNavigationMenu; ButtonWidget mNavigationPan; ButtonWidget mNavigationOrbit; ButtonWidget mNavigationZoom; ButtonWidget mNavigationReset; + ButtonWidget mNavigationSelect; RGBA mBaseColor; }; diff --git a/3DEditor/rsc/shaders/default.vert b/3DEditor/rsc/shaders/default.vert deleted file mode 100644 index 7d1fd94..0000000 --- a/3DEditor/rsc/shaders/default.vert +++ /dev/null @@ -1,11 +0,0 @@ -#version 330 core - -layout(location = 0) in vec3 Point; - -uniform vec4 Origin; -uniform mat4 Basis; -uniform mat4 Camera; - -void main() { - gl_Position = Camera * vec4(Point.xyz, 1.0); -} \ No newline at end of file diff --git a/3DScene/private/LuaFormat.cpp b/3DScene/private/LuaFormat.cpp index 3d61aad..acf4c9a 100644 --- a/3DScene/private/LuaFormat.cpp +++ b/3DScene/private/LuaFormat.cpp @@ -7,6 +7,61 @@ extern "C" { } #include +#include + +tp::Vec3F tp::Scene::getVec3(lua_State* state, const char* name) { + int stackSize = lua_gettop(state); + + // Access the "pos" field and validate it + lua_getfield(state, -1, name); + if (!lua_istable(state, -1) || lua_rawlen(state, -1) != 3) { + throw IOError(std::string("Invalid vec3 named ") + name); + } + + // Read the values from the table + float pos[3]; + for (int i = 0; i < 3; i++) { + lua_rawgeti(state, -1, i + 1); + if (lua_isnumber(state, -1)) { + pos[i] = lua_tonumber(state, -1); + } else { + throw IOError("vec3 values must be numbers"); + } + lua_pop(state, 1); // pop number + } + lua_pop(state, 1); // pop vec3 + + ASSERT(stackSize == lua_gettop(state)) + + return { pos[0], pos[1], pos[2] }; +} + +tp::Vec2F tp::Scene::getVec2(lua_State* state, const char* name) { + int stackSize = lua_gettop(state); + + // Access the "pos" field and validate it + lua_getfield(state, -1, name); + if (!lua_istable(state, -1) || lua_rawlen(state, -1) != 2) { + throw IOError(std::string("Invalid vec2 named ") + name); + } + + // Read the values from the table + float pos[2]; + for (int i = 0; i < 2; i++) { + lua_rawgeti(state, -1, i + 1); + if (lua_isnumber(state, -1)) { + pos[i] = lua_tonumber(state, -1); + } else { + throw IOError("vec3 values must be numbers"); + } + lua_pop(state, 1); // pop number + } + lua_pop(state, 1); // pop vec3 + + ASSERT(stackSize == lua_gettop(state)) + + return { pos[0], pos[1] }; +} // Function to read a Lua table representing RenderSettings int readRenderSettings(lua_State* L, tp::RenderSettings& settings) { @@ -53,23 +108,8 @@ int readRenderSettings(lua_State* L, tp::RenderSettings& settings) { } // Function to read a Lua table representing a light -int readLight(lua_State* L, tp::PointLight* light) { - lua_getfield(L, -1, "pos"); // Get the "pos" field from the light table - if (!lua_istable(L, -1)) { - printf("Light is missing the 'pos' table.\n"); - return 0; // Error - } - for (int i = 0; i < 3; i++) { - lua_rawgeti(L, -1, i + 1); // Index is 1-based in Lua - if (!lua_isnumber(L, -1)) { - printf("Light 'pos' field is not a number at index %d.\n", i); - lua_pop(L, 2); // Pop both the number and the 'pos' table - return 0; // Error - } - light->pos[i] = lua_tonumber(L, -1); - lua_pop(L, 1); // Pop the number - } - lua_pop(L, 1); // Pop the 'pos' table +int tp::Scene::readLight(lua_State* L, tp::PointLight* light) { + light->pos = getVec3(L, "pos"); lua_getfield(L, -1, "intensity"); // Get the "intensity" field from the light table if (!lua_isnumber(L, -1)) { @@ -121,66 +161,29 @@ bool tp::Scene::loadLuaFormat(const std::string& scenePath) { } // --- camera - - // Access Camera table - lua_getglobal(L, "Camera"); - if (!lua_istable(L, -1)) { - printf("Camera is not a table.\n"); - lua_close(L); - return false; - } - - // Verify you are inside the "Camera" table - int cameraTableIndex = lua_gettop(L); // Get the index of the "Camera" table - - // Access the "pos" field and validate it - lua_getfield(L, cameraTableIndex, "pos"); - if (!lua_istable(L, -1) || lua_rawlen(L, -1) != 3) { - printf("Invalid 'pos' field in Camera table.\n"); - lua_close(L); - return false; - } - - // Read the values from the table - float pos[3]; - for (int i = 0; i < 3; i++) { - lua_rawgeti(L, -1, i + 1); - if (lua_isnumber(L, -1)) { - pos[i] = lua_tonumber(L, -1); - } else { - printf("Invalid 'pos' field value at index %d.\n", i + 1); + { + lua_getglobal(L, "Camera"); + if (!lua_istable(L, -1)) { + printf("Camera is not a table.\n"); lua_close(L); return false; } + + Vec3F camPos = getVec3(L, "pos"); + Vec3F camTarget = getVec3(L, "target"); + Vec3F camUp = getVec3(L, "up"); + Vec2F camSize = getVec2(L, "size"); + + mRenderSettings.size = camSize; + + mCamera.lookAtPoint(camTarget, camPos, camUp); + mCamera.setFOV(3.14 / 4); + mCamera.setFar(100); + mCamera.setRatio((tp::halnf) camSize.y / (tp::halnf) camSize.x); + lua_pop(L, 1); } - // Access the "size_x" field and validate it - lua_getfield(L, cameraTableIndex, "size_x"); - if (!lua_isnumber(L, -1)) { - printf("Invalid or missing 'size_x' field in Camera table.\n"); - lua_close(L); - return false; - } - int size_x = lua_tointeger(L, -1); - lua_pop(L, 1); // Pop the 'size_x' value from the stack - - // Access the "size_y" field and validate it - lua_getfield(L, cameraTableIndex, "size_y"); - if (!lua_isnumber(L, -1)) { - printf("Invalid or missing 'size_y' field in Camera table.\n"); - lua_close(L); - return false; - } - int size_y = lua_tointeger(L, -1); - - mRenderSettings.size = { (tp::halnf) size_x, (tp::halnf) size_y }; - - mCamera.lookAtPoint({ 0, 0, 0 }, { pos[0], pos[1], pos[2] }, { 0, 0, 1 }); - mCamera.setFOV(3.14 / 4); - mCamera.setFar(100); - mCamera.setRatio((tp::halnf) size_y / (tp::halnf) size_x); - // ---------- LIGHTS { lua_getglobal(L, "Lights"); diff --git a/3DScene/private/OBJFormat.cpp b/3DScene/private/OBJFormat.cpp index 3fcf8ad..b97b87f 100644 --- a/3DScene/private/OBJFormat.cpp +++ b/3DScene/private/OBJFormat.cpp @@ -48,4 +48,39 @@ bool tp::Scene::loadOBJFormat(const std::string& objetsPath) { } return mObjects.size(); +} + +const tp::RayCastData& tp::Scene::castRay(const Ray& ray, alnf farVal) { + auto& out = mRayCastData; + + out.hit = false; + out.obj = nullptr; + + farVal *= farVal; + + for (auto obj : mObjects) { + for (auto trig : obj->mCache.TrigCaches) { + if (trig->castRay(ray)) { + + auto dist = (TrigCache::getHitPos() - ray.pos).length2(); + + if (farVal > dist && dist > EPSILON) { + out.trig = &trig.data(); + out.hitPos = TrigCache::getHitPos(); + out.obj = &obj.data(); + out.hit = true; + + farVal = dist; + } + } + } + } + + return mRayCastData; +} + +void tp::Scene::updateCache() { + for (auto obj : mObjects) { + obj->mCache.updateCache(); + } } \ No newline at end of file diff --git a/3DScene/private/Scene.cpp b/3DScene/private/Scene.cpp index f1927b2..6ef0399 100644 --- a/3DScene/private/Scene.cpp +++ b/3DScene/private/Scene.cpp @@ -2,5 +2,13 @@ #include "Scene.hpp" bool tp::Scene::load(const std::string& scenePath) { - return loadLuaFormat(scenePath); + try { + auto res = loadLuaFormat(scenePath); + if (!res) throw IOError("Failed loading lua script"); + } catch (const IOError& err) { + printf("Failed loading scene : %s\n", err.description.c_str()); + return false; + } + + return true; } \ No newline at end of file diff --git a/3DScene/public/Scene.hpp b/3DScene/public/Scene.hpp index f7aa56f..ac872f5 100644 --- a/3DScene/public/Scene.hpp +++ b/3DScene/public/Scene.hpp @@ -6,6 +6,8 @@ #include +struct lua_State; + namespace tp { struct RenderSettings { uhalni depth = 2; @@ -42,7 +44,22 @@ namespace tp { halnf intensity = 1.f; }; + struct RayCastData { + Object* obj = nullptr; + TrigCache* trig = nullptr; + Vec3F hitPos = { 0, 0, 0 }; + bool hit = false; + bool inv = false; + }; + class Scene { + struct IOError : public std::exception { + explicit IOError(std::string in) : + description(std::move(in)) {} + + std::string description; + }; + public: Scene() = default; @@ -51,11 +68,23 @@ namespace tp { bool loadLuaFormat(const std::string& scenePath); bool loadOBJFormat(const std::string& objectsPath); + const RayCastData& castRay(const Ray& ray, alnf farVal); + + void updateCache(); + + private: + Vec3F getVec3(lua_State* state, const char* name); + Vec2F getVec2(lua_State* state, const char* name); + + int readLight(lua_State* L, tp::PointLight* light); + public: Buffer mObjects; Buffer mLights; Camera mCamera; RenderSettings mRenderSettings; + + RayCastData mRayCastData; }; } diff --git a/3DEditor/rsc/scene/meshes.mtl b/3DScene/rsc/scene/meshes.mtl similarity index 100% rename from 3DEditor/rsc/scene/meshes.mtl rename to 3DScene/rsc/scene/meshes.mtl diff --git a/3DEditor/rsc/scene/meshes.obj b/3DScene/rsc/scene/meshes.obj similarity index 100% rename from 3DEditor/rsc/scene/meshes.obj rename to 3DScene/rsc/scene/meshes.obj diff --git a/3DEditor/rsc/scene/script.lua b/3DScene/rsc/scene/script.lua similarity index 75% rename from 3DEditor/rsc/scene/script.lua rename to 3DScene/rsc/scene/script.lua index ef6d484..42ea4c7 100644 --- a/3DEditor/rsc/scene/script.lua +++ b/3DScene/rsc/scene/script.lua @@ -2,9 +2,10 @@ Meshes = "meshes.obj" Camera = { - pos = { 0.5, 4.5, 0.2 }, - size_x = 600, - size_y = 800, + pos = { 0, 5, 0 }, + target = { 0, 0, 0 }, + up = { 0, 0, 1 }, + size = { 600, 800 }, } Lights = { diff --git a/Graphics/CMakeLists.txt b/Graphics/CMakeLists.txt index 1159d2a..3d4118b 100644 --- a/Graphics/CMakeLists.txt +++ b/Graphics/CMakeLists.txt @@ -19,5 +19,5 @@ target_link_libraries(Example${PROJECT_NAME} ${PROJECT_NAME} ${BINDINGS_LIBS}) target_include_directories(Example${PROJECT_NAME} PRIVATE ${BINDINGS_INCLUDE}) -file(COPY "examples/Font.ttf" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/") +file(COPY "../Graphics/rsc" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/") diff --git a/Graphics/examples/Font.ttf b/Graphics/examples/Font.ttf deleted file mode 100644 index 8a63054..0000000 Binary files a/Graphics/examples/Font.ttf and /dev/null differ diff --git a/Graphics/private/Canvas.cpp b/Graphics/private/Canvas.cpp index c108eff..bc29a17 100644 --- a/Graphics/private/Canvas.cpp +++ b/Graphics/private/Canvas.cpp @@ -27,7 +27,7 @@ Canvas::Canvas(Window* window) { mContext->vg = nvgCreateGL3(NVG_ANTIALIAS | NVG_STENCIL_STROKES); - if (nvgCreateFont(mContext->vg, "default", "Font.ttf") == -1) { + if (nvgCreateFont(mContext->vg, "default", "rsc/Font.ttf") == -1) { ASSERT(!"Cant create NVG font") } } diff --git a/Graphics/private/DebugGUI.cpp b/Graphics/private/DebugGUI.cpp index 03f6b1f..5880db1 100644 --- a/Graphics/private/DebugGUI.cpp +++ b/Graphics/private/DebugGUI.cpp @@ -36,7 +36,7 @@ DebugGUI::DebugGUI(Window* window) { ImGui_ImplOpenGL3_Init("#version 330"); ImGuiIO& io = ImGui::GetIO(); - io.Fonts->AddFontFromFileTTF("Font.ttf", 20.f); + io.Fonts->AddFontFromFileTTF("rsc/Font.ttf", 20.f); io.ConfigInputTrickleEventQueue = false; diff --git a/Graphics/public/EventHandler.hpp b/Graphics/public/EventHandler.hpp index c62bd89..ea35c89 100644 --- a/Graphics/public/EventHandler.hpp +++ b/Graphics/public/EventHandler.hpp @@ -87,6 +87,7 @@ namespace tp { [[nodiscard]] halnf getPointerPressure() const; void setEnableKeyEvents(bool); + [[nodiscard]] bool isKeyEventsEnabled() const { return mEnableKeyEvents; } private: void processEventUnguarded(); diff --git a/3DEditor/rsc/Font.ttf b/Graphics/rsc/Font.ttf similarity index 100% rename from 3DEditor/rsc/Font.ttf rename to Graphics/rsc/Font.ttf diff --git a/Math/private/Camera.cpp b/Math/private/Camera.cpp index fffc32e..621297e 100644 --- a/Math/private/Camera.cpp +++ b/Math/private/Camera.cpp @@ -88,7 +88,7 @@ void Camera::lookAtPoint(const Vec3F& aTarget, const Vec3F& aPos, Vec3F aUp) { } mPos = aPos; mTarget = aTarget; - Vec3F f = (mPos - mTarget).normalize(); + Vec3F f = (mTarget - mPos).normalize(); mUp = f * (aUp.normalize() * f); } @@ -117,16 +117,20 @@ void Camera::rotate(halnf angleX, halnf angleY) { Vec3F wup(0, 0, 1); mPos -= mTarget; - mat3f rotZ = mat3f::rotatorDir(wup, angleX); + Mat3F rotZ = Mat3F::rotatorDir(wup, angleX); mPos = rotZ * mPos; mUp = rotZ * mUp; Vec3F f = mPos.unitV(); Vec3F s = mUp * f; - mPos = mat3f::rotatorDir(s, -angleY) * mPos; + mPos = Mat3F::rotatorDir(s, -angleY) * mPos; mPos += mTarget; lookAtPoint(mTarget, mPos, mUp); } + +void Camera::setPos(Vec3F pos) { + mPos = pos; +} diff --git a/Math/public/Camera.hpp b/Math/public/Camera.hpp index 2b2ba45..6ebc5b3 100644 --- a/Math/public/Camera.hpp +++ b/Math/public/Camera.hpp @@ -18,6 +18,7 @@ namespace tp { void setRatio(halnf ratio); void setFOV(halnf fov); void setFar(halnf far); + void setPos(Vec3F pos); [[nodiscard]] const Vec3F& getPos() const; [[nodiscard]] const Vec3F& getTarget() const; diff --git a/Math/public/Mat.hpp b/Math/public/Mat.hpp index 4c51bcd..2483284 100644 --- a/Math/public/Mat.hpp +++ b/Math/public/Mat.hpp @@ -465,7 +465,7 @@ namespace tp { } // Matrix Properties - MVec transform(const MVec& in) const { return MVec(i.x * in.x + i.y * in.y, j.x * in.y + j.y * in.x); } + MVec transform(const MVec& in) const { return MVec(i.x * in.x + i.y * in.y, j.x * in.x + j.y * in.y); } Mat transform(const Mat& in) const { Mat out; @@ -495,8 +495,8 @@ namespace tp { }; template - using mat3 = Mat; - using mat3f = mat3; + using Mat3 = Mat; + using Mat3F = Mat3; template class Mat { @@ -647,22 +647,22 @@ namespace tp { Mat inv() { return cofactors() /= det(); } - Mat rotatorX(alnf angle) { + Mat rotatorX(alnf angle) const { alnf cosA = (alnf) cos(angle); alnf sinA = (alnf) sin(angle); return { { 1, 0, 0 }, { 0, cosA, -sinA }, { 0, sinA, cosA } }; } - Mat rotatorY(alnf angle) { + Mat rotatorY(alnf angle) const { alnf cosA = (alnf) cos(angle); alnf sinA = (alnf) sin(angle); return { { cosA, 0, sinA }, { 0, 1, 0 }, { -sinA, 0, cosA } }; } - Mat rotatorZ(alnf angle) { - alnf cosA = (alnf) cos(angle); - alnf sinA = (alnf) sin(angle); - return { { cosA, -sinA, 0 }, { sinA, cosA, 0 }, { 0, 0, 1 } }; + static Mat rotatorZ(alnf angle) { + Type cosA = (Type) cos(angle); + Type sinA = (Type) sin(angle); + return { vec{ cosA, -sinA, 0 }, vec{ sinA, cosA, 0 }, vec{ 0, 0, 1 } }; } static Mat rotatorDir(vec dir, alnf angle) { @@ -707,4 +707,14 @@ namespace tp { Type det() { return Type(); } }; + + template + Mat toMat4(const Mat& in) { + Mat out; + out[0] = { in[0][0], in[0][1], in[0][2], 0 }; + out[1] = { in[1][0], in[1][1], in[1][2], 0 }; + out[2] = { in[2][0], in[2][1], in[2][2], 0 }; + out[3] = { Type(0), Type(0), Type(0), Type(1) }; + return out; + } } diff --git a/Math/public/Topology.hpp b/Math/public/Topology.hpp index 92cb1a0..31efc10 100644 --- a/Math/public/Topology.hpp +++ b/Math/public/Topology.hpp @@ -30,7 +30,7 @@ namespace tp { struct Topology { Vec3F Origin = { 0, 0, 0 }; - mat3f Basis = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; + Mat3F Basis = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; Buffer Points; Buffer Normals; diff --git a/Math/public/Vec.hpp b/Math/public/Vec.hpp index 0441fa8..0729046 100644 --- a/Math/public/Vec.hpp +++ b/Math/public/Vec.hpp @@ -110,6 +110,13 @@ namespace tp { return *this; } + Vec& operator=(Type val) { + for (ualni i = 0; i < tSize; i++) { + get(i) = val; + } + return *this; + } + void assign(Type val) { for (ualni i = 0; i < tSize; i++) { get(i) = val; @@ -576,4 +583,14 @@ namespace tp { using Vec4F = Vec4; using Vec4I = Vec4; + + template + Vec toVec4(const Vec& in) { + Vec out; + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + out[3] = 0; + return out; + } } \ No newline at end of file diff --git a/RasterRender/private/FrameBuffer.cpp b/RasterRender/private/FrameBuffer.cpp index fd61110..0520051 100644 --- a/RasterRender/private/FrameBuffer.cpp +++ b/RasterRender/private/FrameBuffer.cpp @@ -102,7 +102,7 @@ void RenderBuffer::setViewport(const RectF& viewport) { void RenderBuffer::clear() { AssertGL(glClearColor(mClearCol.r, mClearCol.g, mClearCol.b, mClearCol.a)); - AssertGL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); + AssertGL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)); } void RenderBuffer::endDraw() { diff --git a/RasterRender/private/Render.cpp b/RasterRender/private/Render.cpp index 25cea38..9b6da2c 100644 --- a/RasterRender/private/Render.cpp +++ b/RasterRender/private/Render.cpp @@ -3,26 +3,42 @@ using namespace tp; -RasterRender::RasterRender() : - mRenderBuffer({ 100, 100 }) -{ +RasterRender::RasterRender() { mDefaultShader.load("rsc/shaders/default.vert", nullptr, "rsc/shaders/default.frag", true); + mSolidShader.load("rsc/shaders/default.vert", nullptr, "rsc/shaders/solid.frag", true); } RasterRender::~RasterRender() {} +void RasterRender::resize(const Vec2& size) { + auto sizeF = Vec2F(size.x, size.y); + mRenderBuffer.resize(sizeF); + mTempBuffer.resize(sizeF); +} + uint4 RasterRender::getRenderBufferID() { return mRenderBuffer.texId(); } Vec2F RasterRender::getBufferSize() { return mRenderBuffer.getSize(); } -void RasterRender::render(const Scene& geometry, const Vec2& size) { +void RasterRender::bindCameraShaderAttributes(const Mat4F& cameraMat) { + static auto camera = (GLint) mDefaultShader.getu("Camera"); + glUniformMatrix4fv(camera, 1, true, &cameraMat[0][0]); +} - for (auto object : geometry.mObjects) { - if (!object->mGUPBuffers) { - object->mGUPBuffers = new ObjectBuffers(&object.data()); - } - } +void RasterRender::bindObjectShaderAttributes(const Object& object) { + static auto origin = (GLint) mDefaultShader.getu("Origin"); + static auto basis = (GLint) mDefaultShader.getu("Basis"); + Mat4F basisMat = toMat4(object.mTopology.Basis); + Vec4F originPoint = toVec4(object.mTopology.Origin); + + basisMat = basisMat.transpose(); + + glUniform4fv(origin, 1, &originPoint[0]); + glUniformMatrix4fv(basis, 1, false, &basisMat[0][0]); +} + +void RasterRender::renderDefault(const Scene& geometry) { mRenderBuffer.mClearCol = { 0.0f, 0.0f, 0.0f, 0.f }; mRenderBuffer.beginDraw(); @@ -37,24 +53,21 @@ void RasterRender::render(const Scene& geometry, const Vec2& size) { // glPolygonMode(GL_FRONT, GL_LINE); // glPolygonMode(GL_BACK, GL_LINE); + // auto rotator = Mat3F::rotatorZ(alnf(0.001)); + + bindCameraShaderAttributes(cameraMat); + for (auto object : geometry.mObjects) { + // object->mTopology.Origin += 0.01; + // object->mTopology.Basis = rotator * object->mTopology.Basis; - static auto origin = (GLint) mDefaultShader.getu("Origin"); - static auto basis = (GLint) mDefaultShader.getu("Basis"); - static auto camera = (GLint) mDefaultShader.getu("Camera"); - - Mat4F basisMat; - Vec4F originPoint; - - glUniform4fv(origin, 1, &originPoint[0]); - glUniformMatrix4fv(basis, 1, false, &basisMat[0][0]); - glUniformMatrix4fv(camera, 1, true, &cameraMat[0][0]); - + bindObjectShaderAttributes(object.data()); object->mGUPBuffers->drawCall(); } mDefaultShader.unbind(); + glDisable(GL_DEPTH_TEST); // glPolygonMode(GL_FRONT, GL_FILL); // glPolygonMode(GL_BACK, GL_FILL); @@ -62,4 +75,37 @@ void RasterRender::render(const Scene& geometry, const Vec2& size) { mRenderBuffer.endDraw(); } +void RasterRender::renderOutline(const Camera& camera, const Object& object) { + mRenderBuffer.beginDraw(); + + // glEnable(GL_STENCIL_TEST); + + mSolidShader.bind(); + + bindCameraShaderAttributes(camera.calculateTransformationMatrix()); + bindObjectShaderAttributes(object); + + object.mGUPBuffers->drawCall(); + + mSolidShader.unbind(); + + // glDisable(GL_STENCIL_TEST); + + mRenderBuffer.endDraw(); +} + +void RasterRender::beginRender(const Scene& geometry, const Vec2& size) { + resize(size); + + for (auto object : geometry.mObjects) { + if (!object->mGUPBuffers) { + object->mGUPBuffers = new ObjectBuffers(&object.data()); + } + } +} + +void RasterRender::endRender() { + // pass +} + RenderBuffer* RasterRender::getRenderBuffer() { return &mRenderBuffer; } diff --git a/RasterRender/public/FrameBuffer.hpp b/RasterRender/public/FrameBuffer.hpp index 09dc82a..8c20aeb 100644 --- a/RasterRender/public/FrameBuffer.hpp +++ b/RasterRender/public/FrameBuffer.hpp @@ -7,7 +7,7 @@ namespace tp { class RenderBuffer { public: - explicit RenderBuffer(const Vec2F& size); + explicit RenderBuffer(const Vec2F& size = { 10, 10 }); RenderBuffer(const Vec2F& size, tp::uint1 samples); ~RenderBuffer(); diff --git a/RasterRender/public/RasterRender.hpp b/RasterRender/public/RasterRender.hpp index 229029d..4824c93 100644 --- a/RasterRender/public/RasterRender.hpp +++ b/RasterRender/public/RasterRender.hpp @@ -12,13 +12,27 @@ namespace tp { RasterRender(); ~RasterRender(); - void render(const Scene& geometry, const Vec2& size); + void beginRender(const Scene& geometry, const Vec2& size); + void endRender(); + + void renderDefault(const Scene& geometry); + void renderOutline(const Camera& camera, const Object& object); + + void resize(const Vec2& size); + uint4 getRenderBufferID(); RenderBuffer* getRenderBuffer(); Vec2F getBufferSize(); + private: + void bindObjectShaderAttributes(const Object& object); + void bindCameraShaderAttributes(const Mat4F& cameraMat); + private: RenderBuffer mRenderBuffer; + RenderBuffer mTempBuffer; + RenderShader mDefaultShader; + RenderShader mSolidShader; }; } \ No newline at end of file diff --git a/3DEditor/rsc/shaders/default.frag b/RasterRender/rsc/shaders/default.frag similarity index 100% rename from 3DEditor/rsc/shaders/default.frag rename to RasterRender/rsc/shaders/default.frag diff --git a/RasterRender/rsc/shaders/default.vert b/RasterRender/rsc/shaders/default.vert new file mode 100644 index 0000000..1d325ed --- /dev/null +++ b/RasterRender/rsc/shaders/default.vert @@ -0,0 +1,13 @@ +#version 330 core + +layout(location = 0) in vec3 Point; + +uniform vec4 Origin; +uniform mat4 Basis; +uniform mat4 Camera; + +void main() { + // vec4 transformed = vec4(Point.xyz, 1.0); + vec4 transformed = (Basis * vec4(Point.xyz, 1.0)) + Origin; + gl_Position = Camera * transformed; +} \ No newline at end of file diff --git a/RasterRender/rsc/shaders/solid.frag b/RasterRender/rsc/shaders/solid.frag new file mode 100644 index 0000000..31e99ca --- /dev/null +++ b/RasterRender/rsc/shaders/solid.frag @@ -0,0 +1,7 @@ +#version 330 core + +out vec4 FragColor; + +void main() { + FragColor = vec4(1, 0, 0, 1.f); +} \ No newline at end of file diff --git a/RayTracer/private/RayTracer.cpp b/RayTracer/private/RayTracer.cpp index e8355e7..b7b85f0 100644 --- a/RayTracer/private/RayTracer.cpp +++ b/RayTracer/private/RayTracer.cpp @@ -43,6 +43,8 @@ normal = n1 * barycentric.x + n2 * barycentric.y + n3 * barycentric.z; using namespace tp; + +// TODO : de-duplicate in Scene? void RayTracer::castRay(const Ray& ray, RayCastData& out, alnf farVal) { out.hit = false; out.obj = nullptr; diff --git a/RayTracer/public/RayTracer.hpp b/RayTracer/public/RayTracer.hpp index 4067247..a21964a 100644 --- a/RayTracer/public/RayTracer.hpp +++ b/RayTracer/public/RayTracer.hpp @@ -30,14 +30,6 @@ namespace tp { void render(const Scene& scene, OutputBuffers& out, const RenderSettings& settings); private: - struct RayCastData { - const Object* obj = nullptr; - TrigCache* trig = nullptr; - Vec3F hitPos = { 0, 0, 0 }; - bool hit = false; - bool inv = false; - }; - struct LightData { halnf intensity = 0; }; diff --git a/Sketch3D/CMakeLists.txt b/Sketch3D/CMakeLists.txt index 248c80d..34a642c 100644 --- a/Sketch3D/CMakeLists.txt +++ b/Sketch3D/CMakeLists.txt @@ -19,4 +19,4 @@ add_executable(Sketch3DApp ./applications/Entry.cpp) target_link_libraries(Sketch3DApp ${PROJECT_NAME}) file(COPY "rsc" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/") -file(COPY "applications/Font.ttf" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/") +file(COPY "../Graphics/rsc" DESTINATION "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/") diff --git a/Sketch3D/applications/Entry.cpp b/Sketch3D/applications/Entry.cpp index 8574a62..ef799c8 100644 --- a/Sketch3D/applications/Entry.cpp +++ b/Sketch3D/applications/Entry.cpp @@ -11,7 +11,7 @@ public: Sketch3DApplication() { setRoot(&mGui); - mGui.createRenderWidget(mGraphics->getCanvas(), { 1920, 1080 }); + mGui.createRenderWidget(mGraphics->getCanvas(), { 2560, 1440 }); mGui.setProject(&mSketch); } @@ -25,4 +25,4 @@ void runApp() { app.run(); } -int main() { runApp(); } \ No newline at end of file +int main() { runApp(); } diff --git a/Sketch3D/applications/Font.ttf b/Sketch3D/applications/Font.ttf deleted file mode 100644 index 8a63054..0000000 Binary files a/Sketch3D/applications/Font.ttf and /dev/null differ diff --git a/Sketch3D/private/Sketch3D.cpp b/Sketch3D/private/Sketch3D.cpp index 183036b..f366d47 100644 --- a/Sketch3D/private/Sketch3D.cpp +++ b/Sketch3D/private/Sketch3D.cpp @@ -280,7 +280,7 @@ void PencilBrush::draw(Renderer* render, const Camera* camera) { } PencilBrush::~PencilBrush() { - if (mStroke) delete mStroke; + delete mStroke; } void PencilBrush::finish(Project* proj) { diff --git a/Widgets/examples/Example.cpp b/Widgets/examples/Example.cpp index d0e7b31..37e2814 100644 --- a/Widgets/examples/Example.cpp +++ b/Widgets/examples/Example.cpp @@ -14,7 +14,7 @@ using namespace tp; class Example : public WidgetApplication { public: Example() { - exampleColorPicker(); + exampleNestedMenus(); } void exampleAll() { @@ -64,6 +64,7 @@ public: static ButtonWidget buttons[10]; widget.setDirection(false); + // widget.setDirection(false); setRoot(&widget); diff --git a/Widgets/private/RootWidget.cpp b/Widgets/private/RootWidget.cpp index 41697b6..065794c 100644 --- a/Widgets/private/RootWidget.cpp +++ b/Widgets/private/RootWidget.cpp @@ -58,7 +58,9 @@ void RootWidget::processFrame(EventHandler* events, const RectF& screenArea) { updateAreaCache(&mRoot, false); // trigger some widgets by moise pointer - mUpdateManager.handleFocusChanges(&mRoot, *events); + auto prevFocus = mUpdateManager.getFocusWidget(); + auto newFocus = mUpdateManager.findFocusWidget(&mRoot, *events); + mUpdateManager.handleFocusChanges(newFocus, prevFocus); // check triggered widgets for removal mUpdateManager.clean(); @@ -148,9 +150,11 @@ void RootWidget::openPopup(Widget* widget) { } void RootWidget::closePopup(Widget* widget) { - mPopups.removeChild(widget); - mUpdateManager.freeFocus(widget); + if (mUpdateManager.canRemoveWidget(widget)) { + mPopups.removeChild(widget); + mUpdateManager.removeWidget(widget); + } } void RootWidget::lockFocus(Widget* widget) { mUpdateManager.lockFocus(widget); } -void RootWidget::freeFocus(Widget* widget) { mUpdateManager.freeFocus(widget); } +void RootWidget::freeFocus(Widget* widget) { mUpdateManager.freeFocus(widget); } \ No newline at end of file diff --git a/Widgets/private/managers/DebugManager.cpp b/Widgets/private/managers/DebugManager.cpp index 921f7e1..04a5738 100644 --- a/Widgets/private/managers/DebugManager.cpp +++ b/Widgets/private/managers/DebugManager.cpp @@ -113,14 +113,17 @@ void DebugManager::recursiveDraw(Canvas& canvas, Widget* active, const Vec2F& po canvas.text((active->mDebug.id + ":" + std::to_string(depthOrder)).c_str(), area, 22, Canvas::Align::LC, 2, color); } + if (mRootWidget->mUpdateManager.mFocusLockStack.find(active)) { + canvas.frame(area, { 0, 1, 0, 0.5f }); + } + if (active->mFlags.get(Widget::IN_FOCUS)) { if (active->isUpdate()) { canvas.circle(pos + active->mDebug.pLocal, 5, active->mDebug.col); canvas.circle(active->mDebug.pGlobal, 15, active->mDebug.col); } - RGBA color = { 1, 0, 0, 0.3f }; - canvas.debugCross(area, color); + canvas.debugCross(area, { 0, 1, 0, 1 }); } int orderIdx = 0; diff --git a/Widgets/private/managers/LayoutManager.cpp b/Widgets/private/managers/LayoutManager.cpp index 83f51ba..acd8f8f 100644 --- a/Widgets/private/managers/LayoutManager.cpp +++ b/Widgets/private/managers/LayoutManager.cpp @@ -36,7 +36,7 @@ void LayoutManager::findDependencies(Widget* root) { if (!widget->isUpdate()) continue; for (auto child : widget->mChildren) { - // if (!child->isUpdate()) continue; + if (!child->isUpdate()) continue; mDepGraph.insert({ child, {} }); diff --git a/Widgets/private/managers/UpdateManager.cpp b/Widgets/private/managers/UpdateManager.cpp index d1af2cd..aeacc42 100644 --- a/Widgets/private/managers/UpdateManager.cpp +++ b/Widgets/private/managers/UpdateManager.cpp @@ -69,35 +69,36 @@ void UpdateManager::getWidgetPath(Widget* widget, std::vector& out) { } } -void UpdateManager::handleFocusChanges(Widget* root, EventHandler& events) { - auto prevFocus = mInFocusWidget; - +Widget* UpdateManager::findFocusWidget(Widget* root, EventHandler& events) { events.setCursorOrigin({ 0, 0 }); mInFocusWidget = nullptr; + findMouseFocusWidget(root, &mInFocusWidget, events.getPointer()); - findFocusWidget(root, &mInFocusWidget, events.getPointer()); - - if (mFocusLockWidget) { + if (mFocusLockStack.size()) { bool hasLockedWidget = false; for (auto iter = mInFocusWidget; iter; iter = iter->mParent) { - if (iter == mFocusLockWidget) { + if (iter == mFocusLockStack.last()) { hasLockedWidget = true; break; } } if (!hasLockedWidget) { - mInFocusWidget = mFocusLockWidget; + mInFocusWidget = mFocusLockStack.last(); } } - // if (mInFocusWidget == prevFocus) return; - if (mInFocusWidget) scheduleUpdate(mInFocusWidget, "focus entered"); + return mInFocusWidget; +} - if (!mInFocusWidget && !prevFocus) return; +void UpdateManager::handleFocusChanges(Widget* active, Widget* prevActive) { + // if (mInFocusWidget == prevFocus) return; + if (active) scheduleUpdate(active, "focus entered"); + + if (!active && !prevActive) return; std::vector path2; - getWidgetPath(mInFocusWidget, path2); + getWidgetPath(active, path2); size_t propLen2 = path2.size(); for (auto i = 0; i < path2.size(); i++) { if (!path2[i]->propagateEventsToChildren()) { @@ -107,7 +108,7 @@ void UpdateManager::handleFocusChanges(Widget* root, EventHandler& events) { } std::vector path1; - getWidgetPath(prevFocus, path1); + getWidgetPath(prevActive, path1); size_t propLen1 = path1.size(); for (auto i = 0; i < path1.size(); i++) { if (!path1[i]->propagateEventsToChildren()) { @@ -135,7 +136,7 @@ void UpdateManager::handleFocusChanges(Widget* root, EventHandler& events) { } } -void UpdateManager::findFocusWidget(Widget* iter, Widget** focus, const Vec2F& pointer) { +void UpdateManager::findMouseFocusWidget(Widget* iter, Widget** focus, const Vec2F& pointer) { if (!iter->mArea.getTargetRect().isInside(pointer) || !iter->mFlags.get(Widget::ENABLED)) return; if (iter->processesEvents()) { @@ -143,7 +144,7 @@ void UpdateManager::findFocusWidget(Widget* iter, Widget** focus, const Vec2F& p } for (auto child = iter->mDepthOrder.lastNode(); child; child = child->prev) { - findFocusWidget(child->data, focus, pointer - iter->mArea.getTargetRect().pos); + findMouseFocusWidget(child->data, focus, pointer - iter->mArea.getTargetRect().pos); } } @@ -159,8 +160,10 @@ void UpdateManager::processActiveTree(Widget* iter, EventHandler& events, Vec2F procWidget(iter, events, false); } - for (auto child : iter->mDepthOrder) { - processActiveTree(child.data(), events, current); + for (auto child = iter->mDepthOrder.firstNode(); child;) { + auto next = child->next; // any child may be removed at runtime + processActiveTree(child->data, events, current); + child = next; } } @@ -211,10 +214,24 @@ void UpdateManager::procWidget(Widget* widget, EventHandler& events, bool withEv } void UpdateManager::lockFocus(tp::Widget* widget) { - mFocusLockWidget = widget; + DEBUG_ASSERT(mFocusLockStack.find(widget) == nullptr) + // TODO : check that widget is a child of top of the stack + mFocusLockStack.pushBack(widget); } void UpdateManager::freeFocus(tp::Widget* widget) { - // DEBUG_ASSERT(mFocusLockWidget == widget) - mFocusLockWidget = nullptr; -} \ No newline at end of file + DEBUG_ASSERT(mFocusLockStack.last() == widget) + mFocusLockStack.popBack(); +} + +bool UpdateManager::canRemoveWidget(Widget* widget) const { + if (auto node = mFocusLockStack.find(widget)) { + if (node->next) return false; + } + + return true; +} + +void UpdateManager::removeWidget(Widget* widget) { + freeFocus(widget); +} diff --git a/Widgets/private/widgets/DockWidget.cpp b/Widgets/private/widgets/DockWidget.cpp index b75a35c..799bba7 100644 --- a/Widgets/private/widgets/DockWidget.cpp +++ b/Widgets/private/widgets/DockWidget.cpp @@ -86,7 +86,7 @@ void DockWidget::process(const EventHandler& events) { } } else if (events.isReleased(InputID::MOUSE1)) { layout()->endResize(); - freeFocus(); + if (layout()->isResizing()) freeFocus(); } if (layout()->isResizing()) { diff --git a/Widgets/private/widgets/ScrollableWidget.cpp b/Widgets/private/widgets/ScrollableWidget.cpp index e67f0fa..81836c1 100644 --- a/Widgets/private/widgets/ScrollableWidget.cpp +++ b/Widgets/private/widgets/ScrollableWidget.cpp @@ -7,9 +7,10 @@ using namespace tp; void ScrollableBarWidget::process(const EventHandler& events) { // all content is visible no need to process anything if (mSizeFactor >= 1) { + if (mScrolling) freeFocus(); + mScrolling = false; mPosFactor = mSizeFactor / 2.f; - freeFocus(); return; } diff --git a/Widgets/public/Widget.hpp b/Widgets/public/Widget.hpp index c44d718..77c1c50 100644 --- a/Widgets/public/Widget.hpp +++ b/Widgets/public/Widget.hpp @@ -31,6 +31,8 @@ namespace tp { using DFSAction = std::function; + protected: + enum Flags : int1 { ENABLED = 0, NEEDS_UPDATE, diff --git a/Widgets/public/mangers/DebugManager.hpp b/Widgets/public/mangers/DebugManager.hpp index 0b4ce92..6c84ef8 100644 --- a/Widgets/public/mangers/DebugManager.hpp +++ b/Widgets/public/mangers/DebugManager.hpp @@ -87,7 +87,7 @@ namespace tp { bool mDebug = false; bool mDebugStopProcessing = false; bool mDebugRedrawAlways = false; - bool mDetailed = false; + bool mDetailed = true; std::set mProcBreakpoints; std::set mLayBreakpoints; diff --git a/Widgets/public/mangers/UpdateManager.hpp b/Widgets/public/mangers/UpdateManager.hpp index 33c5ecf..2a8f661 100644 --- a/Widgets/public/mangers/UpdateManager.hpp +++ b/Widgets/public/mangers/UpdateManager.hpp @@ -25,20 +25,26 @@ namespace tp { void clean(); void updateTreeToProcess(Widget* root); - void handleFocusChanges(Widget* root, EventHandler& events); + void handleFocusChanges(Widget* active, Widget* prevActive); + Widget* findFocusWidget(Widget* root, EventHandler& events); - void findFocusWidget(Widget* iter, Widget** focus, const Vec2F& pointer); + void findMouseFocusWidget(Widget* iter, Widget** focus, const Vec2F& pointer); static void getWidgetPath(Widget* widget, std::vector& out); void processActiveTree(Widget* iter, EventHandler& events, Vec2F pos); void processFocusItems(EventHandler& events); + Widget* getFocusWidget() { return mInFocusWidget; } + + bool canRemoveWidget(Widget* widget) const; + void removeWidget(Widget* widget); + private: static void procWidget(Widget* widget, EventHandler& events, bool withEvents = false); private: std::map mTriggeredWidgets; Widget* mInFocusWidget = nullptr; - Widget* mFocusLockWidget = nullptr; + List mFocusLockStack; private: int mDebugWidgetsToProcess = 0;