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.
features/potocpav-newer-lines-2
ocornut ago%!(EXTRA string=1 year)
parent 1ff90c52d5
commit fbf45ad149
  1. 2
      docs/CHANGELOG.txt
  2. 6
      imgui.h
  3. 20
      imgui_demo.cpp
  4. 383
      imgui_draw.cpp
  5. 1
      imgui_internal.h

@ -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

@ -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.

@ -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();
} }

@ -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) enum ImTriangulatorNodeType
{ {
const float dx0 = b.x - a.x; ImTriangulatorNodeType_Convex,
const float dy0 = b.y - a.y; ImTriangulatorNodeType_Ear,
ImTriangulatorNodeType_Reflex
const float dx1 = c.x - b.x; };
const float dy1 = c.y - b.y;
return dx0 * dy1 - dx1 * dy0 > 0.0f;
}
struct ImTriangulator struct ImTriangulatorNode
{ {
struct Triangle { int Index[3]; }; ImTriangulatorNodeType Type;
int Index;
ImVec2 Pos;
ImTriangulatorNode* Next;
ImTriangulatorNode* Prev;
static int EstimateTriangleCount(int points_count); void Unlink() { Next->Prev = Prev; Prev->Next = Next; }
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 ImTriangulatorNodeSpan
struct alignas(void*) Span
{ {
Node** Data = nullptr; ImTriangulatorNode** Data = NULL;
int Size = 0; int Size = 0;
void PushBack(Node* node); void push_back(ImTriangulatorNode* node) { Data[Size++] = node; }
void RemoveByIndex(int index); 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; } }
}; };
void BuildNodes(); struct ImTriangulator
{
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 BuildReflexes();
void BuildEars(); void BuildEars();
void FlipNodeList(); void FlipNodeList();
bool IsEar(const Node* node, int i0, int i1, int i2, const ImVec2& v0, const ImVec2& v1, const ImVec2& v2) const; bool IsEar(int i0, int i1, int i2, const ImVec2& v0, const ImVec2& v1, const ImVec2& v2) const;
Type ClassifyNode(const Node* node) const; void ReclassifyNode(ImTriangulatorNode* node);
void ReclasifyNode(Node* node);
const ImVec2* _Points = nullptr; // Internal members
int _PointsCount = 0;
int _PointsStrideBytes = 0;
int _TrianglesLeft = 0; int _TrianglesLeft = 0;
ImTriangulatorNode* _Nodes = NULL;
Node* _Nodes = nullptr; ImTriangulatorNodeSpan _Ears;
Span _Ears; ImTriangulatorNodeSpan _Reflexes;
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. // Distribute storage for nodes, ears and reflexes.
_Nodes = reinterpret_cast<Node*>(scratch_buffer); // FIXME-OPT: if everything is convex, we could report it to caller and let it switch to an convex renderer
_Ears.Data = reinterpret_cast<Node**>(_Nodes + points_count); // (this would require first building reflexes to bail to convex if empty, without even building nodes)
_Reflexes.Data = _Ears.Data + points_count; void ImTriangulator::Init(const ImVec2* points, int points_count, void* scratch_buffer)
{
BuildNodes(); 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 < points_count; i++)
for (int i = 0; i < _PointsCount; ++i)
{ {
_Nodes[i].Type = Convex; _Nodes[i].Type = ImTriangulatorNodeType_Convex;
_Nodes[i].Index = static_cast<int>(i); _Nodes[i].Index = i;
_Nodes[i].Point = IM_POINT_PTR(i); _Nodes[i].Pos = points[i];
_Nodes[i].Next = _Nodes + i + 1; _Nodes[i].Next = _Nodes + i + 1;
_Nodes[i].Prev = _Nodes + i - 1; _Nodes[i].Prev = _Nodes + i - 1;
} }
_Nodes[0].Prev = _Nodes + _PointsCount - 1; _Nodes[0].Prev = _Nodes + points_count - 1;
_Nodes[_PointsCount - 1].Next = _Nodes; _Nodes[points_count - 1].Next = _Nodes;
# undef IM_POINT_PTR
} }
void ImTriangulator::BuildReflexes() void ImTriangulator::BuildReflexes()
{ {
Node* node = _Nodes; ImTriangulatorNode* n1 = _Nodes;
for (int i = 0; i < _TrianglesLeft; ++i, node = node->Next) for (int i = _TrianglesLeft; i >= 0; i--, n1 = n1->Next)
{ {
const ImVec2& v0 = *node->Prev->Point; if (ImTriangleIsClockwise(n1->Prev->Pos, n1->Pos, n1->Next->Pos))
const ImVec2& v1 = *node->Point;
const ImVec2& v2 = *node->Next->Point;
if (ImPathIsConvex(v0, v1, v2))
continue; continue;
n1->Type = ImTriangulatorNodeType_Reflex;
node->Type = Reflex; _Reflexes.push_back(n1);
_Reflexes.PushBack(node);
} }
} }
void ImTriangulator::BuildEars() void ImTriangulator::BuildEars()
{ {
Node* node = _Nodes; ImTriangulatorNode* n1 = _Nodes;
for (int i = 0; i < _TrianglesLeft; ++i, node = node->Next) for (int i = _TrianglesLeft; i >= 0; i--, n1 = n1->Next)
{ {
if (node->Type != Convex) if (n1->Type != ImTriangulatorNodeType_Convex)
continue; continue;
if (!IsEar(n1->Prev->Index, n1->Index, n1->Next->Index, n1->Prev->Pos, n1->Pos, n1->Next->Pos))
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; continue;
n1->Type = ImTriangulatorNodeType_Ear;
node->Type = Ear; _Ears.push_back(n1);
_Ears.PushBack(node);
} }
} }
bool ImTriangulator::HasNext() const void ImTriangulator::GetNextTriangle(unsigned int out_triangle[3])
{ {
return _TrianglesLeft > 0;
}
ImTriangulator::Triangle ImTriangulator::Next()
{
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; ImTriangulatorNode* node = _Nodes;
for (int i = 0; i < _TrianglesLeft; ++i, node = node->Next) for (int i = _TrianglesLeft; i >= 0; i--, node = node->Next)
node->Type = Convex; 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]; ImTriangulatorNode* ear = _Ears.Data[--_Ears.Size];
out_triangle[0] = ear->Prev->Index;
const int i0 = ear->Prev->Index; out_triangle[1] = ear->Index;
const int i1 = ear->Index; out_triangle[2] = ear->Next->Index;
const int i2 = ear->Next->Index;
ear->Unlink(); ear->Unlink();
if (ear == _Nodes) if (ear == _Nodes)
_Nodes = ear->Next; _Nodes = ear->Next;
ReclasifyNode(ear->Prev); ReclassifyNode(ear->Prev);
ReclasifyNode(ear->Next); ReclassifyNode(ear->Next);
_TrianglesLeft--;
--_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() void ImTriangulator::FlipNodeList()
{ {
Node* prev = _Nodes; ImTriangulatorNode* prev = _Nodes;
Node* temp = _Nodes; ImTriangulatorNode* temp = _Nodes;
Node* current = _Nodes->Next; 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,91 +1873,63 @@ 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]; ImTriangulatorNode* reflex = *p;
if (reflex->Index != i0 && reflex->Index != i1 && reflex->Index != i2)
if (reflex->Index == i0 || reflex->Index == i1 || reflex->Index == i2) if (ImTriangleContainsPoint(v0, v1, v2, reflex->Pos))
continue;
if (ImTriangleContainsPoint(v0, v1, v2, *reflex->Point))
return false; 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)) // Classify node
return Ear; ImTriangulatorNodeType type;
else const ImTriangulatorNode* n0 = n1->Prev;
return Convex; 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
{ type = ImTriangulatorNodeType_Convex;
return Reflex;
}
}
void ImTriangulator::ReclasifyNode(Node* node) // Update lists when a type changes
{ if (type == n1->Type)
Type type = ClassifyNode(node);
if (type == node->Type)
return; return;
if (n1->Type == ImTriangulatorNodeType_Reflex)
if (node->Type == Reflex) _Reflexes.find_erase_unsorted(n1->Index);
_Reflexes.RemoveByIndex(node->Index); else if (n1->Type == ImTriangulatorNodeType_Ear)
else if (node->Type == Ear) _Ears.find_erase_unsorted(n1->Index);
_Ears.RemoveByIndex(node->Index); if (type == ImTriangulatorNodeType_Reflex)
_Reflexes.push_back(n1);
if (type == Reflex) else if (type == ImTriangulatorNodeType_Ear)
_Reflexes.PushBack(node); _Ears.push_back(n1);
else if (type == Ear) n1->Type = type;
_Ears.PushBack(node); }
node->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
@ -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); triangulator.Init(points, points_count, _Data->TempBuffer.Data);
while (triangulator.HasNext()) while (triangulator._TrianglesLeft > 0)
{ {
ImTriangulator::Triangle triangle = triangulator.Next(); triangulator.GetNextTriangle(triangle);
_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[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;
} }
@ -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); triangulator.Init(points, points_count, _Data->TempBuffer.Data);
while (triangulator.HasNext()) while (triangulator._TrianglesLeft > 0)
{ {
ImTriangulator::Triangle triangle = triangulator.Next(); triangulator.GetNextTriangle(triangle);
_IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx + triangle.Index[0]); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx + triangle.Index[1]); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx + triangle.Index[2]); _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;

@ -499,6 +499,7 @@ IMGUI_API bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, c
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)

Loading…
Cancel
Save