Browse Source

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

- Simplify and compact some code. Shallow tweaks.
- Add comments.
- Add concave shape demo.
- Remove coarse culling.
- Remove nested types to match coding style and for consistent type nams when translated to other languages.
- Merged ClassifyNode() and ReclassifyNode().
- Extracted ImTriangleIsClockwise().
- Hold copy of points inside nodes instead of pointing to them.
ocornut 1 year ago
parent
commit
fbf45ad149
5 changed files with 162 additions and 274 deletions
  1. 2 0
      docs/CHANGELOG.txt
  2. 4 2
      imgui.h
  3. 13 7
      imgui_demo.cpp
  4. 141 264
      imgui_draw.cpp
  5. 2 1
      imgui_internal.h

+ 2 - 0
docs/CHANGELOG.txt

@@ -47,6 +47,8 @@ Other changes:
   frames would erroneously close the window. While it is technically a popup issue
   it would generally manifest when fast moving the mouse bottom to top in a sub-menu.
   (#7325, #7287, #7063)
+- DrawList: Added AddConcavePolyFilled(), PathFillConcave() concave filling. (#760) [@thedmd]
+  Note that only simple polygons (no self-intersections, no holes) are supported.
 - Docs: added more wiki links to headers of imgui.h/imgui.cpp to facilitate discovery
   of interesting resources, because github doesn't allow Wiki to be crawled by search engines.
   - This is the main wiki: https://github.com/ocornut/imgui/wiki

+ 4 - 2
imgui.h

@@ -28,7 +28,7 @@
 // Library Version
 // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345')
 #define IMGUI_VERSION       "1.90.5 WIP"
-#define IMGUI_VERSION_NUM   19043
+#define IMGUI_VERSION_NUM   19044
 #define IMGUI_HAS_TABLE
 
 /*
@@ -2760,9 +2760,11 @@ struct ImDrawList
     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
+    // - Only simple polygons are supported by filling functions (no self-intersections, no holes).
+    // - Concave polygon fill is more expensive than convex one: it has O(N^2) complexity. Provided as a convenience fo user but not used by main library.
     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);
+    IMGUI_API void  AddConcavePolyFilled(const ImVec2* points, int num_points, ImU32 col);
 
     // Image primitives
     // - Read FAQ to understand what ImTextureID is.

+ 13 - 7
imgui_demo.cpp

@@ -7964,6 +7964,14 @@ static void ShowExampleAppWindowTitles(bool*)
 // [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering()
 //-----------------------------------------------------------------------------
 
+// Add a |_| looking shape
+static void PathConcaveShape(ImDrawList* draw_list, float x, float y, float sz)
+{
+    const ImVec2 pos_norms[] = { { 0.0f, 0.0f }, { 0.3f, 0.0f }, { 0.3f, 0.7f }, { 0.7f, 0.7f }, { 0.7f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f }, { 0.0f, 1.0f } };
+    for (const ImVec2& p : pos_norms)
+        draw_list->PathLineTo(ImVec2(x + 0.5f + (int)(sz * p.x), y + 0.5f + (int)(sz * p.y)));
+}
+
 // Demonstrate using the low-level ImDrawList to draw custom shapes.
 static void ShowExampleAppCustomRendering(bool* p_open)
 {
@@ -8053,6 +8061,8 @@ static void ShowExampleAppCustomRendering(bool* p_open)
                 draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, corners_tl_br, th);         x += sz + spacing;  // Square with two rounded corners
                 draw_list->AddTriangle(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col, th);x += sz + spacing;  // Triangle
                 //draw_list->AddTriangle(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col, th);x+= sz*0.4f + spacing; // Thin triangle
+                PathConcaveShape(draw_list, x, y, sz); draw_list->PathStroke(col, ImDrawFlags_Closed, th);          x += sz + spacing;  // Concave Shape
+                //draw_list->AddPolyline(concave_shape, IM_ARRAYSIZE(concave_shape), col, ImDrawFlags_Closed, th);
                 draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y), col, th);                                       x += sz + spacing;  // Horizontal line (note: drawing a filled rectangle will be faster!)
                 draw_list->AddLine(ImVec2(x, y), ImVec2(x, y + sz), col, th);                                       x += spacing;       // Vertical line (note: drawing a filled rectangle will be faster!)
                 draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th);                                  x += sz + spacing;  // Diagonal line
@@ -8082,6 +8092,7 @@ static void ShowExampleAppCustomRendering(bool* p_open)
             draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 10.0f, corners_tl_br);              x += sz + spacing;  // Square with two rounded corners
             draw_list->AddTriangleFilled(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col);  x += sz + spacing;  // Triangle
             //draw_list->AddTriangleFilled(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col); x += sz*0.4f + spacing; // Thin triangle
+            PathConcaveShape(draw_list, x, y, sz); draw_list->PathFillConcave(col);                                 x += sz + spacing;  // Concave shape
             draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + thickness), col);                             x += sz + spacing;  // Horizontal line (faster than AddLine, but only handle integer thickness)
             draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + thickness, y + sz), col);                             x += spacing * 2.0f;// Vertical line (faster than AddLine, but only handle integer thickness)
             draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + 1, y + 1), col);                                      x += sz;            // Pixel (faster than AddLine)
@@ -8097,15 +8108,10 @@ static void ShowExampleAppCustomRendering(bool* p_open)
             draw_list->PathFillConvex(col);
             x += sz + spacing;
 
-            // Cubic Bezier Curve (4 control points): this is concave so not drawing it yet
-            //draw_list->PathLineTo(ImVec2(x + cp4[0].x, y + cp4[0].y));
-            //draw_list->PathBezierCubicCurveTo(ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), curve_segments);
-            //draw_list->PathFillConvex(col);
-            //x += sz + spacing;
-
             draw_list->AddRectFilledMultiColor(ImVec2(x, y), ImVec2(x + sz, y + sz), IM_COL32(0, 0, 0, 255), IM_COL32(255, 0, 0, 255), IM_COL32(255, 255, 0, 255), IM_COL32(0, 255, 0, 255));
+            x += sz + spacing;
 
-            ImGui::Dummy(ImVec2((sz + spacing) * 12.2f, (sz + spacing) * 3.0f));
+            ImGui::Dummy(ImVec2((sz + spacing) * 13.2f, (sz + spacing) * 3.0f));
             ImGui::PopItemWidth();
             ImGui::EndTabItem();
         }

+ 141 - 264
imgui_draw.cpp

@@ -8,7 +8,7 @@ Index of this file:
 // [SECTION] STB libraries implementation
 // [SECTION] Style functions
 // [SECTION] ImDrawList
-// [SECTION] ImDrawList concave polygon fill
+// [SECTION] ImTriangulator, ImDrawList concave polygon fill
 // [SECTION] ImDrawListSplitter
 // [SECTION] ImDrawData
 // [SECTION] Helpers ShadeVertsXXX functions
@@ -1702,200 +1702,130 @@ void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_mi
 }
 
 //-----------------------------------------------------------------------------
-// [SECTION] ImDrawList concave polygon fill
+// [SECTION] ImTriangulator, ImDrawList concave polygon fill
+//-----------------------------------------------------------------------------
+// Triangulate concave polygons. Based on "Triangulation by Ear Clipping" paper, O(N^2) complexity.
+// Reference: https://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf
+// Provided as a convenience for user but not used by main library.
+//-----------------------------------------------------------------------------
+// - ImTriangulator [Internal]
+// - AddConcavePolyFilled()
 //-----------------------------------------------------------------------------
 
-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
+enum ImTriangulatorNodeType
 {
-    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;
+    ImTriangulatorNodeType_Convex,
+    ImTriangulatorNodeType_Ear,
+    ImTriangulatorNodeType_Reflex
 };
 
-struct alignas(void*) ImTriangulator::Node
+struct ImTriangulatorNode
 {
-    Type          Type  = Convex;
-    int           Index = 0;
-    const ImVec2* Point = nullptr;
-    Node*         Next  = nullptr;
-    Node*         Prev  = nullptr;
+    ImTriangulatorNodeType  Type;
+    int                     Index;
+    ImVec2                  Pos;
+    ImTriangulatorNode*     Next;
+    ImTriangulatorNode*     Prev;
 
-    void Unlink()
-    {
-        Next->Prev = Prev;
-        Prev->Next = Next;
-    }
+    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)
+struct ImTriangulatorNodeSpan
 {
-    return sizeof(Node*) * points_count * 2 + sizeof(Node) * points_count;
-}
+    ImTriangulatorNode**    Data = NULL;
+    int                     Size = 0;
 
-ImTriangulator::ImTriangulator(const ImVec2* points, int points_count, void* scratch_buffer)
-    : ImTriangulator(points, points_count, sizeof(ImVec2), scratch_buffer)
-{
-}
+    void    push_back(ImTriangulatorNode* node) { Data[Size++] = node; }
+    void    find_erase_unsorted(int idx)        { for (int i = Size - 1; i >= 0; i--) if (Data[i]->Index == idx) { Data[i] = Data[Size - 1]; Size--; return; } }
+};
 
-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))
+struct ImTriangulator
 {
-    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;
+    static int EstimateTriangleCount(int points_count)      { return (points_count < 3) ? 0 : points_count - 2; }
+    static int EstimateScratchBufferSize(int points_count)  { return sizeof(ImTriangulatorNode) * points_count + sizeof(ImTriangulatorNode*) * points_count * 2; }
+
+    void    Init(const ImVec2* points, int points_count, void* scratch_buffer);
+    void    GetNextTriangle(unsigned int out_triangle[3]);     // Return relative indexes for next triangle
+
+    // Internal functions
+    void    BuildNodes(const ImVec2* points, int points_count);
+    void    BuildReflexes();
+    void    BuildEars();
+    void    FlipNodeList();
+    bool    IsEar(int i0, int i1, int i2, const ImVec2& v0, const ImVec2& v1, const ImVec2& v2) const;
+    void    ReclassifyNode(ImTriangulatorNode* node);
+
+    // Internal members
+    int                     _TrianglesLeft = 0;
+    ImTriangulatorNode*     _Nodes = NULL;
+    ImTriangulatorNodeSpan  _Ears;
+    ImTriangulatorNodeSpan  _Reflexes;
+};
 
-    BuildNodes();
+// Distribute storage for nodes, ears and reflexes.
+// FIXME-OPT: if everything is convex, we could report it to caller and let it switch to an convex renderer
+// (this would require first building reflexes to bail to convex if empty, without even building nodes)
+void ImTriangulator::Init(const ImVec2* points, int points_count, void* scratch_buffer)
+{
+    IM_ASSERT(scratch_buffer != NULL && points_count >= 3);
+    _TrianglesLeft = EstimateTriangleCount(points_count);
+    _Nodes         = (ImTriangulatorNode*)scratch_buffer;                          // points_count x Node
+    _Ears.Data     = (ImTriangulatorNode**)(_Nodes + points_count);                // points_count x Node*
+    _Reflexes.Data = (ImTriangulatorNode**)(_Nodes + points_count) + points_count; // points_count x Node*
+    BuildNodes(points, points_count);
     BuildReflexes();
     BuildEars();
 }
 
-void ImTriangulator::BuildNodes()
+void ImTriangulator::BuildNodes(const ImVec2* points, int points_count)
 {
-# define IM_POINT_PTR(idx)     reinterpret_cast<const ImVec2*>(reinterpret_cast<const ImU8*>(_Points) + (idx) * _PointsStrideBytes)
-
-    for (int i = 0; i < _PointsCount; ++i)
+    for (int i = 0; i < points_count; 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[i].Type = ImTriangulatorNodeType_Convex;
+        _Nodes[i].Index = i;
+        _Nodes[i].Pos = points[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
+    _Nodes[0].Prev = _Nodes + points_count - 1;
+    _Nodes[points_count - 1].Next = _Nodes;
 }
 
 void ImTriangulator::BuildReflexes()
 {
-    Node* node = _Nodes;
-    for (int i = 0; i < _TrianglesLeft; ++i, node = node->Next)
+    ImTriangulatorNode* n1 = _Nodes;
+    for (int i = _TrianglesLeft; i >= 0; i--, n1 = n1->Next)
     {
-        const ImVec2& v0 = *node->Prev->Point;
-        const ImVec2& v1 = *node->Point;
-        const ImVec2& v2 = *node->Next->Point;
-
-        if (ImPathIsConvex(v0, v1, v2))
+        if (ImTriangleIsClockwise(n1->Prev->Pos, n1->Pos, n1->Next->Pos))
             continue;
-
-        node->Type = Reflex;
-        _Reflexes.PushBack(node);
+        n1->Type = ImTriangulatorNodeType_Reflex;
+        _Reflexes.push_back(n1);
     }
 }
 
 void ImTriangulator::BuildEars()
 {
-    Node* node = _Nodes;
-    for (int i = 0; i < _TrianglesLeft; ++i, node = node->Next)
+    ImTriangulatorNode* n1 = _Nodes;
+    for (int i = _TrianglesLeft; i >= 0; i--, n1 = n1->Next)
     {
-        if (node->Type != Convex)
+        if (n1->Type != ImTriangulatorNodeType_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))
+        if (!IsEar(n1->Prev->Index, n1->Index, n1->Next->Index, n1->Prev->Pos, n1->Pos, n1->Next->Pos))
             continue;
-
-        node->Type = Ear;
-        _Ears.PushBack(node);
+        n1->Type = ImTriangulatorNodeType_Ear;
+        _Ears.push_back(n1);
     }
 }
 
-bool ImTriangulator::HasNext() const
-{
-    return _TrianglesLeft > 0;
-}
-
-ImTriangulator::Triangle ImTriangulator::Next()
+void ImTriangulator::GetNextTriangle(unsigned int out_triangle[3])
 {
-    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;
+        ImTriangulatorNode* node = _Nodes;
+        for (int i = _TrianglesLeft; i >= 0; i--, node = node->Next)
+            node->Type = ImTriangulatorNodeType_Convex;
         _Reflexes.Size = 0;
         BuildReflexes();
         BuildEars();
@@ -1903,59 +1833,34 @@ ImTriangulator::Triangle ImTriangulator::Next()
         // 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.
+            IM_ASSERT(_TrianglesLeft > 0); // Geometry is degenerated
             _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;
+    ImTriangulatorNode* ear = _Ears.Data[--_Ears.Size];
+    out_triangle[0] = ear->Prev->Index;
+    out_triangle[1] = ear->Index;
+    out_triangle[2] = 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;
-        }
-    }
+    ReclassifyNode(ear->Prev);
+    ReclassifyNode(ear->Next);
+    _TrianglesLeft--;
 }
 
 void ImTriangulator::FlipNodeList()
 {
-    Node* prev = _Nodes;
-    Node* temp = _Nodes;
-    Node* current = _Nodes->Next;
-
+    ImTriangulatorNode* prev = _Nodes;
+    ImTriangulatorNode* temp = _Nodes;
+    ImTriangulatorNode* current = _Nodes->Next;
     prev->Next = prev;
     prev->Prev = prev;
-
     while (current != _Nodes)
     {
         temp = current->Next;
@@ -1968,97 +1873,69 @@ void ImTriangulator::FlipNodeList()
         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
+// A triangle is an ear is no other vertex is inside it. We can test reflexes vertices only (see reference algorithm)
+bool ImTriangulator::IsEar(int i0, int i1, int i2, const ImVec2& v0, const ImVec2& v1, const ImVec2& v2) const
 {
-    for (int i = 0; i < _Reflexes.Size; ++i)
+    ImTriangulatorNode** p_end = _Reflexes.Data + _Reflexes.Size;
+    for (ImTriangulatorNode** p = _Reflexes.Data; p < p_end; p++)
     {
-        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;
+        ImTriangulatorNode* reflex = *p;
+        if (reflex->Index != i0 && reflex->Index != i1 && reflex->Index != i2)
+            if (ImTriangleContainsPoint(v0, v1, v2, reflex->Pos))
+                return false;
     }
-
     return true;
 }
 
-ImTriangulator::Type ImTriangulator::ClassifyNode(const Node* node) const
+void ImTriangulator::ReclassifyNode(ImTriangulatorNode* n1)
 {
-    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;
-    }
+    // Classify node
+    ImTriangulatorNodeType type;
+    const ImTriangulatorNode* n0 = n1->Prev;
+    const ImTriangulatorNode* n2 = n1->Next;
+    if (!ImTriangleIsClockwise(n0->Pos, n1->Pos, n2->Pos))
+        type = ImTriangulatorNodeType_Reflex;
+    else if (IsEar(n0->Index, n1->Index, n2->Index, n0->Pos, n1->Pos, n2->Pos))
+        type = ImTriangulatorNodeType_Ear;
     else
-    {
-        return Reflex;
-    }
-}
-
-void ImTriangulator::ReclasifyNode(Node* node)
-{
-    Type type = ClassifyNode(node);
+        type = ImTriangulatorNodeType_Convex;
 
-    if (type == node->Type)
+    // Update lists when a type changes
+    if (type == n1->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;
-}
-
+    if (n1->Type == ImTriangulatorNodeType_Reflex)
+        _Reflexes.find_erase_unsorted(n1->Index);
+    else if (n1->Type == ImTriangulatorNodeType_Ear)
+        _Ears.find_erase_unsorted(n1->Index);
+    if (type == ImTriangulatorNodeType_Reflex)
+        _Reflexes.push_back(n1);
+    else if (type == ImTriangulatorNodeType_Ear)
+        _Ears.push_back(n1);
+    n1->Type = type;
+}
+
+// Use ear-clipping algorithm to triangulate a simple polygon (no self-interaction, no holes).
+// (Reminder: we don't perform any coarse clipping/culling in ImDrawList layer!
+// It is up to caller to ensure not making costly calls that will be outside of visible area.
+// As concave fill is noticeably more expensive than other primitives, be mindful of this...
+// Caller can build AABB of points, and avoid filling if 'draw_list->_CmdHeader.ClipRect.Overlays(points_bb) == false')
 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;
-
+    ImTriangulator triangulator;
+    unsigned int triangle[3];
     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 idx_count = (points_count - 2) * 3 + points_count * 6;
         const int vtx_count = (points_count * 2);
         PrimReserve(idx_count, vtx_count);
 
@@ -2067,11 +1944,11 @@ void ImDrawList::AddConcavePolyFilled(const ImVec2* points, const int points_cou
         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())
+        triangulator.Init(points, points_count, _Data->TempBuffer.Data);
+        while (triangulator._TrianglesLeft > 0)
         {
-            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));
+            triangulator.GetNextTriangle(triangle);
+            _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx + (triangle[0] << 1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + (triangle[1] << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx + (triangle[2] << 1));
             _IdxWritePtr += 3;
         }
 
@@ -2115,7 +1992,7 @@ void ImDrawList::AddConcavePolyFilled(const ImVec2* points, const int points_cou
     else
     {
         // Non Anti-aliased Fill
-        const int idx_count = (points_count - 2)*3;
+        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++)
@@ -2124,11 +2001,11 @@ void ImDrawList::AddConcavePolyFilled(const ImVec2* points, const int points_cou
             _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())
+        triangulator.Init(points, points_count, _Data->TempBuffer.Data);
+        while (triangulator._TrianglesLeft > 0)
         {
-            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]);
+            triangulator.GetNextTriangle(triangle);
+            _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx + triangle[0]); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx + triangle[1]); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx + triangle[2]);
             _IdxWritePtr += 3;
         }
         _VtxCurrentIdx += (ImDrawIdx)vtx_count;

+ 2 - 1
imgui_internal.h

@@ -498,7 +498,8 @@ IMGUI_API ImVec2     ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const
 IMGUI_API bool       ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p);
 IMGUI_API ImVec2     ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p);
 IMGUI_API void       ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w);
-inline float         ImTriangleArea(const ImVec2& a, const ImVec2& b, const ImVec2& c) { return ImFabs((a.x * (b.y - c.y)) + (b.x * (c.y - a.y)) + (c.x * (a.y - b.y))) * 0.5f; }
+inline float         ImTriangleArea(const ImVec2& a, const ImVec2& b, const ImVec2& c)          { return ImFabs((a.x * (b.y - c.y)) + (b.x * (c.y - a.y)) + (c.x * (a.y - b.y))) * 0.5f; }
+inline bool          ImTriangleIsClockwise(const ImVec2& a, const ImVec2& b, const ImVec2& c)   { return ((b.x - a.x) * (c.y - b.y)) - ((c.x - b.x) * (b.y - a.y)) > 0.0f; }
 
 // Helper: ImVec1 (1D vector)
 // (this odd construct is used to facilitate the transition between 1D and 2D, and the maintenance of some branches/patches)