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
   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.
   it would generally manifest when fast moving the mouse bottom to top in a sub-menu.
   (#7325, #7287, #7063)
   (#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
 - 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.
   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
   - This is the main wiki: https://github.com/ocornut/imgui/wiki

+ 4 - 2
imgui.h

@@ -28,7 +28,7 @@
 // Library Version
 // Library Version
 // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345')
 // (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       "1.90.5 WIP"
-#define IMGUI_VERSION_NUM   19043
+#define IMGUI_VERSION_NUM   19044
 #define IMGUI_HAS_TABLE
 #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)
     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
     // 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  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  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
     // Image primitives
     // - Read FAQ to understand what ImTextureID is.
     // - 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()
 // [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.
 // Demonstrate using the low-level ImDrawList to draw custom shapes.
 static void ShowExampleAppCustomRendering(bool* p_open)
 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->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.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
                 //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 + 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, 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
                 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->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.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
             //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 + 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 + 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)
             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);
             draw_list->PathFillConvex(col);
             x += sz + spacing;
             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));
             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::PopItemWidth();
             ImGui::EndTabItem();
             ImGui::EndTabItem();
         }
         }

+ 141 - 264
imgui_draw.cpp

@@ -8,7 +8,7 @@ Index of this file:
 // [SECTION] STB libraries implementation
 // [SECTION] STB libraries implementation
 // [SECTION] Style functions
 // [SECTION] Style functions
 // [SECTION] ImDrawList
 // [SECTION] ImDrawList
-// [SECTION] ImDrawList concave polygon fill
+// [SECTION] ImTriangulator, ImDrawList concave polygon fill
 // [SECTION] ImDrawListSplitter
 // [SECTION] ImDrawListSplitter
 // [SECTION] ImDrawData
 // [SECTION] ImDrawData
 // [SECTION] Helpers ShadeVertsXXX functions
 // [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();
     BuildReflexes();
     BuildEars();
     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()
 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;
             continue;
-
-        node->Type = Reflex;
-        _Reflexes.PushBack(node);
+        n1->Type = ImTriangulatorNodeType_Reflex;
+        _Reflexes.push_back(n1);
     }
     }
 }
 }
 
 
 void ImTriangulator::BuildEars()
 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;
             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;
             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)
     if (_Ears.Size == 0)
     {
     {
         FlipNodeList();
         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;
         _Reflexes.Size = 0;
         BuildReflexes();
         BuildReflexes();
         BuildEars();
         BuildEars();
@@ -1903,59 +1833,34 @@ ImTriangulator::Triangle ImTriangulator::Next()
         // If we still don't have ears, it means geometry is degenerated.
         // If we still don't have ears, it means geometry is degenerated.
         if (_Ears.Size == 0)
         if (_Ears.Size == 0)
         {
         {
-            IM_ASSERT(_TrianglesLeft > 0 && "Geometry is degenerated");
-
             // Return first triangle available, mimicking the behavior of convex fill.
             // Return first triangle available, mimicking the behavior of convex fill.
+            IM_ASSERT(_TrianglesLeft > 0); // Geometry is degenerated
             _Ears.Data[0] = _Nodes;
             _Ears.Data[0] = _Nodes;
             _Ears.Size    = 1;
             _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();
     ear->Unlink();
     if (ear == _Nodes)
     if (ear == _Nodes)
         _Nodes = ear->Next;
         _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()
 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->Next = prev;
     prev->Prev = prev;
     prev->Prev = prev;
-
     while (current != _Nodes)
     while (current != _Nodes)
     {
     {
         temp = current->Next;
         temp = current->Next;
@@ -1968,97 +1873,69 @@ void ImTriangulator::FlipNodeList()
         prev = current;
         prev = current;
         current = temp;
         current = temp;
     }
     }
-
     _Nodes = prev;
     _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;
     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
     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;
         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)
 void ImDrawList::AddConcavePolyFilled(const ImVec2* points, const int points_count, ImU32 col)
 {
 {
     if (points_count < 3 || (col & IM_COL32_A_MASK) == 0)
     if (points_count < 3 || (col & IM_COL32_A_MASK) == 0)
         return;
         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;
     const ImVec2 uv = _Data->TexUvWhitePixel;
-
+    ImTriangulator triangulator;
+    unsigned int triangle[3];
     if (Flags & ImDrawListFlags_AntiAliasedFill)
     if (Flags & ImDrawListFlags_AntiAliasedFill)
     {
     {
         // Anti-aliased Fill
         // Anti-aliased Fill
         const float AA_SIZE = _FringeScale;
         const float AA_SIZE = _FringeScale;
         const ImU32 col_trans = col & ~IM_COL32_A_MASK;
         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);
         const int vtx_count = (points_count * 2);
         PrimReserve(idx_count, vtx_count);
         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;
         unsigned int vtx_outer_idx = _VtxCurrentIdx + 1;
 
 
         _Data->TempBuffer.reserve_discard((ImTriangulator::EstimateScratchBufferSize(points_count) + sizeof(ImVec2)) / sizeof(ImVec2));
         _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;
             _IdxWritePtr += 3;
         }
         }
 
 
@@ -2115,7 +1992,7 @@ void ImDrawList::AddConcavePolyFilled(const ImVec2* points, const int points_cou
     else
     else
     {
     {
         // Non Anti-aliased Fill
         // 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;
         const int vtx_count = points_count;
         PrimReserve(idx_count, vtx_count);
         PrimReserve(idx_count, vtx_count);
         for (int i = 0; i < vtx_count; i++)
         for (int i = 0; i < vtx_count; i++)
@@ -2124,11 +2001,11 @@ void ImDrawList::AddConcavePolyFilled(const ImVec2* points, const int points_cou
             _VtxWritePtr++;
             _VtxWritePtr++;
         }
         }
         _Data->TempBuffer.reserve_discard((ImTriangulator::EstimateScratchBufferSize(points_count) + sizeof(ImVec2)) / sizeof(ImVec2));
         _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;
             _IdxWritePtr += 3;
         }
         }
         _VtxCurrentIdx += (ImDrawIdx)vtx_count;
         _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 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 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);
 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)
 // Helper: ImVec1 (1D vector)
 // (this odd construct is used to facilitate the transition between 1D and 2D, and the maintenance of some branches/patches)
 // (this odd construct is used to facilitate the transition between 1D and 2D, and the maintenance of some branches/patches)