diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 877de107..d65c0bc1 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -35,8 +35,16 @@ HOW TO UPDATE? VERSION 1.82 WIP (In Progresss) ----------------------------------------------------------------------- +Breaking Changes: + + - Style: renamed rarely used style.CircleSegmentMaxError (old default = 1.60f) + to style.CircleTessellationMaxError (new default = 0.30f) as its meaning changed. (#3808) [@thedmd] + Other Changes: +- ImDrawList: AddCircle, AddCircleFilled(): Tweaked default segment count calculation to honor MaxError + with more accuracy. Made default segment count always even for better looking result. (#3808) [@thedmd] +- ImDrawList: AddCircle, AddCircleFilled(): New default for style. - CI: Use a dedicated "scheduled" workflow to trigger scheduled builds. Forks may disable this workflow if scheduled builds builds are not required. [@rokups] diff --git a/imgui.cpp b/imgui.cpp index 268093ac..dfba47e3 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -375,6 +375,7 @@ CODE When you are not sure about a old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. You can read releases logs https://github.com/ocornut/imgui/releases for more details. + - 2021/02/17 (1.82) - renamed rarely used style.CircleSegmentMaxError (old default = 1.60f) to style.CircleTessellationMaxError (new default = 0.30f) as the meaning of the value changed. - 2021/02/03 (1.81) - renamed ListBoxHeader(const char* label, ImVec2 size) to BeginListBox(). Kept inline redirection function (will obsolete). - removed ListBoxHeader(const char* label, int items_count, int height_in_items = -1) in favor of specifying size. Kept inline redirection function (will obsolete). - renamed ListBoxFooter() to EndListBox(). Kept inline redirection function (will obsolete). @@ -986,7 +987,7 @@ ImGuiStyle::ImGuiStyle() AntiAliasedLinesUseTex = true; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering. AntiAliasedFill = true; // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.). CurveTessellationTol = 1.25f; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. - CircleTessellationMaxError = 0.25f; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry. + CircleTessellationMaxError = 0.30f; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry. // Default theme ImGui::StyleColorsDark(this); diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 3d9395e6..b7815ab4 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -6034,41 +6034,39 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) if (style.CurveTessellationTol < 0.10f) style.CurveTessellationTol = 0.10f; // When editing the "Circle Segment Max Error" value, draw a preview of its effect on auto-tessellated circles. - ImGui::DragFloat("Circle Tessellation Max Error", &style.CircleTessellationMaxError , 0.005f, 0.10f, 10.0f, "%.2f", ImGuiSliderFlags_AlwaysClamp); + ImGui::DragFloat("Circle Tessellation Max Error", &style.CircleTessellationMaxError , 0.005f, 0.10f, 5.0f, "%.2f", ImGuiSliderFlags_AlwaysClamp); if (ImGui::IsItemActive()) { ImGui::SetNextWindowPos(ImGui::GetCursorScreenPos()); ImGui::BeginTooltip(); - ImGui::TextUnformatted("N - number of segments"); - ImGui::TextUnformatted("R - radius"); + ImGui::TextUnformatted("(R = radius, N = number of segments)"); ImGui::Spacing(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); - const float min_widget_width = ImGui::CalcTextSize("N: MM\nR: MM.MM").x; - float RAD_MIN = 5.0f, RAD_MAX = 80.0f; - for (int n = 0; n < 9; n++) + const float min_widget_width = ImGui::CalcTextSize("N: MMM\nR: MMM").x; + for (int n = 0; n < 8; n++) { - const float rad = RAD_MIN + (RAD_MAX - RAD_MIN) * (float)n / (9.0f - 1.0f); - - const int segment_count = draw_list->_CalcCircleAutoSegmentCount(rad); + const float RAD_MIN = 5.0f; + const float RAD_MAX = 70.0f; + const float rad = RAD_MIN + (RAD_MAX - RAD_MIN) * (float)n / (8.0f - 1.0f); ImGui::BeginGroup(); - ImGui::Text("R: %.f", rad); - ImGui::Text("N: %d", segment_count); - const float circle_diameter = rad * 2.0f; - const float canvas_width = IM_MAX(min_widget_width, circle_diameter); - const float offset_x = floorf(canvas_width * 0.5f); - const float offset_y = floorf(RAD_MAX); - const ImVec2 p = ImGui::GetCursorScreenPos(); - draw_list->AddCircle(ImVec2(p.x + offset_x, p.y + offset_y), rad, ImGui::GetColorU32(ImGuiCol_Text)); + ImGui::Text("R: %.f\nN: %d", rad, draw_list->_CalcCircleAutoSegmentCount(rad)); + const float canvas_width = IM_MAX(min_widget_width, rad * 2.0f); + const float offset_x = floorf(canvas_width * 0.5f); + const float offset_y = floorf(RAD_MAX); + + const ImVec2 p1 = ImGui::GetCursorScreenPos(); + draw_list->AddCircle(ImVec2(p1.x + offset_x, p1.y + offset_y), rad, ImGui::GetColorU32(ImGuiCol_Text)); ImGui::Dummy(ImVec2(canvas_width, RAD_MAX * 2)); - ImGui::Text("N: %d", segment_count); - const ImVec2 p2 = ImGui::GetCursorScreenPos(); + /* + const ImVec2 p2 = ImGui::GetCursorScreenPos(); draw_list->AddCircleFilled(ImVec2(p2.x + offset_x, p2.y + offset_y), rad, ImGui::GetColorU32(ImGuiCol_Text)); - ImGui::Dummy(ImVec2(canvas_width, RAD_MAX * 2)); + */ + ImGui::EndGroup(); ImGui::SameLine(); } diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 8a031911..b0770eda 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -544,17 +544,12 @@ void ImDrawList::_OnChangedVtxOffset() int ImDrawList::_CalcCircleAutoSegmentCount(float radius) const { - int num_segments = 0; - - const int radius_idx = (int)ImCeil(radius); // Use ceil to never reduce accuracy - // Automatic segment count + const int radius_idx = (int)(radius + 0.999f); // ceil to never reduce accuracy if (radius_idx < IM_ARRAYSIZE(_Data->CircleSegmentCounts)) - num_segments = _Data->CircleSegmentCounts[radius_idx]; // Use cached value + return _Data->CircleSegmentCounts[radius_idx]; // Use cached value else - num_segments = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, _Data->CircleSegmentMaxError); - - return num_segments; + return IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, _Data->CircleSegmentMaxError); } // Render-level scissoring. This is passed down to your render function but not used for CPU-side coarse clipping. Prefer using higher-level ImGui::PushClipRect() to affect logic (hit-testing and widget culling) diff --git a/imgui_internal.h b/imgui_internal.h index c8405c7f..e85230dc 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -617,29 +617,20 @@ struct IMGUI_API ImChunkStream //----------------------------------------------------------------------------- // ImDrawList: Helper function to calculate a circle's segment count given its radius and a "maximum error" value. -// -// Estimation of number of circle segment based on error is derived using method described in -// this post (https://stackoverflow.com/a/2244088/15194693). +// Estimation of number of circle segment based on error is derived using method described in https://stackoverflow.com/a/2244088/15194693 // Number of segments (N) is calculated using equation: -// -// +- -+ -// | pi | -// N = ceil | --------------------- | where r > 0, error <= r -// | acos(1 - error / r) | -// +- -+ -// -// Note: -// Equation is significantly simpler that one in the post thanks for choosing segment -// that is perpendicular to X axis. Follow steps in the article from this starting condition -// and you will get this result. +// N = ceil ( pi / acos(1 - error / r) ) where r > 0, error <= r +// Our equation is significantly simpler that one in the post thanks for choosing segment that is +// perpendicular to X axis. Follow steps in the article from this starting condition and you will +// will get this result. // // Rendering circles with an odd number of segments, while mathematically correct will produce -// asymmetrical results on the raster grid. Therefore we're rounding N to next even number. -// (7 became 8, 11 became 12, but 8 will still be 8). +// asymmetrical results on the raster grid. Therefore we're rounding N to next even number (7->8, 8->8, 9->10 etc.) // +#define IM_ROUNDUP_TO_EVEN(_V) ((((_V) + 1) / 2) * 2) #define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MIN 4 #define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX 512 -#define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(_RAD,_MAXERROR) ImClamp((((int)ImCeil(IM_PI / ImAcos(1 - ImMin((_MAXERROR), (_RAD)) / (_RAD))) + 1) / 2) * 2, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MIN, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX) +#define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(_RAD,_MAXERROR) ImClamp(IM_ROUNDUP_TO_EVEN((int)ImCeil(IM_PI / ImAcos(1 - ImMin((_MAXERROR), (_RAD)) / (_RAD)))), IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MIN, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX) // ImDrawList: You may set this to higher values (e.g. 2 or 3) to increase tessellation of fast rounded corners path. #ifndef IM_DRAWLIST_ARCFAST_TESSELLATION_MULTIPLIER @@ -660,8 +651,8 @@ struct IMGUI_API ImDrawListSharedData // [Internal] Lookup tables ImVec2 ArcFastVtx[12 * IM_DRAWLIST_ARCFAST_TESSELLATION_MULTIPLIER]; // FIXME: Bake rounded corners fill/borders in atlas - ImU8 CircleSegmentCounts[64]; // Precomputed segment count for given radius before we calculate it dynamically (to avoid calculation overhead) - const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas + ImU8 CircleSegmentCounts[64]; // Precomputed segment count for given radius before we calculate it dynamically (to avoid calculation overhead) + const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas ImDrawListSharedData(); void SetCircleTessellationMaxError(float max_error);