Improve on automatic circle segment count calculation. (#3808) Amends

This commit is contained in:
ocornut 2021-02-17 13:06:26 +01:00
parent f107693d9b
commit fb15d8c858
5 changed files with 41 additions and 48 deletions

View File

@ -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]

View File

@ -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);

View File

@ -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();
}

View File

@ -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)

View File

@ -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);