From 2e74a0114ca87ec0e15f07e860c8424a6371244b Mon Sep 17 00:00:00 2001 From: Pepe20129 <72659707+Pepe20129@users.noreply.github.com> Date: Sat, 20 Jun 2026 18:53:41 +0200 Subject: [PATCH 01/10] Add Graph --- soh/soh/Graph/Graph.cpp | 385 ++++++++++++++++++++++++++++++++++++++++ soh/soh/Graph/Graph.h | 101 +++++++++++ 2 files changed, 486 insertions(+) create mode 100644 soh/soh/Graph/Graph.cpp create mode 100644 soh/soh/Graph/Graph.h diff --git a/soh/soh/Graph/Graph.cpp b/soh/soh/Graph/Graph.cpp new file mode 100644 index 00000000000..506a6e0de17 --- /dev/null +++ b/soh/soh/Graph/Graph.cpp @@ -0,0 +1,385 @@ +#include "Graph.h" + +#include + +#include +#include + +typedef struct Rect { + ImVec2 min; + ImVec2 max; +} Rect; + +Rect GetVisibleWorldRect(ImVec2 canvasSize, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom) { + Rect r; + + r.min = (-cameraOffset) / zoom; + r.max = (canvasSize - cameraOffset) / zoom; + + return r; +} + +static bool IsNodeVisible(const Node& node, const Rect& view, float zoom, GraphNodeOptions options, bool scaleWithZoom) { + float radius = options.baseSize * (scaleWithZoom ? zoom : 1); + + return !(node.position.x + radius < view.min.x || + node.position.x - radius > view.max.x || + node.position.y + radius < view.min.y || + node.position.y - radius > view.max.y); +} + +static bool IsEdgeVisible(ImVec2 a, ImVec2 b, const Rect& view) { + float minX = std::min(a.x, b.x); + float maxX = std::max(a.x, b.x); + + float minY = std::min(a.y, b.y); + float maxY = std::max(a.y, b.y); + + return !(maxX < view.min.x || + minX > view.max.x || + maxY < view.min.y || + minY > view.max.y); +} + +ImVec2 WorldSpaceToScreenSpace(ImVec2 vec, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom) { + return canvasPos + cameraOffset + vec * zoom; +} + +Node Node::New(ImVec2 position, std::string label, ImU32 color, ImU32 labelColor) { + return { position, { 0, 0 }, label, { -1, -1 }, color, labelColor }; +} + +void Node::Draw(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphNodeOptions options, bool scaleWithZoom) { + ImVec2 screenPos = WorldSpaceToScreenSpace( + this->position, + canvasPos, + cameraOffset, + zoom + ); + + draw->AddCircleFilled( + screenPos, + options.baseSize * (scaleWithZoom ? zoom : 1), + this->color + ); +} + +void Node::DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphNodeOptions options) { + if (this->labelSize.x == -1) { + this->labelSize = ImGui::CalcTextSize(label.c_str()); + } + + ImU32 labelBackgroundColor = options.label.backgroundColor; + + if (options.label.fadeout) { + float alpha = (std::clamp(zoom, options.label.fadeoutCutoffLower, options.label.fadeoutCutoffUpper) - options.label.fadeoutCutoffLower) / (options.label.fadeoutCutoffUpper - options.label.fadeoutCutoffLower) * 255; + ImU32 byteAlpha = static_cast(alpha); + this->labelColor &= ~IM_COL32_A_MASK; + this->labelColor |= byteAlpha << IM_COL32_A_SHIFT; + + float alphaBackground = (std::clamp(zoom, options.label.fadeoutCutoffLower, options.label.fadeoutCutoffUpper) - options.label.fadeoutCutoffLower) / (options.label.fadeoutCutoffUpper - options.label.fadeoutCutoffLower) * 220; + if (alpha == 0 && alphaBackground == 0) { + return; + } + ImU32 byteAlphaBackground = static_cast(alpha); + labelBackgroundColor &= ~IM_COL32_A_MASK; + labelBackgroundColor |= byteAlphaBackground << IM_COL32_A_SHIFT; + } + + ImVec2 screenPos = WorldSpaceToScreenSpace( + this->position, + canvasPos, + cameraOffset, + zoom + ); + + ImVec2 min = screenPos - this->labelSize * 0.5f - options.label.padding; + + ImVec2 max = screenPos + this->labelSize * 0.5f + options.label.padding; + + draw->AddRectFilled( + min, + max, + labelBackgroundColor, + options.label.rounding + ); + + draw->AddText( + screenPos - this->labelSize * 0.5f, + this->labelColor, + this->label.c_str() + ); +} + +Edge Edge::New(int src, int dst, std::string label, ImU32 color, ImU32 labelColor) { + return { src, dst, label, { -1, -1 }, color, labelColor }; +} + +void Edge::Draw(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphEdgeOptions options, bool scaleWithZoom, const std::vector& nodes) { + //LUSLOG_INFO("[Edge::Draw] Drawing edge: src = %d | dst = %d | label = \"%s\"", this->src, this->dst, this->label.c_str()); + + if (this->src >= nodes.size() || this->dst >= nodes.size()) { + LUSLOG_ERROR("[Edge::Draw] Invalid src (%d) or dst (%d) for node list of size (%d)", this->src, this->dst, nodes.size()); + assert(false); + return; + } + + ImVec2 a = nodes[this->src].position; + ImVec2 b = nodes[this->dst].position; + + draw->AddLine( + WorldSpaceToScreenSpace(a, canvasPos, cameraOffset, zoom), + WorldSpaceToScreenSpace(b, canvasPos, cameraOffset, zoom), + this->color, + options.thickness * (scaleWithZoom ? zoom : 1) + ); +} + +void Edge::DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphEdgeOptions options, ImVec2 a, ImVec2 b) { + ImVec2 delta = b - a; + + float len = std::sqrt(delta.x * delta.x + delta.y * delta.y); + + if (len < 1e-5f) { + return; + } + + ImVec2 normal = { + -delta.y / len, + delta.x / len + }; + + ImVec2 labelPosWorld = a * 0.6f + b * 0.4f + normal * options.label.separation; + + ImVec2 labelPosScreen = WorldSpaceToScreenSpace(labelPosWorld, canvasPos, cameraOffset, zoom); + + if (this->labelSize.x == -1) { + this->labelSize = ImGui::CalcTextSize(label.c_str()); + } + + ImU32 labelBackgroundColor = options.label.backgroundColor; + + if (options.label.fadeout) { + float alpha = (std::clamp(zoom, options.label.fadeoutCutoffLower, options.label.fadeoutCutoffUpper) - options.label.fadeoutCutoffLower) / (options.label.fadeoutCutoffUpper - options.label.fadeoutCutoffLower) * 255; + ImU32 byteAlpha = static_cast(alpha); + this->labelColor &= ~IM_COL32_A_MASK; + this->labelColor |= byteAlpha << IM_COL32_A_SHIFT; + + float alphaBackground = (std::clamp(zoom, options.label.fadeoutCutoffLower, options.label.fadeoutCutoffUpper) - options.label.fadeoutCutoffLower) / (options.label.fadeoutCutoffUpper - options.label.fadeoutCutoffLower) * 220; + if (alpha == 0 && alphaBackground == 0) { + return; + } + ImU32 byteAlphaBackground = static_cast(alpha); + labelBackgroundColor &= ~IM_COL32_A_MASK; + labelBackgroundColor |= byteAlphaBackground << IM_COL32_A_SHIFT; + } + + ImVec2 min = labelPosScreen - this->labelSize * 0.5f - options.label.padding; + + ImVec2 max = labelPosScreen + this->labelSize * 0.5f + options.label.padding; + + draw->AddRectFilled( + min, + max, + labelBackgroundColor, + options.label.rounding + ); + + draw->AddText( + labelPosScreen - this->labelSize * 0.5f, + this->labelColor, + this->label.c_str() + ); +} + +Graph Graph::New(std::vector nodes, std::vector edges, GraphOptions options) { + return Graph(nodes, edges, options); +} + +Graph::Graph(std::vector _nodes, std::vector _edges, GraphOptions _options) : nodes(_nodes), edges(_edges), options(_options) {} + +float Graph::StabilizeStep(float width, float height, float temperature) { + float area = width * height; + float k = std::sqrt(area / nodes.size()); + + auto repulsion_force = [&](float d) { + return this->options.forceMultipliers.repulsion * (k * k) / d; + }; + + auto attraction_force = [&](float d) { + return this->options.forceMultipliers.attraction * (d * d) / k; + }; + + // reset displacement + for (auto& n : this->nodes) { + n.displacement = {0, 0}; + } + + // repulsive forces + for (size_t i = 0; i < this->nodes.size(); i += 1) { + for (size_t j = i + 1; j < this->nodes.size(); j += 1) { + ImVec2 delta = this->nodes[i].position - this->nodes[j].position; + + float dist = std::max(std::sqrt(delta.x * delta.x + delta.y * delta.y), 0.01f); + + float force = repulsion_force(dist); + + ImVec2 dir = delta / dist; + + this->nodes[i].displacement += dir * force; + this->nodes[j].displacement -= dir * force; + } + } + + // attractive forces + for (const auto& e : this->edges) { + auto& u = this->nodes[e.src]; + auto& v = this->nodes[e.dst]; + + ImVec2 delta = u.position - v.position; + + float dist = std::max(std::sqrt(delta.x * delta.x + delta.y * delta.y), 0.01f); + + float force = attraction_force(dist); + + ImVec2 dir = delta / dist; + + u.displacement -= dir * force; + v.displacement += dir * force; + } + + float totalDisplacement = 0; + float maxDisplacement = 0; + + // apply displacements + for (auto& v : this->nodes) { + float len = std::sqrt(v.displacement.x * v.displacement.x + v.displacement.y * v.displacement.y); + + if (len > 0.0f) { + v.position += (v.displacement / len) * std::min(len, temperature); + } + + if (len > maxDisplacement) { + maxDisplacement = len; + } + totalDisplacement += len; + } + + //return totalDisplacement; + return maxDisplacement; +} + +void Graph::Stabilize(float width, float height) { + for (float temperature = this->options.temperature.starting; temperature > this->options.temperature.ending; temperature -= this->options.temperature.decreasePerIteration) { + this->StabilizeStep(width, height, temperature); + } +} + +void Graph::HandleMouse(ImVec2 canvasPos) { + if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { + this->cameraOffset += ImGui::GetIO().MouseDelta; + } else if (ImGui::IsItemHovered()) { + float wheel = ImGui::GetIO().MouseWheel; + + if (wheel != 0.0f) { + ImVec2 mouse = ImGui::GetIO().MousePos; + + // Mouse position in world space before zoom + ImVec2 mouseWorldBefore = (mouse - canvasPos - this->cameraOffset) / this->zoom; + + // Apply zoom + this->zoom *= (wheel > 0.0f) ? 1.1f : 0.9f; + this->zoom = std::clamp(this->zoom, this->options.zoom.min, this->options.zoom.max); + + // Adjust camera so mouseWorldBefore stays under cursor + this->cameraOffset = mouse - canvasPos - mouseWorldBefore * this->zoom; + } + } +} + +void Graph::Draw(ImVec2 canvasSize, ImVec2 canvasPos) { + if (canvasSize.x < 0 || canvasSize.y < 0) { + LUSLOG_ERROR("[Graph::Draw] Invalid canvasSize = (%f, %f)", canvasSize.x, canvasSize.y); + assert(false); + return; + } + + /** / + LUSLOG_INFO( + "[Graph::Draw] Begin: canvasSize = (%f, %f) | canvasPos = (%f, %f) | cameraOffset = (%f, %f) | zoom = %f", + canvasSize.x, canvasSize.y, + canvasPos.x, canvasPos.y, + this->cameraOffset.x, this->cameraOffset.y, + this->zoom + ); + /**/ + ImGui::BeginGroup(); + + ImDrawList* draw = ImGui::GetWindowDrawList(); + + ImGui::SetCursorScreenPos(canvasPos); + ImGui::InvisibleButton( + "graph_canvas", + canvasSize, + ImGuiButtonFlags_MouseButtonLeft + ); + + this->HandleMouse(canvasPos); + + Rect visibleRect = GetVisibleWorldRect(canvasSize, canvasPos, this->cameraOffset, this->zoom); + + for (auto& e : this->edges) { + const ImVec2& a = nodes[e.src].position; + const ImVec2& b = nodes[e.dst].position; + + if (!IsEdgeVisible(a, b, visibleRect)) { + continue; + } + + e.Draw(draw, canvasPos, this->cameraOffset, this->zoom, this->options.edges, this->options.zoom.edgesScaleWithZoom, this->nodes); + } + + for (auto& n : this->nodes) { + if (!IsNodeVisible(n, visibleRect, this->zoom, this->options.nodes, this->options.zoom.nodesScaleWithZoom)) { + continue; + } + + n.Draw(draw, canvasPos, this->cameraOffset, this->zoom, this->options.nodes, this->options.zoom.nodesScaleWithZoom); + } + + for (auto& e : this->edges) { + const ImVec2& a = nodes[e.src].position; + const ImVec2& b = nodes[e.dst].position; + + if (!IsEdgeVisible(a, b, visibleRect)) { + continue; + } + + if (e.label != "") { + e.DrawLabel(draw, canvasPos, this->cameraOffset, this->zoom, this->options.edges, a, b); + } + } + + for (auto& n : this->nodes) { + if (!IsNodeVisible(n, visibleRect, this->zoom, this->options.nodes, this->options.zoom.nodesScaleWithZoom)) { + continue; + } + + if (n.label != "") { + n.DrawLabel(draw, canvasPos, cameraOffset, zoom, this->options.nodes); + } + } + + ImGui::EndGroup(); + + //LUSLOG_INFO("[Graph::Draw] End\n"); +} + +GraphOptions& Graph::GetOptions() { + return this->options; +} + +void Graph::ResetView() { + this->cameraOffset = { 0, 0 }; + this->zoom = 1.0f; +} \ No newline at end of file diff --git a/soh/soh/Graph/Graph.h b/soh/soh/Graph/Graph.h new file mode 100644 index 00000000000..3617f69c0f4 --- /dev/null +++ b/soh/soh/Graph/Graph.h @@ -0,0 +1,101 @@ +#pragma once + +#include +#include + +#include + +typedef struct GraphNodeOptions { + float baseSize; + struct { + ImVec2 padding; + float rounding; + ImU32 backgroundColor; + bool fadeout; + float fadeoutCutoffLower; + float fadeoutCutoffUpper; + } label; +} GraphNodeOptions; + +typedef struct GraphEdgeOptions { + float thickness; + struct { + float separation; + ImVec2 padding; + float rounding; + ImU32 backgroundColor; + bool fadeout; + float fadeoutCutoffLower; + float fadeoutCutoffUpper; + } label; +} GraphEdgeOptions; + +typedef struct Node { + ImVec2 position; + ImVec2 displacement; + std::string label; + // must be the result of ImGui::CalcTextSize(this->label.c_str()); + ImVec2 labelSize; + ImU32 color; + ImU32 labelColor; + + static Node New(ImVec2 position, std::string label, ImU32 color, ImU32 labelColor); + void Draw(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphNodeOptions options, bool scaleWithZoom); + void DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphNodeOptions options); +} Node; + +typedef struct Edge { + // index of the source node in the nodes vector + int src; + // index of the destination node in the nodes vector + int dst; + std::string label; + // must be the result of ImGui::CalcTextSize(this->label.c_str()); + ImVec2 labelSize; + ImU32 color; + ImU32 labelColor; + + static Edge New(int src, int dst, std::string label, ImU32 color, ImU32 labelColor); + void Draw(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphEdgeOptions options, bool scaleWithZoom, const std::vector& nodes); + void DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphEdgeOptions options, ImVec2 a, ImVec2 b); +} Edge; + +typedef struct GraphOptions { + struct { + float min; + float max; + bool nodesScaleWithZoom; + bool edgesScaleWithZoom; + } zoom; + struct { + float repulsion; + float attraction; + } forceMultipliers; + struct { + float starting; + float decreasePerIteration; + float ending; + } temperature; + GraphNodeOptions nodes; + GraphEdgeOptions edges; +} GraphOptions; + +class Graph { + public: + void Stabilize(float width, float height); + float StabilizeStep(float width, float height, float temperature); + void Draw(ImVec2 canvasSize, ImVec2 canvasPos); + GraphOptions& GetOptions(); + void ResetView(); + static Graph New(std::vector nodes, std::vector edges, GraphOptions options); + + private: + Graph() = delete; + Graph(std::vector _nodes, std::vector _edges, GraphOptions _options); + void HandleMouse(ImVec2 canvasPos); + std::vector nodes; + std::vector edges; + ImVec2 cameraOffset = { 0, 0 }; + float zoom = 1.0f; + GraphOptions options; +}; \ No newline at end of file From 0ba904192194c7f30d9772353c2acd91f8fefdc4 Mon Sep 17 00:00:00 2001 From: Pepe20129 <72659707+Pepe20129@users.noreply.github.com> Date: Sat, 20 Jun 2026 18:54:04 +0200 Subject: [PATCH 02/10] Add a graph window --- .../randomizer_entrance_tracker_graph.cpp | 328 ++++++++++++++++++ .../randomizer_entrance_tracker_graph.h | 21 ++ soh/soh/SohGui/SohGui.cpp | 6 + soh/soh/SohGui/SohMenuRandomizer.cpp | 7 + 4 files changed, 362 insertions(+) create mode 100644 soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.cpp create mode 100644 soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.h diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.cpp b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.cpp new file mode 100644 index 00000000000..2470c1ee92e --- /dev/null +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.cpp @@ -0,0 +1,328 @@ +#include "randomizer_entrance_tracker_graph.h" + +#include +#include "soh/SohGui/SohGui.hpp" +#include "entrance.h" +#include "location_access.h" +#include "randomizer_entrance_tracker.h" +#include "randomizerEnumStrings.h" +#include "randomizerTypes.h" + +const float initialSize = 2500.0f; + +void EntranceTrackerGraphWindow::Draw() { + if (!IsVisible()) { + return; + } + DrawElement(); + // Sync up the IsVisible flag if it was changed by ImGui + SyncVisibilityConsoleVariable(); +} + +const GraphOptions defaultOptions = { + { + 0.05f, + 5.0f, + true, + true, + }, + { + 1.0f, + 10.0f, + }, + { + 80.0f, + 5.0f, + 30.0f, + }, + { + 20.0f, + { + { 3.0f, 2.0f }, + 3.0f, + IM_COL32(48, 48, 64, 220), + true, + 0.75f, + 1.0f, + }, + }, + { + 2.0f, + { + 0.0f, + { 3.0f, 2.0f }, + 3.0f, + IM_COL32(48, 48, 64, 220), + true, + 2.0f, + 3.0f, + }, + }, +}; + +void EntranceTrackerGraphWindow::DrawElement() { + if (!this->graph.has_value()) { + LUSLOG_ERROR("[EntranceTrackerGraphWindow::DrawElement] this->graph is std::nullopt"); + return; + } + + Color_RGBA8 bgColor = { 0, 0, 0, 255 }; + if (Trackers::BeginFloatWindows("Entrance Tracker Graph", mIsVisible, bgColor, TRACKER_WINDOW_WINDOW, true, ImGuiWindowFlags_NoScrollbar)) { + ImVec2 canvasPos = ImGui::GetCursorScreenPos(); + ImVec2 canvasSize = ImGui::GetContentRegionAvail(); + + if (!this->sufficientlyStabilized) { + float displacement = 0; + displacement += this->graph.value().StabilizeStep(initialSize * 2.0f, initialSize * 2.0f, 10.0f); + displacement += this->graph.value().StabilizeStep(initialSize * 2.0f, initialSize * 2.0f, 10.0f); + + LUSLOG_INFO("[EntranceTrackerGraphWindow::DrawElement] Displacement = %f", displacement); + + if (displacement < 28000) { + this->sufficientlyStabilized = true; + } + } + + ImGui::SetNextItemAllowOverlap(); + + this->graph.value().Draw(canvasSize, canvasPos); + + ImGui::SetCursorScreenPos(canvasPos); + + ImGui::SetNextItemAllowOverlap(); + + ImGui::BeginGroup(); + + if (menuOpen) { + if (ImGui::Button(ICON_FA_COG)) { + menuOpen = false; + } + + if (ImGui::Button("Reset View")) { + this->graph.value().ResetView(); + } + + #define TEST_FLOAT(option_path, text, min, max) \ + if ( \ + CVarSliderFloat( \ + text, \ + CVAR_TRACKER_ENTRANCE("Graph." #option_path), \ + UIWidgets::FloatSliderOptions() \ + .Min(min) \ + .Max(max) \ + .DefaultValue(CVarGetFloat(CVAR_TRACKER_ENTRANCE("Graph." #option_path), defaultOptions.option_path)) \ + .Format("%.1f") \ + .Size({ 300.0f, 0.0f }) \ + .Step(0.1f) \ + .Color(THEME_COLOR) \ + ) \ + ) { \ + this->UpdateGraphOptions(); \ + } + + TEST_FLOAT(forceMultipliers.repulsion, "Repulsion Force", 1.0f, 50.0f); + TEST_FLOAT(forceMultipliers.attraction, "Attraction Force", 1.0f, 50.0f); + + TEST_FLOAT(temperature.starting, "Starting Temperature", 10.0f, 1000.0f); + TEST_FLOAT(temperature.decreasePerIteration, "Temperature Decrease Per Iteration", 0.01f, 10.0f); + + TEST_FLOAT(edges.thickness, "Edge Thickness", 1.0f, 10.0f); + + } else { + if (ImGui::Button(ICON_FA_COG)) { + menuOpen = true; + } + } + + ImGui::EndGroup(); + } + Trackers::EndFloatWindows(); +} + +ImU32 GetColorForArea(RandomizerArea area) { + switch (area) { + case RA_NONE: + case RA_LINKS_POCKET: + return IM_COL32(0xD8, 0xD2, 0xE8, 0xFF); + case RA_KOKIRI_FOREST: + return IM_COL32(0xB8, 0xE3, 0xC2, 0xFF); + case RA_THE_LOST_WOODS : + return IM_COL32(0xAF, 0xCF, 0xA8, 0xFF); + case RA_SACRED_FOREST_MEADOW: + return IM_COL32(0xCB, 0xE8, 0xB5, 0xFF); + case RA_HYRULE_FIELD: + return IM_COL32(0xDD, 0xE8, 0xA8, 0xFF); + case RA_LAKE_HYLIA : + return IM_COL32(0xA9, 0xDC, 0xE3, 0xFF); + case RA_GERUDO_VALLEY: + return IM_COL32(0xF2, 0xC6, 0xA0, 0xFF); + case RA_GERUDO_FORTRESS: + return IM_COL32(0xE8, 0xB3, 0xA7, 0xFF); + case RA_HAUNTED_WASTELAND: + return IM_COL32(0xC9, 0xB2, 0xC8, 0xFF); + case RA_DESERT_COLOSSUS: + return IM_COL32(0xEB, 0xCB, 0x8B, 0xFF); + case RA_THE_MARKET : + return IM_COL32(0xF4, 0xB7, 0xC5, 0xFF); + case RA_TEMPLE_OF_TIME : + return IM_COL32(0xED, 0xE2, 0xC6, 0xFF); + case RA_HYRULE_CASTLE: + return IM_COL32(0xB9, 0xC1, 0xE8, 0xFF); + case RA_OUTSIDE_GANONS_CASTLE: + return IM_COL32(0xA8, 0x9B, 0xBE, 0xFF); + case RA_CASTLE_GROUNDS : + return IM_COL32(0xB8, 0xCB, 0xE8, 0xFF); + case RA_KAKARIKO_VILLAGE: + return IM_COL32(0xE7, 0xB2, 0x8D, 0xFF); + case RA_THE_GRAVEYARD: + return IM_COL32(0xAA, 0xA0, 0xBA, 0xFF); + case RA_DEATH_MOUNTAIN_TRAIL: + return IM_COL32(0xE8, 0xA9, 0x8F, 0xFF); + case RA_GORON_CITY: + return IM_COL32(0xF0, 0x8F, 0x78, 0xFF); + case RA_DEATH_MOUNTAIN_CRATER: + return IM_COL32(0xF4, 0xB0, 0x8A, 0xFF); + case RA_ZORAS_RIVER: + return IM_COL32(0x9F, 0xD8, 0xD2, 0xFF); + case RA_ZORAS_DOMAIN: + return IM_COL32(0xA8, 0xD7, 0xE8, 0xFF); + case RA_ZORAS_FOUNTAIN: + return IM_COL32(0xC1, 0xE8, 0xE5, 0xFF); + case RA_LON_LON_RANCH: + return IM_COL32(0xF4, 0xE1, 0xA1, 0xFF); + case RA_DEKU_TREE: + return IM_COL32(0xA9, 0xD6, 0xA0, 0xFF); + case RA_DODONGOS_CAVERN: + return IM_COL32(0xC9, 0x9F, 0x88, 0xFF); + case RA_JABU_JABUS_BELLY: + return IM_COL32(0xEF, 0xAF, 0xC0, 0xFF); + case RA_FOREST_TEMPLE: + return IM_COL32(0xA9, 0xC8, 0xB5, 0xFF); + case RA_FIRE_TEMPLE: + return IM_COL32(0xE9, 0x96, 0x7A, 0xFF); + case RA_WATER_TEMPLE: + return IM_COL32(0x91, 0xC8, 0xD8, 0xFF); + case RA_SPIRIT_TEMPLE: + return IM_COL32(0xE8, 0xC2, 0x9A, 0xFF); + case RA_SHADOW_TEMPLE: + return IM_COL32(0xAA, 0xA6, 0xB8, 0xFF); + case RA_BOTTOM_OF_THE_WELL: + return IM_COL32(0xB5, 0xB0, 0xA8, 0xFF); + case RA_ICE_CAVERN: + return IM_COL32(0xB8, 0xDF, 0xF0, 0xFF); + case RA_GERUDO_TRAINING_GROUND: + return IM_COL32(0xD7, 0xA0, 0x8D, 0xFF); + case RA_GANONS_CASTLE: + return IM_COL32(0x92, 0x7C, 0x9F, 0xFF); + default: + LUSLOG_ERROR("[GetColorForArea] Invalid area (%d)", static_cast(area)); + assert(false); + return IM_COL32(0xD8, 0xD2, 0xE8, 0xFF); + } +} + +ImU32 GetColorForAreas(std::set areas) { + switch (areas.size()) { + case 0: + return IM_COL32(0xD8, 0xD2, 0xE8, 0xFF); + case 1: + return GetColorForArea(*areas.begin()); + default: + // for now we just pick a random one (sets aren't ordered) + return GetColorForArea(*areas.begin()); + } +} + +uint64_t randoState = 0; + +void EntranceTrackerGraphWindow::InitElement() { + ImU32 labelColor = IM_COL32_WHITE; + ImU32 edgeColor = IM_COL32_WHITE; + std::vector nodes = {}; + + std::vector edges = {}; + + RegionTable_Init(); + + for (const auto& region : areaTable) { + if (region.randomizerRegionKey == RR_NONE) { + continue; + } + + std::optional regionString = EnumToString(region.randomizerRegionKey); + + if (!regionString.has_value()) { + regionString = "???"; + } + + // random starting positions to allow the forces to move them + nodes.push_back(Node::New({ static_cast(ShipUtils::next32(&randoState)) / INT32_MAX * initialSize - initialSize / 2, static_cast(ShipUtils::next32(&randoState)) / INT32_MAX * initialSize - initialSize / 2 }, std::string(regionString.value()), GetColorForAreas(region.areas), labelColor)); + } + + for (const auto& region : areaTable) { + for (const auto& exit : region.exits) { + // -1 due to skipping RR_NONE + edges.push_back(Edge::New(exit.GetParentRegionKey() - 1, exit.GetConnectedRegionKey() - 1, exit.GetConditionStr(), edgeColor, labelColor)); + } + } + + this->graph = Graph::New(nodes, edges, defaultOptions); + + this->UpdateGraphOptions(); + + this->graph.value().Stabilize(initialSize * 2.0f, initialSize * 2.0f); +} + +void EntranceTrackerGraphWindow::UpdateGraphOptions() { + if (!this->graph.has_value()) { + return; + } + + GraphOptions& currentGraphOptions = this->graph.value().GetOptions(); + + #define UPDATE_OPTION_PATH_SIMPLE(option_path, type) currentGraphOptions.option_path = CVarGet ## type (CVAR_TRACKER_ENTRANCE("Graph." #option_path), defaultOptions.option_path) + #define UPDATE_OPTION_PATH_INT(option_path) UPDATE_OPTION_PATH_SIMPLE(option_path, Integer) + #define UPDATE_OPTION_PATH_FLOAT(option_path) UPDATE_OPTION_PATH_SIMPLE(option_path, Float) + #define UPDATE_OPTION_PATH_IMVEC2(option_path) currentGraphOptions.option_path = { \ + CVarGetFloat(CVAR_TRACKER_ENTRANCE("Graph." #option_path ".x"), defaultOptions.option_path.x), \ + CVarGetFloat(CVAR_TRACKER_ENTRANCE("Graph." #option_path ".y"), defaultOptions.option_path.y), \ + } + #define UPDATE_OPTION_PATH_IMU32(option_path) currentGraphOptions.option_path = static_cast(CVarGetInteger(CVAR_TRACKER_ENTRANCE("Graph." #option_path), defaultOptions.option_path)) + + UPDATE_OPTION_PATH_FLOAT(zoom.min); + UPDATE_OPTION_PATH_FLOAT(zoom.max); + UPDATE_OPTION_PATH_INT(zoom.nodesScaleWithZoom); + UPDATE_OPTION_PATH_INT(zoom.edgesScaleWithZoom); + + UPDATE_OPTION_PATH_FLOAT(forceMultipliers.repulsion); + UPDATE_OPTION_PATH_FLOAT(forceMultipliers.attraction); + + UPDATE_OPTION_PATH_FLOAT(temperature.starting); + UPDATE_OPTION_PATH_FLOAT(temperature.decreasePerIteration); + UPDATE_OPTION_PATH_FLOAT(temperature.ending); + + UPDATE_OPTION_PATH_FLOAT(nodes.baseSize); + UPDATE_OPTION_PATH_IMVEC2(nodes.label.padding); + UPDATE_OPTION_PATH_FLOAT(nodes.label.rounding); + UPDATE_OPTION_PATH_IMU32(nodes.label.backgroundColor); + UPDATE_OPTION_PATH_INT(nodes.label.fadeout); + UPDATE_OPTION_PATH_FLOAT(nodes.label.fadeoutCutoffLower); + UPDATE_OPTION_PATH_FLOAT(nodes.label.fadeoutCutoffUpper); + + UPDATE_OPTION_PATH_FLOAT(edges.thickness); + UPDATE_OPTION_PATH_FLOAT(edges.label.separation); + UPDATE_OPTION_PATH_IMVEC2(edges.label.padding); + UPDATE_OPTION_PATH_FLOAT(edges.label.rounding); + UPDATE_OPTION_PATH_IMU32(edges.label.backgroundColor); + UPDATE_OPTION_PATH_INT(edges.label.fadeout); + UPDATE_OPTION_PATH_FLOAT(edges.label.fadeoutCutoffLower); + UPDATE_OPTION_PATH_FLOAT(edges.label.fadeoutCutoffUpper); + + #undef UPDATE_OPTION_PATH_SIMPLE + #undef UPDATE_OPTION_PATH_INT + #undef UPDATE_OPTION_PATH_FLOAT + #undef UPDATE_OPTION_PATH_IMVEC2 + #undef UPDATE_OPTION_PATH_IMU32 + + this->sufficientlyStabilized = false; +} \ No newline at end of file diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.h b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.h new file mode 100644 index 00000000000..f5d77d6eb08 --- /dev/null +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.h @@ -0,0 +1,21 @@ + +#include +#include "randomizerTypes.h" +#include "soh/Graph/Graph.h" +#include + +class EntranceTrackerGraphWindow final : public Ship::GuiWindow { + public: + using GuiWindow::GuiWindow; + void Draw() override; + + void InitElement() override; + void DrawElement() override; + void UpdateElement() override{}; + void UpdateGraphOptions(); + + private: + std::optional graph = std::nullopt; + bool menuOpen = false; + bool sufficientlyStabilized = false; +}; \ No newline at end of file diff --git a/soh/soh/SohGui/SohGui.cpp b/soh/soh/SohGui/SohGui.cpp index 2aa79b1d000..898f506f0e6 100644 --- a/soh/soh/SohGui/SohGui.cpp +++ b/soh/soh/SohGui/SohGui.cpp @@ -26,6 +26,7 @@ #include "soh/Enhancements/TimeDisplay/TimeDisplay.h" #include "soh/Enhancements/mod_menu.h" #include "soh/Network/Anchor/Anchor.h" +#include "soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.h" namespace SohGui { @@ -83,6 +84,7 @@ std::shared_ptr mCheckTrackerSettingsW std::shared_ptr mCheckTrackerWindow; std::shared_ptr mEntranceTrackerSettingsWindow; std::shared_ptr mEntranceTrackerWindow; +std::shared_ptr mEntranceTrackerGraphWindow; std::shared_ptr mItemTrackerSettingsWindow; std::shared_ptr mItemTrackerWindow; std::shared_ptr mTimeSplitWindow; @@ -176,6 +178,9 @@ void SetupGuiElements() { mEntranceTrackerWindow = std::make_shared( CVAR_WINDOW("EntranceTracker"), "Entrance Tracker", ImVec2(500, 750)); gui->AddGuiWindow(mEntranceTrackerWindow); + mEntranceTrackerGraphWindow = std::make_shared( + CVAR_WINDOW("EntranceTrackerGraph"), "Entrance Tracker Graph", ImVec2(500, 750)); + gui->AddGuiWindow(mEntranceTrackerGraphWindow); mEntranceTrackerSettingsWindow = std::make_shared( CVAR_WINDOW("EntranceTrackerSettings"), "Entrance Tracker Settings", ImVec2(600, 375)); gui->AddGuiWindow(mEntranceTrackerSettingsWindow); @@ -208,6 +213,7 @@ void Destroy() { mItemTrackerWindow = nullptr; mItemTrackerSettingsWindow = nullptr; mEntranceTrackerWindow = nullptr; + mEntranceTrackerGraphWindow = nullptr; mEntranceTrackerSettingsWindow = nullptr; mCheckTrackerWindow = nullptr; mCheckTrackerSettingsWindow = nullptr; diff --git a/soh/soh/SohGui/SohMenuRandomizer.cpp b/soh/soh/SohGui/SohMenuRandomizer.cpp index a51a4fbca4d..0fcbd302958 100644 --- a/soh/soh/SohGui/SohMenuRandomizer.cpp +++ b/soh/soh/SohGui/SohMenuRandomizer.cpp @@ -741,6 +741,13 @@ void SohMenu::AddMenuRandomizer() { .HideInSearch(true) .Options(WindowButtonOptions().Tooltip("Toggles the Entrance Tracker.").EmbedWindow(false)); + AddWidget(path, "Toggle Entrance Tracker Graph", WIDGET_WINDOW_BUTTON) + .CVar(CVAR_WINDOW("EntranceTrackerGraph")) + .RaceDisable(false) + .WindowName("Entrance Tracker Graph") + .HideInSearch(true) + .Options(WindowButtonOptions().Tooltip("Toggles the Entrance Tracker Graph.").EmbedWindow(false)); + AddWidget(path, "Entrance Tracker Settings", WIDGET_SEPARATOR_TEXT); AddWidget(path, "Popout Entrance Tracker Settings", WIDGET_WINDOW_BUTTON) .CVar(CVAR_WINDOW("EntranceTrackerSettings")) From 4c0e35e7f98d4c6f3231df62009eaecebaa15916 Mon Sep 17 00:00:00 2001 From: Pepe20129 <72659707+Pepe20129@users.noreply.github.com> Date: Sat, 20 Jun 2026 18:54:26 +0200 Subject: [PATCH 03/10] Fix some things that have been noticed due to the graph --- .../randomizer/location_access/dungeons/ganons_castle.cpp | 6 +++--- .../randomizer/randomizerEnums/RandomizerRegion.h | 5 ----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/location_access/dungeons/ganons_castle.cpp b/soh/soh/Enhancements/randomizer/location_access/dungeons/ganons_castle.cpp index 3af0ffcad27..15e2b4c903e 100644 --- a/soh/soh/Enhancements/randomizer/location_access/dungeons/ganons_castle.cpp +++ b/soh/soh/Enhancements/randomizer/location_access/dungeons/ganons_castle.cpp @@ -853,8 +853,8 @@ void RegionTable_Init_GanonsCastle() { LOCATION(RC_GANONS_CASTLE_GANONS_TOWER_POT_18, logic->CanBreakPots()), }, { //Exits - ENTRANCE(RR_GANONS_TOWER_STAIRS_4, true;), - ENTRANCE(RR_GANONS_TOWER_BEFORE_GANONDORF_LAIR, true;), + ENTRANCE(RR_GANONS_TOWER_STAIRS_4, true), + ENTRANCE(RR_GANONS_TOWER_BEFORE_GANONDORF_LAIR, true), }); areaTable[RR_GANONS_TOWER_BEFORE_GANONDORF_LAIR] = Region("Ganon's Tower Before Ganondorf's Lair", SCENE_GANONS_TOWER, {}, { @@ -862,7 +862,7 @@ void RegionTable_Init_GanonsCastle() { LOCATION(RC_GANONS_BOSS_KEY_HINT, true), }, { //Exits - ENTRANCE(RR_GANONS_TOWER_POT_ROOM, false;), + ENTRANCE(RR_GANONS_TOWER_POT_ROOM, false), ENTRANCE(RR_GANONS_TOWER_GANONDORF_LAIR, AnyAgeTime([]{return logic->HasItem(RG_GANONS_CASTLE_BOSS_KEY);})), }); diff --git a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerRegion.h b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerRegion.h index bde9ade8812..3cecfa963e4 100644 --- a/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerRegion.h +++ b/soh/soh/Enhancements/randomizer/randomizerEnums/RandomizerRegion.h @@ -46,7 +46,6 @@ RANDO_ENUM_ITEM(RR_LW_NEAR_SHORTCUTS_GROTTO) RANDO_ENUM_ITEM(RR_DEKU_THEATER) RANDO_ENUM_ITEM(RR_LW_SCRUBS_GROTTO) RANDO_ENUM_ITEM(RR_SFM_ENTRYWAY) -RANDO_ENUM_ITEM(RR_SFM_MAZE) RANDO_ENUM_ITEM(RR_SFM_ABOVE_MAZE) RANDO_ENUM_ITEM(RR_SFM_OUTSIDE_FAIRY_GROTTO) RANDO_ENUM_ITEM(RR_SACRED_FOREST_MEADOW) @@ -683,7 +682,6 @@ RANDO_ENUM_ITEM(RR_WATER_TEMPLE_MQ_STORAGE_ROOM) RANDO_ENUM_ITEM(RR_WATER_TEMPLE_MQ_LIZALFOS_LOOP_A) RANDO_ENUM_ITEM(RR_WATER_TEMPLE_MQ_LIZALFOS_LOOP_LM) RANDO_ENUM_ITEM(RR_WATER_TEMPLE_MQ_LIZALFOS_CAGE) -RANDO_ENUM_ITEM(RR_WATER_TEMPLE_MQ_3F_EAST_LEDGE) RANDO_ENUM_ITEM(RR_WATER_TEMPLE_MQ_OUTSIDE_WATERFALL) RANDO_ENUM_ITEM(RR_WATER_TEMPLE_MQ_WATERFALL) RANDO_ENUM_ITEM(RR_WATER_TEMPLE_MQ_WATERFALL_TOP) @@ -861,7 +859,6 @@ RANDO_ENUM_ITEM(RR_SHADOW_TEMPLE_WOODEN_SPIKES) RANDO_ENUM_ITEM(RR_SHADOW_TEMPLE_PRE_BOSS_ROOM) RANDO_ENUM_ITEM(RR_SHADOW_TEMPLE_BOSS_DOOR) -RANDO_ENUM_ITEM(RR_SHADOW_TEMPLE_MQ_ENTRYWAY) RANDO_ENUM_ITEM(RR_SHADOW_TEMPLE_MQ_BEGINNING) RANDO_ENUM_ITEM(RR_SHADOW_TEMPLE_MQ_SPINNER_ROOM) RANDO_ENUM_ITEM(RR_SHADOW_TEMPLE_MQ_WHISPERING_WALLS_START) @@ -1017,7 +1014,6 @@ RANDO_ENUM_ITEM(RR_GANONS_CASTLE_FIRE_TRIAL_FROM_BARRED) RANDO_ENUM_ITEM(RR_GANONS_CASTLE_FIRE_TRIAL_BARRED_DOOR) RANDO_ENUM_ITEM(RR_GANONS_CASTLE_FIRE_TRIAL_FINAL_ROOM) RANDO_ENUM_ITEM(RR_GANONS_CASTLE_WATER_TRIAL_BLUE_FIRE_ROOM) -RANDO_ENUM_ITEM(RR_GANONS_CASTLE_WATER_TRIAL_BLUE_FIRE_ROOM_END) RANDO_ENUM_ITEM(RR_GANONS_CASTLE_WATER_TRIAL_BLOCK_ROOM) RANDO_ENUM_ITEM(RR_GANONS_CASTLE_WATER_TRIAL_BLOCK_ROOM_SWITCH) RANDO_ENUM_ITEM(RR_GANONS_CASTLE_WATER_TRIAL_BLOCK_ROOM_END) @@ -1049,7 +1045,6 @@ RANDO_ENUM_ITEM(RR_GANONS_CASTLE_MQ_FIRE_TRIAL_OPEN_DOOR) RANDO_ENUM_ITEM(RR_GANONS_CASTLE_MQ_FIRE_TRIAL_FROM_OPEN) RANDO_ENUM_ITEM(RR_GANONS_CASTLE_MQ_FIRE_TRIAL_FROM_BARRED) RANDO_ENUM_ITEM(RR_GANONS_CASTLE_MQ_FIRE_TRIAL_BARRED_DOOR) -RANDO_ENUM_ITEM(RR_GANONS_CASTLE_MQ_FIRE_TRIAL_TARGET_DOOR) RANDO_ENUM_ITEM(RR_GANONS_CASTLE_MQ_FIRE_TRIAL_FINAL_ROOM) RANDO_ENUM_ITEM(RR_GANONS_CASTLE_MQ_WATER_TRIAL_GEYSER_ROOM) RANDO_ENUM_ITEM(RR_GANONS_CASTLE_MQ_WATER_TRIAL_BLOCK_ROOM) From 8840056caf7a3a76da3267e0a2784e89d9c17077 Mon Sep 17 00:00:00 2001 From: Pepe20129 <72659707+Pepe20129@users.noreply.github.com> Date: Mon, 22 Jun 2026 15:31:04 +0200 Subject: [PATCH 04/10] Specifiers & attributes --- soh/soh/Graph/Graph.cpp | 35 ++++++++++++++++++----------------- soh/soh/Graph/Graph.h | 41 ++++++++++++++++++++++------------------- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/soh/soh/Graph/Graph.cpp b/soh/soh/Graph/Graph.cpp index 506a6e0de17..8767c455229 100644 --- a/soh/soh/Graph/Graph.cpp +++ b/soh/soh/Graph/Graph.cpp @@ -10,7 +10,7 @@ typedef struct Rect { ImVec2 max; } Rect; -Rect GetVisibleWorldRect(ImVec2 canvasSize, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom) { +Rect GetVisibleWorldRect(ImVec2 canvasSize, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom) noexcept { Rect r; r.min = (-cameraOffset) / zoom; @@ -19,7 +19,7 @@ Rect GetVisibleWorldRect(ImVec2 canvasSize, ImVec2 canvasPos, ImVec2 cameraOffse return r; } -static bool IsNodeVisible(const Node& node, const Rect& view, float zoom, GraphNodeOptions options, bool scaleWithZoom) { +static bool IsNodeVisible(const Node& node, const Rect& view, float zoom, GraphNodeOptions options, bool scaleWithZoom) noexcept { float radius = options.baseSize * (scaleWithZoom ? zoom : 1); return !(node.position.x + radius < view.min.x || @@ -28,7 +28,7 @@ static bool IsNodeVisible(const Node& node, const Rect& view, float zoom, GraphN node.position.y - radius > view.max.y); } -static bool IsEdgeVisible(ImVec2 a, ImVec2 b, const Rect& view) { +static bool IsEdgeVisible(ImVec2 a, ImVec2 b, const Rect& view) noexcept { float minX = std::min(a.x, b.x); float maxX = std::max(a.x, b.x); @@ -41,15 +41,15 @@ static bool IsEdgeVisible(ImVec2 a, ImVec2 b, const Rect& view) { minY > view.max.y); } -ImVec2 WorldSpaceToScreenSpace(ImVec2 vec, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom) { +ImVec2 WorldSpaceToScreenSpace(ImVec2 vec, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom) noexcept { return canvasPos + cameraOffset + vec * zoom; } -Node Node::New(ImVec2 position, std::string label, ImU32 color, ImU32 labelColor) { +Node Node::New(ImVec2 position, std::string label, ImU32 color, ImU32 labelColor) noexcept { return { position, { 0, 0 }, label, { -1, -1 }, color, labelColor }; } -void Node::Draw(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphNodeOptions options, bool scaleWithZoom) { +void Node::Draw(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphNodeOptions options, bool scaleWithZoom) noexcept { ImVec2 screenPos = WorldSpaceToScreenSpace( this->position, canvasPos, @@ -64,7 +64,7 @@ void Node::Draw(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float z ); } -void Node::DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphNodeOptions options) { +void Node::DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphNodeOptions options) noexcept { if (this->labelSize.x == -1) { this->labelSize = ImGui::CalcTextSize(label.c_str()); } @@ -111,11 +111,11 @@ void Node::DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, fl ); } -Edge Edge::New(int src, int dst, std::string label, ImU32 color, ImU32 labelColor) { +Edge Edge::New(int src, int dst, std::string label, ImU32 color, ImU32 labelColor) noexcept { return { src, dst, label, { -1, -1 }, color, labelColor }; } -void Edge::Draw(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphEdgeOptions options, bool scaleWithZoom, const std::vector& nodes) { +void Edge::Draw(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphEdgeOptions options, bool scaleWithZoom, const std::vector& nodes) noexcept { //LUSLOG_INFO("[Edge::Draw] Drawing edge: src = %d | dst = %d | label = \"%s\"", this->src, this->dst, this->label.c_str()); if (this->src >= nodes.size() || this->dst >= nodes.size()) { @@ -135,7 +135,7 @@ void Edge::Draw(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float z ); } -void Edge::DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphEdgeOptions options, ImVec2 a, ImVec2 b) { +void Edge::DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphEdgeOptions options, ImVec2 a, ImVec2 b) noexcept { ImVec2 delta = b - a; float len = std::sqrt(delta.x * delta.x + delta.y * delta.y); @@ -192,13 +192,14 @@ void Edge::DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, fl ); } -Graph Graph::New(std::vector nodes, std::vector edges, GraphOptions options) { +Graph Graph::New(std::vector nodes, std::vector edges, GraphOptions options) noexcept { return Graph(nodes, edges, options); } Graph::Graph(std::vector _nodes, std::vector _edges, GraphOptions _options) : nodes(_nodes), edges(_edges), options(_options) {} -float Graph::StabilizeStep(float width, float height, float temperature) { +[[gnu::hot]] +float Graph::StabilizeStep(float width, float height, float temperature) noexcept { float area = width * height; float k = std::sqrt(area / nodes.size()); @@ -269,13 +270,13 @@ float Graph::StabilizeStep(float width, float height, float temperature) { return maxDisplacement; } -void Graph::Stabilize(float width, float height) { +void Graph::Stabilize(float width, float height) noexcept { for (float temperature = this->options.temperature.starting; temperature > this->options.temperature.ending; temperature -= this->options.temperature.decreasePerIteration) { this->StabilizeStep(width, height, temperature); } } -void Graph::HandleMouse(ImVec2 canvasPos) { +void Graph::HandleMouse(ImVec2 canvasPos) noexcept { if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { this->cameraOffset += ImGui::GetIO().MouseDelta; } else if (ImGui::IsItemHovered()) { @@ -297,7 +298,7 @@ void Graph::HandleMouse(ImVec2 canvasPos) { } } -void Graph::Draw(ImVec2 canvasSize, ImVec2 canvasPos) { +void Graph::Draw(ImVec2 canvasSize, ImVec2 canvasPos) noexcept { if (canvasSize.x < 0 || canvasSize.y < 0) { LUSLOG_ERROR("[Graph::Draw] Invalid canvasSize = (%f, %f)", canvasSize.x, canvasSize.y); assert(false); @@ -375,11 +376,11 @@ void Graph::Draw(ImVec2 canvasSize, ImVec2 canvasPos) { //LUSLOG_INFO("[Graph::Draw] End\n"); } -GraphOptions& Graph::GetOptions() { +GraphOptions& Graph::GetOptions() noexcept { return this->options; } -void Graph::ResetView() { +void Graph::ResetView() noexcept { this->cameraOffset = { 0, 0 }; this->zoom = 1.0f; } \ No newline at end of file diff --git a/soh/soh/Graph/Graph.h b/soh/soh/Graph/Graph.h index 3617f69c0f4..dea66028ba1 100644 --- a/soh/soh/Graph/Graph.h +++ b/soh/soh/Graph/Graph.h @@ -5,7 +5,7 @@ #include -typedef struct GraphNodeOptions { +typedef struct GraphNodeOptions final { float baseSize; struct { ImVec2 padding; @@ -17,7 +17,7 @@ typedef struct GraphNodeOptions { } label; } GraphNodeOptions; -typedef struct GraphEdgeOptions { +typedef struct GraphEdgeOptions final { float thickness; struct { float separation; @@ -30,7 +30,7 @@ typedef struct GraphEdgeOptions { } label; } GraphEdgeOptions; -typedef struct Node { +typedef struct Node final { ImVec2 position; ImVec2 displacement; std::string label; @@ -39,12 +39,12 @@ typedef struct Node { ImU32 color; ImU32 labelColor; - static Node New(ImVec2 position, std::string label, ImU32 color, ImU32 labelColor); - void Draw(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphNodeOptions options, bool scaleWithZoom); - void DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphNodeOptions options); + static Node New(ImVec2 position, std::string label, ImU32 color, ImU32 labelColor) noexcept; + void Draw(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphNodeOptions options, bool scaleWithZoom) noexcept; + void DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphNodeOptions options) noexcept; } Node; -typedef struct Edge { +typedef struct Edge final { // index of the source node in the nodes vector int src; // index of the destination node in the nodes vector @@ -55,12 +55,12 @@ typedef struct Edge { ImU32 color; ImU32 labelColor; - static Edge New(int src, int dst, std::string label, ImU32 color, ImU32 labelColor); - void Draw(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphEdgeOptions options, bool scaleWithZoom, const std::vector& nodes); - void DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphEdgeOptions options, ImVec2 a, ImVec2 b); + static Edge New(int src, int dst, std::string label, ImU32 color, ImU32 labelColor) noexcept; + void Draw(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphEdgeOptions options, bool scaleWithZoom, const std::vector& nodes) noexcept; + void DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphEdgeOptions options, ImVec2 a, ImVec2 b) noexcept; } Edge; -typedef struct GraphOptions { +typedef struct GraphOptions final { struct { float min; float max; @@ -80,19 +80,22 @@ typedef struct GraphOptions { GraphEdgeOptions edges; } GraphOptions; -class Graph { +class Graph final { public: - void Stabilize(float width, float height); - float StabilizeStep(float width, float height, float temperature); - void Draw(ImVec2 canvasSize, ImVec2 canvasPos); - GraphOptions& GetOptions(); - void ResetView(); - static Graph New(std::vector nodes, std::vector edges, GraphOptions options); + void Stabilize(float width, float height) noexcept; + float StabilizeStep(float width, float height, float temperature) noexcept; + void Draw(ImVec2 canvasSize, ImVec2 canvasPos) noexcept; + [[nodiscard("There's no point in calling the function without using the options returned")]] + GraphOptions& GetOptions() noexcept; + void ResetView() noexcept; + [[nodiscard("There's no point in calling the function without using the graph returned")]] + [[gnu::pure]] + static Graph New(std::vector nodes, std::vector edges, GraphOptions options) noexcept; private: Graph() = delete; Graph(std::vector _nodes, std::vector _edges, GraphOptions _options); - void HandleMouse(ImVec2 canvasPos); + void HandleMouse(ImVec2 canvasPos) noexcept; std::vector nodes; std::vector edges; ImVec2 cameraOffset = { 0, 0 }; From 7237c74486899e0f8e903ff34beb462385d91081 Mon Sep 17 00:00:00 2001 From: Pepe20129 <72659707+Pepe20129@users.noreply.github.com> Date: Mon, 22 Jun 2026 19:48:39 +0200 Subject: [PATCH 05/10] Optimization --- soh/soh/Graph/Graph.cpp | 43 +++++++++++++++++++---------------------- soh/soh/Graph/Graph.h | 2 ++ 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/soh/soh/Graph/Graph.cpp b/soh/soh/Graph/Graph.cpp index 8767c455229..f4edc1e7cfc 100644 --- a/soh/soh/Graph/Graph.cpp +++ b/soh/soh/Graph/Graph.cpp @@ -198,41 +198,37 @@ Graph Graph::New(std::vector nodes, std::vector edges, GraphOptions Graph::Graph(std::vector _nodes, std::vector _edges, GraphOptions _options) : nodes(_nodes), edges(_edges), options(_options) {} -[[gnu::hot]] +#ifdef _MSC_VER +#pragma optimize("t", on) +#endif +[[gnu::hot, gnu::optimize(3)]] float Graph::StabilizeStep(float width, float height, float temperature) noexcept { - float area = width * height; - float k = std::sqrt(area / nodes.size()); + const float area = width * height; + const float k = std::sqrt(area / nodes.size()); - auto repulsion_force = [&](float d) { - return this->options.forceMultipliers.repulsion * (k * k) / d; - }; - - auto attraction_force = [&](float d) { - return this->options.forceMultipliers.attraction * (d * d) / k; - }; + const float repulsionK = options.forceMultipliers.repulsion * k * k; + const float attractionK = options.forceMultipliers.attraction / k; // reset displacement for (auto& n : this->nodes) { n.displacement = {0, 0}; } - // repulsive forces + // repulsive forces O(n^2) for (size_t i = 0; i < this->nodes.size(); i += 1) { for (size_t j = i + 1; j < this->nodes.size(); j += 1) { ImVec2 delta = this->nodes[i].position - this->nodes[j].position; - float dist = std::max(std::sqrt(delta.x * delta.x + delta.y * delta.y), 0.01f); + float distSquared = std::max(delta.x * delta.x + delta.y * delta.y, 0.01f); - float force = repulsion_force(dist); + ImVec2 displacement = delta * repulsionK / distSquared; - ImVec2 dir = delta / dist; - - this->nodes[i].displacement += dir * force; - this->nodes[j].displacement -= dir * force; + this->nodes[i].displacement += displacement; + this->nodes[j].displacement -= displacement; } } - // attractive forces + // attractive forces O(n) for (const auto& e : this->edges) { auto& u = this->nodes[e.src]; auto& v = this->nodes[e.dst]; @@ -241,12 +237,10 @@ float Graph::StabilizeStep(float width, float height, float temperature) noexcep float dist = std::max(std::sqrt(delta.x * delta.x + delta.y * delta.y), 0.01f); - float force = attraction_force(dist); - - ImVec2 dir = delta / dist; + ImVec2 displacement = delta * attractionK * dist; - u.displacement -= dir * force; - v.displacement += dir * force; + u.displacement -= displacement; + v.displacement += displacement; } float totalDisplacement = 0; @@ -269,6 +263,9 @@ float Graph::StabilizeStep(float width, float height, float temperature) noexcep //return totalDisplacement; return maxDisplacement; } +#ifdef _MSC_VER +#pragma optimize("", on) +#endif void Graph::Stabilize(float width, float height) noexcept { for (float temperature = this->options.temperature.starting; temperature > this->options.temperature.ending; temperature -= this->options.temperature.decreasePerIteration) { diff --git a/soh/soh/Graph/Graph.h b/soh/soh/Graph/Graph.h index dea66028ba1..42ae87dbc1f 100644 --- a/soh/soh/Graph/Graph.h +++ b/soh/soh/Graph/Graph.h @@ -89,7 +89,9 @@ class Graph final { GraphOptions& GetOptions() noexcept; void ResetView() noexcept; [[nodiscard("There's no point in calling the function without using the graph returned")]] +#ifndef _MSC_VER // msvc complains about an unknown attribute [[gnu::pure]] +#endif static Graph New(std::vector nodes, std::vector edges, GraphOptions options) noexcept; private: From c239ffa9a56dd2bf0aec997b8adecf7d79436055 Mon Sep 17 00:00:00 2001 From: Pepe20129 <72659707+Pepe20129@users.noreply.github.com> Date: Mon, 22 Jun 2026 19:48:48 +0200 Subject: [PATCH 06/10] Update randomizer_entrance_tracker_graph.cpp --- .../randomizer_entrance_tracker_graph.cpp | 157 +++++++++++++----- 1 file changed, 116 insertions(+), 41 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.cpp b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.cpp index 2470c1ee92e..0490699118d 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.cpp @@ -32,8 +32,8 @@ const GraphOptions defaultOptions = { }, { 80.0f, - 5.0f, - 30.0f, + 10.0f, + 40.0f, }, { 20.0f, @@ -76,9 +76,7 @@ void EntranceTrackerGraphWindow::DrawElement() { displacement += this->graph.value().StabilizeStep(initialSize * 2.0f, initialSize * 2.0f, 10.0f); displacement += this->graph.value().StabilizeStep(initialSize * 2.0f, initialSize * 2.0f, 10.0f); - LUSLOG_INFO("[EntranceTrackerGraphWindow::DrawElement] Displacement = %f", displacement); - - if (displacement < 28000) { + if (displacement < 29000) { this->sufficientlyStabilized = true; } } @@ -91,50 +89,127 @@ void EntranceTrackerGraphWindow::DrawElement() { ImGui::SetNextItemAllowOverlap(); - ImGui::BeginGroup(); - if (menuOpen) { - if (ImGui::Button(ICON_FA_COG)) { - menuOpen = false; + ImVec2 panelSize = { 325, 400 }; + + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 12.0f); + ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(40, 40, 40, 255)); + + if (ImGui::BeginChild("ControlsPanel", panelSize, ImGuiChildFlags_Borders, ImGuiWindowFlags_AlwaysVerticalScrollbar)) { + if (UIWidgets::Button(ICON_FA_COG, UIWidgets::ButtonOptions().Color(THEME_COLOR).Size({ 40.0f, 40.0f }))) { + menuOpen = false; + } + + if (UIWidgets::Button("Reset View", UIWidgets::ButtonOptions().Color(THEME_COLOR))) { + this->graph.value().ResetView(); + } + + if (UIWidgets::Button("Stabilize Step", UIWidgets::ButtonOptions().Color(THEME_COLOR))) { + this->graph.value().StabilizeStep(initialSize * 2.0f, initialSize * 2.0f, 10.0f); + this->graph.value().StabilizeStep(initialSize * 2.0f, initialSize * 2.0f, 10.0f); + } + + #define CONFIG_INPUT_BOOL(option_path, text) \ + if ( \ + CVarCheckbox( \ + text, \ + CVAR_TRACKER_ENTRANCE("Graph." #option_path), \ + UIWidgets::CheckboxOptions() \ + .DefaultValue(defaultOptions.option_path) \ + .Color(THEME_COLOR) \ + ) \ + ) { \ + this->UpdateGraphOptions(); \ + } + + #define CONFIG_INPUT_INT(option_path, text, min, max) \ + if ( \ + CVarSliderInt( \ + text, \ + CVAR_TRACKER_ENTRANCE("Graph." #option_path), \ + UIWidgets::IntegerSliderOptions() \ + .Min(min) \ + .Max(max) \ + .DefaultValue(defaultOptions.option_path) \ + .Format("%.1f") \ + .Size({ 300.0f, 0.0f }) \ + .Color(THEME_COLOR) \ + ) \ + ) { \ + this->UpdateGraphOptions(); \ + } + + #define CONFIG_INPUT_FLOAT(option_path, text, min, max, step, format) \ + if ( \ + CVarSliderFloat( \ + text, \ + CVAR_TRACKER_ENTRANCE("Graph." #option_path), \ + UIWidgets::FloatSliderOptions() \ + .Min(min) \ + .Max(max) \ + .DefaultValue(defaultOptions.option_path) \ + .Format(format) \ + .Size({ 300.0f, 0.0f }) \ + .Step(step) \ + .Color(THEME_COLOR) \ + ) \ + ) { \ + this->UpdateGraphOptions(); \ + } + + UIWidgets::Separator(); + + CONFIG_INPUT_FLOAT(zoom.min, "Minimum Zoom", 0.01f, 10.0f, 0.01f, "%.2f"); + CONFIG_INPUT_FLOAT(zoom.max, "Maximum Zoom", 0.01f, 10.0f, 0.1f, "%.1f"); + + UIWidgets::Separator(); + + CONFIG_INPUT_FLOAT(forceMultipliers.repulsion, "Repulsion Force", 1.0f, 50.0f, 0.1f, "%.1f"); + CONFIG_INPUT_FLOAT(forceMultipliers.attraction, "Attraction Force", 1.0f, 50.0f, 0.1f, "%.1f"); + + UIWidgets::Separator(); + + CONFIG_INPUT_FLOAT(temperature.starting, "Initial Starting Temperature", 10.0f, 1000.0f, 0.1f, "%.1f"); + CONFIG_INPUT_FLOAT(temperature.decreasePerIteration, "Initial Temperature Decrease Per Iteration", 0.01f, 10.0f, 0.1f, "%.1f"); + CONFIG_INPUT_FLOAT(temperature.ending, "Initial Ending Temperature", 0.0f, 1000.0f, 0.1f, "%.1f"); + + UIWidgets::Separator(); + + CONFIG_INPUT_BOOL(zoom.nodesScaleWithZoom, "Nodes Scale With Zoom"); + CONFIG_INPUT_FLOAT(nodes.baseSize, "Node Base Size", 1.0f, 10.0f, 0.1f, "%.1f"); + //CONFIG_INPUT_IMVEC2(nodes.label.padding, "Node Label Padding", 0.0f, 20.0f); + CONFIG_INPUT_FLOAT(nodes.label.rounding, "Node Label Rounding", 1.0f, 10.0f, 0.1f, "%.1f"); + //CONFIG_INPUT_COLOR(nodes.label.backgroundColor, "Node Label Rounding"); + CONFIG_INPUT_BOOL(nodes.label.fadeout, "Node Label Fades out"); + CONFIG_INPUT_FLOAT(nodes.label.fadeoutCutoffLower, "Node Label Fadeout Cutoff Lower", 0.1f, 5.0f, 0.1f, "%.1f"); + CONFIG_INPUT_FLOAT(nodes.label.fadeoutCutoffUpper, "Node Label Fadeout Cutoff Upper", 0.1f, 5.0f, 0.1f, "%.1f"); + + UIWidgets::Separator(); + + CONFIG_INPUT_BOOL(zoom.edgesScaleWithZoom, "Edges Scale With Zoom"); + CONFIG_INPUT_FLOAT(edges.thickness, "Edge Thickness", 1.0f, 10.0f, 0.1f, "%.1f"); + CONFIG_INPUT_FLOAT(edges.label.separation, "Edge Label Separation", 0.0f, 20.0f, 0.1f, "%.1f"); + //CONFIG_INPUT_IMVEC2(edges.label.padding, "Edge Label Padding", 0.0f, 20.0f); + CONFIG_INPUT_FLOAT(edges.label.rounding, "Edge Label Rounding", 1.0f, 10.0f, 0.1f, "%.1f"); + //CONFIG_INPUT_COLOR(edges.label.backgroundColor, "Edge Label Rounding"); + CONFIG_INPUT_BOOL(edges.label.fadeout, "Edge Label Fades out"); + CONFIG_INPUT_FLOAT(edges.label.fadeoutCutoffLower, "Edge Label Fadeout Cutoff Lower", 0.1f, 5.0f, 0.1f, "%.1f"); + CONFIG_INPUT_FLOAT(edges.label.fadeoutCutoffUpper, "Edge Label Fadeout Cutoff Upper", 0.1f, 5.0f, 0.1f, "%.1f"); + + #undef CONFIG_INPUT_BOOL + #undef CONFIG_INPUT_INT + #undef CONFIG_INPUT_FLOAT } - if (ImGui::Button("Reset View")) { - this->graph.value().ResetView(); - } - - #define TEST_FLOAT(option_path, text, min, max) \ - if ( \ - CVarSliderFloat( \ - text, \ - CVAR_TRACKER_ENTRANCE("Graph." #option_path), \ - UIWidgets::FloatSliderOptions() \ - .Min(min) \ - .Max(max) \ - .DefaultValue(CVarGetFloat(CVAR_TRACKER_ENTRANCE("Graph." #option_path), defaultOptions.option_path)) \ - .Format("%.1f") \ - .Size({ 300.0f, 0.0f }) \ - .Step(0.1f) \ - .Color(THEME_COLOR) \ - ) \ - ) { \ - this->UpdateGraphOptions(); \ - } - - TEST_FLOAT(forceMultipliers.repulsion, "Repulsion Force", 1.0f, 50.0f); - TEST_FLOAT(forceMultipliers.attraction, "Attraction Force", 1.0f, 50.0f); - - TEST_FLOAT(temperature.starting, "Starting Temperature", 10.0f, 1000.0f); - TEST_FLOAT(temperature.decreasePerIteration, "Temperature Decrease Per Iteration", 0.01f, 10.0f); - - TEST_FLOAT(edges.thickness, "Edge Thickness", 1.0f, 10.0f); + ImGui::PopStyleColor(); + ImGui::PopStyleVar(); + ImGui::EndChild(); } else { - if (ImGui::Button(ICON_FA_COG)) { + if (UIWidgets::Button(ICON_FA_COG, UIWidgets::ButtonOptions().Color(THEME_COLOR).Size({ 40.0f, 40.0f }))) { menuOpen = true; } } - - ImGui::EndGroup(); } Trackers::EndFloatWindows(); } From 70ad8c033ce9738b83d11cb08ee723d7d4ab6442 Mon Sep 17 00:00:00 2001 From: Pepe20129 <72659707+Pepe20129@users.noreply.github.com> Date: Mon, 22 Jun 2026 21:08:41 +0200 Subject: [PATCH 07/10] Update Graph.cpp --- soh/soh/Graph/Graph.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/soh/soh/Graph/Graph.cpp b/soh/soh/Graph/Graph.cpp index f4edc1e7cfc..cc254184db3 100644 --- a/soh/soh/Graph/Graph.cpp +++ b/soh/soh/Graph/Graph.cpp @@ -200,8 +200,9 @@ Graph::Graph(std::vector _nodes, std::vector _edges, GraphOptions _o #ifdef _MSC_VER #pragma optimize("t", on) -#endif +#else // msvc complains about unknown attributes [[gnu::hot, gnu::optimize(3)]] +#endif float Graph::StabilizeStep(float width, float height, float temperature) noexcept { const float area = width * height; const float k = std::sqrt(area / nodes.size()); From 7005a432f824e58c60c066c4fe964767d140ac2a Mon Sep 17 00:00:00 2001 From: Pepe20129 <72659707+Pepe20129@users.noreply.github.com> Date: Mon, 22 Jun 2026 22:00:40 +0200 Subject: [PATCH 08/10] Touchups --- .../randomizer_entrance_tracker_graph.cpp | 58 ++++++++++++++++--- .../randomizer_entrance_tracker_graph.h | 2 + soh/soh/Graph/Graph.cpp | 4 ++ soh/soh/Graph/Graph.h | 1 + soh/soh/SohGui/UIWidgets.hpp | 6 ++ 5 files changed, 64 insertions(+), 7 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.cpp b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.cpp index 0490699118d..daba7c916ad 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.cpp @@ -104,7 +104,17 @@ void EntranceTrackerGraphWindow::DrawElement() { this->graph.value().ResetView(); } - if (UIWidgets::Button("Stabilize Step", UIWidgets::ButtonOptions().Color(THEME_COLOR))) { + if (this->sufficientlyStabilized) { + if (UIWidgets::Button("Continue Stabilization", UIWidgets::ButtonOptions().Color(UIWidgets::Colors::Green))) { + this->sufficientlyStabilized = false; + } + } else { + if (UIWidgets::Button("Stop Stabilization", UIWidgets::ButtonOptions().Color(UIWidgets::Colors::Red))) { + this->sufficientlyStabilized = true; + } + } + + if (UIWidgets::Button("Stabilize Step", UIWidgets::ButtonOptions().Color(THEME_COLOR).Disabled(!sufficientlyStabilized))) { this->graph.value().StabilizeStep(initialSize * 2.0f, initialSize * 2.0f, 10.0f); this->graph.value().StabilizeStep(initialSize * 2.0f, initialSize * 2.0f, 10.0f); } @@ -311,14 +321,19 @@ ImU32 GetColorForAreas(std::set areas) { uint64_t randoState = 0; void EntranceTrackerGraphWindow::InitElement() { - ImU32 labelColor = IM_COL32_WHITE; - ImU32 edgeColor = IM_COL32_WHITE; + RegionTable_Init(); + + this->InitGraph(true); +} + +const ImU32 labelColor = IM_COL32_WHITE; +const ImU32 edgeColor = IM_COL32_WHITE; + +void EntranceTrackerGraphWindow::InitGraph(bool initialStabilization) { std::vector nodes = {}; std::vector edges = {}; - RegionTable_Init(); - for (const auto& region : areaTable) { if (region.randomizerRegionKey == RR_NONE) { continue; @@ -345,7 +360,11 @@ void EntranceTrackerGraphWindow::InitElement() { this->UpdateGraphOptions(); - this->graph.value().Stabilize(initialSize * 2.0f, initialSize * 2.0f); + if (initialStabilization) { + this->graph.value().Stabilize(initialSize * 2.0f, initialSize * 2.0f); + } + + this->sufficientlyStabilized = false; } void EntranceTrackerGraphWindow::UpdateGraphOptions() { @@ -400,4 +419,29 @@ void EntranceTrackerGraphWindow::UpdateGraphOptions() { #undef UPDATE_OPTION_PATH_IMU32 this->sufficientlyStabilized = false; -} \ No newline at end of file +} + +void EntranceTrackerGraphWindow::UpdateEdges() { + if (!this->graph.has_value()) { + return; + } + + std::vector edges = {}; + + for (const auto& region : areaTable) { + for (const auto& exit : region.exits) { + // -1 due to skipping RR_NONE + edges.push_back(Edge::New(exit.GetParentRegionKey() - 1, exit.GetConnectedRegionKey() - 1, exit.GetConditionStr(), edgeColor, labelColor)); + } + } + + this->graph.value().ReplaceEdges(edges); + + this->UpdateGraphOptions(); +} + +void ReInitGraph() { + std::dynamic_pointer_cast(Ship::Context::GetRawInstance()->GetWindow()->GetGui()->GetGuiWindow("Entrance Tracker Graph"))->UpdateEdges(); +} + +static RegisterShipInitFunc initFunc(ReInitGraph, { "IS_RANDO" }); \ No newline at end of file diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.h b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.h index f5d77d6eb08..9ea7441ff71 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.h +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.h @@ -12,7 +12,9 @@ class EntranceTrackerGraphWindow final : public Ship::GuiWindow { void InitElement() override; void DrawElement() override; void UpdateElement() override{}; + void InitGraph(bool initialStabilization); void UpdateGraphOptions(); + void UpdateEdges(); private: std::optional graph = std::nullopt; diff --git a/soh/soh/Graph/Graph.cpp b/soh/soh/Graph/Graph.cpp index cc254184db3..21740ec5cd7 100644 --- a/soh/soh/Graph/Graph.cpp +++ b/soh/soh/Graph/Graph.cpp @@ -381,4 +381,8 @@ GraphOptions& Graph::GetOptions() noexcept { void Graph::ResetView() noexcept { this->cameraOffset = { 0, 0 }; this->zoom = 1.0f; +} + +void Graph::ReplaceEdges(std::vector edges) noexcept { + this->edges = edges; } \ No newline at end of file diff --git a/soh/soh/Graph/Graph.h b/soh/soh/Graph/Graph.h index 42ae87dbc1f..1ce401d1f73 100644 --- a/soh/soh/Graph/Graph.h +++ b/soh/soh/Graph/Graph.h @@ -88,6 +88,7 @@ class Graph final { [[nodiscard("There's no point in calling the function without using the options returned")]] GraphOptions& GetOptions() noexcept; void ResetView() noexcept; + void ReplaceEdges(std::vector edges) noexcept; [[nodiscard("There's no point in calling the function without using the graph returned")]] #ifndef _MSC_VER // msvc complains about an unknown attribute [[gnu::pure]] diff --git a/soh/soh/SohGui/UIWidgets.hpp b/soh/soh/SohGui/UIWidgets.hpp index f9902f97d8b..65233853233 100644 --- a/soh/soh/SohGui/UIWidgets.hpp +++ b/soh/soh/SohGui/UIWidgets.hpp @@ -159,6 +159,12 @@ struct ButtonOptions : WidgetOptions { color = color_; return *this; } + + ButtonOptions& Disabled(bool disabled_) { + WidgetOptions::disabled = disabled_; + return *this; + } + ButtonOptions& DisabledTooltip(const char* disabledTooltip_) { WidgetOptions::disabledTooltip = disabledTooltip_; return *this; From 7b5928d9e0b574ebc653f2e6e06b1005715e1823 Mon Sep 17 00:00:00 2001 From: Pepe20129 <72659707+Pepe20129@users.noreply.github.com> Date: Mon, 22 Jun 2026 23:02:48 +0200 Subject: [PATCH 09/10] clang --- .../randomizer_entrance_tracker_graph.cpp | 173 +++++++++--------- .../randomizer_entrance_tracker_graph.h | 26 +-- soh/soh/Graph/Graph.cpp | 133 ++++++-------- soh/soh/Graph/Graph.h | 55 +++--- 4 files changed, 187 insertions(+), 200 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.cpp b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.cpp index daba7c916ad..7e751a644e0 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.cpp @@ -67,7 +67,8 @@ void EntranceTrackerGraphWindow::DrawElement() { } Color_RGBA8 bgColor = { 0, 0, 0, 255 }; - if (Trackers::BeginFloatWindows("Entrance Tracker Graph", mIsVisible, bgColor, TRACKER_WINDOW_WINDOW, true, ImGuiWindowFlags_NoScrollbar)) { + if (Trackers::BeginFloatWindows("Entrance Tracker Graph", mIsVisible, bgColor, TRACKER_WINDOW_WINDOW, true, + ImGuiWindowFlags_NoScrollbar)) { ImVec2 canvasPos = ImGui::GetCursorScreenPos(); ImVec2 canvasSize = ImGui::GetContentRegionAvail(); @@ -95,8 +96,10 @@ void EntranceTrackerGraphWindow::DrawElement() { ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 12.0f); ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(40, 40, 40, 255)); - if (ImGui::BeginChild("ControlsPanel", panelSize, ImGuiChildFlags_Borders, ImGuiWindowFlags_AlwaysVerticalScrollbar)) { - if (UIWidgets::Button(ICON_FA_COG, UIWidgets::ButtonOptions().Color(THEME_COLOR).Size({ 40.0f, 40.0f }))) { + if (ImGui::BeginChild("ControlsPanel", panelSize, ImGuiChildFlags_Borders, + ImGuiWindowFlags_AlwaysVerticalScrollbar)) { + if (UIWidgets::Button(ICON_FA_COG, + UIWidgets::ButtonOptions().Color(THEME_COLOR).Size({ 40.0f, 40.0f }))) { menuOpen = false; } @@ -105,67 +108,54 @@ void EntranceTrackerGraphWindow::DrawElement() { } if (this->sufficientlyStabilized) { - if (UIWidgets::Button("Continue Stabilization", UIWidgets::ButtonOptions().Color(UIWidgets::Colors::Green))) { + if (UIWidgets::Button("Continue Stabilization", + UIWidgets::ButtonOptions().Color(UIWidgets::Colors::Green))) { this->sufficientlyStabilized = false; } } else { - if (UIWidgets::Button("Stop Stabilization", UIWidgets::ButtonOptions().Color(UIWidgets::Colors::Red))) { + if (UIWidgets::Button("Stop Stabilization", + UIWidgets::ButtonOptions().Color(UIWidgets::Colors::Red))) { this->sufficientlyStabilized = true; } } - if (UIWidgets::Button("Stabilize Step", UIWidgets::ButtonOptions().Color(THEME_COLOR).Disabled(!sufficientlyStabilized))) { + if (UIWidgets::Button( + "Stabilize Step", + UIWidgets::ButtonOptions().Color(THEME_COLOR).Disabled(!sufficientlyStabilized))) { this->graph.value().StabilizeStep(initialSize * 2.0f, initialSize * 2.0f, 10.0f); this->graph.value().StabilizeStep(initialSize * 2.0f, initialSize * 2.0f, 10.0f); } - #define CONFIG_INPUT_BOOL(option_path, text) \ - if ( \ - CVarCheckbox( \ - text, \ - CVAR_TRACKER_ENTRANCE("Graph." #option_path), \ - UIWidgets::CheckboxOptions() \ - .DefaultValue(defaultOptions.option_path) \ - .Color(THEME_COLOR) \ - ) \ - ) { \ - this->UpdateGraphOptions(); \ - } +#define CONFIG_INPUT_BOOL(option_path, text) \ + if (CVarCheckbox(text, CVAR_TRACKER_ENTRANCE("Graph." #option_path), \ + UIWidgets::CheckboxOptions().DefaultValue(defaultOptions.option_path).Color(THEME_COLOR))) { \ + this->UpdateGraphOptions(); \ + } - #define CONFIG_INPUT_INT(option_path, text, min, max) \ - if ( \ - CVarSliderInt( \ - text, \ - CVAR_TRACKER_ENTRANCE("Graph." #option_path), \ - UIWidgets::IntegerSliderOptions() \ - .Min(min) \ - .Max(max) \ - .DefaultValue(defaultOptions.option_path) \ - .Format("%.1f") \ - .Size({ 300.0f, 0.0f }) \ - .Color(THEME_COLOR) \ - ) \ - ) { \ - this->UpdateGraphOptions(); \ - } +#define CONFIG_INPUT_INT(option_path, text, min, max) \ + if (CVarSliderInt(text, CVAR_TRACKER_ENTRANCE("Graph." #option_path), \ + UIWidgets::IntegerSliderOptions() \ + .Min(min) \ + .Max(max) \ + .DefaultValue(defaultOptions.option_path) \ + .Format("%.1f") \ + .Size({ 300.0f, 0.0f }) \ + .Color(THEME_COLOR))) { \ + this->UpdateGraphOptions(); \ + } - #define CONFIG_INPUT_FLOAT(option_path, text, min, max, step, format) \ - if ( \ - CVarSliderFloat( \ - text, \ - CVAR_TRACKER_ENTRANCE("Graph." #option_path), \ - UIWidgets::FloatSliderOptions() \ - .Min(min) \ - .Max(max) \ - .DefaultValue(defaultOptions.option_path) \ - .Format(format) \ - .Size({ 300.0f, 0.0f }) \ - .Step(step) \ - .Color(THEME_COLOR) \ - ) \ - ) { \ - this->UpdateGraphOptions(); \ - } +#define CONFIG_INPUT_FLOAT(option_path, text, min, max, step, format) \ + if (CVarSliderFloat(text, CVAR_TRACKER_ENTRANCE("Graph." #option_path), \ + UIWidgets::FloatSliderOptions() \ + .Min(min) \ + .Max(max) \ + .DefaultValue(defaultOptions.option_path) \ + .Format(format) \ + .Size({ 300.0f, 0.0f }) \ + .Step(step) \ + .Color(THEME_COLOR))) { \ + this->UpdateGraphOptions(); \ + } UIWidgets::Separator(); @@ -180,35 +170,40 @@ void EntranceTrackerGraphWindow::DrawElement() { UIWidgets::Separator(); CONFIG_INPUT_FLOAT(temperature.starting, "Initial Starting Temperature", 10.0f, 1000.0f, 0.1f, "%.1f"); - CONFIG_INPUT_FLOAT(temperature.decreasePerIteration, "Initial Temperature Decrease Per Iteration", 0.01f, 10.0f, 0.1f, "%.1f"); + CONFIG_INPUT_FLOAT(temperature.decreasePerIteration, "Initial Temperature Decrease Per Iteration", + 0.01f, 10.0f, 0.1f, "%.1f"); CONFIG_INPUT_FLOAT(temperature.ending, "Initial Ending Temperature", 0.0f, 1000.0f, 0.1f, "%.1f"); UIWidgets::Separator(); CONFIG_INPUT_BOOL(zoom.nodesScaleWithZoom, "Nodes Scale With Zoom"); CONFIG_INPUT_FLOAT(nodes.baseSize, "Node Base Size", 1.0f, 10.0f, 0.1f, "%.1f"); - //CONFIG_INPUT_IMVEC2(nodes.label.padding, "Node Label Padding", 0.0f, 20.0f); + // CONFIG_INPUT_IMVEC2(nodes.label.padding, "Node Label Padding", 0.0f, 20.0f); CONFIG_INPUT_FLOAT(nodes.label.rounding, "Node Label Rounding", 1.0f, 10.0f, 0.1f, "%.1f"); - //CONFIG_INPUT_COLOR(nodes.label.backgroundColor, "Node Label Rounding"); + // CONFIG_INPUT_COLOR(nodes.label.backgroundColor, "Node Label Rounding"); CONFIG_INPUT_BOOL(nodes.label.fadeout, "Node Label Fades out"); - CONFIG_INPUT_FLOAT(nodes.label.fadeoutCutoffLower, "Node Label Fadeout Cutoff Lower", 0.1f, 5.0f, 0.1f, "%.1f"); - CONFIG_INPUT_FLOAT(nodes.label.fadeoutCutoffUpper, "Node Label Fadeout Cutoff Upper", 0.1f, 5.0f, 0.1f, "%.1f"); + CONFIG_INPUT_FLOAT(nodes.label.fadeoutCutoffLower, "Node Label Fadeout Cutoff Lower", 0.1f, 5.0f, 0.1f, + "%.1f"); + CONFIG_INPUT_FLOAT(nodes.label.fadeoutCutoffUpper, "Node Label Fadeout Cutoff Upper", 0.1f, 5.0f, 0.1f, + "%.1f"); UIWidgets::Separator(); CONFIG_INPUT_BOOL(zoom.edgesScaleWithZoom, "Edges Scale With Zoom"); CONFIG_INPUT_FLOAT(edges.thickness, "Edge Thickness", 1.0f, 10.0f, 0.1f, "%.1f"); CONFIG_INPUT_FLOAT(edges.label.separation, "Edge Label Separation", 0.0f, 20.0f, 0.1f, "%.1f"); - //CONFIG_INPUT_IMVEC2(edges.label.padding, "Edge Label Padding", 0.0f, 20.0f); + // CONFIG_INPUT_IMVEC2(edges.label.padding, "Edge Label Padding", 0.0f, 20.0f); CONFIG_INPUT_FLOAT(edges.label.rounding, "Edge Label Rounding", 1.0f, 10.0f, 0.1f, "%.1f"); - //CONFIG_INPUT_COLOR(edges.label.backgroundColor, "Edge Label Rounding"); + // CONFIG_INPUT_COLOR(edges.label.backgroundColor, "Edge Label Rounding"); CONFIG_INPUT_BOOL(edges.label.fadeout, "Edge Label Fades out"); - CONFIG_INPUT_FLOAT(edges.label.fadeoutCutoffLower, "Edge Label Fadeout Cutoff Lower", 0.1f, 5.0f, 0.1f, "%.1f"); - CONFIG_INPUT_FLOAT(edges.label.fadeoutCutoffUpper, "Edge Label Fadeout Cutoff Upper", 0.1f, 5.0f, 0.1f, "%.1f"); - - #undef CONFIG_INPUT_BOOL - #undef CONFIG_INPUT_INT - #undef CONFIG_INPUT_FLOAT + CONFIG_INPUT_FLOAT(edges.label.fadeoutCutoffLower, "Edge Label Fadeout Cutoff Lower", 0.1f, 5.0f, 0.1f, + "%.1f"); + CONFIG_INPUT_FLOAT(edges.label.fadeoutCutoffUpper, "Edge Label Fadeout Cutoff Upper", 0.1f, 5.0f, 0.1f, + "%.1f"); + +#undef CONFIG_INPUT_BOOL +#undef CONFIG_INPUT_INT +#undef CONFIG_INPUT_FLOAT } ImGui::PopStyleColor(); @@ -231,13 +226,13 @@ ImU32 GetColorForArea(RandomizerArea area) { return IM_COL32(0xD8, 0xD2, 0xE8, 0xFF); case RA_KOKIRI_FOREST: return IM_COL32(0xB8, 0xE3, 0xC2, 0xFF); - case RA_THE_LOST_WOODS : + case RA_THE_LOST_WOODS: return IM_COL32(0xAF, 0xCF, 0xA8, 0xFF); case RA_SACRED_FOREST_MEADOW: return IM_COL32(0xCB, 0xE8, 0xB5, 0xFF); case RA_HYRULE_FIELD: return IM_COL32(0xDD, 0xE8, 0xA8, 0xFF); - case RA_LAKE_HYLIA : + case RA_LAKE_HYLIA: return IM_COL32(0xA9, 0xDC, 0xE3, 0xFF); case RA_GERUDO_VALLEY: return IM_COL32(0xF2, 0xC6, 0xA0, 0xFF); @@ -247,15 +242,15 @@ ImU32 GetColorForArea(RandomizerArea area) { return IM_COL32(0xC9, 0xB2, 0xC8, 0xFF); case RA_DESERT_COLOSSUS: return IM_COL32(0xEB, 0xCB, 0x8B, 0xFF); - case RA_THE_MARKET : + case RA_THE_MARKET: return IM_COL32(0xF4, 0xB7, 0xC5, 0xFF); - case RA_TEMPLE_OF_TIME : + case RA_TEMPLE_OF_TIME: return IM_COL32(0xED, 0xE2, 0xC6, 0xFF); case RA_HYRULE_CASTLE: return IM_COL32(0xB9, 0xC1, 0xE8, 0xFF); case RA_OUTSIDE_GANONS_CASTLE: return IM_COL32(0xA8, 0x9B, 0xBE, 0xFF); - case RA_CASTLE_GROUNDS : + case RA_CASTLE_GROUNDS: return IM_COL32(0xB8, 0xCB, 0xE8, 0xFF); case RA_KAKARIKO_VILLAGE: return IM_COL32(0xE7, 0xB2, 0x8D, 0xFF); @@ -346,13 +341,17 @@ void EntranceTrackerGraphWindow::InitGraph(bool initialStabilization) { } // random starting positions to allow the forces to move them - nodes.push_back(Node::New({ static_cast(ShipUtils::next32(&randoState)) / INT32_MAX * initialSize - initialSize / 2, static_cast(ShipUtils::next32(&randoState)) / INT32_MAX * initialSize - initialSize / 2 }, std::string(regionString.value()), GetColorForAreas(region.areas), labelColor)); + nodes.push_back(Node::New( + { static_cast(ShipUtils::next32(&randoState)) / INT32_MAX * initialSize - initialSize / 2, + static_cast(ShipUtils::next32(&randoState)) / INT32_MAX * initialSize - initialSize / 2 }, + std::string(regionString.value()), GetColorForAreas(region.areas), labelColor)); } for (const auto& region : areaTable) { for (const auto& exit : region.exits) { // -1 due to skipping RR_NONE - edges.push_back(Edge::New(exit.GetParentRegionKey() - 1, exit.GetConnectedRegionKey() - 1, exit.GetConditionStr(), edgeColor, labelColor)); + edges.push_back(Edge::New(exit.GetParentRegionKey() - 1, exit.GetConnectedRegionKey() - 1, + exit.GetConditionStr(), edgeColor, labelColor)); } } @@ -374,14 +373,19 @@ void EntranceTrackerGraphWindow::UpdateGraphOptions() { GraphOptions& currentGraphOptions = this->graph.value().GetOptions(); - #define UPDATE_OPTION_PATH_SIMPLE(option_path, type) currentGraphOptions.option_path = CVarGet ## type (CVAR_TRACKER_ENTRANCE("Graph." #option_path), defaultOptions.option_path) - #define UPDATE_OPTION_PATH_INT(option_path) UPDATE_OPTION_PATH_SIMPLE(option_path, Integer) - #define UPDATE_OPTION_PATH_FLOAT(option_path) UPDATE_OPTION_PATH_SIMPLE(option_path, Float) - #define UPDATE_OPTION_PATH_IMVEC2(option_path) currentGraphOptions.option_path = { \ +#define UPDATE_OPTION_PATH_SIMPLE(option_path, type) \ + currentGraphOptions.option_path = \ + CVarGet##type(CVAR_TRACKER_ENTRANCE("Graph." #option_path), defaultOptions.option_path) +#define UPDATE_OPTION_PATH_INT(option_path) UPDATE_OPTION_PATH_SIMPLE(option_path, Integer) +#define UPDATE_OPTION_PATH_FLOAT(option_path) UPDATE_OPTION_PATH_SIMPLE(option_path, Float) +#define UPDATE_OPTION_PATH_IMVEC2(option_path) \ + currentGraphOptions.option_path = { \ CVarGetFloat(CVAR_TRACKER_ENTRANCE("Graph." #option_path ".x"), defaultOptions.option_path.x), \ CVarGetFloat(CVAR_TRACKER_ENTRANCE("Graph." #option_path ".y"), defaultOptions.option_path.y), \ } - #define UPDATE_OPTION_PATH_IMU32(option_path) currentGraphOptions.option_path = static_cast(CVarGetInteger(CVAR_TRACKER_ENTRANCE("Graph." #option_path), defaultOptions.option_path)) +#define UPDATE_OPTION_PATH_IMU32(option_path) \ + currentGraphOptions.option_path = \ + static_cast(CVarGetInteger(CVAR_TRACKER_ENTRANCE("Graph." #option_path), defaultOptions.option_path)) UPDATE_OPTION_PATH_FLOAT(zoom.min); UPDATE_OPTION_PATH_FLOAT(zoom.max); @@ -412,11 +416,11 @@ void EntranceTrackerGraphWindow::UpdateGraphOptions() { UPDATE_OPTION_PATH_FLOAT(edges.label.fadeoutCutoffLower); UPDATE_OPTION_PATH_FLOAT(edges.label.fadeoutCutoffUpper); - #undef UPDATE_OPTION_PATH_SIMPLE - #undef UPDATE_OPTION_PATH_INT - #undef UPDATE_OPTION_PATH_FLOAT - #undef UPDATE_OPTION_PATH_IMVEC2 - #undef UPDATE_OPTION_PATH_IMU32 +#undef UPDATE_OPTION_PATH_SIMPLE +#undef UPDATE_OPTION_PATH_INT +#undef UPDATE_OPTION_PATH_FLOAT +#undef UPDATE_OPTION_PATH_IMVEC2 +#undef UPDATE_OPTION_PATH_IMU32 this->sufficientlyStabilized = false; } @@ -431,7 +435,8 @@ void EntranceTrackerGraphWindow::UpdateEdges() { for (const auto& region : areaTable) { for (const auto& exit : region.exits) { // -1 due to skipping RR_NONE - edges.push_back(Edge::New(exit.GetParentRegionKey() - 1, exit.GetConnectedRegionKey() - 1, exit.GetConditionStr(), edgeColor, labelColor)); + edges.push_back(Edge::New(exit.GetParentRegionKey() - 1, exit.GetConnectedRegionKey() - 1, + exit.GetConditionStr(), edgeColor, labelColor)); } } @@ -441,7 +446,9 @@ void EntranceTrackerGraphWindow::UpdateEdges() { } void ReInitGraph() { - std::dynamic_pointer_cast(Ship::Context::GetRawInstance()->GetWindow()->GetGui()->GetGuiWindow("Entrance Tracker Graph"))->UpdateEdges(); + std::dynamic_pointer_cast( + Ship::Context::GetRawInstance()->GetWindow()->GetGui()->GetGuiWindow("Entrance Tracker Graph")) + ->UpdateEdges(); } static RegisterShipInitFunc initFunc(ReInitGraph, { "IS_RANDO" }); \ No newline at end of file diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.h b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.h index 9ea7441ff71..bc005d326aa 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.h +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.h @@ -5,19 +5,19 @@ #include class EntranceTrackerGraphWindow final : public Ship::GuiWindow { - public: - using GuiWindow::GuiWindow; - void Draw() override; + public: + using GuiWindow::GuiWindow; + void Draw() override; - void InitElement() override; - void DrawElement() override; - void UpdateElement() override{}; - void InitGraph(bool initialStabilization); - void UpdateGraphOptions(); - void UpdateEdges(); + void InitElement() override; + void DrawElement() override; + void UpdateElement() override{}; + void InitGraph(bool initialStabilization); + void UpdateGraphOptions(); + void UpdateEdges(); - private: - std::optional graph = std::nullopt; - bool menuOpen = false; - bool sufficientlyStabilized = false; + private: + std::optional graph = std::nullopt; + bool menuOpen = false; + bool sufficientlyStabilized = false; }; \ No newline at end of file diff --git a/soh/soh/Graph/Graph.cpp b/soh/soh/Graph/Graph.cpp index 21740ec5cd7..3eda0bd22a8 100644 --- a/soh/soh/Graph/Graph.cpp +++ b/soh/soh/Graph/Graph.cpp @@ -19,13 +19,12 @@ Rect GetVisibleWorldRect(ImVec2 canvasSize, ImVec2 canvasPos, ImVec2 cameraOffse return r; } -static bool IsNodeVisible(const Node& node, const Rect& view, float zoom, GraphNodeOptions options, bool scaleWithZoom) noexcept { +static bool IsNodeVisible(const Node& node, const Rect& view, float zoom, GraphNodeOptions options, + bool scaleWithZoom) noexcept { float radius = options.baseSize * (scaleWithZoom ? zoom : 1); - return !(node.position.x + radius < view.min.x || - node.position.x - radius > view.max.x || - node.position.y + radius < view.min.y || - node.position.y - radius > view.max.y); + return !(node.position.x + radius < view.min.x || node.position.x - radius > view.max.x || + node.position.y + radius < view.min.y || node.position.y - radius > view.max.y); } static bool IsEdgeVisible(ImVec2 a, ImVec2 b, const Rect& view) noexcept { @@ -35,10 +34,7 @@ static bool IsEdgeVisible(ImVec2 a, ImVec2 b, const Rect& view) noexcept { float minY = std::min(a.y, b.y); float maxY = std::max(a.y, b.y); - return !(maxX < view.min.x || - minX > view.max.x || - maxY < view.min.y || - minY > view.max.y); + return !(maxX < view.min.x || minX > view.max.x || maxY < view.min.y || minY > view.max.y); } ImVec2 WorldSpaceToScreenSpace(ImVec2 vec, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom) noexcept { @@ -49,22 +45,15 @@ Node Node::New(ImVec2 position, std::string label, ImU32 color, ImU32 labelColor return { position, { 0, 0 }, label, { -1, -1 }, color, labelColor }; } -void Node::Draw(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphNodeOptions options, bool scaleWithZoom) noexcept { - ImVec2 screenPos = WorldSpaceToScreenSpace( - this->position, - canvasPos, - cameraOffset, - zoom - ); +void Node::Draw(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphNodeOptions options, + bool scaleWithZoom) noexcept { + ImVec2 screenPos = WorldSpaceToScreenSpace(this->position, canvasPos, cameraOffset, zoom); - draw->AddCircleFilled( - screenPos, - options.baseSize * (scaleWithZoom ? zoom : 1), - this->color - ); + draw->AddCircleFilled(screenPos, options.baseSize * (scaleWithZoom ? zoom : 1), this->color); } -void Node::DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphNodeOptions options) noexcept { +void Node::DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, + GraphNodeOptions options) noexcept { if (this->labelSize.x == -1) { this->labelSize = ImGui::CalcTextSize(label.c_str()); } @@ -72,12 +61,16 @@ void Node::DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, fl ImU32 labelBackgroundColor = options.label.backgroundColor; if (options.label.fadeout) { - float alpha = (std::clamp(zoom, options.label.fadeoutCutoffLower, options.label.fadeoutCutoffUpper) - options.label.fadeoutCutoffLower) / (options.label.fadeoutCutoffUpper - options.label.fadeoutCutoffLower) * 255; + float alpha = (std::clamp(zoom, options.label.fadeoutCutoffLower, options.label.fadeoutCutoffUpper) - + options.label.fadeoutCutoffLower) / + (options.label.fadeoutCutoffUpper - options.label.fadeoutCutoffLower) * 255; ImU32 byteAlpha = static_cast(alpha); this->labelColor &= ~IM_COL32_A_MASK; this->labelColor |= byteAlpha << IM_COL32_A_SHIFT; - float alphaBackground = (std::clamp(zoom, options.label.fadeoutCutoffLower, options.label.fadeoutCutoffUpper) - options.label.fadeoutCutoffLower) / (options.label.fadeoutCutoffUpper - options.label.fadeoutCutoffLower) * 220; + float alphaBackground = (std::clamp(zoom, options.label.fadeoutCutoffLower, options.label.fadeoutCutoffUpper) - + options.label.fadeoutCutoffLower) / + (options.label.fadeoutCutoffUpper - options.label.fadeoutCutoffLower) * 220; if (alpha == 0 && alphaBackground == 0) { return; } @@ -86,40 +79,29 @@ void Node::DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, fl labelBackgroundColor |= byteAlphaBackground << IM_COL32_A_SHIFT; } - ImVec2 screenPos = WorldSpaceToScreenSpace( - this->position, - canvasPos, - cameraOffset, - zoom - ); + ImVec2 screenPos = WorldSpaceToScreenSpace(this->position, canvasPos, cameraOffset, zoom); ImVec2 min = screenPos - this->labelSize * 0.5f - options.label.padding; ImVec2 max = screenPos + this->labelSize * 0.5f + options.label.padding; - draw->AddRectFilled( - min, - max, - labelBackgroundColor, - options.label.rounding - ); + draw->AddRectFilled(min, max, labelBackgroundColor, options.label.rounding); - draw->AddText( - screenPos - this->labelSize * 0.5f, - this->labelColor, - this->label.c_str() - ); + draw->AddText(screenPos - this->labelSize * 0.5f, this->labelColor, this->label.c_str()); } Edge Edge::New(int src, int dst, std::string label, ImU32 color, ImU32 labelColor) noexcept { return { src, dst, label, { -1, -1 }, color, labelColor }; } -void Edge::Draw(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphEdgeOptions options, bool scaleWithZoom, const std::vector& nodes) noexcept { - //LUSLOG_INFO("[Edge::Draw] Drawing edge: src = %d | dst = %d | label = \"%s\"", this->src, this->dst, this->label.c_str()); +void Edge::Draw(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphEdgeOptions options, + bool scaleWithZoom, const std::vector& nodes) noexcept { + // LUSLOG_INFO("[Edge::Draw] Drawing edge: src = %d | dst = %d | label = \"%s\"", this->src, this->dst, + // this->label.c_str()); if (this->src >= nodes.size() || this->dst >= nodes.size()) { - LUSLOG_ERROR("[Edge::Draw] Invalid src (%d) or dst (%d) for node list of size (%d)", this->src, this->dst, nodes.size()); + LUSLOG_ERROR("[Edge::Draw] Invalid src (%d) or dst (%d) for node list of size (%d)", this->src, this->dst, + nodes.size()); assert(false); return; } @@ -127,15 +109,13 @@ void Edge::Draw(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float z ImVec2 a = nodes[this->src].position; ImVec2 b = nodes[this->dst].position; - draw->AddLine( - WorldSpaceToScreenSpace(a, canvasPos, cameraOffset, zoom), - WorldSpaceToScreenSpace(b, canvasPos, cameraOffset, zoom), - this->color, - options.thickness * (scaleWithZoom ? zoom : 1) - ); + draw->AddLine(WorldSpaceToScreenSpace(a, canvasPos, cameraOffset, zoom), + WorldSpaceToScreenSpace(b, canvasPos, cameraOffset, zoom), this->color, + options.thickness * (scaleWithZoom ? zoom : 1)); } -void Edge::DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphEdgeOptions options, ImVec2 a, ImVec2 b) noexcept { +void Edge::DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphEdgeOptions options, + ImVec2 a, ImVec2 b) noexcept { ImVec2 delta = b - a; float len = std::sqrt(delta.x * delta.x + delta.y * delta.y); @@ -144,10 +124,7 @@ void Edge::DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, fl return; } - ImVec2 normal = { - -delta.y / len, - delta.x / len - }; + ImVec2 normal = { -delta.y / len, delta.x / len }; ImVec2 labelPosWorld = a * 0.6f + b * 0.4f + normal * options.label.separation; @@ -160,12 +137,16 @@ void Edge::DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, fl ImU32 labelBackgroundColor = options.label.backgroundColor; if (options.label.fadeout) { - float alpha = (std::clamp(zoom, options.label.fadeoutCutoffLower, options.label.fadeoutCutoffUpper) - options.label.fadeoutCutoffLower) / (options.label.fadeoutCutoffUpper - options.label.fadeoutCutoffLower) * 255; + float alpha = (std::clamp(zoom, options.label.fadeoutCutoffLower, options.label.fadeoutCutoffUpper) - + options.label.fadeoutCutoffLower) / + (options.label.fadeoutCutoffUpper - options.label.fadeoutCutoffLower) * 255; ImU32 byteAlpha = static_cast(alpha); this->labelColor &= ~IM_COL32_A_MASK; this->labelColor |= byteAlpha << IM_COL32_A_SHIFT; - float alphaBackground = (std::clamp(zoom, options.label.fadeoutCutoffLower, options.label.fadeoutCutoffUpper) - options.label.fadeoutCutoffLower) / (options.label.fadeoutCutoffUpper - options.label.fadeoutCutoffLower) * 220; + float alphaBackground = (std::clamp(zoom, options.label.fadeoutCutoffLower, options.label.fadeoutCutoffUpper) - + options.label.fadeoutCutoffLower) / + (options.label.fadeoutCutoffUpper - options.label.fadeoutCutoffLower) * 220; if (alpha == 0 && alphaBackground == 0) { return; } @@ -178,25 +159,18 @@ void Edge::DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, fl ImVec2 max = labelPosScreen + this->labelSize * 0.5f + options.label.padding; - draw->AddRectFilled( - min, - max, - labelBackgroundColor, - options.label.rounding - ); + draw->AddRectFilled(min, max, labelBackgroundColor, options.label.rounding); - draw->AddText( - labelPosScreen - this->labelSize * 0.5f, - this->labelColor, - this->label.c_str() - ); + draw->AddText(labelPosScreen - this->labelSize * 0.5f, this->labelColor, this->label.c_str()); } Graph Graph::New(std::vector nodes, std::vector edges, GraphOptions options) noexcept { return Graph(nodes, edges, options); } -Graph::Graph(std::vector _nodes, std::vector _edges, GraphOptions _options) : nodes(_nodes), edges(_edges), options(_options) {} +Graph::Graph(std::vector _nodes, std::vector _edges, GraphOptions _options) + : nodes(_nodes), edges(_edges), options(_options) { +} #ifdef _MSC_VER #pragma optimize("t", on) @@ -212,7 +186,7 @@ float Graph::StabilizeStep(float width, float height, float temperature) noexcep // reset displacement for (auto& n : this->nodes) { - n.displacement = {0, 0}; + n.displacement = { 0, 0 }; } // repulsive forces O(n^2) @@ -261,7 +235,7 @@ float Graph::StabilizeStep(float width, float height, float temperature) noexcep totalDisplacement += len; } - //return totalDisplacement; + // return totalDisplacement; return maxDisplacement; } #ifdef _MSC_VER @@ -269,7 +243,8 @@ float Graph::StabilizeStep(float width, float height, float temperature) noexcep #endif void Graph::Stabilize(float width, float height) noexcept { - for (float temperature = this->options.temperature.starting; temperature > this->options.temperature.ending; temperature -= this->options.temperature.decreasePerIteration) { + for (float temperature = this->options.temperature.starting; temperature > this->options.temperature.ending; + temperature -= this->options.temperature.decreasePerIteration) { this->StabilizeStep(width, height, temperature); } } @@ -317,11 +292,7 @@ void Graph::Draw(ImVec2 canvasSize, ImVec2 canvasPos) noexcept { ImDrawList* draw = ImGui::GetWindowDrawList(); ImGui::SetCursorScreenPos(canvasPos); - ImGui::InvisibleButton( - "graph_canvas", - canvasSize, - ImGuiButtonFlags_MouseButtonLeft - ); + ImGui::InvisibleButton("graph_canvas", canvasSize, ImGuiButtonFlags_MouseButtonLeft); this->HandleMouse(canvasPos); @@ -335,7 +306,8 @@ void Graph::Draw(ImVec2 canvasSize, ImVec2 canvasPos) noexcept { continue; } - e.Draw(draw, canvasPos, this->cameraOffset, this->zoom, this->options.edges, this->options.zoom.edgesScaleWithZoom, this->nodes); + e.Draw(draw, canvasPos, this->cameraOffset, this->zoom, this->options.edges, + this->options.zoom.edgesScaleWithZoom, this->nodes); } for (auto& n : this->nodes) { @@ -343,7 +315,8 @@ void Graph::Draw(ImVec2 canvasSize, ImVec2 canvasPos) noexcept { continue; } - n.Draw(draw, canvasPos, this->cameraOffset, this->zoom, this->options.nodes, this->options.zoom.nodesScaleWithZoom); + n.Draw(draw, canvasPos, this->cameraOffset, this->zoom, this->options.nodes, + this->options.zoom.nodesScaleWithZoom); } for (auto& e : this->edges) { @@ -371,7 +344,7 @@ void Graph::Draw(ImVec2 canvasSize, ImVec2 canvasPos) noexcept { ImGui::EndGroup(); - //LUSLOG_INFO("[Graph::Draw] End\n"); + // LUSLOG_INFO("[Graph::Draw] End\n"); } GraphOptions& Graph::GetOptions() noexcept { diff --git a/soh/soh/Graph/Graph.h b/soh/soh/Graph/Graph.h index 1ce401d1f73..440596d7ee8 100644 --- a/soh/soh/Graph/Graph.h +++ b/soh/soh/Graph/Graph.h @@ -40,8 +40,10 @@ typedef struct Node final { ImU32 labelColor; static Node New(ImVec2 position, std::string label, ImU32 color, ImU32 labelColor) noexcept; - void Draw(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphNodeOptions options, bool scaleWithZoom) noexcept; - void DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphNodeOptions options) noexcept; + void Draw(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphNodeOptions options, + bool scaleWithZoom) noexcept; + void DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, + GraphNodeOptions options) noexcept; } Node; typedef struct Edge final { @@ -56,8 +58,10 @@ typedef struct Edge final { ImU32 labelColor; static Edge New(int src, int dst, std::string label, ImU32 color, ImU32 labelColor) noexcept; - void Draw(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphEdgeOptions options, bool scaleWithZoom, const std::vector& nodes) noexcept; - void DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphEdgeOptions options, ImVec2 a, ImVec2 b) noexcept; + void Draw(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphEdgeOptions options, + bool scaleWithZoom, const std::vector& nodes) noexcept; + void DrawLabel(ImDrawList* draw, ImVec2 canvasPos, ImVec2 cameraOffset, float zoom, GraphEdgeOptions options, + ImVec2 a, ImVec2 b) noexcept; } Edge; typedef struct GraphOptions final { @@ -81,27 +85,30 @@ typedef struct GraphOptions final { } GraphOptions; class Graph final { - public: - void Stabilize(float width, float height) noexcept; - float StabilizeStep(float width, float height, float temperature) noexcept; - void Draw(ImVec2 canvasSize, ImVec2 canvasPos) noexcept; - [[nodiscard("There's no point in calling the function without using the options returned")]] - GraphOptions& GetOptions() noexcept; - void ResetView() noexcept; - void ReplaceEdges(std::vector edges) noexcept; - [[nodiscard("There's no point in calling the function without using the graph returned")]] + public: + void Stabilize(float width, float height) noexcept; + float StabilizeStep(float width, float height, float temperature) noexcept; + void Draw(ImVec2 canvasSize, ImVec2 canvasPos) noexcept; + [[nodiscard("There's no point in calling the function without using the options returned")]] + // comment so clang format doesn't mess this up too much + GraphOptions& + GetOptions() noexcept; + void ResetView() noexcept; + void ReplaceEdges(std::vector edges) noexcept; + [[nodiscard("There's no point in calling the function without using the graph returned")]] #ifndef _MSC_VER // msvc complains about an unknown attribute - [[gnu::pure]] + [[gnu::pure]] #endif - static Graph New(std::vector nodes, std::vector edges, GraphOptions options) noexcept; + static Graph + New(std::vector nodes, std::vector edges, GraphOptions options) noexcept; - private: - Graph() = delete; - Graph(std::vector _nodes, std::vector _edges, GraphOptions _options); - void HandleMouse(ImVec2 canvasPos) noexcept; - std::vector nodes; - std::vector edges; - ImVec2 cameraOffset = { 0, 0 }; - float zoom = 1.0f; - GraphOptions options; + private: + Graph() = delete; + Graph(std::vector _nodes, std::vector _edges, GraphOptions _options); + void HandleMouse(ImVec2 canvasPos) noexcept; + std::vector nodes; + std::vector edges; + ImVec2 cameraOffset = { 0, 0 }; + float zoom = 1.0f; + GraphOptions options; }; \ No newline at end of file From 7b29ee5090cff2ac11a373afeab12cf923574b6f Mon Sep 17 00:00:00 2001 From: Pepe20129 <72659707+Pepe20129@users.noreply.github.com> Date: Fri, 26 Jun 2026 17:44:32 +0200 Subject: [PATCH 10/10] Tighten the assert and check before calling Graph::Draw --- .../randomizer/randomizer_entrance_tracker_graph.cpp | 4 +++- soh/soh/Graph/Graph.cpp | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.cpp b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.cpp index 7e751a644e0..a5e931c1973 100644 --- a/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.cpp +++ b/soh/soh/Enhancements/randomizer/randomizer_entrance_tracker_graph.cpp @@ -84,7 +84,9 @@ void EntranceTrackerGraphWindow::DrawElement() { ImGui::SetNextItemAllowOverlap(); - this->graph.value().Draw(canvasSize, canvasPos); + if (canvasSize.x != 0.0f && canvasSize.y != 0.0f) { + this->graph.value().Draw(canvasSize, canvasPos); + } ImGui::SetCursorScreenPos(canvasPos); diff --git a/soh/soh/Graph/Graph.cpp b/soh/soh/Graph/Graph.cpp index 3eda0bd22a8..b59df94c796 100644 --- a/soh/soh/Graph/Graph.cpp +++ b/soh/soh/Graph/Graph.cpp @@ -272,7 +272,7 @@ void Graph::HandleMouse(ImVec2 canvasPos) noexcept { } void Graph::Draw(ImVec2 canvasSize, ImVec2 canvasPos) noexcept { - if (canvasSize.x < 0 || canvasSize.y < 0) { + if (canvasSize.x <= 0 || canvasSize.y <= 0) { LUSLOG_ERROR("[Graph::Draw] Invalid canvasSize = (%f, %f)", canvasSize.x, canvasSize.y); assert(false); return;