mirror of
				https://github.com/Drezil/imgui.git
				synced 2025-10-30 20:51:06 +01:00 
			
		
		
		
	Improve on automatic circle segment count calculation. (#3808)
This commit is contained in:
		| @@ -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. | ||||
|     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. | ||||
|  | ||||
|     // Default theme | ||||
|     ImGui::StyleColorsDark(this); | ||||
| @@ -3878,7 +3878,7 @@ void ImGui::NewFrame() | ||||
|         virtual_space.Add(g.Viewports[n]->GetMainRect()); | ||||
|     g.DrawListSharedData.ClipRectFullscreen = virtual_space.ToVec4(); | ||||
|     g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol; | ||||
|     g.DrawListSharedData.SetCircleSegmentMaxError(g.Style.CircleSegmentMaxError); | ||||
|     g.DrawListSharedData.SetCircleTessellationMaxError(g.Style.CircleTessellationMaxError); | ||||
|     g.DrawListSharedData.InitialFlags = ImDrawListFlags_None; | ||||
|     if (g.Style.AntiAliasedLines) | ||||
|         g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLines; | ||||
| @@ -7056,7 +7056,7 @@ static void ImGui::ErrorCheckNewFrameSanityChecks() | ||||
|     IM_ASSERT(g.IO.Fonts->Fonts.Size > 0                                && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()?"); | ||||
|     IM_ASSERT(g.IO.Fonts->Fonts[0]->IsLoaded()                          && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()?"); | ||||
|     IM_ASSERT(g.Style.CurveTessellationTol > 0.0f                       && "Invalid style setting!"); | ||||
|     IM_ASSERT(g.Style.CircleSegmentMaxError > 0.0f                      && "Invalid style setting!"); | ||||
|     IM_ASSERT(g.Style.CircleTessellationMaxError  > 0.0f                && "Invalid style setting!"); | ||||
|     IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f            && "Invalid style setting!"); // Allows us to avoid a few clamps in color computations | ||||
|     IM_ASSERT(g.Style.WindowMinSize.x >= 1.0f && g.Style.WindowMinSize.y >= 1.0f && "Invalid style setting."); | ||||
|     IM_ASSERT(g.Style.WindowMenuButtonPosition == ImGuiDir_None || g.Style.WindowMenuButtonPosition == ImGuiDir_Left || g.Style.WindowMenuButtonPosition == ImGuiDir_Right); | ||||
|   | ||||
							
								
								
									
										3
									
								
								imgui.h
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								imgui.h
									
									
									
									
									
								
							| @@ -1723,7 +1723,7 @@ struct ImGuiStyle | ||||
|     bool        AntiAliasedLinesUseTex;     // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering. Latched at the beginning of the frame (copied to ImDrawList). | ||||
|     bool        AntiAliasedFill;            // Enable anti-aliased edges around filled shapes (rounded rectangles, circles, etc.). Disable if you are really tight on CPU/GPU. Latched at the beginning of the frame (copied to ImDrawList). | ||||
|     float       CurveTessellationTol;       // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. | ||||
|     float       CircleSegmentMaxError;      // 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. | ||||
|     float       CircleTessellationMaxError; // 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. | ||||
|     ImVec4      Colors[ImGuiCol_COUNT]; | ||||
|  | ||||
|     IMGUI_API ImGuiStyle(); | ||||
| @@ -2463,6 +2463,7 @@ struct ImDrawList | ||||
|     IMGUI_API void  _OnChangedClipRect(); | ||||
|     IMGUI_API void  _OnChangedTextureID(); | ||||
|     IMGUI_API void  _OnChangedVtxOffset(); | ||||
|     IMGUI_API int   _CalcCircleAutoSegmentCount(float radius) const; | ||||
| }; | ||||
|  | ||||
| // All draw data to render a Dear ImGui frame | ||||
|   | ||||
| @@ -6034,22 +6034,44 @@ 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 Segment Max Error", &style.CircleSegmentMaxError, 0.01f, 0.10f, 10.0f, "%.2f"); | ||||
|             ImGui::DragFloat("Circle Tessellation Max Error", &style.CircleTessellationMaxError , 0.005f, 0.10f, 10.0f, "%.2f", ImGuiSliderFlags_AlwaysClamp); | ||||
|             if (ImGui::IsItemActive()) | ||||
|             { | ||||
|                 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); | ||||
|  | ||||
|                     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::Dummy(ImVec2(canvas_width, RAD_MAX * 2)); | ||||
|                     ImGui::Text("N: %d", 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(); | ||||
|   | ||||
| @@ -376,7 +376,7 @@ ImDrawListSharedData::ImDrawListSharedData() | ||||
|     } | ||||
| } | ||||
|  | ||||
| void ImDrawListSharedData::SetCircleSegmentMaxError(float max_error) | ||||
| void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error) | ||||
| { | ||||
|     if (CircleSegmentMaxError == max_error) | ||||
|         return; | ||||
| @@ -384,8 +384,7 @@ 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); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -543,6 +542,21 @@ void ImDrawList::_OnChangedVtxOffset() | ||||
|     curr_cmd->VtxOffset = _CmdHeader.VtxOffset; | ||||
| } | ||||
|  | ||||
| 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 | ||||
|     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); | ||||
|  | ||||
|     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) | ||||
| { | ||||
| @@ -1286,11 +1300,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); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
| @@ -1316,11 +1326,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); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|   | ||||
| @@ -617,10 +617,29 @@ 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). | ||||
| // | ||||
| #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_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 | ||||
| @@ -641,11 +660,11 @@ 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 SetCircleSegmentMaxError(float max_error); | ||||
|     void SetCircleTessellationMaxError(float max_error); | ||||
| }; | ||||
|  | ||||
| struct ImDrawDataBuilder | ||||
|   | ||||
		Reference in New Issue
	
	Block a user