diff --git a/imgui.h b/imgui.h index a6a3ec8b7..4d5a8f012 100644 --- a/imgui.h +++ b/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 diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 218498f1a..1bb7cfed9 100644 --- a/imgui_draw.cpp +++ b/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(scratch_buffer); + _Ears.Data = reinterpret_cast(_Nodes + points_count); + _Reflexes.Data = _Ears.Data + points_count; + + BuildNodes(); + BuildReflexes(); + BuildEars(); +} + +void ImTriangulator::BuildNodes() +{ +# define IM_POINT_PTR(idx) reinterpret_cast(reinterpret_cast(_Points) + (idx) * _PointsStrideBytes) + + for (int i = 0; i < _PointsCount; ++i) + { + _Nodes[i].Type = Convex; + _Nodes[i].Index = static_cast(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