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:
parent
04f40014a6
commit
1ff90c52d5
8
imgui.h
8
imgui.h
@ -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
|
||||
|
434
imgui_draw.cpp
434
imgui_draw.cpp
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user