From 93fd5cd98b626fa867b24c1f2b720cac4d44fc3c Mon Sep 17 00:00:00 2001 From: atulya srivastava Date: Thu, 23 Oct 2025 17:00:15 +0530 Subject: [PATCH 1/4] feat: add function to check the top level equal sign --- src/core/Core/Application.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/core/Core/Application.cpp b/src/core/Core/Application.cpp index 75c44b2..fee01d4 100644 --- a/src/core/Core/Application.cpp +++ b/src/core/Core/Application.cpp @@ -19,6 +19,34 @@ #include "funcs.hpp" namespace App { + +// function to find top-level '=' that's not part of ==, <=, >=, != +static size_t findTopLevelEquals(const std::string& str) { + int depth = 0; + + for (size_t i = 0; i < str.size(); ++i) { + char c = str[i]; + if (c == '(') { + ++depth; + } else if (c == ')') { + --depth; + } else if (c == '=' && depth == 0) { + // check it's not part of ==, <=, >=, != + bool isOperator = false; + if (i > 0 && (str[i-1] == '=' || str[i-1] == '<' || str[i-1] == '>' || str[i-1] == '!')) { + isOperator = true; + } + if (i + 1 < str.size() && str[i+1] == '=') { + isOperator = true; + } + + if (!isOperator) { + return i; + } + } + } + return std::string::npos; +} Application::Application(const std::string& title) { APP_PROFILE_FUNCTION(); From 9277261316785ae90f03c19c553874ad419a9a60 Mon Sep 17 00:00:00 2001 From: atulya srivastava Date: Thu, 23 Oct 2025 17:09:06 +0530 Subject: [PATCH 2/4] feat: add logic to plot points for implicit relations --- src/core/Core/Application.cpp | 93 ++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/src/core/Core/Application.cpp b/src/core/Core/Application.cpp index fee01d4..e710532 100644 --- a/src/core/Core/Application.cpp +++ b/src/core/Core/Application.cpp @@ -19,7 +19,7 @@ #include "funcs.hpp" namespace App { - + // function to find top-level '=' that's not part of ==, <=, >=, != static size_t findTopLevelEquals(const std::string& str) { int depth = 0; @@ -239,6 +239,97 @@ ExitStatus App::Application::run() { } } + // check for implicit form: f(x,y) = g(x,y) + if (!plotted) { + size_t equals_pos = findTopLevelEquals(func_str); + + if (equals_pos != std::string::npos) { + // split into LHS and RHS + std::string lhs = trim(func_str.substr(0, equals_pos)); + std::string rhs = trim(func_str.substr(equals_pos + 1)); + + // create expression: LHS - RHS + std::string implicit_expr = "(" + lhs + ") - (" + rhs + ")"; + + // setup exprtk with x and y variables + double x = 0.0, y = 0.0; + exprtk::symbol_table symbolTable; + symbolTable.add_constants(); + addConstants(symbolTable); + symbolTable.add_variable("x", x); + symbolTable.add_variable("y", y); + + exprtk::expression expression; + expression.register_symbol_table(symbolTable); + + exprtk::parser parser; + bool compile_ok = parser.compile(implicit_expr, expression); + + if (compile_ok) { + // grid parameters + const double x_min = -canvas_sz.x / (2 * zoom); + const double x_max = canvas_sz.x / (2 * zoom); + const double y_min = -canvas_sz.y / (2 * zoom); + const double y_max = canvas_sz.y / (2 * zoom); + const double step = 0.02; + + const ImU32 implicit_color = IM_COL32(64, 199, 128, 255); + const float dot_radius = 2.5f; + + // scan horizontally for sign changes + for (y = y_min; y <= y_max; y += step) { + double prev_val = 0.0; + bool first = true; + + for (x = x_min; x <= x_max; x += step) { + double curr_val = expression.value(); + + if (!first && prev_val * curr_val < 0) { + // sign change detected + double t = prev_val / (prev_val - curr_val); + double x_zero = (x - step) + t * step; + double y_zero = y; + + // transform to screen coordinates and draw immediately + ImVec2 screen_pos(origin.x + static_cast(x_zero * zoom), + origin.y - static_cast(y_zero * zoom)); + draw_list->AddCircleFilled(screen_pos, dot_radius, implicit_color); + } + + prev_val = curr_val; + first = false; + } + } + + // vertical scan + for (x = x_min; x <= x_max; x += step) { + double prev_val = 0.0; + bool first = true; + + for (y = y_min; y <= y_max; y += step) { + double curr_val = expression.value(); + + if (!first && prev_val * curr_val < 0) { + // sign change detected + double t = prev_val / (prev_val - curr_val); + double x_zero = x; + double y_zero = (y - step) + t * step; + + ImVec2 screen_pos(origin.x + static_cast(x_zero * zoom), + origin.y - static_cast(y_zero * zoom)); + draw_list->AddCircleFilled(screen_pos, dot_radius, implicit_color); + } + + prev_val = curr_val; + first = false; + } + } + + plotted = true; + } + } + } + if (!plotted) { // Fallback to y = f(x) plotting using variable x double x; From 81033dceb256f11099af6b185e6f9200a9c17c70 Mon Sep 17 00:00:00 2001 From: atulya srivastava Date: Thu, 23 Oct 2025 17:11:41 +0530 Subject: [PATCH 3/4] fix: modify the step calculation to prevent lag --- src/core/Core/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/Core/Application.cpp b/src/core/Core/Application.cpp index e710532..e8509e8 100644 --- a/src/core/Core/Application.cpp +++ b/src/core/Core/Application.cpp @@ -271,7 +271,7 @@ ExitStatus App::Application::run() { const double x_max = canvas_sz.x / (2 * zoom); const double y_min = -canvas_sz.y / (2 * zoom); const double y_max = canvas_sz.y / (2 * zoom); - const double step = 0.02; + const double step = std::max(0.008, 1.0 / zoom); //dynamic step based on zoom level const ImU32 implicit_color = IM_COL32(64, 199, 128, 255); const float dot_radius = 2.5f; From 8af05412f1c73cbab68b0201f0cabf7a4068f30f Mon Sep 17 00:00:00 2001 From: atulya srivastava Date: Thu, 23 Oct 2025 18:52:17 +0530 Subject: [PATCH 4/4] refactor: extract helper function into funcs.hpp --- src/core/Core/Application.cpp | 28 ---------------------------- src/core/Core/funcs.hpp | 28 ++++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/core/Core/Application.cpp b/src/core/Core/Application.cpp index e8509e8..c2d066e 100644 --- a/src/core/Core/Application.cpp +++ b/src/core/Core/Application.cpp @@ -20,34 +20,6 @@ namespace App { -// function to find top-level '=' that's not part of ==, <=, >=, != -static size_t findTopLevelEquals(const std::string& str) { - int depth = 0; - - for (size_t i = 0; i < str.size(); ++i) { - char c = str[i]; - if (c == '(') { - ++depth; - } else if (c == ')') { - --depth; - } else if (c == '=' && depth == 0) { - // check it's not part of ==, <=, >=, != - bool isOperator = false; - if (i > 0 && (str[i-1] == '=' || str[i-1] == '<' || str[i-1] == '>' || str[i-1] == '!')) { - isOperator = true; - } - if (i + 1 < str.size() && str[i+1] == '=') { - isOperator = true; - } - - if (!isOperator) { - return i; - } - } - } - return std::string::npos; -} - Application::Application(const std::string& title) { APP_PROFILE_FUNCTION(); diff --git a/src/core/Core/funcs.hpp b/src/core/Core/funcs.hpp index 91a39ea..d945fc5 100644 --- a/src/core/Core/funcs.hpp +++ b/src/core/Core/funcs.hpp @@ -28,5 +28,33 @@ inline std::string trim(const std::string& s) { return s.substr(start, end - start + 1); } +// function to find top-level '=' that's not part of ==, <=, >=, != +static size_t findTopLevelEquals(const std::string& str) { + int depth = 0; + + for (size_t i = 0; i < str.size(); ++i) { + char c = str[i]; + if (c == '(') { + ++depth; + } else if (c == ')') { + --depth; + } else if (c == '=' && depth == 0) { + // check it's not part of ==, <=, >=, != + bool isOperator = false; + if (i > 0 && (str[i-1] == '=' || str[i-1] == '<' || str[i-1] == '>' || str[i-1] == '!')) { + isOperator = true; + } + if (i + 1 < str.size() && str[i+1] == '=') { + isOperator = true; + } + + if (!isOperator) { + return i; + } + } + } + return std::string::npos; +} + #endif // IMGRAPH_FUNCS_HPP