diff --git a/src/WeatherEditor/EditorWindow.cpp b/src/WeatherEditor/EditorWindow.cpp index 9979154a3c..7598cd6f16 100644 --- a/src/WeatherEditor/EditorWindow.cpp +++ b/src/WeatherEditor/EditorWindow.cpp @@ -509,7 +509,7 @@ void EditorWindow::ShowObjectsWindow() if (Util::TableRowSelectable(label.c_str(), isOpen, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick)) { if (ImGui::IsMouseDoubleClicked(0)) { // Open or reuse the cell lighting widget - if (currentCellLightingWidget && currentCellLightingWidget->cell == cell) { + if (currentCellLightingWidget && currentCellLightingWidget->GetCell() == cell) { currentCellLightingWidget->SetOpen(true); } else { currentCellLightingWidget = std::make_unique(cell); @@ -522,7 +522,7 @@ void EditorWindow::ShowObjectsWindow() // Enter key to open if (isOpen && ImGui::IsKeyPressed(ImGuiKey_Enter)) { - if (currentCellLightingWidget && currentCellLightingWidget->cell == cell) { + if (currentCellLightingWidget && currentCellLightingWidget->GetCell() == cell) { currentCellLightingWidget->SetOpen(true); } } @@ -584,7 +584,7 @@ void EditorWindow::ShowObjectsWindow() if (currentCellLightingTemplate && m_selectedCategory == "Lighting Template") { for (int i = 0; i < sortedWidgets.size(); ++i) { auto* ltWidget = dynamic_cast(sortedWidgets[i]); - if (!ltWidget || ltWidget->lightingTemplate != currentCellLightingTemplate) + if (!ltWidget || ltWidget->GetLightingTemplate() != currentCellLightingTemplate) continue; if (!shouldShowWidget(sortedWidgets[i])) @@ -667,7 +667,7 @@ void EditorWindow::ShowObjectsWindow() // Skip current cell's lighting template if already shown if (currentCellLightingTemplate && m_selectedCategory == "Lighting Template") { auto* ltWidget = dynamic_cast(sortedWidgets[i]); - if (ltWidget && ltWidget->lightingTemplate == currentCellLightingTemplate) + if (ltWidget && ltWidget->GetLightingTemplate() == currentCellLightingTemplate) continue; } @@ -975,7 +975,7 @@ void EditorWindow::RenderUI() if (ImGui::MenuItem("Edit Current Cell Lighting")) { // Check if widget already exists bool found = false; - if (currentCellLightingWidget && currentCellLightingWidget->cell == player->parentCell) { + if (currentCellLightingWidget && currentCellLightingWidget->GetCell() == player->parentCell) { currentCellLightingWidget->SetOpen(true); found = true; } diff --git a/src/WeatherEditor/Weather/CellLightingWidget.cpp b/src/WeatherEditor/Weather/CellLightingWidget.cpp index cc13dc6f22..060bc53737 100644 --- a/src/WeatherEditor/Weather/CellLightingWidget.cpp +++ b/src/WeatherEditor/Weather/CellLightingWidget.cpp @@ -1,20 +1,65 @@ #include "CellLightingWidget.h" #include "../EditorWindow.h" #include "../WeatherUtils.h" +#include "Menu.h" + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( + CellLightingWidget::DALC, + xPlus, xMinus, + yPlus, yMinus, + zPlus, zMinus, + specular, + fresnelPower) + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( + CellLightingWidget::Inherit, + ambientColor, + directionalColor, + fogColor, + fogNear, + fogFar, + directionalRotation, + directionalFade, + clipDistance, + fogPower, + fogMax, + lightFadeDistances) + +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( + CellLightingWidget::Settings, + ambient, + directional, + fogColorNear, + fogColorFar, + fogNear, + fogFar, + fogPower, + fogClamp, + directionalFade, + clipDist, + lightFadeStart, + lightFadeEnd, + directionalXY, + directionalZ, + dalc, + inherit) void CellLightingWidget::DrawWidget() { - WeatherUtils::SetCurrentWidget(this); ImGui::SetNextWindowSizeConstraints(ImVec2(600, 0), ImVec2(FLT_MAX, FLT_MAX)); - if (ImGui::Begin(GetEditorID().c_str(), &open, ImGuiWindowFlags_NoSavedSettings | kStickyHeaderFlags)) { - DrawWidgetHeader("##CellLightingSearch", true, true); + if (!ImGui::Begin(GetEditorID().c_str(), &open, ImGuiWindowFlags_NoSavedSettings | kStickyHeaderFlags)) { + ImGui::End(); + return; } + WeatherUtils::SetCurrentWidget(this); + DrawWidgetHeader("##CellLightingSearch", true, true); if (!cell || !cell->IsInteriorCell()) { - ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.0f, 1.0f), "This cell is not an interior cell."); + auto& palette = Menu::GetSingleton()->GetTheme().StatusPalette; + ImGui::TextColored(palette.Warning, "This cell is not an interior cell."); ImGui::TextWrapped("Cell lighting properties only apply to interior cells."); } else if (!cell->GetLighting()) { - ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "No lighting data available for this cell."); + ImGui::TextColored(Menu::GetSingleton()->GetTheme().StatusPalette.Error, "No lighting data available for this cell."); } else { bool changed = false; @@ -61,21 +106,21 @@ void CellLightingWidget::DrawWidget() BeginScrollableContent("##DAmbientScroll"); ImGui::SeparatorText("Directional Ambient Lighting (DALC)"); - if (WeatherUtils::DrawColorEdit("X+ (Right)", settings.directionalXPlus)) + if (WeatherUtils::DrawColorEdit("X+ (Right)", settings.dalc.xPlus)) changed = true; - if (WeatherUtils::DrawColorEdit("X- (Left)", settings.directionalXMinus)) + if (WeatherUtils::DrawColorEdit("X- (Left)", settings.dalc.xMinus)) changed = true; - if (WeatherUtils::DrawColorEdit("Y+ (Front)", settings.directionalYPlus)) + if (WeatherUtils::DrawColorEdit("Y+ (Front)", settings.dalc.yPlus)) changed = true; - if (WeatherUtils::DrawColorEdit("Y- (Back)", settings.directionalYMinus)) + if (WeatherUtils::DrawColorEdit("Y- (Back)", settings.dalc.yMinus)) changed = true; - if (WeatherUtils::DrawColorEdit("Z+ (Up)", settings.directionalZPlus)) + if (WeatherUtils::DrawColorEdit("Z+ (Up)", settings.dalc.zPlus)) changed = true; - if (WeatherUtils::DrawColorEdit("Z- (Down)", settings.directionalZMinus)) + if (WeatherUtils::DrawColorEdit("Z- (Down)", settings.dalc.zMinus)) changed = true; - if (WeatherUtils::DrawColorEdit("Specular", settings.directionalSpecular)) + if (WeatherUtils::DrawColorEdit("Specular", settings.dalc.specular)) changed = true; - if (WeatherUtils::DrawSliderFloat("Fresnel Power", settings.fresnelPower, 0.0f, 10.0f)) + if (WeatherUtils::DrawSliderFloat("Fresnel Power", settings.dalc.fresnelPower, 0.0f, 10.0f)) changed = true; EndScrollableContent(); @@ -113,27 +158,27 @@ void CellLightingWidget::DrawWidget() ImGui::TextWrapped("These flags control which lighting properties are inherited from the cell's lighting template."); ImGui::Separator(); - if (ImGui::Checkbox("Inherit Ambient Color", &settings.inheritAmbientColor)) + if (ImGui::Checkbox("Inherit Ambient Color", &settings.inherit.ambientColor)) changed = true; - if (ImGui::Checkbox("Inherit Directional Color", &settings.inheritDirectionalColor)) + if (ImGui::Checkbox("Inherit Directional Color", &settings.inherit.directionalColor)) changed = true; - if (ImGui::Checkbox("Inherit Fog Color", &settings.inheritFogColor)) + if (ImGui::Checkbox("Inherit Fog Color", &settings.inherit.fogColor)) changed = true; - if (ImGui::Checkbox("Inherit Fog Near", &settings.inheritFogNear)) + if (ImGui::Checkbox("Inherit Fog Near", &settings.inherit.fogNear)) changed = true; - if (ImGui::Checkbox("Inherit Fog Far", &settings.inheritFogFar)) + if (ImGui::Checkbox("Inherit Fog Far", &settings.inherit.fogFar)) changed = true; - if (ImGui::Checkbox("Inherit Directional Rotation", &settings.inheritDirectionalRotation)) + if (ImGui::Checkbox("Inherit Directional Rotation", &settings.inherit.directionalRotation)) changed = true; - if (ImGui::Checkbox("Inherit Directional Fade", &settings.inheritDirectionalFade)) + if (ImGui::Checkbox("Inherit Directional Fade", &settings.inherit.directionalFade)) changed = true; - if (ImGui::Checkbox("Inherit Clip Distance", &settings.inheritClipDistance)) + if (ImGui::Checkbox("Inherit Clip Distance", &settings.inherit.clipDistance)) changed = true; - if (ImGui::Checkbox("Inherit Fog Power", &settings.inheritFogPower)) + if (ImGui::Checkbox("Inherit Fog Power", &settings.inherit.fogPower)) changed = true; - if (ImGui::Checkbox("Inherit Fog Max (Clamp)", &settings.inheritFogMax)) + if (ImGui::Checkbox("Inherit Fog Max (Clamp)", &settings.inherit.fogMax)) changed = true; - if (ImGui::Checkbox("Inherit Light Fade Distances", &settings.inheritLightFadeDistances)) + if (ImGui::Checkbox("Inherit Light Fade Distances", &settings.inherit.lightFadeDistances)) changed = true; EndScrollableContent(); @@ -146,7 +191,7 @@ void CellLightingWidget::DrawWidget() if (changed && EditorWindow::GetSingleton()->settings.autoApplyChanges) { ApplyChanges(); } - } + } // end interior cell else-branch ImGui::End(); } @@ -159,114 +204,16 @@ void CellLightingWidget::LoadSettings() if (!lighting) return; - // Try to load from JSON first + settings = vanillaSettings; if (!js.empty()) { - settings = vanillaSettings; try { - if (js.contains("ambient")) { - auto arr = js["ambient"]; - if (arr.is_array() && arr.size() == 3) { - settings.ambient = { arr[0], arr[1], arr[2] }; - } - } - if (js.contains("directional")) { - auto arr = js["directional"]; - if (arr.is_array() && arr.size() == 3) { - settings.directional = { arr[0], arr[1], arr[2] }; - } - } - if (js.contains("fogColorNear")) { - auto arr = js["fogColorNear"]; - if (arr.is_array() && arr.size() == 3) { - settings.fogColorNear = { arr[0], arr[1], arr[2] }; - } - } - if (js.contains("fogColorFar")) { - auto arr = js["fogColorFar"]; - if (arr.is_array() && arr.size() == 3) { - settings.fogColorFar = { arr[0], arr[1], arr[2] }; - } - } - if (js.contains("fogNear")) - settings.fogNear = js["fogNear"]; - if (js.contains("fogFar")) - settings.fogFar = js["fogFar"]; - if (js.contains("fogPower")) - settings.fogPower = js["fogPower"]; - if (js.contains("fogClamp")) - settings.fogClamp = js["fogClamp"]; - if (js.contains("directionalFade")) - settings.directionalFade = js["directionalFade"]; - if (js.contains("clipDist")) - settings.clipDist = js["clipDist"]; - if (js.contains("lightFadeStart")) - settings.lightFadeStart = js["lightFadeStart"]; - if (js.contains("lightFadeEnd")) - settings.lightFadeEnd = js["lightFadeEnd"]; - if (js.contains("directionalXY")) - settings.directionalXY = js["directionalXY"]; - if (js.contains("directionalZ")) - settings.directionalZ = js["directionalZ"]; - - if (js.contains("dalc")) { - auto& dalc = js["dalc"]; - if (dalc.contains("xPlus") && dalc["xPlus"].is_array() && dalc["xPlus"].size() == 3) { - settings.directionalXPlus = { dalc["xPlus"][0], dalc["xPlus"][1], dalc["xPlus"][2] }; - } - if (dalc.contains("xMinus") && dalc["xMinus"].is_array() && dalc["xMinus"].size() == 3) { - settings.directionalXMinus = { dalc["xMinus"][0], dalc["xMinus"][1], dalc["xMinus"][2] }; - } - if (dalc.contains("yPlus") && dalc["yPlus"].is_array() && dalc["yPlus"].size() == 3) { - settings.directionalYPlus = { dalc["yPlus"][0], dalc["yPlus"][1], dalc["yPlus"][2] }; - } - if (dalc.contains("yMinus") && dalc["yMinus"].is_array() && dalc["yMinus"].size() == 3) { - settings.directionalYMinus = { dalc["yMinus"][0], dalc["yMinus"][1], dalc["yMinus"][2] }; - } - if (dalc.contains("zPlus") && dalc["zPlus"].is_array() && dalc["zPlus"].size() == 3) { - settings.directionalZPlus = { dalc["zPlus"][0], dalc["zPlus"][1], dalc["zPlus"][2] }; - } - if (dalc.contains("zMinus") && dalc["zMinus"].is_array() && dalc["zMinus"].size() == 3) { - settings.directionalZMinus = { dalc["zMinus"][0], dalc["zMinus"][1], dalc["zMinus"][2] }; - } - if (dalc.contains("specular") && dalc["specular"].is_array() && dalc["specular"].size() == 3) { - settings.directionalSpecular = { dalc["specular"][0], dalc["specular"][1], dalc["specular"][2] }; - } - if (dalc.contains("fresnelPower")) - settings.fresnelPower = dalc["fresnelPower"]; - } - - if (js.contains("inherit")) { - auto& inherit = js["inherit"]; - if (inherit.contains("ambientColor")) - settings.inheritAmbientColor = inherit["ambientColor"]; - if (inherit.contains("directionalColor")) - settings.inheritDirectionalColor = inherit["directionalColor"]; - if (inherit.contains("fogColor")) - settings.inheritFogColor = inherit["fogColor"]; - if (inherit.contains("fogNear")) - settings.inheritFogNear = inherit["fogNear"]; - if (inherit.contains("fogFar")) - settings.inheritFogFar = inherit["fogFar"]; - if (inherit.contains("directionalRotation")) - settings.inheritDirectionalRotation = inherit["directionalRotation"]; - if (inherit.contains("directionalFade")) - settings.inheritDirectionalFade = inherit["directionalFade"]; - if (inherit.contains("clipDistance")) - settings.inheritClipDistance = inherit["clipDistance"]; - if (inherit.contains("fogPower")) - settings.inheritFogPower = inherit["fogPower"]; - if (inherit.contains("fogMax")) - settings.inheritFogMax = inherit["fogMax"]; - if (inherit.contains("lightFadeDistances")) - settings.inheritLightFadeDistances = inherit["lightFadeDistances"]; - } - + nlohmann::json merged = vanillaSettings; + merged.merge_patch(js); + settings = merged.get(); } catch (const std::exception& e) { logger::error("CellLighting {}: Failed to load from JSON: {}", GetEditorID(), e.what()); settings = vanillaSettings; } - } else { - settings = vanillaSettings; } originalSettings = settings; @@ -300,66 +247,32 @@ void CellLightingWidget::LoadFromGameSettings() settings.directionalZ = lighting->directionalZ; auto& dalc = lighting->directionalAmbientLightingColors; - ColorToFloat3(dalc.directional.x.max, settings.directionalXPlus); - ColorToFloat3(dalc.directional.x.min, settings.directionalXMinus); - ColorToFloat3(dalc.directional.y.max, settings.directionalYPlus); - ColorToFloat3(dalc.directional.y.min, settings.directionalYMinus); - ColorToFloat3(dalc.directional.z.max, settings.directionalZPlus); - ColorToFloat3(dalc.directional.z.min, settings.directionalZMinus); - ColorToFloat3(dalc.specular, settings.directionalSpecular); - settings.fresnelPower = dalc.fresnelPower; + ColorToFloat3(dalc.directional.x.max, settings.dalc.xPlus); + ColorToFloat3(dalc.directional.x.min, settings.dalc.xMinus); + ColorToFloat3(dalc.directional.y.max, settings.dalc.yPlus); + ColorToFloat3(dalc.directional.y.min, settings.dalc.yMinus); + ColorToFloat3(dalc.directional.z.max, settings.dalc.zPlus); + ColorToFloat3(dalc.directional.z.min, settings.dalc.zMinus); + ColorToFloat3(dalc.specular, settings.dalc.specular); + settings.dalc.fresnelPower = dalc.fresnelPower; auto flags = lighting->lightingTemplateInheritanceFlags; - settings.inheritAmbientColor = flags.any(RE::INTERIOR_DATA::Inherit::kAmbientColor); - settings.inheritDirectionalColor = flags.any(RE::INTERIOR_DATA::Inherit::kDirectionalColor); - settings.inheritFogColor = flags.any(RE::INTERIOR_DATA::Inherit::kFogColor); - settings.inheritFogNear = flags.any(RE::INTERIOR_DATA::Inherit::kFogNear); - settings.inheritFogFar = flags.any(RE::INTERIOR_DATA::Inherit::kFogFar); - settings.inheritDirectionalRotation = flags.any(RE::INTERIOR_DATA::Inherit::kDirectionalRotation); - settings.inheritDirectionalFade = flags.any(RE::INTERIOR_DATA::Inherit::kDirectionalFade); - settings.inheritClipDistance = flags.any(RE::INTERIOR_DATA::Inherit::kClipDistance); - settings.inheritFogPower = flags.any(RE::INTERIOR_DATA::Inherit::kFogPower); - settings.inheritFogMax = flags.any(RE::INTERIOR_DATA::Inherit::kFogMax); - settings.inheritLightFadeDistances = flags.any(RE::INTERIOR_DATA::Inherit::kLightFadeDistances); + settings.inherit.ambientColor = flags.any(RE::INTERIOR_DATA::Inherit::kAmbientColor); + settings.inherit.directionalColor = flags.any(RE::INTERIOR_DATA::Inherit::kDirectionalColor); + settings.inherit.fogColor = flags.any(RE::INTERIOR_DATA::Inherit::kFogColor); + settings.inherit.fogNear = flags.any(RE::INTERIOR_DATA::Inherit::kFogNear); + settings.inherit.fogFar = flags.any(RE::INTERIOR_DATA::Inherit::kFogFar); + settings.inherit.directionalRotation = flags.any(RE::INTERIOR_DATA::Inherit::kDirectionalRotation); + settings.inherit.directionalFade = flags.any(RE::INTERIOR_DATA::Inherit::kDirectionalFade); + settings.inherit.clipDistance = flags.any(RE::INTERIOR_DATA::Inherit::kClipDistance); + settings.inherit.fogPower = flags.any(RE::INTERIOR_DATA::Inherit::kFogPower); + settings.inherit.fogMax = flags.any(RE::INTERIOR_DATA::Inherit::kFogMax); + settings.inherit.lightFadeDistances = flags.any(RE::INTERIOR_DATA::Inherit::kLightFadeDistances); } void CellLightingWidget::SaveSettings() { - js["ambient"] = { settings.ambient.x, settings.ambient.y, settings.ambient.z }; - js["directional"] = { settings.directional.x, settings.directional.y, settings.directional.z }; - js["fogColorNear"] = { settings.fogColorNear.x, settings.fogColorNear.y, settings.fogColorNear.z }; - js["fogColorFar"] = { settings.fogColorFar.x, settings.fogColorFar.y, settings.fogColorFar.z }; - js["fogNear"] = settings.fogNear; - js["fogFar"] = settings.fogFar; - js["fogPower"] = settings.fogPower; - js["fogClamp"] = settings.fogClamp; - js["directionalFade"] = settings.directionalFade; - js["clipDist"] = settings.clipDist; - js["lightFadeStart"] = settings.lightFadeStart; - js["lightFadeEnd"] = settings.lightFadeEnd; - js["directionalXY"] = settings.directionalXY; - js["directionalZ"] = settings.directionalZ; - - js["dalc"]["xPlus"] = { settings.directionalXPlus.x, settings.directionalXPlus.y, settings.directionalXPlus.z }; - js["dalc"]["xMinus"] = { settings.directionalXMinus.x, settings.directionalXMinus.y, settings.directionalXMinus.z }; - js["dalc"]["yPlus"] = { settings.directionalYPlus.x, settings.directionalYPlus.y, settings.directionalYPlus.z }; - js["dalc"]["yMinus"] = { settings.directionalYMinus.x, settings.directionalYMinus.y, settings.directionalYMinus.z }; - js["dalc"]["zPlus"] = { settings.directionalZPlus.x, settings.directionalZPlus.y, settings.directionalZPlus.z }; - js["dalc"]["zMinus"] = { settings.directionalZMinus.x, settings.directionalZMinus.y, settings.directionalZMinus.z }; - js["dalc"]["specular"] = { settings.directionalSpecular.x, settings.directionalSpecular.y, settings.directionalSpecular.z }; - js["dalc"]["fresnelPower"] = settings.fresnelPower; - - js["inherit"]["ambientColor"] = settings.inheritAmbientColor; - js["inherit"]["directionalColor"] = settings.inheritDirectionalColor; - js["inherit"]["fogColor"] = settings.inheritFogColor; - js["inherit"]["fogNear"] = settings.inheritFogNear; - js["inherit"]["fogFar"] = settings.inheritFogFar; - js["inherit"]["directionalRotation"] = settings.inheritDirectionalRotation; - js["inherit"]["directionalFade"] = settings.inheritDirectionalFade; - js["inherit"]["clipDistance"] = settings.inheritClipDistance; - js["inherit"]["fogPower"] = settings.inheritFogPower; - js["inherit"]["fogMax"] = settings.inheritFogMax; - js["inherit"]["lightFadeDistances"] = settings.inheritLightFadeDistances; + js = settings; originalSettings = settings; } @@ -394,38 +307,38 @@ void CellLightingWidget::ApplyChanges() // Apply directional ambient lighting colors auto& dalc = lighting->directionalAmbientLightingColors; - Float3ToColor(settings.directionalXPlus, dalc.directional.x.max); - Float3ToColor(settings.directionalXMinus, dalc.directional.x.min); - Float3ToColor(settings.directionalYPlus, dalc.directional.y.max); - Float3ToColor(settings.directionalYMinus, dalc.directional.y.min); - Float3ToColor(settings.directionalZPlus, dalc.directional.z.max); - Float3ToColor(settings.directionalZMinus, dalc.directional.z.min); - Float3ToColor(settings.directionalSpecular, dalc.specular); - dalc.fresnelPower = settings.fresnelPower; + Float3ToColor(settings.dalc.xPlus, dalc.directional.x.max); + Float3ToColor(settings.dalc.xMinus, dalc.directional.x.min); + Float3ToColor(settings.dalc.yPlus, dalc.directional.y.max); + Float3ToColor(settings.dalc.yMinus, dalc.directional.y.min); + Float3ToColor(settings.dalc.zPlus, dalc.directional.z.max); + Float3ToColor(settings.dalc.zMinus, dalc.directional.z.min); + Float3ToColor(settings.dalc.specular, dalc.specular); + dalc.fresnelPower = settings.dalc.fresnelPower; // Apply inheritance flags lighting->lightingTemplateInheritanceFlags.reset(); - if (settings.inheritAmbientColor) + if (settings.inherit.ambientColor) lighting->lightingTemplateInheritanceFlags.set(RE::INTERIOR_DATA::Inherit::kAmbientColor); - if (settings.inheritDirectionalColor) + if (settings.inherit.directionalColor) lighting->lightingTemplateInheritanceFlags.set(RE::INTERIOR_DATA::Inherit::kDirectionalColor); - if (settings.inheritFogColor) + if (settings.inherit.fogColor) lighting->lightingTemplateInheritanceFlags.set(RE::INTERIOR_DATA::Inherit::kFogColor); - if (settings.inheritFogNear) + if (settings.inherit.fogNear) lighting->lightingTemplateInheritanceFlags.set(RE::INTERIOR_DATA::Inherit::kFogNear); - if (settings.inheritFogFar) + if (settings.inherit.fogFar) lighting->lightingTemplateInheritanceFlags.set(RE::INTERIOR_DATA::Inherit::kFogFar); - if (settings.inheritDirectionalRotation) + if (settings.inherit.directionalRotation) lighting->lightingTemplateInheritanceFlags.set(RE::INTERIOR_DATA::Inherit::kDirectionalRotation); - if (settings.inheritDirectionalFade) + if (settings.inherit.directionalFade) lighting->lightingTemplateInheritanceFlags.set(RE::INTERIOR_DATA::Inherit::kDirectionalFade); - if (settings.inheritClipDistance) + if (settings.inherit.clipDistance) lighting->lightingTemplateInheritanceFlags.set(RE::INTERIOR_DATA::Inherit::kClipDistance); - if (settings.inheritFogPower) + if (settings.inherit.fogPower) lighting->lightingTemplateInheritanceFlags.set(RE::INTERIOR_DATA::Inherit::kFogPower); - if (settings.inheritFogMax) + if (settings.inherit.fogMax) lighting->lightingTemplateInheritanceFlags.set(RE::INTERIOR_DATA::Inherit::kFogMax); - if (settings.inheritLightFadeDistances) + if (settings.inherit.lightFadeDistances) lighting->lightingTemplateInheritanceFlags.set(RE::INTERIOR_DATA::Inherit::kLightFadeDistances); } diff --git a/src/WeatherEditor/Weather/CellLightingWidget.h b/src/WeatherEditor/Weather/CellLightingWidget.h index 29e352a21d..f62f9a33c5 100644 --- a/src/WeatherEditor/Weather/CellLightingWidget.h +++ b/src/WeatherEditor/Weather/CellLightingWidget.h @@ -25,14 +25,40 @@ class CellLightingWidget : public Widget void RevertChanges() override; bool HasUnsavedChanges() const override; - RE::TESObjectCELL* cell = nullptr; + [[nodiscard]] RE::TESObjectCELL* GetCell() const { return cell; } -private: - void LoadFromGameSettings(); + // Public types required by NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT macro + struct DALC + { + float3 xPlus = { 1.0f, 1.0f, 1.0f }; + float3 xMinus = { 1.0f, 1.0f, 1.0f }; + float3 yPlus = { 1.0f, 1.0f, 1.0f }; + float3 yMinus = { 1.0f, 1.0f, 1.0f }; + float3 zPlus = { 1.0f, 1.0f, 1.0f }; + float3 zMinus = { 1.0f, 1.0f, 1.0f }; + float3 specular = { 1.0f, 1.0f, 1.0f }; + float fresnelPower = 1.0f; + bool operator==(const DALC&) const = default; + }; + + struct Inherit + { + bool ambientColor = false; + bool directionalColor = false; + bool fogColor = false; + bool fogNear = false; + bool fogFar = false; + bool directionalRotation = false; + bool directionalFade = false; + bool clipDistance = false; + bool fogPower = false; + bool fogMax = false; + bool lightFadeDistances = false; + bool operator==(const Inherit&) const = default; + }; struct Settings { - // INTERIOR_DATA properties float3 ambient = { 1.0f, 1.0f, 1.0f }; float3 directional = { 1.0f, 1.0f, 1.0f }; float3 fogColorNear = { 1.0f, 1.0f, 1.0f }; @@ -47,32 +73,16 @@ class CellLightingWidget : public Widget float lightFadeEnd = 5000.0f; uint32_t directionalXY = 0; uint32_t directionalZ = 0; - - // Directional ambient lighting colors (DALC equivalent) - float3 directionalXPlus = { 1.0f, 1.0f, 1.0f }; - float3 directionalXMinus = { 1.0f, 1.0f, 1.0f }; - float3 directionalYPlus = { 1.0f, 1.0f, 1.0f }; - float3 directionalYMinus = { 1.0f, 1.0f, 1.0f }; - float3 directionalZPlus = { 1.0f, 1.0f, 1.0f }; - float3 directionalZMinus = { 1.0f, 1.0f, 1.0f }; - float3 directionalSpecular = { 1.0f, 1.0f, 1.0f }; - float fresnelPower = 1.0f; - - // Inheritance flags - bool inheritAmbientColor = false; - bool inheritDirectionalColor = false; - bool inheritFogColor = false; - bool inheritFogNear = false; - bool inheritFogFar = false; - bool inheritDirectionalRotation = false; - bool inheritDirectionalFade = false; - bool inheritClipDistance = false; - bool inheritFogPower = false; - bool inheritFogMax = false; - bool inheritLightFadeDistances = false; + DALC dalc; + Inherit inherit; bool operator==(const Settings&) const = default; }; +private: + void LoadFromGameSettings(); + + RE::TESObjectCELL* cell = nullptr; + Settings settings; Settings vanillaSettings; Settings originalSettings; diff --git a/src/WeatherEditor/Weather/ImageSpaceWidget.cpp b/src/WeatherEditor/Weather/ImageSpaceWidget.cpp index 9521e4cee2..dd02231cfd 100644 --- a/src/WeatherEditor/Weather/ImageSpaceWidget.cpp +++ b/src/WeatherEditor/Weather/ImageSpaceWidget.cpp @@ -2,9 +2,6 @@ #include "../EditorWindow.h" #include "../WeatherUtils.h" -#include "Util.h" - -#include NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( ImageSpaceWidget::Settings, @@ -24,20 +21,19 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( dofDistance, dofRange) -ImageSpaceWidget::~ImageSpaceWidget() -{ -} - void ImageSpaceWidget::DrawWidget() { - WeatherUtils::SetCurrentWidget(this); auto editorWindow = EditorWindow::GetSingleton(); ImGui::SetNextWindowSizeConstraints(ImVec2(600, 0), ImVec2(FLT_MAX, FLT_MAX)); - if (ImGui::Begin(GetEditorID().c_str(), &open, ImGuiWindowFlags_NoSavedSettings | kStickyHeaderFlags)) { - // Draw header with search and Save/Load/Delete buttons - DrawWidgetHeader("##ImageSpaceSearch", false, true); + if (!ImGui::Begin(GetEditorID().c_str(), &open, ImGuiWindowFlags_NoSavedSettings | kStickyHeaderFlags)) { + ImGui::End(); + return; } + WeatherUtils::SetCurrentWidget(this); + // Draw header with search and Save/Load/Delete buttons + DrawWidgetHeader("##ImageSpaceSearch", false, true); + BeginScrollableContent("##ISScroll"); { // Draw all settings in a unified table @@ -91,6 +87,11 @@ void ImageSpaceWidget::DrawWidget() void ImageSpaceWidget::LoadSettings() { + if (!imageSpace) { + logger::warn("ImageSpaceWidget {}: imageSpace is null, skipping LoadSettings", GetEditorID()); + return; + } + try { if (!js.empty() && js.contains("Settings") && js["Settings"].is_object()) { settings = js["Settings"]; @@ -111,40 +112,7 @@ void ImageSpaceWidget::SaveSettings() originalSettings = settings; } -void ImageSpaceWidget::SetImageSpaceValues() -{ - if (!imageSpace) - return; - - auto& data = imageSpace->data; - - // HDR - data.hdr.eyeAdaptSpeed = settings.hdrEyeAdaptSpeed; - data.hdr.bloomBlurRadius = settings.hdrBloomBlurRadius; - data.hdr.bloomThreshold = settings.hdrBloomThreshold; - data.hdr.bloomScale = settings.hdrBloomScale; - data.hdr.white = settings.hdrWhite; - data.hdr.sunlightScale = settings.hdrSunlightScale; - data.hdr.skyScale = settings.hdrSkyScale; - - // Cinematic - data.cinematic.saturation = settings.cinematicSaturation; - data.cinematic.brightness = settings.cinematicBrightness; - data.cinematic.contrast = settings.cinematicContrast; - - // Tint - data.tint.color.red = settings.tintColor.x; - data.tint.color.green = settings.tintColor.y; - data.tint.color.blue = settings.tintColor.z; - data.tint.amount = settings.tintAmount; - - // Depth of Field - data.depthOfField.strength = settings.dofStrength; - data.depthOfField.distance = settings.dofDistance; - data.depthOfField.range = settings.dofRange; -} - -void ImageSpaceWidget::LoadImageSpaceValues() +void ImageSpaceWidget::LoadFromGameSettings() { if (!imageSpace) return; @@ -177,14 +145,42 @@ void ImageSpaceWidget::LoadImageSpaceValues() settings.dofRange = data.depthOfField.range; } -void ImageSpaceWidget::LoadFromGameSettings() -{ - LoadImageSpaceValues(); -} - void ImageSpaceWidget::ApplyChanges() { - SetImageSpaceValues(); + if (!imageSpace) + return; + + auto& data = imageSpace->data; + + // Clamp/sanitize all fields; reject non-finite values by substituting the low bound + auto safeClamp = [](float v, float lo, float hi) { + return std::isfinite(v) ? std::clamp(v, lo, hi) : lo; + }; + + // HDR + data.hdr.eyeAdaptSpeed = safeClamp(settings.hdrEyeAdaptSpeed, 0.0f, 10.0f); + data.hdr.bloomBlurRadius = safeClamp(settings.hdrBloomBlurRadius, 0.0f, 10.0f); + data.hdr.bloomThreshold = safeClamp(settings.hdrBloomThreshold, 0.0f, 10.0f); + data.hdr.bloomScale = safeClamp(settings.hdrBloomScale, 0.0f, 10.0f); + data.hdr.white = safeClamp(settings.hdrWhite, 0.0f, 10.0f); + data.hdr.sunlightScale = safeClamp(settings.hdrSunlightScale, 0.0f, 50.0f); + data.hdr.skyScale = safeClamp(settings.hdrSkyScale, 0.0f, 10.0f); + + // Cinematic + data.cinematic.saturation = safeClamp(settings.cinematicSaturation, 0.0f, 2.0f); + data.cinematic.brightness = safeClamp(settings.cinematicBrightness, 0.0f, 2.0f); + data.cinematic.contrast = safeClamp(settings.cinematicContrast, 0.0f, 2.0f); + + // Tint + data.tint.color.red = safeClamp(settings.tintColor.x, 0.0f, 1.0f); + data.tint.color.green = safeClamp(settings.tintColor.y, 0.0f, 1.0f); + data.tint.color.blue = safeClamp(settings.tintColor.z, 0.0f, 1.0f); + data.tint.amount = safeClamp(settings.tintAmount, 0.0f, 1.0f); + + // Depth of Field + data.depthOfField.strength = safeClamp(settings.dofStrength, 0.0f, 10.0f); + data.depthOfField.distance = safeClamp(settings.dofDistance, 0.0f, 10000.0f); + data.depthOfField.range = safeClamp(settings.dofRange, 0.0f, 10000.0f); } void ImageSpaceWidget::RevertChanges() diff --git a/src/WeatherEditor/Weather/ImageSpaceWidget.h b/src/WeatherEditor/Weather/ImageSpaceWidget.h index 18ed36cda2..f340efedeb 100644 --- a/src/WeatherEditor/Weather/ImageSpaceWidget.h +++ b/src/WeatherEditor/Weather/ImageSpaceWidget.h @@ -5,21 +5,29 @@ class ImageSpaceWidget : public Widget { public: - RE::TESImageSpace* imageSpace = nullptr; - - ImageSpaceWidget(RE::TESImageSpace* a_imageSpace) + ImageSpaceWidget(RE::TESImageSpace* a_imageSpace) : + imageSpace(a_imageSpace) { if (!a_imageSpace) { logger::error("ImageSpaceWidget created with null pointer"); return; } form = a_imageSpace; - imageSpace = a_imageSpace; LoadFromGameSettings(); vanillaSettings = settings; originalSettings = settings; } + ~ImageSpaceWidget() override = default; + + void DrawWidget() override; + void LoadSettings() override; + void SaveSettings() override; + void ApplyChanges() override; + void RevertChanges() override; + bool HasUnsavedChanges() const override; + + // Public type required by NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT macro struct Settings { // HDR Settings @@ -47,20 +55,12 @@ class ImageSpaceWidget : public Widget bool operator==(const Settings&) const = default; }; +private: + void LoadFromGameSettings(); + + RE::TESImageSpace* imageSpace = nullptr; + Settings settings; Settings vanillaSettings; Settings originalSettings; - - ~ImageSpaceWidget(); - - virtual void DrawWidget() override; - virtual void LoadSettings() override; - virtual void SaveSettings() override; - virtual bool HasUnsavedChanges() const override; - - void SetImageSpaceValues(); - void LoadImageSpaceValues(); - void LoadFromGameSettings(); - void ApplyChanges() override; - void RevertChanges() override; }; diff --git a/src/WeatherEditor/Weather/LensFlareWidget.cpp b/src/WeatherEditor/Weather/LensFlareWidget.cpp index 924c1164db..f0d0e0f7e4 100644 --- a/src/WeatherEditor/Weather/LensFlareWidget.cpp +++ b/src/WeatherEditor/Weather/LensFlareWidget.cpp @@ -2,27 +2,34 @@ #include "../EditorWindow.h" #include "../WeatherUtils.h" +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( + LensFlareWidget::Settings, + fadeDistRadiusScale, + colorInfluence) + void LensFlareWidget::DrawWidget() { ImGui::SetNextWindowSizeConstraints(ImVec2(600, 0), ImVec2(FLT_MAX, FLT_MAX)); - if (ImGui::Begin(GetEditorID().c_str(), &open, ImGuiWindowFlags_NoSavedSettings | kStickyHeaderFlags)) { - DrawWidgetHeader("##LensFlareSearch", true, true); + if (!ImGui::Begin(GetEditorID().c_str(), &open, ImGuiWindowFlags_NoSavedSettings | kStickyHeaderFlags)) { + ImGui::End(); + return; } + WeatherUtils::SetCurrentWidget(this); + DrawWidgetHeader("##LensFlareSearch", true, true); + BeginScrollableContent("##LFScroll"); - { - bool changed = false; + bool changed = false; - ImGui::SeparatorText("Fade Distance"); - if (ImGui::SliderFloat("Fade Dist Radius Scale", &settings.fadeDistRadiusScale, 0.0f, 10.0f)) - changed = true; + ImGui::SeparatorText("Fade Distance"); + if (WeatherUtils::DrawSliderFloat("Fade Dist Radius Scale", settings.fadeDistRadiusScale, 0.0f, 10.0f)) + changed = true; - ImGui::SeparatorText("Color"); - if (ImGui::SliderFloat("Color Influence", &settings.colorInfluence, 0.0f, 1.0f)) - changed = true; + ImGui::SeparatorText("Color"); + if (WeatherUtils::DrawSliderFloat("Color Influence", settings.colorInfluence, 0.0f, 1.0f)) + changed = true; - if (changed && EditorWindow::GetSingleton()->settings.autoApplyChanges) { - ApplyChanges(); - } + if (changed && EditorWindow::GetSingleton()->settings.autoApplyChanges) { + ApplyChanges(); } EndScrollableContent(); ImGui::End(); @@ -33,20 +40,22 @@ void LensFlareWidget::LoadSettings() if (!lensFlare) return; + settings = vanillaSettings; if (!js.empty()) { - settings = vanillaSettings; try { - if (js.contains("fadeDistRadiusScale")) - settings.fadeDistRadiusScale = js["fadeDistRadiusScale"]; - if (js.contains("colorInfluence")) - settings.colorInfluence = js["colorInfluence"]; + nlohmann::json merged = vanillaSettings; + merged.merge_patch(js); + settings = merged.get(); } catch (const std::exception& e) { logger::error("LensFlare {}: Failed to load from JSON: {}", GetEditorID(), e.what()); settings = vanillaSettings; } - } else { - settings = vanillaSettings; } + + // Validate and clamp fields to safe ranges + settings.fadeDistRadiusScale = std::clamp(settings.fadeDistRadiusScale, 0.0f, 10.0f); + settings.colorInfluence = std::clamp(settings.colorInfluence, 0.0f, 1.0f); + originalSettings = settings; ApplyChanges(); } @@ -61,8 +70,7 @@ void LensFlareWidget::LoadFromGameSettings() void LensFlareWidget::SaveSettings() { - js["fadeDistRadiusScale"] = settings.fadeDistRadiusScale; - js["colorInfluence"] = settings.colorInfluence; + js = settings; originalSettings = settings; } diff --git a/src/WeatherEditor/Weather/LensFlareWidget.h b/src/WeatherEditor/Weather/LensFlareWidget.h index 03f3d14083..9237d9a1d2 100644 --- a/src/WeatherEditor/Weather/LensFlareWidget.h +++ b/src/WeatherEditor/Weather/LensFlareWidget.h @@ -25,11 +25,7 @@ class LensFlareWidget : public Widget void RevertChanges() override; bool HasUnsavedChanges() const override; - RE::BGSLensFlare* lensFlare = nullptr; - -private: - void LoadFromGameSettings(); - + // Public type required by NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT macro struct Settings { float fadeDistRadiusScale = 1.0f; @@ -37,6 +33,11 @@ class LensFlareWidget : public Widget bool operator==(const Settings&) const = default; }; +private: + void LoadFromGameSettings(); + + RE::BGSLensFlare* lensFlare = nullptr; + Settings settings; Settings vanillaSettings; Settings originalSettings; diff --git a/src/WeatherEditor/Weather/LightingTemplateWidget.cpp b/src/WeatherEditor/Weather/LightingTemplateWidget.cpp index d75a2c18a3..1aa959274b 100644 --- a/src/WeatherEditor/Weather/LightingTemplateWidget.cpp +++ b/src/WeatherEditor/Weather/LightingTemplateWidget.cpp @@ -5,7 +5,7 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(LightingTemplateWidget::DirectionalColor, max, min) NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(LightingTemplateWidget::DALC, specular, fresnelPower, directional) -NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(LightingTemplateWidget::Settings, +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(LightingTemplateWidget::Settings, ambient, directional, fogColorNear, @@ -22,217 +22,166 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(LightingTemplateWidget::Settings, lightFadeEnd, dalc) -LightingTemplateWidget::~LightingTemplateWidget() -{ -} - void LightingTemplateWidget::DrawWidget() { - WeatherUtils::SetCurrentWidget(this); ImGui::SetNextWindowSizeConstraints(ImVec2(600, 0), ImVec2(FLT_MAX, FLT_MAX)); - if (ImGui::Begin(GetEditorID().c_str(), &open, ImGuiWindowFlags_NoSavedSettings | kStickyHeaderFlags)) { - // Draw header with search and Save/Load/Delete buttons - DrawWidgetHeader("##LightingTemplateSearch", false, true); + if (!ImGui::Begin(GetEditorID().c_str(), &open, ImGuiWindowFlags_NoSavedSettings | kStickyHeaderFlags)) { + ImGui::End(); + return; } - if (ImGui::BeginTabBar("LightingTemplateSettingsTabs", ImGuiTabBarFlags_None)) { + WeatherUtils::SetCurrentWidget(this); + DrawWidgetHeader("##LightingTemplateSearch", false, true); + + bool changed = false; + + if (ImGui::BeginTabBar("LightingTemplateSettingsTabs")) { if (ImGui::BeginTabItem("Basic")) { BeginScrollableContent("##BasicScroll"); - DrawBasicSettings(); + changed |= DrawBasicSettings(); EndScrollableContent(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Fog")) { BeginScrollableContent("##FogScroll"); - DrawFogSettings(); + changed |= DrawFogSettings(); EndScrollableContent(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("DALC")) { BeginScrollableContent("##DALCScroll"); - DrawDALCSettings(); + changed |= DrawDALCSettings(); EndScrollableContent(); ImGui::EndTabItem(); } ImGui::EndTabBar(); } + + if (changed && EditorWindow::GetSingleton()->settings.autoApplyChanges) { + ApplyChanges(); + } ImGui::End(); } -void LightingTemplateWidget::DrawBasicSettings() +bool LightingTemplateWidget::DrawBasicSettings() { bool changed = false; - if (ImGui::CollapsingHeader("Ambient & Directional", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Spacing(); - if (MatchesSearch("Ambient Color") && WeatherUtils::DrawColorEdit("Ambient Color", settings.ambient)) - changed = true; - if (MatchesSearch("Ambient Color")) - ImGui::Spacing(); - if (MatchesSearch("Directional Color") && WeatherUtils::DrawColorEdit("Directional Color", settings.directional)) - changed = true; - if (MatchesSearch("Directional Color")) - ImGui::Spacing(); - } + ImGui::SeparatorText("Ambient & Directional"); + if (WeatherUtils::DrawColorEdit("Ambient Color", settings.ambient)) + changed = true; + if (WeatherUtils::DrawColorEdit("Directional Color", settings.directional)) + changed = true; - if (ImGui::CollapsingHeader("Directional Settings", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Spacing(); - if (MatchesSearch("Directional XY") && WeatherUtils::DrawSliderFloat("Directional XY", settings.directionalXY)) - changed = true; - if (MatchesSearch("Directional XY")) - ImGui::Spacing(); - if (MatchesSearch("Directional Z") && WeatherUtils::DrawSliderFloat("Directional Z", settings.directionalZ)) - changed = true; - if (MatchesSearch("Directional Z")) - ImGui::Spacing(); - if (MatchesSearch("Directional Fade") && WeatherUtils::DrawSliderFloat("Directional Fade", settings.directionalFade)) - changed = true; - if (MatchesSearch("Directional Fade")) - ImGui::Spacing(); - } + ImGui::SeparatorText("Directional Settings"); + if (WeatherUtils::DrawSliderFloat("Directional XY", settings.directionalXY)) + changed = true; + if (WeatherUtils::DrawSliderFloat("Directional Z", settings.directionalZ)) + changed = true; + if (WeatherUtils::DrawSliderFloat("Directional Fade", settings.directionalFade)) + changed = true; - if (ImGui::CollapsingHeader("Light Fade", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Spacing(); - if (MatchesSearch("Light Fade Start") && WeatherUtils::DrawSliderFloat("Light Fade Start", settings.lightFadeStart)) - changed = true; - if (MatchesSearch("Light Fade Start")) - ImGui::Spacing(); - if (MatchesSearch("Light Fade End") && WeatherUtils::DrawSliderFloat("Light Fade End", settings.lightFadeEnd)) - changed = true; - if (MatchesSearch("Light Fade End")) - ImGui::Spacing(); - } + ImGui::SeparatorText("Light Fade"); + if (WeatherUtils::DrawSliderFloat("Light Fade Start", settings.lightFadeStart)) + changed = true; + if (WeatherUtils::DrawSliderFloat("Light Fade End", settings.lightFadeEnd)) + changed = true; - if (ImGui::CollapsingHeader("Other", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Spacing(); - if (MatchesSearch("Clip Distance") && WeatherUtils::DrawSliderFloat("Clip Distance", settings.clipDist)) - changed = true; - if (MatchesSearch("Clip Distance")) - ImGui::Spacing(); - } + ImGui::SeparatorText("Other"); + if (WeatherUtils::DrawSliderFloat("Clip Distance", settings.clipDist)) + changed = true; - if (changed && EditorWindow::GetSingleton()->settings.autoApplyChanges) { - ApplyChanges(); - } + return changed; } -void LightingTemplateWidget::DrawFogSettings() +bool LightingTemplateWidget::DrawFogSettings() { bool changed = false; - ImGui::Spacing(); - if (MatchesSearch("Fog Color Near") && WeatherUtils::DrawColorEdit("Fog Color Near", settings.fogColorNear)) + ImGui::SeparatorText("Fog Colors"); + if (WeatherUtils::DrawColorEdit("Fog Color Near", settings.fogColorNear)) changed = true; - if (MatchesSearch("Fog Color Near")) - ImGui::Spacing(); - if (MatchesSearch("Fog Color Far") && WeatherUtils::DrawColorEdit("Fog Color Far", settings.fogColorFar)) + if (WeatherUtils::DrawColorEdit("Fog Color Far", settings.fogColorFar)) changed = true; - if (MatchesSearch("Fog Color Far")) - ImGui::Spacing(); - ImGui::Spacing(); - if (MatchesSearch("Fog Near") && WeatherUtils::DrawSliderFloat("Fog Near", settings.fogNear)) + ImGui::SeparatorText("Fog Distance"); + if (WeatherUtils::DrawSliderFloat("Fog Near", settings.fogNear)) changed = true; - if (MatchesSearch("Fog Near")) - ImGui::Spacing(); - if (MatchesSearch("Fog Far") && WeatherUtils::DrawSliderFloat("Fog Far", settings.fogFar)) + if (WeatherUtils::DrawSliderFloat("Fog Far", settings.fogFar)) changed = true; - if (MatchesSearch("Fog Far")) - ImGui::Spacing(); - ImGui::Spacing(); - if (MatchesSearch("Fog Power") && WeatherUtils::DrawSliderFloat("Fog Power", settings.fogPower)) + ImGui::SeparatorText("Fog Properties"); + if (WeatherUtils::DrawSliderFloat("Fog Power", settings.fogPower)) changed = true; - if (MatchesSearch("Fog Power")) - ImGui::Spacing(); - if (MatchesSearch("Fog Clamp") && WeatherUtils::DrawSliderFloat("Fog Clamp", settings.fogClamp)) + if (WeatherUtils::DrawSliderFloat("Fog Clamp", settings.fogClamp)) changed = true; - if (MatchesSearch("Fog Clamp")) - ImGui::Spacing(); - if (changed && EditorWindow::GetSingleton()->settings.autoApplyChanges) { - ApplyChanges(); - } + return changed; } -void LightingTemplateWidget::DrawDALCSettings() +bool LightingTemplateWidget::DrawDALCSettings() { bool changed = false; - if (ImGui::CollapsingHeader("Basic DALC", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Spacing(); - if (WeatherUtils::DrawColorEdit("Specular", settings.dalc.specular)) - changed = true; - ImGui::Spacing(); - if (WeatherUtils::DrawSliderFloat("Fresnel Power", settings.dalc.fresnelPower)) + ImGui::SeparatorText("Basic DALC"); + if (WeatherUtils::DrawColorEdit("Specular", settings.dalc.specular)) + changed = true; + if (WeatherUtils::DrawSliderFloat("Fresnel Power", settings.dalc.fresnelPower)) + changed = true; + + ImGui::SeparatorText("Directional Colors (Time of Day)"); + if (TOD::BeginTODTable("DALCDirectionalTable")) { + TOD::RenderTODHeader(); + TOD::DrawTODSeparator(); + + // Prepare arrays for TOD rendering (map X,Y,Z to Sunrise,Day,Sunset,Night) + float3 maxColors[4]; + float3 minColors[4]; + + // Map X (index 0) to Sunrise and Day + maxColors[TOD::Sunrise] = settings.dalc.directional[0].max; + maxColors[TOD::Day] = settings.dalc.directional[0].max; + minColors[TOD::Sunrise] = settings.dalc.directional[0].min; + minColors[TOD::Day] = settings.dalc.directional[0].min; + + // Map Y (index 1) to Sunset + maxColors[TOD::Sunset] = settings.dalc.directional[1].max; + minColors[TOD::Sunset] = settings.dalc.directional[1].min; + + // Map Z (index 2) to Night + maxColors[TOD::Night] = settings.dalc.directional[2].max; + minColors[TOD::Night] = settings.dalc.directional[2].min; + + if (TOD::DrawTODColorRow("Positive Direction (+)", maxColors)) { + // Sunrise and Day both map to directional[0] - detect which column was actually edited + bool sunriseEdited = !(maxColors[TOD::Sunrise] == settings.dalc.directional[0].max); + settings.dalc.directional[0].max = sunriseEdited ? maxColors[TOD::Sunrise] : maxColors[TOD::Day]; + settings.dalc.directional[1].max = maxColors[TOD::Sunset]; + settings.dalc.directional[2].max = maxColors[TOD::Night]; changed = true; - ImGui::Spacing(); - } + } - if (ImGui::CollapsingHeader("Directional Colors (Time of Day)", ImGuiTreeNodeFlags_DefaultOpen)) { - // Note: The game engine uses X, Y, Z to represent time periods - // We map them to: X=Sunrise/Day, Y=Sunset, Z=Night for TOD display - - if (TOD::BeginTODTable("DALCDirectionalTable")) { - TOD::RenderTODHeader(); - TOD::DrawTODSeparator(); - - // Prepare arrays for TOD rendering (map X,Y,Z to Sunrise,Day,Sunset,Night) - float3 maxColors[4]; - float3 minColors[4]; - - // Map X (index 0) to Sunrise and Day - maxColors[TOD::Sunrise] = settings.dalc.directional[0].max; - maxColors[TOD::Day] = settings.dalc.directional[0].max; - minColors[TOD::Sunrise] = settings.dalc.directional[0].min; - minColors[TOD::Day] = settings.dalc.directional[0].min; - - // Map Y (index 1) to Sunset - maxColors[TOD::Sunset] = settings.dalc.directional[1].max; - minColors[TOD::Sunset] = settings.dalc.directional[1].min; - - // Map Z (index 2) to Night - maxColors[TOD::Night] = settings.dalc.directional[2].max; - minColors[TOD::Night] = settings.dalc.directional[2].min; - - if (TOD::DrawTODColorRow("Positive Direction (+)", maxColors)) { - settings.dalc.directional[0].max = maxColors[TOD::Sunrise]; // X from Sunrise - settings.dalc.directional[1].max = maxColors[TOD::Sunset]; // Y from Sunset - settings.dalc.directional[2].max = maxColors[TOD::Night]; // Z from Night - changed = true; - } - - if (TOD::DrawTODColorRow("Negative Direction (-)", minColors)) { - settings.dalc.directional[0].min = minColors[TOD::Sunrise]; // X from Sunrise - settings.dalc.directional[1].min = minColors[TOD::Sunset]; // Y from Sunset - settings.dalc.directional[2].min = minColors[TOD::Night]; // Z from Night - changed = true; - } - - TOD::EndTODTable(); + if (TOD::DrawTODColorRow("Negative Direction (-)", minColors)) { + bool sunriseEdited = !(minColors[TOD::Sunrise] == settings.dalc.directional[0].min); + settings.dalc.directional[0].min = sunriseEdited ? minColors[TOD::Sunrise] : minColors[TOD::Day]; + settings.dalc.directional[1].min = minColors[TOD::Sunset]; + settings.dalc.directional[2].min = minColors[TOD::Night]; + changed = true; } - } - if (changed && EditorWindow::GetSingleton()->settings.autoApplyChanges) { - ApplyChanges(); + TOD::EndTODTable(); } -} -void LightingTemplateWidget::ApplyChanges() -{ - SetLightingTemplateValues(); + return changed; } -void LightingTemplateWidget::RevertChanges() +void LightingTemplateWidget::ApplyChanges() { - settings = vanillaSettings; - ApplyChanges(); -} + if (!lightingTemplate) + return; -void LightingTemplateWidget::SetLightingTemplateValues() -{ auto& data = lightingTemplate->data; auto& dalc = lightingTemplate->directionalAmbientLightingColors; @@ -243,8 +192,8 @@ void LightingTemplateWidget::SetLightingTemplateValues() data.fogNear = settings.fogNear; data.fogFar = settings.fogFar; - data.directionalXY = static_cast(settings.directionalXY); - data.directionalZ = static_cast(settings.directionalZ); + data.directionalXY = static_cast(std::clamp(std::round(settings.directionalXY), 0.0f, static_cast(std::numeric_limits::max()))); + data.directionalZ = static_cast(std::clamp(std::round(settings.directionalZ), 0.0f, static_cast(std::numeric_limits::max()))); data.directionalFade = settings.directionalFade; data.clipDist = settings.clipDist; data.fogPower = settings.fogPower; @@ -257,15 +206,19 @@ void LightingTemplateWidget::SetLightingTemplateValues() Float3ToColor(settings.dalc.directional[0].max, dalc.directional.x.max); Float3ToColor(settings.dalc.directional[0].min, dalc.directional.x.min); - Float3ToColor(settings.dalc.directional[1].max, dalc.directional.y.max); Float3ToColor(settings.dalc.directional[1].min, dalc.directional.y.min); - Float3ToColor(settings.dalc.directional[2].max, dalc.directional.z.max); Float3ToColor(settings.dalc.directional[2].min, dalc.directional.z.min); } -void LightingTemplateWidget::LoadLightingTemplateValues() +void LightingTemplateWidget::RevertChanges() +{ + settings = originalSettings; + ApplyChanges(); +} + +void LightingTemplateWidget::LoadFromGameSettings() { if (!lightingTemplate) return; @@ -294,25 +247,53 @@ void LightingTemplateWidget::LoadLightingTemplateValues() ColorToFloat3(dalc.directional.x.max, settings.dalc.directional[0].max); ColorToFloat3(dalc.directional.x.min, settings.dalc.directional[0].min); - ColorToFloat3(dalc.directional.y.max, settings.dalc.directional[1].max); ColorToFloat3(dalc.directional.y.min, settings.dalc.directional[1].min); - ColorToFloat3(dalc.directional.z.max, settings.dalc.directional[2].max); ColorToFloat3(dalc.directional.z.min, settings.dalc.directional[2].min); } -void LightingTemplateWidget::LoadFromGameSettings() +static void ValidateSettings(LightingTemplateWidget::Settings& s, const LightingTemplateWidget::Settings& vanilla) { - LoadLightingTemplateValues(); + auto safeF = [](float v, float fallback) { return std::isfinite(v) ? v : fallback; }; + auto safeF3 = [](float3 v, float3 fallback) { + return (std::isfinite(v.x) && std::isfinite(v.y) && std::isfinite(v.z)) ? v : fallback; + }; + s.ambient = safeF3(s.ambient, vanilla.ambient); + s.directional = safeF3(s.directional, vanilla.directional); + s.fogColorNear = safeF3(s.fogColorNear, vanilla.fogColorNear); + s.fogColorFar = safeF3(s.fogColorFar, vanilla.fogColorFar); + s.fogNear = safeF(s.fogNear, vanilla.fogNear); + s.fogFar = safeF(s.fogFar, vanilla.fogFar); + s.directionalXY = safeF(s.directionalXY, vanilla.directionalXY); + s.directionalZ = safeF(s.directionalZ, vanilla.directionalZ); + s.directionalFade = safeF(s.directionalFade, vanilla.directionalFade); + s.clipDist = safeF(s.clipDist, vanilla.clipDist); + s.fogPower = safeF(s.fogPower, vanilla.fogPower); + s.fogClamp = safeF(s.fogClamp, vanilla.fogClamp); + s.lightFadeStart = safeF(s.lightFadeStart, vanilla.lightFadeStart); + s.lightFadeEnd = safeF(s.lightFadeEnd, vanilla.lightFadeEnd); + s.dalc.fresnelPower = safeF(s.dalc.fresnelPower, vanilla.dalc.fresnelPower); + s.dalc.specular = safeF3(s.dalc.specular, vanilla.dalc.specular); + for (int i = 0; i < 3; i++) { + s.dalc.directional[i].max = safeF3(s.dalc.directional[i].max, vanilla.dalc.directional[i].max); + s.dalc.directional[i].min = safeF3(s.dalc.directional[i].min, vanilla.dalc.directional[i].min); + } } void LightingTemplateWidget::LoadSettings() { + settings = vanillaSettings; if (!js.empty()) { - settings = js; - } else { - settings = vanillaSettings; + try { + nlohmann::json merged = vanillaSettings; + merged.merge_patch(js); + settings = merged.get(); + ValidateSettings(settings, vanillaSettings); + } catch (const nlohmann::json::exception& e) { + logger::error("LightingTemplate {}: Failed to load from JSON: {}", GetEditorID(), e.what()); + settings = vanillaSettings; + } } originalSettings = settings; ApplyChanges(); diff --git a/src/WeatherEditor/Weather/LightingTemplateWidget.h b/src/WeatherEditor/Weather/LightingTemplateWidget.h index a59e78d59c..d03c4dcdf8 100644 --- a/src/WeatherEditor/Weather/LightingTemplateWidget.h +++ b/src/WeatherEditor/Weather/LightingTemplateWidget.h @@ -5,21 +5,34 @@ class LightingTemplateWidget : public Widget { public: - RE::BGSLightingTemplate* lightingTemplate = nullptr; - - LightingTemplateWidget(RE::BGSLightingTemplate* a_lightingTemplate) + LightingTemplateWidget(RE::BGSLightingTemplate* a_lightingTemplate) : + lightingTemplate(a_lightingTemplate) { if (!a_lightingTemplate) { logger::error("LightingTemplateWidget created with null pointer"); + settings = {}; + vanillaSettings = {}; + originalSettings = {}; return; } form = a_lightingTemplate; - lightingTemplate = a_lightingTemplate; LoadFromGameSettings(); vanillaSettings = settings; originalSettings = settings; } + ~LightingTemplateWidget() override = default; + + void DrawWidget() override; + void LoadSettings() override; + void SaveSettings() override; + void ApplyChanges() override; + void RevertChanges() override; + bool HasUnsavedChanges() const override; + + RE::BGSLightingTemplate* GetLightingTemplate() const { return lightingTemplate; } + + // Public types required by NLOHMANN macros struct DirectionalColor { float3 min; @@ -55,25 +68,15 @@ class LightingTemplateWidget : public Widget bool operator==(const Settings&) const = default; }; +private: + void LoadFromGameSettings(); + bool DrawBasicSettings(); + bool DrawFogSettings(); + bool DrawDALCSettings(); + + RE::BGSLightingTemplate* lightingTemplate = nullptr; + Settings settings; Settings vanillaSettings; Settings originalSettings; - - ~LightingTemplateWidget(); - - virtual void DrawWidget() override; - virtual void LoadSettings() override; - virtual void SaveSettings() override; - virtual bool HasUnsavedChanges() const override; - - void SetLightingTemplateValues(); - void LoadLightingTemplateValues(); - void LoadFromGameSettings(); - void ApplyChanges() override; - void RevertChanges() override; - -private: - void DrawDALCSettings(); - void DrawBasicSettings(); - void DrawFogSettings(); }; \ No newline at end of file diff --git a/src/WeatherEditor/Weather/PrecipitationWidget.cpp b/src/WeatherEditor/Weather/PrecipitationWidget.cpp index 5094f0547f..27f8dd2a14 100644 --- a/src/WeatherEditor/Weather/PrecipitationWidget.cpp +++ b/src/WeatherEditor/Weather/PrecipitationWidget.cpp @@ -2,92 +2,110 @@ #include "../EditorWindow.h" #include "../WeatherUtils.h" +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( + PrecipitationWidget::Settings, + gravityVelocity, + rotationVelocity, + particleSizeX, + particleSizeY, + centerOffsetMin, + centerOffsetMax, + startRotationRange, + numSubtexturesX, + numSubtexturesY, + particleType, + boxSize, + particleDensity, + particleTexture) + void PrecipitationWidget::DrawWidget() { - WeatherUtils::SetCurrentWidget(this); ImGui::SetNextWindowSizeConstraints(ImVec2(600, 0), ImVec2(FLT_MAX, FLT_MAX)); - if (ImGui::Begin(GetEditorID().c_str(), &open, ImGuiWindowFlags_NoSavedSettings | kStickyHeaderFlags)) { - DrawWidgetHeader("##PrecipitationSearch", true, true); - - bool changed = false; - - if (ImGui::BeginTabBar("PrecipitationTabs")) { - if (ImGui::BeginTabItem("Particle")) { - BeginScrollableContent("##ParticleScroll"); - ImGui::SeparatorText("Particle Type"); - const char* types[] = { "Rain", "Snow" }; - int currentType = static_cast(settings.particleType); - if (ImGui::Combo("Type", ¤tType, types, IM_ARRAYSIZE(types))) { - settings.particleType = static_cast(currentType); - changed = true; - } - - ImGui::SeparatorText("Particle Size"); - if (WeatherUtils::DrawSliderFloat("Size X", settings.particleSizeX, 0.0f, 10.0f)) - changed = true; - if (WeatherUtils::DrawSliderFloat("Size Y", settings.particleSizeY, 0.0f, 10.0f)) - changed = true; - - ImGui::SeparatorText("Velocity"); - if (WeatherUtils::DrawSliderFloat("Gravity Velocity", settings.gravityVelocity, -100.0f, 100.0f)) - changed = true; - if (WeatherUtils::DrawSliderFloat("Rotation Velocity", settings.rotationVelocity, -360.0f, 360.0f)) - changed = true; - - EndScrollableContent(); - ImGui::EndTabItem(); + if (!ImGui::Begin(GetEditorID().c_str(), &open, ImGuiWindowFlags_NoSavedSettings | kStickyHeaderFlags)) { + ImGui::End(); + return; + } + WeatherUtils::SetCurrentWidget(this); + DrawWidgetHeader("##PrecipitationSearch", true, true); + + bool changed = false; + + if (ImGui::BeginTabBar("PrecipitationTabs")) { + if (ImGui::BeginTabItem("Particle")) { + BeginScrollableContent("##ParticleScroll"); + ImGui::SeparatorText("Particle Type"); + static constexpr const char* kParticleTypes[] = { "Rain", "Snow" }; + int currentType = static_cast(settings.particleType); + if (ImGui::Combo("Type", ¤tType, kParticleTypes, IM_ARRAYSIZE(kParticleTypes))) { + settings.particleType = static_cast(currentType); + changed = true; } - if (ImGui::BeginTabItem("Position")) { - BeginScrollableContent("##PositionScroll"); - ImGui::SeparatorText("Offset"); - if (WeatherUtils::DrawSliderFloat("Center Offset Min", settings.centerOffsetMin, -1000.0f, 1000.0f)) - changed = true; - if (WeatherUtils::DrawSliderFloat("Center Offset Max", settings.centerOffsetMax, -1000.0f, 1000.0f)) - changed = true; - if (WeatherUtils::DrawSliderFloat("Start Rotation Range", settings.startRotationRange, 0.0f, 360.0f)) - changed = true; - - ImGui::SeparatorText("Volume"); - if (WeatherUtils::DrawSliderFloat("Box Size", settings.boxSize, 0.0f, 10000.0f)) - changed = true; - if (WeatherUtils::DrawSliderFloat("Particle Density", settings.particleDensity, 0.0f, 10.0f)) - changed = true; - - EndScrollableContent(); - ImGui::EndTabItem(); + ImGui::SeparatorText("Particle Size"); + if (WeatherUtils::DrawSliderFloat("Size X", settings.particleSizeX, 0.0f, 10.0f)) + changed = true; + if (WeatherUtils::DrawSliderFloat("Size Y", settings.particleSizeY, 0.0f, 10.0f)) + changed = true; + + ImGui::SeparatorText("Velocity"); + if (WeatherUtils::DrawSliderFloat("Gravity Velocity", settings.gravityVelocity, -100.0f, 100.0f)) + changed = true; + if (WeatherUtils::DrawSliderFloat("Rotation Velocity", settings.rotationVelocity, -360.0f, 360.0f)) + changed = true; + + EndScrollableContent(); + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Position")) { + BeginScrollableContent("##PositionScroll"); + ImGui::SeparatorText("Offset"); + if (WeatherUtils::DrawSliderFloat("Center Offset Min", settings.centerOffsetMin, -1000.0f, 1000.0f)) + changed = true; + if (WeatherUtils::DrawSliderFloat("Center Offset Max", settings.centerOffsetMax, -1000.0f, 1000.0f)) + changed = true; + if (WeatherUtils::DrawSliderFloat("Start Rotation Range", settings.startRotationRange, 0.0f, 360.0f)) + changed = true; + + ImGui::SeparatorText("Volume"); + if (WeatherUtils::DrawSliderFloat("Box Size", settings.boxSize, 0.0f, 10000.0f)) + changed = true; + if (WeatherUtils::DrawSliderFloat("Particle Density", settings.particleDensity, 0.0f, 10.0f)) + changed = true; + + EndScrollableContent(); + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Texture")) { + BeginScrollableContent("##TextureScroll"); + ImGui::SeparatorText("Subtextures"); + int numX = static_cast(settings.numSubtexturesX); + int numY = static_cast(settings.numSubtexturesY); + if (ImGui::InputInt("Num Subtextures X", &numX)) { + settings.numSubtexturesX = std::max(1, numX); + changed = true; + } + if (ImGui::InputInt("Num Subtextures Y", &numY)) { + settings.numSubtexturesY = std::max(1, numY); + changed = true; } - if (ImGui::BeginTabItem("Texture")) { - BeginScrollableContent("##TextureScroll"); - ImGui::SeparatorText("Subtextures"); - int numX = static_cast(settings.numSubtexturesX); - int numY = static_cast(settings.numSubtexturesY); - if (ImGui::InputInt("Num Subtextures X", &numX)) { - settings.numSubtexturesX = std::max(1, numX); - changed = true; - } - if (ImGui::InputInt("Num Subtextures Y", &numY)) { - settings.numSubtexturesY = std::max(1, numY); - changed = true; - } - - ImGui::SeparatorText("Texture Path"); - if (ImGui::InputText("Particle Texture", textureBuffer, sizeof(textureBuffer))) { - settings.particleTexture = textureBuffer; - changed = true; - } - - EndScrollableContent(); - ImGui::EndTabItem(); + ImGui::SeparatorText("Texture Path"); + if (ImGui::InputText("Particle Texture", textureBuffer, sizeof(textureBuffer))) { + settings.particleTexture = textureBuffer; + changed = true; } - ImGui::EndTabBar(); + EndScrollableContent(); + ImGui::EndTabItem(); } - if (changed && EditorWindow::GetSingleton()->settings.autoApplyChanges) { - ApplyChanges(); - } + ImGui::EndTabBar(); + } + + if (changed && EditorWindow::GetSingleton()->settings.autoApplyChanges) { + ApplyChanges(); } ImGui::End(); } @@ -97,43 +115,27 @@ void PrecipitationWidget::LoadSettings() if (!precipitation) return; + settings = vanillaSettings; if (!js.empty()) { - settings = vanillaSettings; try { - if (js.contains("gravityVelocity")) - settings.gravityVelocity = js["gravityVelocity"]; - if (js.contains("rotationVelocity")) - settings.rotationVelocity = js["rotationVelocity"]; - if (js.contains("particleSizeX")) - settings.particleSizeX = js["particleSizeX"]; - if (js.contains("particleSizeY")) - settings.particleSizeY = js["particleSizeY"]; - if (js.contains("centerOffsetMin")) - settings.centerOffsetMin = js["centerOffsetMin"]; - if (js.contains("centerOffsetMax")) - settings.centerOffsetMax = js["centerOffsetMax"]; - if (js.contains("startRotationRange")) - settings.startRotationRange = js["startRotationRange"]; - if (js.contains("numSubtexturesX")) - settings.numSubtexturesX = js["numSubtexturesX"]; - if (js.contains("numSubtexturesY")) - settings.numSubtexturesY = js["numSubtexturesY"]; - if (js.contains("particleType")) - settings.particleType = js["particleType"]; - if (js.contains("boxSize")) - settings.boxSize = js["boxSize"]; - if (js.contains("particleDensity")) - settings.particleDensity = js["particleDensity"]; - if (js.contains("particleTexture")) - settings.particleTexture = js["particleTexture"].get(); + nlohmann::json merged = vanillaSettings; + merged.merge_patch(js); + settings = merged.get(); } catch (const std::exception& e) { logger::error("Precipitation {}: Failed to load from JSON: {}", GetEditorID(), e.what()); settings = vanillaSettings; } - } else { - settings = vanillaSettings; } + // Normalize settings so originalSettings matches the clamped values applied in ApplyChanges + settings.numSubtexturesX = std::max(1u, settings.numSubtexturesX); + settings.numSubtexturesY = std::max(1u, settings.numSubtexturesY); + settings.particleDensity = std::max(0.0f, settings.particleDensity); + settings.particleType = std::min(settings.particleType, 1u); + settings.boxSize = std::max(0.0f, settings.boxSize); + settings.particleSizeX = std::clamp(settings.particleSizeX, 0.0f, 10.0f); + settings.particleSizeY = std::clamp(settings.particleSizeY, 0.0f, 10.0f); + originalSettings = settings; strncpy_s(textureBuffer, sizeof(textureBuffer), settings.particleTexture.c_str(), _TRUNCATE); ApplyChanges(); @@ -144,38 +146,27 @@ void PrecipitationWidget::LoadFromGameSettings() if (!precipitation) return; + using DataID = RE::BGSShaderParticleGeometryData::DataID; auto& runtime = precipitation->GetRuntimeData(); - settings.gravityVelocity = precipitation->GetSettingValue(RE::BGSShaderParticleGeometryData::DataID::kGravityVelocity).f; - settings.rotationVelocity = precipitation->GetSettingValue(RE::BGSShaderParticleGeometryData::DataID::kRotationVelocity).f; - settings.particleSizeX = precipitation->GetSettingValue(RE::BGSShaderParticleGeometryData::DataID::kParticleSizeX).f; - settings.particleSizeY = precipitation->GetSettingValue(RE::BGSShaderParticleGeometryData::DataID::kParticleSizeY).f; - settings.centerOffsetMin = precipitation->GetSettingValue(RE::BGSShaderParticleGeometryData::DataID::kCenterOffsetMin).f; - settings.centerOffsetMax = precipitation->GetSettingValue(RE::BGSShaderParticleGeometryData::DataID::kCenterOffsetMax).f; - settings.startRotationRange = precipitation->GetSettingValue(RE::BGSShaderParticleGeometryData::DataID::kStartRotationRange).f; - settings.numSubtexturesX = precipitation->GetSettingValue(RE::BGSShaderParticleGeometryData::DataID::kNumSubtexturesX).i; - settings.numSubtexturesY = precipitation->GetSettingValue(RE::BGSShaderParticleGeometryData::DataID::kNumSubtexturesY).i; - settings.particleType = precipitation->GetSettingValue(RE::BGSShaderParticleGeometryData::DataID::kParticleType).i; - settings.boxSize = precipitation->GetSettingValue(RE::BGSShaderParticleGeometryData::DataID::kBoxSize).f; - settings.particleDensity = precipitation->GetSettingValue(RE::BGSShaderParticleGeometryData::DataID::kParticleDensity).f; + settings.gravityVelocity = precipitation->GetSettingValue(DataID::kGravityVelocity).f; + settings.rotationVelocity = precipitation->GetSettingValue(DataID::kRotationVelocity).f; + settings.particleSizeX = precipitation->GetSettingValue(DataID::kParticleSizeX).f; + settings.particleSizeY = precipitation->GetSettingValue(DataID::kParticleSizeY).f; + settings.centerOffsetMin = precipitation->GetSettingValue(DataID::kCenterOffsetMin).f; + settings.centerOffsetMax = precipitation->GetSettingValue(DataID::kCenterOffsetMax).f; + settings.startRotationRange = precipitation->GetSettingValue(DataID::kStartRotationRange).f; + settings.numSubtexturesX = precipitation->GetSettingValue(DataID::kNumSubtexturesX).i; + settings.numSubtexturesY = precipitation->GetSettingValue(DataID::kNumSubtexturesY).i; + settings.particleType = precipitation->GetSettingValue(DataID::kParticleType).i; + settings.boxSize = precipitation->GetSettingValue(DataID::kBoxSize).f; + settings.particleDensity = precipitation->GetSettingValue(DataID::kParticleDensity).f; settings.particleTexture = runtime.particleTexture.textureName.c_str(); } void PrecipitationWidget::SaveSettings() { - js["gravityVelocity"] = settings.gravityVelocity; - js["rotationVelocity"] = settings.rotationVelocity; - js["particleSizeX"] = settings.particleSizeX; - js["particleSizeY"] = settings.particleSizeY; - js["centerOffsetMin"] = settings.centerOffsetMin; - js["centerOffsetMax"] = settings.centerOffsetMax; - js["startRotationRange"] = settings.startRotationRange; - js["numSubtexturesX"] = settings.numSubtexturesX; - js["numSubtexturesY"] = settings.numSubtexturesY; - js["particleType"] = settings.particleType; - js["boxSize"] = settings.boxSize; - js["particleDensity"] = settings.particleDensity; - js["particleTexture"] = settings.particleTexture; + js = settings; originalSettings = settings; } @@ -184,20 +175,30 @@ void PrecipitationWidget::ApplyChanges() if (!precipitation) return; + // Validate and clamp fields to safe ranges before applying + settings.numSubtexturesX = std::max(1u, settings.numSubtexturesX); + settings.numSubtexturesY = std::max(1u, settings.numSubtexturesY); + settings.particleDensity = std::max(0.0f, settings.particleDensity); + settings.particleType = std::min(settings.particleType, 1u); + settings.boxSize = std::max(0.0f, settings.boxSize); + settings.particleSizeX = std::clamp(settings.particleSizeX, 0.0f, 10.0f); + settings.particleSizeY = std::clamp(settings.particleSizeY, 0.0f, 10.0f); + + using DataID = RE::BGSShaderParticleGeometryData::DataID; auto& runtime = precipitation->GetRuntimeData(); - runtime.data[(uint32_t)RE::BGSShaderParticleGeometryData::DataID::kGravityVelocity].f = settings.gravityVelocity; - runtime.data[(uint32_t)RE::BGSShaderParticleGeometryData::DataID::kRotationVelocity].f = settings.rotationVelocity; - runtime.data[(uint32_t)RE::BGSShaderParticleGeometryData::DataID::kParticleSizeX].f = settings.particleSizeX; - runtime.data[(uint32_t)RE::BGSShaderParticleGeometryData::DataID::kParticleSizeY].f = settings.particleSizeY; - runtime.data[(uint32_t)RE::BGSShaderParticleGeometryData::DataID::kCenterOffsetMin].f = settings.centerOffsetMin; - runtime.data[(uint32_t)RE::BGSShaderParticleGeometryData::DataID::kCenterOffsetMax].f = settings.centerOffsetMax; - runtime.data[(uint32_t)RE::BGSShaderParticleGeometryData::DataID::kStartRotationRange].f = settings.startRotationRange; - runtime.data[(uint32_t)RE::BGSShaderParticleGeometryData::DataID::kNumSubtexturesX].i = settings.numSubtexturesX; - runtime.data[(uint32_t)RE::BGSShaderParticleGeometryData::DataID::kNumSubtexturesY].i = settings.numSubtexturesY; - runtime.data[(uint32_t)RE::BGSShaderParticleGeometryData::DataID::kParticleType].i = settings.particleType; - runtime.data[(uint32_t)RE::BGSShaderParticleGeometryData::DataID::kBoxSize].f = settings.boxSize; - runtime.data[(uint32_t)RE::BGSShaderParticleGeometryData::DataID::kParticleDensity].f = settings.particleDensity; + runtime.data[static_cast(DataID::kGravityVelocity)].f = settings.gravityVelocity; + runtime.data[static_cast(DataID::kRotationVelocity)].f = settings.rotationVelocity; + runtime.data[static_cast(DataID::kParticleSizeX)].f = settings.particleSizeX; + runtime.data[static_cast(DataID::kParticleSizeY)].f = settings.particleSizeY; + runtime.data[static_cast(DataID::kCenterOffsetMin)].f = settings.centerOffsetMin; + runtime.data[static_cast(DataID::kCenterOffsetMax)].f = settings.centerOffsetMax; + runtime.data[static_cast(DataID::kStartRotationRange)].f = settings.startRotationRange; + runtime.data[static_cast(DataID::kNumSubtexturesX)].i = settings.numSubtexturesX; + runtime.data[static_cast(DataID::kNumSubtexturesY)].i = settings.numSubtexturesY; + runtime.data[static_cast(DataID::kParticleType)].i = settings.particleType; + runtime.data[static_cast(DataID::kBoxSize)].f = settings.boxSize; + runtime.data[static_cast(DataID::kParticleDensity)].f = settings.particleDensity; runtime.particleTexture.textureName = settings.particleTexture.c_str(); } diff --git a/src/WeatherEditor/Weather/PrecipitationWidget.h b/src/WeatherEditor/Weather/PrecipitationWidget.h index 76775691cb..a555f48120 100644 --- a/src/WeatherEditor/Weather/PrecipitationWidget.h +++ b/src/WeatherEditor/Weather/PrecipitationWidget.h @@ -26,11 +26,7 @@ class PrecipitationWidget : public Widget void RevertChanges() override; bool HasUnsavedChanges() const override; - RE::BGSShaderParticleGeometryData* precipitation = nullptr; - -private: - void LoadFromGameSettings(); - + // Public type required by NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT macro struct Settings { float gravityVelocity = 0.0f; @@ -49,6 +45,11 @@ class PrecipitationWidget : public Widget bool operator==(const Settings&) const = default; }; +private: + void LoadFromGameSettings(); + + RE::BGSShaderParticleGeometryData* precipitation = nullptr; + Settings settings; Settings vanillaSettings; Settings originalSettings; diff --git a/src/WeatherEditor/Weather/ReferenceEffectWidget.cpp b/src/WeatherEditor/Weather/ReferenceEffectWidget.cpp index 3f00dab722..9089f38c51 100644 --- a/src/WeatherEditor/Weather/ReferenceEffectWidget.cpp +++ b/src/WeatherEditor/Weather/ReferenceEffectWidget.cpp @@ -5,45 +5,47 @@ void ReferenceEffectWidget::DrawWidget() { ImGui::SetNextWindowSizeConstraints(ImVec2(600, 0), ImVec2(FLT_MAX, FLT_MAX)); - if (ImGui::Begin(GetEditorID().c_str(), &open, ImGuiWindowFlags_NoSavedSettings | kStickyHeaderFlags)) { - DrawWidgetHeader("##ReferenceEffectSearch", true, true); - BeginScrollableContent("##REScroll"); - { - bool changed = false; - - auto editorWindow = EditorWindow::GetSingleton(); - - ImGui::SeparatorText("Art Object"); - if (editorWindow->artObjectWidgets.empty()) { - ImGui::TextDisabled("No Art Objects available"); - } else { - if (WeatherUtils::DrawFormPickerCached("Art Object", settings.artObject, editorWindow->artObjectWidgets, false, true)) - changed = true; - } + if (!ImGui::Begin(GetEditorID().c_str(), &open, ImGuiWindowFlags_NoSavedSettings | kStickyHeaderFlags)) { + ImGui::End(); + return; + } + WeatherUtils::SetCurrentWidget(this); + DrawWidgetHeader("##ReferenceEffectSearch", true, true); - ImGui::SeparatorText("Effect Shader"); - if (editorWindow->effectShaderWidgets.empty()) { - ImGui::TextDisabled("No Effect Shaders available"); - } else { - if (WeatherUtils::DrawFormPickerCached("Effect Shader", settings.effectShader, editorWindow->effectShaderWidgets, false, true)) - changed = true; - } + BeginScrollableContent("##REScroll"); + bool changed = false; - ImGui::SeparatorText("Flags"); - if (ImGui::Checkbox("Face Target", &settings.faceTarget)) - changed = true; - if (ImGui::Checkbox("Attach To Camera", &settings.attachToCamera)) - changed = true; - if (ImGui::Checkbox("Inherit Rotation", &settings.inheritRotation)) - changed = true; - - if (changed && editorWindow->settings.autoApplyChanges) { - editorWindow->PushUndoState(this); - ApplyChanges(); - } - } - EndScrollableContent(); + auto editorWindow = EditorWindow::GetSingleton(); + + ImGui::SeparatorText("Art Object"); + if (editorWindow->artObjectWidgets.empty()) { + ImGui::TextDisabled("No Art Objects available"); + } else { + if (WeatherUtils::DrawFormPickerCached("Art Object", settings.artObject, editorWindow->artObjectWidgets, false, true)) + changed = true; + } + + ImGui::SeparatorText("Effect Shader"); + if (editorWindow->effectShaderWidgets.empty()) { + ImGui::TextDisabled("No Effect Shaders available"); + } else { + if (WeatherUtils::DrawFormPickerCached("Effect Shader", settings.effectShader, editorWindow->effectShaderWidgets, false, true)) + changed = true; + } + + ImGui::SeparatorText("Flags"); + if (ImGui::Checkbox("Face Target", &settings.faceTarget)) + changed = true; + if (ImGui::Checkbox("Attach To Camera", &settings.attachToCamera)) + changed = true; + if (ImGui::Checkbox("Inherit Rotation", &settings.inheritRotation)) + changed = true; + + if (changed && editorWindow->settings.autoApplyChanges) { + editorWindow->PushUndoState(this); + ApplyChanges(); } + EndScrollableContent(); ImGui::End(); } diff --git a/src/WeatherEditor/Weather/ReferenceEffectWidget.h b/src/WeatherEditor/Weather/ReferenceEffectWidget.h index 9a2ad66dfb..7eb67c4fc4 100644 --- a/src/WeatherEditor/Weather/ReferenceEffectWidget.h +++ b/src/WeatherEditor/Weather/ReferenceEffectWidget.h @@ -43,7 +43,4 @@ class ReferenceEffectWidget : public Widget Settings settings; Settings vanillaSettings; Settings originalSettings; - - std::vector artObjectArray; - std::vector effectShaderArray; }; diff --git a/src/WeatherEditor/Weather/VolumetricLightingWidget.cpp b/src/WeatherEditor/Weather/VolumetricLightingWidget.cpp index e464845754..4b6ae93ea6 100644 --- a/src/WeatherEditor/Weather/VolumetricLightingWidget.cpp +++ b/src/WeatherEditor/Weather/VolumetricLightingWidget.cpp @@ -2,13 +2,31 @@ #include "../EditorWindow.h" #include "../WeatherUtils.h" +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( + VolumetricLightingWidget::Settings, + intensity, + customColorContribution, + red, + green, + blue, + densityContribution, + densitySize, + densityWindSpeed, + densityFallingSpeed, + phaseFunctionContribution, + phaseFunctionScattering, + samplingRangeFactor) + void VolumetricLightingWidget::DrawWidget() { - WeatherUtils::SetCurrentWidget(this); ImGui::SetNextWindowSizeConstraints(ImVec2(600, 0), ImVec2(FLT_MAX, FLT_MAX)); - if (ImGui::Begin(GetEditorID().c_str(), &open, ImGuiWindowFlags_NoSavedSettings | kStickyHeaderFlags)) { - DrawWidgetHeader("##VolumetricLightingSearch", true, true); + if (!ImGui::Begin(GetEditorID().c_str(), &open, ImGuiWindowFlags_NoSavedSettings | kStickyHeaderFlags)) { + ImGui::End(); + return; } + WeatherUtils::SetCurrentWidget(this); + DrawWidgetHeader("##VolumetricLightingSearch", true, true); + bool changed = false; if (ImGui::BeginTabBar("VolumetricLightingTabs")) { @@ -81,39 +99,33 @@ void VolumetricLightingWidget::LoadSettings() if (!volumetricLighting) return; + settings = vanillaSettings; if (!js.empty()) { - settings = vanillaSettings; try { - if (js.contains("intensity")) - settings.intensity = js["intensity"]; - if (js.contains("customColorContribution")) - settings.customColorContribution = js["customColorContribution"]; - if (js.contains("red")) - settings.red = js["red"]; - if (js.contains("green")) - settings.green = js["green"]; - if (js.contains("blue")) - settings.blue = js["blue"]; - if (js.contains("densityContribution")) - settings.densityContribution = js["densityContribution"]; - if (js.contains("densitySize")) - settings.densitySize = js["densitySize"]; - if (js.contains("densityWindSpeed")) - settings.densityWindSpeed = js["densityWindSpeed"]; - if (js.contains("densityFallingSpeed")) - settings.densityFallingSpeed = js["densityFallingSpeed"]; - if (js.contains("phaseFunctionContribution")) - settings.phaseFunctionContribution = js["phaseFunctionContribution"]; - if (js.contains("phaseFunctionScattering")) - settings.phaseFunctionScattering = js["phaseFunctionScattering"]; - if (js.contains("samplingRangeFactor")) - settings.samplingRangeFactor = js["samplingRangeFactor"]; + nlohmann::json merged = vanillaSettings; + merged.merge_patch(js); + settings = merged.get(); + + // Clamp/validate all numeric fields; replace non-finite values with vanilla fallbacks + auto safeClamp = [](float v, float lo, float hi, float fallback) { + return std::isfinite(v) ? std::clamp(v, lo, hi) : fallback; + }; + settings.intensity = safeClamp(settings.intensity, 0.0f, 50.0f, vanillaSettings.intensity); + settings.customColorContribution = safeClamp(settings.customColorContribution, 0.0f, 1.0f, vanillaSettings.customColorContribution); + settings.red = safeClamp(settings.red, 0.0f, 1.0f, vanillaSettings.red); + settings.green = safeClamp(settings.green, 0.0f, 1.0f, vanillaSettings.green); + settings.blue = safeClamp(settings.blue, 0.0f, 1.0f, vanillaSettings.blue); + settings.densityContribution = safeClamp(settings.densityContribution, 0.0f, 1.0f, vanillaSettings.densityContribution); + settings.densitySize = safeClamp(settings.densitySize, 0.1f, 10000.0f, vanillaSettings.densitySize); + settings.densityWindSpeed = safeClamp(settings.densityWindSpeed, 0.0f, 100.0f, vanillaSettings.densityWindSpeed); + settings.densityFallingSpeed = safeClamp(settings.densityFallingSpeed, 0.0f, 100.0f, vanillaSettings.densityFallingSpeed); + settings.phaseFunctionContribution = safeClamp(settings.phaseFunctionContribution, 0.0f, 1.0f, vanillaSettings.phaseFunctionContribution); + settings.phaseFunctionScattering = safeClamp(settings.phaseFunctionScattering, 0.0f, 1.0f, vanillaSettings.phaseFunctionScattering); + settings.samplingRangeFactor = safeClamp(settings.samplingRangeFactor, 0.0f, 160.0f, vanillaSettings.samplingRangeFactor); } catch (const std::exception& e) { logger::error("VolumetricLighting {}: Failed to load from JSON: {}", GetEditorID(), e.what()); settings = vanillaSettings; } - } else { - settings = vanillaSettings; } originalSettings = settings; @@ -140,18 +152,7 @@ void VolumetricLightingWidget::LoadFromGameSettings() void VolumetricLightingWidget::SaveSettings() { - js["intensity"] = settings.intensity; - js["customColorContribution"] = settings.customColorContribution; - js["red"] = settings.red; - js["green"] = settings.green; - js["blue"] = settings.blue; - js["densityContribution"] = settings.densityContribution; - js["densitySize"] = settings.densitySize; - js["densityWindSpeed"] = settings.densityWindSpeed; - js["densityFallingSpeed"] = settings.densityFallingSpeed; - js["phaseFunctionContribution"] = settings.phaseFunctionContribution; - js["phaseFunctionScattering"] = settings.phaseFunctionScattering; - js["samplingRangeFactor"] = settings.samplingRangeFactor; + js = settings; originalSettings = settings; } diff --git a/src/WeatherEditor/Weather/VolumetricLightingWidget.h b/src/WeatherEditor/Weather/VolumetricLightingWidget.h index 842d68d74f..f56e5b7b66 100644 --- a/src/WeatherEditor/Weather/VolumetricLightingWidget.h +++ b/src/WeatherEditor/Weather/VolumetricLightingWidget.h @@ -25,11 +25,7 @@ class VolumetricLightingWidget : public Widget void RevertChanges() override; bool HasUnsavedChanges() const override; - RE::BGSVolumetricLighting* volumetricLighting = nullptr; - -private: - void LoadFromGameSettings(); - + // Public type required by NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT macro struct Settings { float intensity = 1.0f; @@ -47,6 +43,11 @@ class VolumetricLightingWidget : public Widget bool operator==(const Settings&) const = default; }; +private: + void LoadFromGameSettings(); + + RE::BGSVolumetricLighting* volumetricLighting = nullptr; + Settings settings; Settings vanillaSettings; Settings originalSettings; diff --git a/src/WeatherEditor/Weather/WeatherWidget.cpp b/src/WeatherEditor/Weather/WeatherWidget.cpp index 6745c38edf..c843463662 100644 --- a/src/WeatherEditor/Weather/WeatherWidget.cpp +++ b/src/WeatherEditor/Weather/WeatherWidget.cpp @@ -54,117 +54,117 @@ WeatherWidget::~WeatherWidget() void WeatherWidget::DrawWidget() { - WeatherUtils::SetCurrentWidget(this); ImGui::SetNextWindowSizeConstraints(ImVec2(600, 0), ImVec2(FLT_MAX, FLT_MAX)); - if (ImGui::Begin(GetEditorID().c_str(), &open, ImGuiWindowFlags_NoSavedSettings | kStickyHeaderFlags)) { - // Draw header with search and all buttons - DrawWidgetHeader("##WeatherSearch", false, true, true, weather); - - // Update search results when search buffer changes - if (searchActive) { - UpdateSearchResults(); - } - - // Show search results dropdown - if (searchBuffer[0] != '\0' && !searchResults.empty()) { - // Find the search input position for dropdown placement - ImGui::SetNextWindowPos(ImVec2(ImGui::GetCursorScreenPos().x, ImGui::GetCursorScreenPos().y)); - ImGui::SetNextWindowSize(ImVec2(200.0f * 1.5f, 0)); - ImGui::SetNextWindowFocus(); - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.0f); - ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.16f, 0.16f, 0.16f, 1.0f)); - if (ImGui::Begin("##SearchDropdown", nullptr, - ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings)) { - for (size_t i = 0; i < std::min(size_t(5), searchResults.size()); ++i) { - const auto& result = searchResults[i]; - std::string label = std::format("{} ({})", result.displayName, result.tabName); - - if (ImGui::Selectable(label.c_str(), false, ImGuiSelectableFlags_DontClosePopups)) { - NavigateToSetting(result); - searchBuffer[0] = '\0'; - searchResults.clear(); - } - } + if (!ImGui::Begin(GetEditorID().c_str(), &open, ImGuiWindowFlags_NoSavedSettings | kStickyHeaderFlags)) { + ImGui::End(); + return; + } + WeatherUtils::SetCurrentWidget(this); + // Draw header with search and all buttons + DrawWidgetHeader("##WeatherSearch", false, true, true, weather); - if (searchResults.size() > 5) { - ImGui::Separator(); - ImGui::TextDisabled("... %zu more results", searchResults.size() - 5); - } + // Update search results when search buffer changes + if (searchActive) { + UpdateSearchResults(); + } - // Close dropdown if clicking outside or pressing Escape - if (!ImGui::IsWindowFocused() || ImGui::IsKeyPressed(ImGuiKey_Escape)) { + // Show search results dropdown + if (searchBuffer[0] != '\0' && !searchResults.empty()) { + // Find the search input position for dropdown placement + ImGui::SetNextWindowPos(ImVec2(ImGui::GetCursorScreenPos().x, ImGui::GetCursorScreenPos().y)); + ImGui::SetNextWindowSize(ImVec2(200.0f * 1.5f, 0)); + ImGui::SetNextWindowFocus(); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 1.0f); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.16f, 0.16f, 0.16f, 1.0f)); + if (ImGui::Begin(std::format("##SearchDropdown_{}", GetEditorID()).c_str(), nullptr, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings)) { + for (size_t i = 0; i < std::min(size_t(5), searchResults.size()); ++i) { + const auto& result = searchResults[i]; + std::string label = std::format("{} ({})", result.displayName, result.tabName); + + if (ImGui::Selectable(label.c_str(), false, ImGuiSelectableFlags_DontClosePopups)) { + NavigateToSetting(result); searchBuffer[0] = '\0'; searchResults.clear(); } } - ImGui::End(); - ImGui::PopStyleColor(); - ImGui::PopStyleVar(); - } - auto editorWindow = EditorWindow::GetSingleton(); - auto& widgets = editorWindow->weatherWidgets; + if (searchResults.size() > 5) { + ImGui::Separator(); + ImGui::TextDisabled("... %zu more results", searchResults.size() - 5); + } - // Sets the parent widget if settings have been loaded. - if (settings.parent != "None") { - parent = GetParent(); - if (parent == nullptr) - settings.parent = "None"; + // Close dropdown if clicking outside or pressing Escape + if (!ImGui::IsWindowFocused() || ImGui::IsKeyPressed(ImGuiKey_Escape)) { + searchBuffer[0] = '\0'; + searchResults.clear(); + } } + ImGui::End(); + ImGui::PopStyleColor(); + ImGui::PopStyleVar(); + } - if (editorWindow->settings.enableInheritFromParent) { - if (ImGui::BeginCombo("Parent", settings.parent.c_str())) { - // Option for "None" - if (ImGui::Selectable("None", parent == nullptr)) { - parent = nullptr; - settings.parent = "None"; - } + auto editorWindow = EditorWindow::GetSingleton(); + auto& widgets = editorWindow->weatherWidgets; - for (int i = 0; i < widgets.size(); i++) { - auto& widget = widgets[i]; + // Sets the parent widget if settings have been loaded. + if (settings.parent != "None") { + parent = GetParent(); + if (parent == nullptr) + settings.parent = "None"; + } - // Skip self-selection - if (widget.get() == this) - continue; + if (editorWindow->settings.enableInheritFromParent) { + if (ImGui::BeginCombo("Parent", settings.parent.c_str())) { + // Option for "None" + if (ImGui::Selectable("None", parent == nullptr)) { + parent = nullptr; + settings.parent = "None"; + } - // Option for each widget - if (ImGui::Selectable(widget->GetEditorID().c_str(), parent == widget.get())) { - parent = (WeatherWidget*)widget.get(); - settings.parent = widget->GetEditorID(); - } + for (auto& widget : widgets) { + // Skip self-selection + if (widget.get() == this) + continue; - // Set default focus to the current parent - if (parent == widget.get()) { - ImGui::SetItemDefaultFocus(); - } + // Option for each widget + if (ImGui::Selectable(widget->GetEditorID().c_str(), parent == widget.get())) { + parent = (WeatherWidget*)widget.get(); + settings.parent = widget->GetEditorID(); + } + + // Set default focus to the current parent + if (parent == widget.get()) { + ImGui::SetItemDefaultFocus(); } - ImGui::EndCombo(); } + ImGui::EndCombo(); + } + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::TextUnformatted("Editor-only feature: Set a parent weather to copy settings from."); + ImGui::TextUnformatted("Use 'Inherit From Parent' checkboxes to copy specific values."); + ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.0f, 1.0f), "Note: This is NOT the same as cell lighting template inheritance."); + ImGui::EndTooltip(); + } + + if (parent) { ImGui::SameLine(); - ImGui::TextDisabled("(?)"); + if (Util::ButtonWithFlash("Inherit All")) { + InheritAllFromParent(); + } if (ImGui::IsItemHovered()) { - ImGui::BeginTooltip(); - ImGui::TextUnformatted("Editor-only feature: Set a parent weather to copy settings from."); - ImGui::TextUnformatted("Use 'Inherit From Parent' checkboxes to copy specific values."); - ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.0f, 1.0f), "Note: This is NOT the same as cell lighting template inheritance."); - ImGui::EndTooltip(); + ImGui::SetTooltip("Copy all parameter values from parent weather"); } - if (parent) { + if (!parent->IsOpen()) { ImGui::SameLine(); - if (Util::ButtonWithFlash("Inherit All")) { - InheritAllFromParent(); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Copy all parameter values from parent weather"); - } - - if (!parent->IsOpen()) { - ImGui::SameLine(); - if (Util::ButtonWithFlash("Open")) - parent->SetOpen(true); - } + if (Util::ButtonWithFlash("Open")) + parent->SetOpen(true); } } } @@ -237,7 +237,6 @@ void WeatherWidget::DrawWidget() ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); - auto* editorWindow = EditorWindow::GetSingleton(); bool recordChanged = false; bool hasParent = editorWindow->settings.enableInheritFromParent && HasParent(); @@ -418,6 +417,7 @@ void WeatherWidget::DrawWidget() } if (recordChanged && EditorWindow::GetSingleton()->settings.autoApplyChanges) { + EditorWindow::GetSingleton()->PushUndoState(this); ApplyChanges(); } @@ -940,6 +940,7 @@ void WeatherWidget::DrawDALCSettings() TOD::EndTODTable(); } if (changed && EditorWindow::GetSingleton()->settings.autoApplyChanges) { + EditorWindow::GetSingleton()->PushUndoState(this); ApplyChanges(); } } @@ -1001,6 +1002,7 @@ void WeatherWidget::DrawWeatherColorSettings() } if (changed && EditorWindow::GetSingleton()->settings.autoApplyChanges) { + EditorWindow::GetSingleton()->PushUndoState(this); ApplyChanges(); } } @@ -1289,6 +1291,7 @@ void WeatherWidget::DrawFogSettings() } if (changed && EditorWindow::GetSingleton()->settings.autoApplyChanges) { + EditorWindow::GetSingleton()->PushUndoState(this); ApplyChanges(); } } @@ -1375,6 +1378,7 @@ void WeatherWidget::DrawProperties(std::string category, std::mapsettings.autoApplyChanges) { + EditorWindow::GetSingleton()->PushUndoState(this); ApplyChanges(); } @@ -1471,6 +1475,7 @@ void WeatherWidget::InheritAllFromParent() // Apply the changes if (EditorWindow::GetSingleton()->settings.autoApplyChanges) { + EditorWindow::GetSingleton()->PushUndoState(this); ApplyChanges(); } @@ -2015,8 +2020,14 @@ ID3D11ShaderResourceView* WeatherWidget::GetCloudTexture(int layerIndex) // Note: Skyrim texture paths don't include .dds extension, so we add it std::string resourcePath = std::string("Textures\\") + texturePath; - // Add .dds extension if not present - if (resourcePath.size() < 4 || resourcePath.substr(resourcePath.size() - 4) != ".dds") { + // Add .dds extension if not present (case-insensitive check) + if (resourcePath.size() >= 4) { + std::string ext = resourcePath.substr(resourcePath.size() - 4); + std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c) { return std::tolower(c); }); + if (ext != ".dds") { + resourcePath += ".dds"; + } + } else { resourcePath += ".dds"; }