Bläddra i källkod

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:
thedmd 1 år sedan
förälder
incheckning
1ff90c52d5
2 ändrade filer med 440 tillägg och 2 borttagningar
  1. 6 2
      imgui.h
  2. 434 0
      imgui_draw.cpp

+ 6 - 2
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  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 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  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  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)
     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
     // Image primitives
     // - Read FAQ to understand what ImTextureID is.
     // - Read FAQ to understand what ImTextureID is.
     // - "p_min" and "p_max" represent the upper-left and lower-right corners of the rectangle.
     // - "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  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  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  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; }
     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  PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments = 0);
     IMGUI_API void  PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12);                // Use precomputed angles for a 12 steps circle
     IMGUI_API void  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 - 0
imgui_draw.cpp

@@ -8,6 +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] ImDrawListSplitter
 // [SECTION] ImDrawListSplitter
 // [SECTION] ImDrawData
 // [SECTION] ImDrawData
 // [SECTION] Helpers ShadeVertsXXX functions
 // [SECTION] Helpers ShadeVertsXXX functions
@@ -1700,6 +1701,439 @@ void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_mi
         PopTextureID();
         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
 // [SECTION] ImDrawListSplitter