Add version of PathArcTo() and PathArcToFast() with adaptive rendering quality. (#3491)
This commit is contained in:
parent
8ed34af6f8
commit
e45847d99a
@ -75,9 +75,13 @@ Other Changes:
|
||||
This can currently only ever be set by the Freetype renderer.
|
||||
- imgui_freetype: Added ImGuiFreeTypeBuilderFlags_Bitmap flag to request Freetype loading bitmap data.
|
||||
This may have an effect on size and must be called with correct size values. (#3879) [@metarutaiga]
|
||||
- ImDrawList: PathArcTo() now supports "int num_segments = 0" (new default) and adaptively tesselate.
|
||||
The adapative tesselation uses look up tables, tends to be faster than old PathArcTo() while maintaining
|
||||
quality for large arcs (tesselation quality derived from "style.CircleTessellationMaxError") (#3491) [@thedmd]
|
||||
- ImDrawList: PathArcToFast() also adaptively tesselate efficiently. This means that large rounded corners
|
||||
in e.g. hi-dpi settings will generally look better. (#3491) [@thedmd]
|
||||
- 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.
|
||||
- Backends: Android: Added native Android backend. (#3446) [@duddel]
|
||||
- Backends: Win32: Added ImGui_ImplWin32_EnableAlphaCompositing() to facilitate experimenting with
|
||||
alpha compositing and transparent windows. (#2766, #3447 etc.).
|
||||
|
4
imgui.h
4
imgui.h
@ -2436,7 +2436,7 @@ struct ImDrawList
|
||||
inline void PathLineToMergeDuplicate(const ImVec2& pos) { if (_Path.Size == 0 || memcmp(&_Path.Data[_Path.Size - 1], &pos, 8) != 0) _Path.push_back(pos); }
|
||||
inline void PathFillConvex(ImU32 col) { AddConvexPolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; } // Note: Anti-aliased filling requires points to be in clockwise order.
|
||||
inline void PathStroke(ImU32 col, ImDrawFlags flags = 0, float thickness = 1.0f) { AddPolyline(_Path.Data, _Path.Size, col, flags, thickness); _Path.Size = 0; }
|
||||
IMGUI_API void PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments = 10);
|
||||
IMGUI_API void PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments = 0);
|
||||
IMGUI_API void PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12); // Use precomputed angles for a 12 steps circle
|
||||
IMGUI_API void PathBezierCubicCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments = 0); // Cubic Bezier (4 control points)
|
||||
IMGUI_API void PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3, int num_segments = 0); // Quadratic Bezier (3 control points)
|
||||
@ -2482,6 +2482,8 @@ struct ImDrawList
|
||||
IMGUI_API void _OnChangedTextureID();
|
||||
IMGUI_API void _OnChangedVtxOffset();
|
||||
IMGUI_API int _CalcCircleAutoSegmentCount(float radius) const;
|
||||
IMGUI_API void _PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step);
|
||||
IMGUI_API void _PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments);
|
||||
};
|
||||
|
||||
// All draw data to render a Dear ImGui frame
|
||||
|
144
imgui_draw.cpp
144
imgui_draw.cpp
@ -374,18 +374,22 @@ ImDrawListSharedData::ImDrawListSharedData()
|
||||
const float a = ((float)i * 2 * IM_PI) / (float)IM_ARRAYSIZE(ArcFastVtx);
|
||||
ArcFastVtx[i] = ImVec2(ImCos(a), ImSin(a));
|
||||
}
|
||||
ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError);
|
||||
}
|
||||
|
||||
void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error)
|
||||
{
|
||||
if (CircleSegmentMaxError == max_error)
|
||||
return;
|
||||
|
||||
IM_ASSERT(max_error > 0.0f);
|
||||
CircleSegmentMaxError = max_error;
|
||||
for (int i = 0; i < IM_ARRAYSIZE(CircleSegmentCounts); i++)
|
||||
{
|
||||
const float radius = (float)i;
|
||||
CircleSegmentCounts[i] = (ImU8)((i > 0) ? IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, CircleSegmentMaxError) : 0);
|
||||
}
|
||||
ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError);
|
||||
}
|
||||
|
||||
// Initialize before use in a new frame. We always have a command ready in the buffer.
|
||||
@ -1026,32 +1030,86 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun
|
||||
}
|
||||
}
|
||||
|
||||
// 0: East, 3: South, 6: West, 9: North, 12: East
|
||||
void ImDrawList::PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12)
|
||||
void ImDrawList::_PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step)
|
||||
{
|
||||
if (radius <= 0.0f)
|
||||
{
|
||||
_Path.push_back(center);
|
||||
return;
|
||||
}
|
||||
IM_ASSERT(a_min_of_12 <= a_max_of_12);
|
||||
IM_ASSERT(a_min_sample <= a_max_sample);
|
||||
|
||||
// For legacy reason the PathArcToFast() always takes angles where 2*PI is represented by 12,
|
||||
// but it is possible to set IM_DRAWLIST_ARCFAST_TESSELATION_MULTIPLIER to a higher value. This should compile to a no-op otherwise.
|
||||
#if IM_DRAWLIST_ARCFAST_TESSELLATION_MULTIPLIER != 1
|
||||
a_min_of_12 *= IM_DRAWLIST_ARCFAST_TESSELLATION_MULTIPLIER;
|
||||
a_max_of_12 *= IM_DRAWLIST_ARCFAST_TESSELLATION_MULTIPLIER;
|
||||
#endif
|
||||
// Calculate arc auto segment step size
|
||||
if (a_step <= 0)
|
||||
a_step = IM_DRAWLIST_ARCFAST_SAMPLE_MAX / _CalcCircleAutoSegmentCount(radius);
|
||||
|
||||
_Path.reserve(_Path.Size + (a_max_of_12 - a_min_of_12 + 1));
|
||||
for (int a = a_min_of_12; a <= a_max_of_12; a++)
|
||||
// Make sure we never do steps larger than one quarter of the circle
|
||||
a_step = ImClamp(a_step, 1, IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4);
|
||||
|
||||
// Normalize a_min_sample to always start lie in [0..IM_DRAWLIST_ARCFAST_SAMPLE_MAX] range.
|
||||
if (a_min_sample < 0)
|
||||
{
|
||||
const ImVec2& c = _Data->ArcFastVtx[a % IM_ARRAYSIZE(_Data->ArcFastVtx)];
|
||||
_Path.push_back(ImVec2(center.x + c.x * radius, center.y + c.y * radius));
|
||||
int normalized_sample = a_min_sample % IM_DRAWLIST_ARCFAST_SAMPLE_MAX;
|
||||
if (normalized_sample < 0)
|
||||
normalized_sample += IM_DRAWLIST_ARCFAST_SAMPLE_MAX;
|
||||
a_max_sample += (normalized_sample - a_min_sample);
|
||||
a_min_sample = normalized_sample;
|
||||
}
|
||||
|
||||
const int sample_range = a_max_sample - a_min_sample;
|
||||
const int a_next_step = a_step;
|
||||
|
||||
int samples = sample_range + 1;
|
||||
bool extra_max_sample = false;
|
||||
if (a_step > 1)
|
||||
{
|
||||
samples = sample_range / a_step + 1;
|
||||
const int overstep = sample_range % a_step;
|
||||
|
||||
if (overstep > 0)
|
||||
{
|
||||
extra_max_sample = true;
|
||||
samples++;
|
||||
|
||||
// When we have overstep to avoid awkwardly looking one long line and one tiny one at the end,
|
||||
// distribute first step range evenly between them by reducing first step size.
|
||||
if (sample_range > 0)
|
||||
a_step -= (a_step - overstep) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
_Path.resize(_Path.Size + samples);
|
||||
ImVec2* out_ptr = _Path.Data + (_Path.Size - samples);
|
||||
|
||||
int sample_index = a_min_sample;
|
||||
for (int a = a_min_sample; a <= a_max_sample; a += a_step, sample_index += a_step, a_step = a_next_step)
|
||||
{
|
||||
// a_step is clamped to IM_DRAWLIST_ARCFAST_SAMPLE_MAX, so we have guaranteed that it will not wrap over range twice or more
|
||||
if (sample_index >= IM_DRAWLIST_ARCFAST_SAMPLE_MAX)
|
||||
sample_index -= IM_DRAWLIST_ARCFAST_SAMPLE_MAX;
|
||||
|
||||
const ImVec2 s = _Data->ArcFastVtx[sample_index];
|
||||
out_ptr->x = center.x + s.x * radius;
|
||||
out_ptr->y = center.y + s.y * radius;
|
||||
out_ptr++;
|
||||
}
|
||||
|
||||
if (extra_max_sample)
|
||||
{
|
||||
int normalized_max_sample = a_max_sample % IM_DRAWLIST_ARCFAST_SAMPLE_MAX;
|
||||
if (normalized_max_sample < 0)
|
||||
normalized_max_sample += IM_DRAWLIST_ARCFAST_SAMPLE_MAX;
|
||||
|
||||
const ImVec2 s = _Data->ArcFastVtx[normalized_max_sample];
|
||||
out_ptr->x = center.x + s.x * radius;
|
||||
out_ptr->y = center.y + s.y * radius;
|
||||
out_ptr++;
|
||||
}
|
||||
|
||||
IM_ASSERT_PARANOID(_Path.Data + _Path.Size == out_ptr);
|
||||
}
|
||||
|
||||
void ImDrawList::PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments)
|
||||
void ImDrawList::_PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments)
|
||||
{
|
||||
if (radius <= 0.0f)
|
||||
{
|
||||
@ -1070,6 +1128,64 @@ void ImDrawList::PathArcTo(const ImVec2& center, float radius, float a_min, floa
|
||||
}
|
||||
}
|
||||
|
||||
// 0: East, 3: South, 6: West, 9: North, 12: East
|
||||
void ImDrawList::PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12)
|
||||
{
|
||||
if (radius <= 0.0f)
|
||||
{
|
||||
_Path.push_back(center);
|
||||
return;
|
||||
}
|
||||
IM_ASSERT(a_min_of_12 <= a_max_of_12);
|
||||
_PathArcToFastEx(center, radius, a_min_of_12 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, a_max_of_12 * IM_DRAWLIST_ARCFAST_SAMPLE_MAX / 12, 0);
|
||||
}
|
||||
|
||||
void ImDrawList::PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments)
|
||||
{
|
||||
if (radius <= 0.0f)
|
||||
{
|
||||
_Path.push_back(center);
|
||||
return;
|
||||
}
|
||||
IM_ASSERT(a_min <= a_max);
|
||||
|
||||
if (num_segments > 0)
|
||||
{
|
||||
_PathArcToN(center, radius, a_min, a_max, num_segments);
|
||||
return;
|
||||
}
|
||||
|
||||
// Automatic segment count
|
||||
if (radius <= _Data->ArcFastRadiusCutoff)
|
||||
{
|
||||
// We are going to use precomputed values for mid samples.
|
||||
// Determine first and last sample in lookup table that belong to the arc.
|
||||
const int a_min_sample = (int)ImCeil(IM_DRAWLIST_ARCFAST_SAMPLE_MAX * a_min / (IM_PI * 2.0f));
|
||||
const int a_max_sample = (int)( IM_DRAWLIST_ARCFAST_SAMPLE_MAX * a_max / (IM_PI * 2.0f));
|
||||
const int a_mid_samples = ImMax(a_max_sample - a_min_sample, 0);
|
||||
|
||||
const float a_min_segment_angle = a_min_sample * IM_PI * 2.0f / IM_DRAWLIST_ARCFAST_SAMPLE_MAX;
|
||||
const float a_max_segment_angle = a_max_sample * IM_PI * 2.0f / IM_DRAWLIST_ARCFAST_SAMPLE_MAX;
|
||||
const bool a_emit_start = (a_min_segment_angle - a_min) > 0.0f;
|
||||
const bool a_emit_end = (a_max - a_max_segment_angle) > 0.0f;
|
||||
|
||||
_Path.reserve(_Path.Size + (a_mid_samples + 1 + (a_emit_start ? 1 : 0) + (a_emit_end ? 1 : 0)));
|
||||
if (a_emit_start)
|
||||
_Path.push_back(ImVec2(center.x + ImCos(a_min) * radius, center.y + ImSin(a_min) * radius));
|
||||
if (a_max_sample >= a_min_sample)
|
||||
_PathArcToFastEx(center, radius, a_min_sample, a_max_sample, 0);
|
||||
if (a_emit_end)
|
||||
_Path.push_back(ImVec2(center.x + ImCos(a_max) * radius, center.y + ImSin(a_max) * radius));
|
||||
}
|
||||
else
|
||||
{
|
||||
const float arc_length = a_max - a_min;
|
||||
const int circle_segment_count = _CalcCircleAutoSegmentCount(radius);
|
||||
const int arc_segment_count = ImMax((int)ImCeil(circle_segment_count * arc_length / (IM_PI * 2.0f)), (int)(2.0f * IM_PI / arc_length));
|
||||
_PathArcToN(center, radius, a_min, a_max, arc_segment_count);
|
||||
}
|
||||
}
|
||||
|
||||
ImVec2 ImBezierCubicCalc(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, float t)
|
||||
{
|
||||
float u = 1.0f - t;
|
||||
|
@ -636,10 +636,15 @@ struct IMGUI_API ImChunkStream
|
||||
#define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX 512
|
||||
#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
|
||||
#define IM_DRAWLIST_ARCFAST_TESSELLATION_MULTIPLIER 1
|
||||
// Raw equation from IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC rewritten for 'r' and 'error'.
|
||||
#define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(_N,_MAXERROR) ((_MAXERROR) / (1 - ImCos(IM_PI / ImMax((float)(_N), IM_PI))))
|
||||
#define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_ERROR(_N,_RAD) ((1 - ImCos(IM_PI / ImMax((float)(_N), IM_PI))) / (_RAD))
|
||||
|
||||
// ImDrawList: Lookup table size for adaptive arc drawing, cover full circle.
|
||||
#ifndef IM_DRAWLIST_ARCFAST_TABLE_SIZE
|
||||
#define IM_DRAWLIST_ARCFAST_TABLE_SIZE 48 // Number of samples in lookup table.
|
||||
#endif
|
||||
#define IM_DRAWLIST_ARCFAST_SAMPLE_MAX IM_DRAWLIST_ARCFAST_TABLE_SIZE // Sample index _PathArcToFastEx() for 360 angle.
|
||||
|
||||
// Data shared between all ImDrawList instances
|
||||
// You may want to create your own instance of this if you want to use ImDrawList completely without ImGui. In that case, watch out for future changes to this structure.
|
||||
@ -654,7 +659,8 @@ struct IMGUI_API ImDrawListSharedData
|
||||
ImDrawListFlags InitialFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards)
|
||||
|
||||
// [Internal] Lookup tables
|
||||
ImVec2 ArcFastVtx[12 * IM_DRAWLIST_ARCFAST_TESSELLATION_MULTIPLIER]; // FIXME: Bake rounded corners fill/borders in atlas
|
||||
ImVec2 ArcFastVtx[IM_DRAWLIST_ARCFAST_TABLE_SIZE]; // Sample points on the quarter of the circle.
|
||||
float ArcFastRadiusCutoff; // Cutoff radius after which arc drawing will fallback to slower PathArcTo()
|
||||
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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user