Skip to content
Closed
10 changes: 5 additions & 5 deletions src/WeatherEditor/EditorWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<CellLightingWidget>(cell);
Expand All @@ -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);
}
}
Expand Down Expand Up @@ -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<LightingTemplateWidget*>(sortedWidgets[i]);
if (!ltWidget || ltWidget->lightingTemplate != currentCellLightingTemplate)
if (!ltWidget || ltWidget->GetLightingTemplate() != currentCellLightingTemplate)
continue;

if (!shouldShowWidget(sortedWidgets[i]))
Expand Down Expand Up @@ -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<LightingTemplateWidget*>(sortedWidgets[i]);
if (ltWidget && ltWidget->lightingTemplate == currentCellLightingTemplate)
if (ltWidget && ltWidget->GetLightingTemplate() == currentCellLightingTemplate)
continue;
}

Expand Down Expand Up @@ -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;
}
Expand Down
313 changes: 113 additions & 200 deletions src/WeatherEditor/Weather/CellLightingWidget.cpp

Large diffs are not rendered by default.

64 changes: 37 additions & 27 deletions src/WeatherEditor/Weather/CellLightingWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand All @@ -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;
Expand Down
98 changes: 47 additions & 51 deletions src/WeatherEditor/Weather/ImageSpaceWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@

#include "../EditorWindow.h"
#include "../WeatherUtils.h"
#include "Util.h"

#include <filesystem>

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(
ImageSpaceWidget::Settings,
Expand All @@ -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
Expand Down Expand Up @@ -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"];
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Comment on lines +155 to +183

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Normalize settings after clamp to keep persisted and applied state consistent.

ApplyChanges() clamps before writing to imageSpace->data, but settings keeps original out-of-range values. That can leave JSON persistence and HasUnsavedChanges() out of sync with what is actually applied.

Proposed fix
 	// 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);
+	settings.hdrEyeAdaptSpeed = safeClamp(settings.hdrEyeAdaptSpeed, 0.0f, 10.0f);
+	settings.hdrBloomBlurRadius = safeClamp(settings.hdrBloomBlurRadius, 0.0f, 10.0f);
+	settings.hdrBloomThreshold = safeClamp(settings.hdrBloomThreshold, 0.0f, 10.0f);
+	settings.hdrBloomScale = safeClamp(settings.hdrBloomScale, 0.0f, 10.0f);
+	settings.hdrWhite = safeClamp(settings.hdrWhite, 0.0f, 10.0f);
+	settings.hdrSunlightScale = safeClamp(settings.hdrSunlightScale, 0.0f, 50.0f);
+	settings.hdrSkyScale = safeClamp(settings.hdrSkyScale, 0.0f, 10.0f);
+	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 = 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);
+	settings.cinematicSaturation = safeClamp(settings.cinematicSaturation, 0.0f, 2.0f);
+	settings.cinematicBrightness = safeClamp(settings.cinematicBrightness, 0.0f, 2.0f);
+	settings.cinematicContrast = safeClamp(settings.cinematicContrast, 0.0f, 2.0f);
+	data.cinematic.saturation = settings.cinematicSaturation;
+	data.cinematic.brightness = settings.cinematicBrightness;
+	data.cinematic.contrast = settings.cinematicContrast;

 	// 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);
+	settings.tintColor.x = safeClamp(settings.tintColor.x, 0.0f, 1.0f);
+	settings.tintColor.y = safeClamp(settings.tintColor.y, 0.0f, 1.0f);
+	settings.tintColor.z = safeClamp(settings.tintColor.z, 0.0f, 1.0f);
+	settings.tintAmount = safeClamp(settings.tintAmount, 0.0f, 1.0f);
+	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 = 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);
+	settings.dofStrength = safeClamp(settings.dofStrength, 0.0f, 10.0f);
+	settings.dofDistance = safeClamp(settings.dofDistance, 0.0f, 10000.0f);
+	settings.dofRange = safeClamp(settings.dofRange, 0.0f, 10000.0f);
+	data.depthOfField.strength = settings.dofStrength;
+	data.depthOfField.distance = settings.dofDistance;
+	data.depthOfField.range = settings.dofRange;

As per coding guidelines "Include proper resource management and graceful degradation for DirectX 11 resources and user input validation to prevent crashes from malformed configurations".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/WeatherEditor/Weather/ImageSpaceWidget.cpp` around lines 155 - 183,
ApplyChanges() currently clamps values into local data (using safeClamp) but
leaves the original settings object unchanged, causing persisted JSON and
HasUnsavedChanges() to mismatch applied state; after clamping each value into
data (for HDR, Cinematic, Tint, Depth of Field), write the sanitized values back
into the corresponding settings members (e.g., settings.hdrEyeAdaptSpeed,
settings.hdrBloomBlurRadius, settings.cinematicSaturation,
settings.tintColor.x/y/z, settings.dofStrength, etc.) so settings mirrors data;
keep using safeClamp/std::isfinite for sanitization and ensure the same bounds
are applied when updating settings to maintain consistency between
imageSpace->data and persisted settings.

}

void ImageSpaceWidget::RevertChanges()
Expand Down
34 changes: 17 additions & 17 deletions src/WeatherEditor/Weather/ImageSpaceWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
};
Loading