ImDrawList: add PathFillConcave(), AddConcavePolyFilled() (#760)

Extracted from 2023/12/29 post.
WIP add PathFillConcave(), AddConcavePolyFilled()
* remove use of 'auto'
* IsConvex -> ImPathIsConvex
* Triangulator -> ImTriangulator
* ImTriangulator: split declaration from definition, ImTriangulator can be put in the header if necessary
* ImTriangulator: Add node list flip to reverse winding order and handle degenerate cases
* ImTriangulator: Remove _HeapStorage, always require scratch buffer to be provided
* ImTriangulator: Use ImTriangleContainsPoint
* AddConcavePolyFilled: Clone AddConvexPolyFilled and use triangulator
* AddConcavePolyFilled: Remove ImDrawListEx_AddPolyFilled_xxx
* AddConcavePolyFilled: Use _Data->TempBuffer in triangulator
* AddConcavePolyFilled:
This commit is contained in:
thedmd 2024-01-03 14:31:45 +01:00 committed by ocornut
parent 04f40014a6
commit 1ff90c52d5
2 changed files with 440 additions and 2 deletions

View File

@ -2756,11 +2756,14 @@ struct ImDrawList
IMGUI_API void AddEllipseFilled(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0);
IMGUI_API void AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL);
IMGUI_API void AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL);
IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness);
IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col);
IMGUI_API void AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0); // Cubic Bezier (4 control points)
IMGUI_API void AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments = 0); // Quadratic Bezier (3 control points)
// General polygon
IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness);
IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col);
IMGUI_API void AddConcavePolyFilled(const ImVec2* points, const int points_count, ImU32 col);
// Image primitives
// - Read FAQ to understand what ImTextureID is.
// - "p_min" and "p_max" represent the upper-left and lower-right corners of the rectangle.
@ -2776,6 +2779,7 @@ struct ImDrawList
inline void PathLineTo(const ImVec2& pos) { _Path.push_back(pos); }
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; }
inline void PathFillConcave(ImU32 col) { AddConcavePolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; }
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 = 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

View File

@ -8,6 +8,7 @@ Index of this file:
// [SECTION] STB libraries implementation
// [SECTION] Style functions
// [SECTION] ImDrawList
// [SECTION] ImDrawList concave polygon fill
// [SECTION] ImDrawListSplitter
// [SECTION] ImDrawData
// [SECTION] Helpers ShadeVertsXXX functions
@ -1700,6 +1701,439 @@ void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_mi
PopTextureID();
}
//-----------------------------------------------------------------------------
// [SECTION] ImDrawList concave polygon fill
//-----------------------------------------------------------------------------
static bool ImPathIsConvex(const ImVec2& a, const ImVec2& b, const ImVec2& c)
{
const float dx0 = b.x - a.x;
const float dy0 = b.y - a.y;
const float dx1 = c.x - b.x;
const float dy1 = c.y - b.y;
return dx0 * dy1 - dx1 * dy0 > 0.0f;
}
struct ImTriangulator
{
struct Triangle { int Index[3]; };
static int EstimateTriangleCount(int points_count);
static int EstimateScratchBufferSize(int points_count);
ImTriangulator(const ImVec2* points, int points_count, void* scratch_buffer);
ImTriangulator(const ImVec2* points, int points_count, int points_stride_bytes, void* scratch_buffer);
bool HasNext() const;
Triangle Next();
private:
enum Type { Convex, Ear, Reflex };
struct Node;
struct alignas(void*) Span
{
Node** Data = nullptr;
int Size = 0;
void PushBack(Node* node);
void RemoveByIndex(int index);
};
void BuildNodes();
void BuildReflexes();
void BuildEars();
void FlipNodeList();
bool IsEar(const Node* node, int i0, int i1, int i2, const ImVec2& v0, const ImVec2& v1, const ImVec2& v2) const;
Type ClassifyNode(const Node* node) const;
void ReclasifyNode(Node* node);
const ImVec2* _Points = nullptr;
int _PointsCount = 0;
int _PointsStrideBytes = 0;
int _TrianglesLeft = 0;
Node* _Nodes = nullptr;
Span _Ears;
Span _Reflexes;
};
struct alignas(void*) ImTriangulator::Node
{
Type Type = Convex;
int Index = 0;
const ImVec2* Point = nullptr;
Node* Next = nullptr;
Node* Prev = nullptr;
void Unlink()
{
Next->Prev = Prev;
Prev->Next = Next;
}
};
int ImTriangulator::EstimateTriangleCount(int points_count)
{
if (points_count < 3)
return 0;
return points_count - 2;
}
int ImTriangulator::EstimateScratchBufferSize(int points_count)
{
return sizeof(Node*) * points_count * 2 + sizeof(Node) * points_count;
}
ImTriangulator::ImTriangulator(const ImVec2* points, int points_count, void* scratch_buffer)
: ImTriangulator(points, points_count, sizeof(ImVec2), scratch_buffer)
{
}
ImTriangulator::ImTriangulator(const ImVec2* points, int points_count, int points_stride_bytes, void* scratch_buffer)
: _Points(points)
, _PointsCount(points_count)
, _PointsStrideBytes(points_stride_bytes)
, _TrianglesLeft(EstimateTriangleCount(points_count))
{
IM_ASSERT(scratch_buffer != nullptr && "Must provide scratch buffer.");
IM_ASSERT(points_count >= 3);
// Disable triangulator if scratch buffer isn't provided.
if (scratch_buffer == nullptr)
{
_TrianglesLeft = 0;
points_count = 0;
return;
}
// Distribute storage for nodes, ears and reflexes.
_Nodes = reinterpret_cast<Node*>(scratch_buffer);
_Ears.Data = reinterpret_cast<Node**>(_Nodes + points_count);
_Reflexes.Data = _Ears.Data + points_count;
BuildNodes();
BuildReflexes();
BuildEars();
}
void ImTriangulator::BuildNodes()
{
# define IM_POINT_PTR(idx) reinterpret_cast<const ImVec2*>(reinterpret_cast<const ImU8*>(_Points) + (idx) * _PointsStrideBytes)
for (int i = 0; i < _PointsCount; ++i)
{
_Nodes[i].Type = Convex;
_Nodes[i].Index = static_cast<int>(i);
_Nodes[i].Point = IM_POINT_PTR(i);
_Nodes[i].Next = _Nodes + i + 1;
_Nodes[i].Prev = _Nodes + i - 1;
}
_Nodes[0].Prev = _Nodes + _PointsCount - 1;
_Nodes[_PointsCount - 1].Next = _Nodes;
# undef IM_POINT_PTR
}
void ImTriangulator::BuildReflexes()
{
Node* node = _Nodes;
for (int i = 0; i < _TrianglesLeft; ++i, node = node->Next)
{
const ImVec2& v0 = *node->Prev->Point;
const ImVec2& v1 = *node->Point;
const ImVec2& v2 = *node->Next->Point;
if (ImPathIsConvex(v0, v1, v2))
continue;
node->Type = Reflex;
_Reflexes.PushBack(node);
}
}
void ImTriangulator::BuildEars()
{
Node* node = _Nodes;
for (int i = 0; i < _TrianglesLeft; ++i, node = node->Next)
{
if (node->Type != Convex)
continue;
const int i0 = node->Prev->Index;
const int i1 = node->Index;
const int i2 = node->Next->Index;
const ImVec2& v0 = *node->Prev->Point;
const ImVec2& v1 = *node->Point;
const ImVec2& v2 = *node->Next->Point;
if (!IsEar(node, i0, i1, i2, v0, v1, v2))
continue;
node->Type = Ear;
_Ears.PushBack(node);
}
}
bool ImTriangulator::HasNext() const
{
return _TrianglesLeft > 0;
}
ImTriangulator::Triangle ImTriangulator::Next()
{
IM_ASSERT(_TrianglesLeft > 0 && "Do not call Next() until HasNext() return true");
if (_Ears.Size == 0)
{
FlipNodeList();
Node* node = _Nodes;
for (int i = 0; i < _TrianglesLeft; ++i, node = node->Next)
node->Type = Convex;
_Reflexes.Size = 0;
BuildReflexes();
BuildEars();
// If we still don't have ears, it means geometry is degenerated.
if (_Ears.Size == 0)
{
IM_ASSERT(_TrianglesLeft > 0 && "Geometry is degenerated");
// Return first triangle available, mimicking the behavior of convex fill.
_Ears.Data[0] = _Nodes;
_Ears.Size = 1;
}
}
Node* ear = _Ears.Data[--_Ears.Size];
const int i0 = ear->Prev->Index;
const int i1 = ear->Index;
const int i2 = ear->Next->Index;
ear->Unlink();
if (ear == _Nodes)
_Nodes = ear->Next;
ReclasifyNode(ear->Prev);
ReclasifyNode(ear->Next);
--_TrianglesLeft;
return Triangle{ { i0, i1, i2 } };
}
void ImTriangulator::Span::PushBack(Node* node)
{
Data[Size++] = node;
}
void ImTriangulator::Span::RemoveByIndex(int index)
{
for (int i = Size - 1; i >= 0; --i)
{
if (Data[i]->Index == index)
{
Data[i] = Data[Size - 1];
--Size;
break;
}
}
}
void ImTriangulator::FlipNodeList()
{
Node* prev = _Nodes;
Node* temp = _Nodes;
Node* current = _Nodes->Next;
prev->Next = prev;
prev->Prev = prev;
while (current != _Nodes)
{
temp = current->Next;
current->Next = prev;
prev->Prev = current;
_Nodes->Next = current;
current->Prev = _Nodes;
prev = current;
current = temp;
}
_Nodes = prev;
}
bool ImTriangulator::IsEar(const Node* node, int i0, int i1, int i2, const ImVec2& v0, const ImVec2& v1, const ImVec2& v2) const
{
for (int i = 0; i < _Reflexes.Size; ++i)
{
Node* reflex = _Reflexes.Data[i];
if (reflex->Index == i0 || reflex->Index == i1 || reflex->Index == i2)
continue;
if (ImTriangleContainsPoint(v0, v1, v2, *reflex->Point))
return false;
}
return true;
}
ImTriangulator::Type ImTriangulator::ClassifyNode(const Node* node) const
{
const int i0 = node->Prev->Index;
const int i1 = node->Index;
const int i2 = node->Next->Index;
const ImVec2& v0 = *node->Prev->Point;
const ImVec2& v1 = *node->Point;
const ImVec2& v2 = *node->Next->Point;
if (ImPathIsConvex(v0, v1, v2))
{
if (IsEar(node, i0, i1, i2, v0, v1, v2))
return Ear;
else
return Convex;
}
else
{
return Reflex;
}
}
void ImTriangulator::ReclasifyNode(Node* node)
{
Type type = ClassifyNode(node);
if (type == node->Type)
return;
if (node->Type == Reflex)
_Reflexes.RemoveByIndex(node->Index);
else if (node->Type == Ear)
_Ears.RemoveByIndex(node->Index);
if (type == Reflex)
_Reflexes.PushBack(node);
else if (type == Ear)
_Ears.PushBack(node);
node->Type = type;
}
void ImDrawList::AddConcavePolyFilled(const ImVec2* points, const int points_count, ImU32 col)
{
if (points_count < 3 || (col & IM_COL32_A_MASK) == 0)
return;
// coarse culling against viewport to avoid processing triangles outside of the visible area
ImVec2 bounds_min = ImVec2(FLT_MAX, FLT_MAX);
ImVec2 bounds_max = ImVec2(-FLT_MAX, -FLT_MAX);
for (int i = 0; i < points_count; ++i)
{
const ImVec2& pos = points[i];
bounds_min = ImMin(bounds_min, pos);
bounds_max = ImMax(bounds_max, pos);
}
if (!ImRect(_ClipRectStack.back()).Overlaps(ImRect(bounds_min, bounds_max)))
return;
const ImVec2 uv = _Data->TexUvWhitePixel;
if (Flags & ImDrawListFlags_AntiAliasedFill)
{
// Anti-aliased Fill
const float AA_SIZE = _FringeScale;
const ImU32 col_trans = col & ~IM_COL32_A_MASK;
const int idx_count = (points_count - 2)*3 + points_count * 6;
const int vtx_count = (points_count * 2);
PrimReserve(idx_count, vtx_count);
// Add indexes for fill
unsigned int vtx_inner_idx = _VtxCurrentIdx;
unsigned int vtx_outer_idx = _VtxCurrentIdx + 1;
_Data->TempBuffer.reserve_discard((ImTriangulator::EstimateScratchBufferSize(points_count) + sizeof(ImVec2)) / sizeof(ImVec2));
ImTriangulator triangulator = ImTriangulator(points, points_count, _Data->TempBuffer.Data);
while (triangulator.HasNext())
{
ImTriangulator::Triangle triangle = triangulator.Next();
_IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx + (triangle.Index[0] << 1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + (triangle.Index[1] << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx + (triangle.Index[2] << 1));
_IdxWritePtr += 3;
}
// Compute normals
_Data->TempBuffer.reserve_discard(points_count);
ImVec2* temp_normals = _Data->TempBuffer.Data;
for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++)
{
const ImVec2& p0 = points[i0];
const ImVec2& p1 = points[i1];
float dx = p1.x - p0.x;
float dy = p1.y - p0.y;
IM_NORMALIZE2F_OVER_ZERO(dx, dy);
temp_normals[i0].x = dy;
temp_normals[i0].y = -dx;
}
for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++)
{
// Average normals
const ImVec2& n0 = temp_normals[i0];
const ImVec2& n1 = temp_normals[i1];
float dm_x = (n0.x + n1.x) * 0.5f;
float dm_y = (n0.y + n1.y) * 0.5f;
IM_FIXNORMAL2F(dm_x, dm_y);
dm_x *= AA_SIZE * 0.5f;
dm_y *= AA_SIZE * 0.5f;
// Add vertices
_VtxWritePtr[0].pos.x = (points[i1].x - dm_x); _VtxWritePtr[0].pos.y = (points[i1].y - dm_y); _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; // Inner
_VtxWritePtr[1].pos.x = (points[i1].x + dm_x); _VtxWritePtr[1].pos.y = (points[i1].y + dm_y); _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans; // Outer
_VtxWritePtr += 2;
// Add indexes for fringes
_IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx + (i1 << 1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + (i0 << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_outer_idx + (i0 << 1));
_IdxWritePtr[3] = (ImDrawIdx)(vtx_outer_idx + (i0 << 1)); _IdxWritePtr[4] = (ImDrawIdx)(vtx_outer_idx + (i1 << 1)); _IdxWritePtr[5] = (ImDrawIdx)(vtx_inner_idx + (i1 << 1));
_IdxWritePtr += 6;
}
_VtxCurrentIdx += (ImDrawIdx)vtx_count;
}
else
{
// Non Anti-aliased Fill
const int idx_count = (points_count - 2)*3;
const int vtx_count = points_count;
PrimReserve(idx_count, vtx_count);
for (int i = 0; i < vtx_count; i++)
{
_VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col;
_VtxWritePtr++;
}
_Data->TempBuffer.reserve_discard((ImTriangulator::EstimateScratchBufferSize(points_count) + sizeof(ImVec2)) / sizeof(ImVec2));
ImTriangulator triangulator = ImTriangulator(points, points_count, _Data->TempBuffer.Data);
while (triangulator.HasNext())
{
ImTriangulator::Triangle triangle = triangulator.Next();
_IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx + triangle.Index[0]); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx + triangle.Index[1]); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx + triangle.Index[2]);
_IdxWritePtr += 3;
}
_VtxCurrentIdx += (ImDrawIdx)vtx_count;
}
}
//-----------------------------------------------------------------------------
// [SECTION] ImDrawListSplitter