Skip to content

Commit

Permalink
Improve on automatic circle segment count calculation.
Browse files Browse the repository at this point in the history
  • Loading branch information
thedmd committed Feb 13, 2021
1 parent 4df5713 commit eb71b82
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 26 deletions.
2 changes: 1 addition & 1 deletion imgui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -986,7 +986,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.
CircleSegmentMaxError = 1.60f; // 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.
CircleSegmentMaxError = 0.50f; // 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);
Expand Down
1 change: 1 addition & 0 deletions imgui.h
Original file line number Diff line number Diff line change
Expand Up @@ -2463,6 +2463,7 @@ struct ImDrawList
IMGUI_API void _OnChangedClipRect();
IMGUI_API void _OnChangedTextureID();
IMGUI_API void _OnChangedVtxOffset();
IMGUI_API int _CalcCircleAutoSegmentCount(float radius, bool anti_aliased) const;
};

// All draw data to render a Dear ImGui frame
Expand Down
39 changes: 31 additions & 8 deletions imgui_demo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6035,17 +6035,40 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref)
{
ImGui::SetNextWindowPos(ImGui::GetCursorScreenPos());
ImGui::BeginTooltip();
ImVec2 p = ImGui::GetCursorScreenPos();
ImGui::TextUnformatted("N - number of segments");
ImGui::TextUnformatted("R - radius");
ImGui::Spacing();
ImDrawList* draw_list = ImGui::GetWindowDrawList();
float RAD_MIN = 10.0f, RAD_MAX = 80.0f;
float off_x = 10.0f;
for (int n = 0; n < 7; n++)
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 rad = RAD_MIN + (RAD_MAX - RAD_MIN) * (float)n / (7.0f - 1.0f);
draw_list->AddCircle(ImVec2(p.x + off_x + rad, p.y + RAD_MAX), rad, ImGui::GetColorU32(ImGuiCol_Text), 0);
off_x += 10.0f + rad * 2.0f;
const float rad = RAD_MIN + (RAD_MAX - RAD_MIN) * (float)n / (9.0f - 1.0f);

const int segment_count = draw_list->_CalcCircleAutoSegmentCount(rad, false);
const int anti_aliased_segment_count = draw_list->_CalcCircleAutoSegmentCount(rad, true);

ImGui::BeginGroup();
ImGui::Text("R: %.f", rad);
ImGui::Text("N: %d", draw_list->Flags & ImDrawListFlags_AntiAliasedLines ? anti_aliased_segment_count : 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::Dummy(ImVec2(canvas_width, RAD_MAX * 2));
ImGui::Text("N: %d", draw_list->Flags & ImDrawListFlags_AntiAliasedFill ? anti_aliased_segment_count : segment_count);

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();
}
ImGui::Dummy(ImVec2(off_x, RAD_MAX * 2.0f));
ImGui::EndTooltip();
}
ImGui::SameLine();
Expand Down
41 changes: 29 additions & 12 deletions imgui_draw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -384,8 +384,8 @@ void ImDrawListSharedData::SetCircleSegmentMaxError(float max_error)
for (int i = 0; i < IM_ARRAYSIZE(CircleSegmentCounts); i++)
{
const float radius = (float)i;
const int segment_count = (i > 0) ? IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, CircleSegmentMaxError) : 0;
CircleSegmentCounts[i] = (ImU8)ImMin(segment_count, 255);
CircleSegmentCounts[i] = (ImU8)((i > 0) ? IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, CircleSegmentMaxError) : 0);
AntiAliasedCircleSegmentCounts[i] = (ImU8)((i > 0) ? IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, CircleSegmentMaxError * IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_AA_ERROR_SCALE_FACTOR) : 0);
}
}

Expand Down Expand Up @@ -543,6 +543,31 @@ void ImDrawList::_OnChangedVtxOffset()
curr_cmd->VtxOffset = _CmdHeader.VtxOffset;
}

int ImDrawList::_CalcCircleAutoSegmentCount(float radius, bool anti_aliased) const
{
int num_segments = 0;

const int radius_idx = (int)ImCeil(radius); // Use ceil to never reduce accuracy

// Automatic segment count
if (radius_idx < IM_ARRAYSIZE(_Data->CircleSegmentCounts))
{
if (anti_aliased)
num_segments = _Data->AntiAliasedCircleSegmentCounts[radius_idx]; // Use cached value
else
num_segments = _Data->CircleSegmentCounts[radius_idx]; // Use cached value
}
else
{
if (anti_aliased)
num_segments = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, _Data->CircleSegmentMaxError * IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_AA_ERROR_SCALE_FACTOR);
else
num_segments = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, _Data->CircleSegmentMaxError);
}

return num_segments;
}

// 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)
void ImDrawList::PushClipRect(ImVec2 cr_min, ImVec2 cr_max, bool intersect_with_current_clip_rect)
{
Expand Down Expand Up @@ -1286,11 +1311,7 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu
if (num_segments <= 0)
{
// Automatic segment count
const int radius_idx = (int)radius;
if (radius_idx < IM_ARRAYSIZE(_Data->CircleSegmentCounts))
num_segments = _Data->CircleSegmentCounts[radius_idx]; // Use cached value
else
num_segments = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, _Data->CircleSegmentMaxError);
num_segments = _CalcCircleAutoSegmentCount(radius, (Flags & ImDrawListFlags_AntiAliasedLines) != 0);
}
else
{
Expand All @@ -1316,11 +1337,7 @@ void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col,
if (num_segments <= 0)
{
// Automatic segment count
const int radius_idx = (int)radius;
if (radius_idx < IM_ARRAYSIZE(_Data->CircleSegmentCounts))
num_segments = _Data->CircleSegmentCounts[radius_idx]; // Use cached value
else
num_segments = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, _Data->CircleSegmentMaxError);
num_segments = _CalcCircleAutoSegmentCount(radius, (Flags & ImDrawListFlags_AntiAliasedFill) != 0);
}
else
{
Expand Down
35 changes: 30 additions & 5 deletions imgui_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -617,10 +617,34 @@ struct IMGUI_API ImChunkStream
//-----------------------------------------------------------------------------

// ImDrawList: Helper function to calculate a circle's segment count given its radius and a "maximum error" value.
// FIXME: the minimum number of auto-segment may be undesirably high for very small radiuses (e.g. 1.0f)
#define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MIN 12
//
// Estimation of number of circle segment based on error is derived using method described in
// this post (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.
//
// 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).
//
// Error value is expressed in pixels and defaults to 0.5f, half a pixel. Which produce
// non anti-aliased circles where segments cannot be spotted.
// Anti-aliasing work on much finer grid than pixels which make segments visible. To mitigate this
// issue error is scaled by IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_AA_ERROR_SCALE_FACTOR defaulting to 0.5f.
// This make anti-aliased circles use 0.25f as an error value, making segments unnoticeable.
//
#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)((IM_PI * 2.0f) / ImAcos(((_RAD) - (_MAXERROR)) / (_RAD))), IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MIN, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX)
#define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_AA_ERROR_SCALE_FACTOR 0.5f // Anti-aliased geometry need better accuracy since it renders on more fine grined grid than pixels
#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)

// 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
Expand All @@ -641,8 +665,9 @@ 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)
ImU8 AntiAliasedCircleSegmentCounts[64]; // Precomputed circle segment count anti-aliased primitives
const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas

ImDrawListSharedData();
void SetCircleSegmentMaxError(float max_error);
Expand Down

0 comments on commit eb71b82

Please # to comment.