From f2c694bac553856f1e67f650621337aef00a1f05 Mon Sep 17 00:00:00 2001 From: 4DBug <4DBug@github.com> Date: Thu, 15 Oct 2026 07:43:21 -0500 Subject: [PATCH] Port ImTriangulator + upstream AddConcavePolyFilled (AA fringe, no per-triangle seams); [Luau] handles CCW winding and duplicate/collinear points that upstream mis-triangulates --- build/ImGui.linemap.json | 2 +- build/ImGui.luau | 380 ++++++++++++++++++++++++++++++---- build/imgui_love.linemap.json | 2 +- build/imgui_love.lua | 380 ++++++++++++++++++++++++++++++---- docs/PORT_STATUS.txt | 266 ++++++++++++------------ imgui_draw.lua | 364 ++++++++++++++++++++++++++++---- imgui_internal.lua | 12 ++ 7 files changed, 1164 insertions(+), 242 deletions(-) diff --git a/build/ImGui.linemap.json b/build/ImGui.linemap.json index 063d3be..bdc1109 100644 --- a/build/ImGui.linemap.json +++ b/build/ImGui.linemap.json @@ -1 +1 @@ -[[46, "imgui.lua"], [21466, "imgui_demo.lua"], [33318, "imgui_draw.lua"], [40827, "imgui_h.lua"], [43733, "imgui_impl_roblox.lua"], [45381, "imgui_impl_roblox_fonts.lua"], [51784, "imgui_internal.lua"], [55494, "imgui_tables.lua"], [60553, "imgui_widgets.lua"], [72529, "imstb_rectpack.lua"], [73193, "imstb_textedit.lua"], [74735, "imstb_truetype.lua"], [77523, "imstd_minstdio.lua"]] \ No newline at end of file +[[46, "imgui.lua"], [21466, "imgui_demo.lua"], [33318, "imgui_draw.lua"], [41121, "imgui_h.lua"], [44027, "imgui_impl_roblox.lua"], [45675, "imgui_impl_roblox_fonts.lua"], [52078, "imgui_internal.lua"], [55800, "imgui_tables.lua"], [60859, "imgui_widgets.lua"], [72835, "imstb_rectpack.lua"], [73499, "imstb_textedit.lua"], [75041, "imstb_truetype.lua"], [77829, "imstd_minstdio.lua"]] \ No newline at end of file diff --git a/build/ImGui.luau b/build/ImGui.luau index e0b78f7..6ed1f5a 100644 --- a/build/ImGui.luau +++ b/build/ImGui.luau @@ -1,7 +1,7 @@ --!optimize 2 -- GENERATED by tools/bundle.py from *.lua, backends/, fonts/ - do not edit -local ImGui, ImVec2, ImVec4, ImRect, HelpMarker, ImVec2_Copy, IMGUI_DEMO_MARKER, ImGuiTableFlags, ImStd, ImVector, ImDrawFlags, ImGuiInputFlags, ImRect_Copy, ImGuiDataType, ImStb, IM_TRUNC, ImGuiKey, ImVec2_CopyV, ImGuiStyleVarInfo, IM_ASSERT_USER_ERROR, ImGuiHoveredFlags, ImFormatString, IM_ASSERT_USER_ERROR_RET, ImHashStr, ImS32, ImGuiColorEditFlags, ImIsPowerOfTwo, ImAbs, ImVec4_Copy, ExampleAppConsoleMT, IM_ROUND, ImGuiTableColumnFlags, ImGuiTreeNodeFlags, PopStyleCompact, PushStyleCompact, IM_ASSERT_PARANOID, ImU16, ExampleNames, ImGuiWindowFlags, ImGuiItemFlags, ImGui_ImplRoblox_GetBackendData, stbte, ImColor, ImCos, ImGuiButtonFlags, ImGuiDebugLogFlags, ImGuiLocKey, ImU32, ExampleTreeFuncs, ImGuiListClipper, ImS16, _PathArcToFastExXY, ImGuiMultiSelectFlags, ImSin, ImGuiTabBarFlags, ImUpperPowerOfTwo, IM_F32_TO_INT8_SAT, ImFontAtlasPackGetRect, DockSettingsRenameNodeReferences, ExampleDualListBoxMT, IM_DRAWLIST_TEX_LINES_WIDTH_MAX, ImCeil, ImGuiCol, ImGuiSelectionBasicStorage, ImLengthSqr, ImS8, ImU8, ExampleAppPropertyEditorMT, ImFabs, ImGuiChildFlags, ImGuiLastItemData_Copy, ImGuiScrollFlags, ImLog, ImMemchr, ImStrByte, ExampleSelectionWithDeletionMT, IMGUI_DEBUG_LOG_VIEWPORT, ImGuiInputEvent, ImGuiTextBuffer, ImPow, ImTrunc64, CustomConstraints, ExampleAppLogMT, IMGUI_DEBUG_LOG_FONT, IMGUI_DEBUG_LOG_SELECTION, ImAddClampOverflow, ImFontAtlasBuildInit, ImFontCalcWordWrapPositionEx, ImGuiComboFlags, ImGuiLastItemData, ImGuiPayload_IsDataType, ImGuiPopupFlags, ImGuiSliderFlags, ImGuiTextFilter, ImRotate, ImSqrt, ImSubClampOverflow, ImTextClassifierSetCharClass, MyDocument, ShowExampleMenuFile, CalcCornerArcStepAndCount, CalcNextScrollFromScrollTargetAndClamp, IMGUI_PAYLOAD_TYPE_WINDOW, IMGUI_VERSION, ImCharIsBlankW, ImCharIsSeparatorW, ImFmod, ImFontAtlasBakedAddFontGlyph, ImFontAtlasPackAddRect, ImFontBaked_BuildLoadGlyph, ImGuiInputTextFlags, ImGuiNavMoveFlags, ImGuiStyle_Copy, ImHashData, ImVec1_Copy, ExampleAsset_s_current_sort_specs, ExampleMemberInfo, ExampleTree_CreateNode, IMGUI_PAYLOAD_TYPE_COLOR_4F, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC, IM_F32_TO_INT8_UNBOUND, IM_PI, ImCharIsBlankA, ImFontAtlasBuildRenderBitmapFromString, ImFontAtlasFontDestroyOutput, ImFontAtlasFontDiscardBakes, ImFontAtlasFontSourceAddToFont, ImFontAtlasFontSourceInit, ImFontAtlasRectId_GetIndex, ImFontAtlasTextureAdd, ImFontAtlasTextureBlockQueueUpload, ImFontAtlasTextureGrow, ImFontBaked_BuildGrowIndex, ImFontConfig, ImFontGlyph, ImGuiTableColumn, ImGuiWindowClass, ImGui_ImplRoblox_RenderDrawData, ImParseFormatFindStart, ImRect_CopyFromV4, ImTextureRect, ImTextureRef, MyItem_s_current_sort_specs, stb_textedit_key, stbtt_GetGlyphBitmapBoxSubpixel, DockSettingsHandler_ClearAll, EditTableSizingFlags, ExampleSelectionWithDeletion, IMGUI_DEBUG_LOG_TABLE, IMGUI_PAYLOAD_TYPE_COLOR_3F, IM_FREE, ImDrawListSplitter, ImFontAtlasBakedDiscard, ImFontAtlasBuildDestroy, ImFontAtlasBuildNotifySetFont, ImFontAtlasBuildSetTexture, ImFontAtlasBuildSetupFontBakedFallback, ImFontAtlasFontDestroySourceData, ImFontAtlasPackInit, ImFontAtlasRect +local ImGui, ImVec2, ImVec4, ImRect, HelpMarker, ImVec2_Copy, IMGUI_DEMO_MARKER, ImGuiTableFlags, ImStd, ImVector, ImDrawFlags, ImGuiInputFlags, ImRect_Copy, ImGuiDataType, ImStb, IM_TRUNC, ImGuiKey, ImVec2_CopyV, ImGuiStyleVarInfo, IM_ASSERT_USER_ERROR, ImGuiHoveredFlags, ImFormatString, IM_ASSERT_USER_ERROR_RET, ImHashStr, ImS32, ImGuiColorEditFlags, ImIsPowerOfTwo, ImAbs, ImVec4_Copy, ExampleAppConsoleMT, IM_ROUND, ImGuiTableColumnFlags, ImGuiTreeNodeFlags, PopStyleCompact, PushStyleCompact, IM_ASSERT_PARANOID, ImU16, ExampleNames, ImGuiWindowFlags, ImGuiItemFlags, ImGui_ImplRoblox_GetBackendData, stbte, ImColor, ImCos, ImGuiButtonFlags, ImGuiDebugLogFlags, ImGuiLocKey, ImU32, ExampleTreeFuncs, ImGuiListClipper, ImS16, _PathArcToFastExXY, ImGuiMultiSelectFlags, ImSin, ImGuiTabBarFlags, ImUpperPowerOfTwo, IM_F32_TO_INT8_SAT, ImFontAtlasPackGetRect, DockSettingsRenameNodeReferences, ExampleDualListBoxMT, IM_DRAWLIST_TEX_LINES_WIDTH_MAX, ImCeil, ImFabs, ImGuiCol, ImGuiSelectionBasicStorage, ImLengthSqr, ImS8, ImU8, ExampleAppPropertyEditorMT, ImGuiChildFlags, ImGuiLastItemData_Copy, ImGuiScrollFlags, ImLog, ImMemchr, ImStrByte, ExampleSelectionWithDeletionMT, IMGUI_DEBUG_LOG_VIEWPORT, ImGuiInputEvent, ImGuiTextBuffer, ImPow, ImTrunc64, CustomConstraints, ExampleAppLogMT, IMGUI_DEBUG_LOG_FONT, IMGUI_DEBUG_LOG_SELECTION, ImAddClampOverflow, ImFontAtlasBuildInit, ImFontCalcWordWrapPositionEx, ImGuiComboFlags, ImGuiLastItemData, ImGuiPayload_IsDataType, ImGuiPopupFlags, ImGuiSliderFlags, ImGuiTextFilter, ImRotate, ImSqrt, ImSubClampOverflow, ImTextClassifierSetCharClass, MyDocument, ShowExampleMenuFile, CalcCornerArcStepAndCount, CalcNextScrollFromScrollTargetAndClamp, IMGUI_PAYLOAD_TYPE_WINDOW, IMGUI_VERSION, ImCharIsBlankW, ImCharIsSeparatorW, ImFmod, ImFontAtlasBakedAddFontGlyph, ImFontAtlasPackAddRect, ImFontBaked_BuildLoadGlyph, ImGuiInputTextFlags, ImGuiNavMoveFlags, ImGuiStyle_Copy, ImHashData, ImVec1_Copy, ExampleAsset_s_current_sort_specs, ExampleMemberInfo, ExampleTree_CreateNode, IMGUI_PAYLOAD_TYPE_COLOR_4F, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC, IM_F32_TO_INT8_UNBOUND, IM_PI, ImCharIsBlankA, ImFontAtlasBuildRenderBitmapFromString, ImFontAtlasFontDestroyOutput, ImFontAtlasFontDiscardBakes, ImFontAtlasFontSourceAddToFont, ImFontAtlasFontSourceInit, ImFontAtlasRectId_GetIndex, ImFontAtlasTextureAdd, ImFontAtlasTextureBlockQueueUpload, ImFontAtlasTextureGrow, ImFontBaked_BuildGrowIndex, ImFontConfig, ImFontGlyph, ImGuiTableColumn, ImGuiWindowClass, ImGui_ImplRoblox_RenderDrawData, ImParseFormatFindStart, ImRect_CopyFromV4, ImTextureRect, ImTextureRef, MyItem_s_current_sort_specs, stb_textedit_key, stbtt_GetGlyphBitmapBoxSubpixel, DockSettingsHandler_ClearAll, EditTableSizingFlags, ExampleSelectionWithDeletion, IMGUI_DEBUG_LOG_TABLE, IMGUI_PAYLOAD_TYPE_COLOR_3F, IM_FREE, ImDrawListSplitter, ImFontAtlasBakedDiscard, ImFontAtlasBuildDestroy, ImFontAtlasBuildNotifySetFont, ImFontAtlasBuildSetTexture, ImFontAtlasBuildSetupFontBakedFallback, ImFontAtlasFontDestroySourceData, ImFontAtlasPackInit, ImFontAtlasRect local type, mathMin, mathMax, mathFloor, mathCeil, bitOr, bitLShift = type, math.min, math.max, math.floor, math.ceil, bit32.bor, bit32.lshift local function IM_COL32(R, G, B, A) return (bitOr(bitLShift(A, 24 --[[IM_COL32_A_SHIFT]]), bitLShift(B, 16 --[[IM_COL32_B_SHIFT]]), bitLShift(G, 8 --[[IM_COL32_G_SHIFT]]), bitLShift(R, 0 --[[IM_COL32_R_SHIFT]]))) end @native @@ -40772,47 +40772,341 @@ function MT.ImDrawList:PathBezierQuadraticCurveTo(p2, p3, num_segments) end end --- ponytail: ear-clipping, no anti-aliased fringe (upstream's AA triangulator not ported). O(n^2) is fine for UI polygons. +-------------------------------------------------------------------------------- +-- [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() +-------------------------------------------------------------------------------- + +local ImTriangulatorNodeType = +{ + Convex = 0, + Ear = 1, + Reflex = 2, +} + +-- [Luau] No pointers: a node is an int handle (1-based slot) into the parallel arrays of its ImTriangulator +-- (tri.Type[n], tri.Index[n], tri.Pos[n], tri.Next[n], tri.Prev[n]). node->Index (0-based, as upstream) == n - 1. +local function ImTriangulatorNode_Unlink(tri, n) tri.Prev[tri.Next[n]] = tri.Prev[n]; tri.Next[tri.Prev[n]] = tri.Next[n] end + +-- [Luau] Deviation: upstream classifies a node as reflex when '!ImTriangleIsClockwise()', i.e. also when its corner is +-- degenerate (collinear or duplicate points). A duplicate point then sits as a reflex on top of every candidate ear's vertex +-- and blocks it, ending in the degenerate fallback with overlapping triangles. Only strictly counter-clockwise corners are +-- reflex here: degenerate corners are clipped as zero-area ears. +local function ImTriangulator_IsReflex(a, b, c) return ((b.x - a.x) * (c.y - b.y)) - ((c.x - b.x) * (b.y - a.y)) < 0.0 end + +local function ImTriangulatorNodeSpan() + return { Data = {}, Size = 0 } -- Data: node handles, 1-based +end +MT.ImTriangulatorNodeSpan = {} +MT.ImTriangulatorNodeSpan.__index = MT.ImTriangulatorNodeSpan +function MT.ImTriangulatorNodeSpan:push_back(node) self.Size = self.Size + 1; self.Data[self.Size] = node end +function MT.ImTriangulatorNodeSpan:find_erase_unsorted(idx) for i = self.Size, 1, -1 do if self.Data[i] - 1 == idx then self.Data[i] = self.Data[self.Size]; self.Size = self.Size - 1; return end end end + +-- [Luau] The node/ears/reflexes storage (upstream: carved out of _Data->TempBuffer via scratch_buffer) is owned by the +-- triangulator itself and only ever grows. AddConcavePolyFilled() reuses a single module-level instance (no per-call allocations). +local function ImTriangulator() + return setmetatable({ + _TrianglesLeft = 0, + _Nodes = 0, -- node handle (head of the circular list) + _Ears = setmetatable(ImTriangulatorNodeSpan(), MT.ImTriangulatorNodeSpan), + _Reflexes = setmetatable(ImTriangulatorNodeSpan(), MT.ImTriangulatorNodeSpan), + Type = {}, Index = {}, Pos = {}, Next = {}, Prev = {}, -- [Luau] ImTriangulatorNode fields, one array each + }, MT.ImTriangulator) +end +MT.ImTriangulator = {} +MT.ImTriangulator.__index = MT.ImTriangulator + +function MT.ImTriangulator.EstimateTriangleCount(points_count) return (points_count < 3) and 0 or points_count - 2 end +-- [Luau] In elements (nodes + ears + reflexes) rather than bytes. Unused by the port: arrays grow on demand. +function MT.ImTriangulator.EstimateScratchBufferSize(points_count) return points_count + points_count * 2 end + +-- 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) --- @param points ImVec2[] # 1-based -function MT.ImDrawList:AddConcavePolyFilled(points, points_count, col) - if points_count < 3 or bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - local idx = {} - local area = 0.0 +function MT.ImTriangulator:Init(points, points_count) + assert(points_count >= 3) + self._TrianglesLeft = MT.ImTriangulator.EstimateTriangleCount(points_count) + self._Nodes = 1 + self._Ears.Size = 0 + self._Reflexes.Size = 0 + self:BuildNodes(points, points_count) + + -- [Luau] Deviation: upstream only flips the node list when no ear is found, so a counter-clockwise *concave* polygon + -- (whose reflex corners look like ears) yields triangles outside of it. Normalize to clockwise order up-front (O(N)). + local area2 = 0.0 + local p0 = points[points_count] for i = 1, points_count do - idx[i] = i - local a, b = points[i], points[i % points_count + 1] - area = area + (a.x * b.y - b.x * a.y) + local p1 = points[i] + area2 = area2 + (p0.x * p1.y - p1.x * p0.y) + p0 = p1 end - local sign = (area >= 0) and 1 or -1 - local function cross(o, a, b) return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x) end - local guard = 0 - while #idx > 3 and guard < points_count * points_count do - guard = guard + 1 - local n = #idx - local clipped = false - for i = 1, n do - local ia, ib, ic = idx[(i - 2) % n + 1], idx[i], idx[i % n + 1] - local a, b, c = points[ia], points[ib], points[ic] - if cross(a, b, c) * sign > 0 then - local inside = false - for j = 1, n do - local p = idx[j] - if p ~= ia and p ~= ib and p ~= ic then - local q = points[p] - if cross(a, b, q) * sign >= 0 and cross(b, c, q) * sign >= 0 and cross(c, a, q) * sign >= 0 then inside = true; break end - end - end - if not inside then - self:AddTriangleFilled(a, b, c, col) - table.remove(idx, i) - clipped = true - break - end + if area2 < 0.0 then + self:FlipNodeList() + end + + self:BuildReflexes() + self:BuildEars() +end + +function MT.ImTriangulator:BuildNodes(points, points_count) + local Type, Index, Pos, Next, Prev = self.Type, self.Index, self.Pos, self.Next, self.Prev + for i = 1, points_count do + Type[i] = ImTriangulatorNodeType.Convex + Index[i] = i - 1 + Pos[i] = points[i] -- [Luau] reference, not a copy (points are not modified while triangulating) + Next[i] = i + 1 + Prev[i] = i - 1 + end + Prev[1] = points_count + Next[points_count] = 1 +end + +function MT.ImTriangulator:BuildReflexes() + local Pos, Next, Prev = self.Pos, self.Next, self.Prev + local n1 = self._Nodes + for _ = self._TrianglesLeft, 0, -1 do + if ImTriangulator_IsReflex(Pos[Prev[n1]], Pos[n1], Pos[Next[n1]]) then + self.Type[n1] = ImTriangulatorNodeType.Reflex + self._Reflexes:push_back(n1) + end + n1 = Next[n1] + end +end + +function MT.ImTriangulator:BuildEars() + local Index, Pos, Next, Prev = self.Index, self.Pos, self.Next, self.Prev + local n1 = self._Nodes + for _ = self._TrianglesLeft, 0, -1 do + if self.Type[n1] == ImTriangulatorNodeType.Convex then + local n0, n2 = Prev[n1], Next[n1] + if self:IsEar(Index[n0], Index[n1], Index[n2], Pos[n0], Pos[n1], Pos[n2]) then + self.Type[n1] = ImTriangulatorNodeType.Ear + self._Ears:push_back(n1) end end - if not clipped then break end + n1 = Next[n1] + end +end + +-- [Luau] out_triangle[3] is returned as 3 values (relative 0-based indexes) +function MT.ImTriangulator:GetNextTriangle() + if self._Ears.Size == 0 then + self:FlipNodeList() + + local node = self._Nodes + for _ = self._TrianglesLeft, 0, -1 do + self.Type[node] = ImTriangulatorNodeType.Convex + node = self.Next[node] + end + self._Reflexes.Size = 0 + self:BuildReflexes() + self:BuildEars() + + -- If we still don't have ears, it means geometry is degenerated. + if self._Ears.Size == 0 then + -- Return first triangle available, mimicking the behavior of convex fill. + assert(self._TrianglesLeft > 0) -- Geometry is degenerated + self._Ears.Data[1] = self._Nodes + self._Ears.Size = 1 + end + end + + local ear = self._Ears.Data[self._Ears.Size] + self._Ears.Size = self._Ears.Size - 1 + local Index, Next, Prev = self.Index, self.Next, self.Prev + local t0, t1, t2 = Index[Prev[ear]], Index[ear], Index[Next[ear]] + + ImTriangulatorNode_Unlink(self, ear) + if ear == self._Nodes then + self._Nodes = Next[ear] + end + + self:ReclassifyNode(Prev[ear]) + self:ReclassifyNode(Next[ear]) + self._TrianglesLeft = self._TrianglesLeft - 1 + return t0, t1, t2 +end + +function MT.ImTriangulator:FlipNodeList() + local Next, Prev = self.Next, self.Prev + local nodes = self._Nodes + local prev = nodes + local temp = nodes + local current = Next[nodes] + Next[prev] = prev + Prev[prev] = prev + while current ~= nodes do + temp = Next[current] + + Next[current] = prev + Prev[prev] = current + Next[nodes] = current + Prev[current] = nodes + + prev = current + current = temp + end + self._Nodes = prev +end + +-- A triangle is an ear is no other vertex is inside it. We can test reflexes vertices only (see reference algorithm) +function MT.ImTriangulator:IsEar(i0, i1, i2, v0, v1, v2) + local Index, Pos = self.Index, self.Pos + local data = self._Reflexes.Data + for p = 1, self._Reflexes.Size do + local reflex = data[p] + local idx = Index[reflex] + if idx ~= i0 and idx ~= i1 and idx ~= i2 then + if ImStd.ImTriangleContainsPoint(v0, v1, v2, Pos[reflex]) then + return false + end + end + end + return true +end + +function MT.ImTriangulator:ReclassifyNode(n1) + -- Classify node + local type + local n0 = self.Prev[n1] + local n2 = self.Next[n1] + local Index, Pos = self.Index, self.Pos + if ImTriangulator_IsReflex(Pos[n0], Pos[n1], Pos[n2]) then + type = ImTriangulatorNodeType.Reflex + elseif self:IsEar(Index[n0], Index[n1], Index[n2], Pos[n0], Pos[n1], Pos[n2]) then + type = ImTriangulatorNodeType.Ear + else + type = ImTriangulatorNodeType.Convex + end + + -- Update lists when a type changes + local n1_type = self.Type[n1] + if type == n1_type then + return + end + if n1_type == ImTriangulatorNodeType.Reflex then + self._Reflexes:find_erase_unsorted(Index[n1]) + elseif n1_type == ImTriangulatorNodeType.Ear then + self._Ears:find_erase_unsorted(Index[n1]) + end + if type == ImTriangulatorNodeType.Reflex then + self._Reflexes:push_back(n1) + elseif type == ImTriangulatorNodeType.Ear then + self._Ears:push_back(n1) + end + self.Type[n1] = type +end + +local concave_triangulator = ImTriangulator() -- [Luau] reused scratch (see ImTriangulator()) + +-- 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') +--- @param points ImVec2[] # 1-based +--- @param points_count int +--- @param col ImU32 +@native +function MT.ImDrawList:AddConcavePolyFilled(points, points_count, col) + if points_count < 3 or bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + local uv = self._Data.TexUvWhitePixel + local uv_x, uv_y = uv.x, uv.y + local triangulator = concave_triangulator + if bit32.band(self.Flags, 4096 --[[ImDrawFlags.AAFill]]) ~= 0 then + -- Anti-aliased Fill + local AA_SIZE = self._FringeScale + local col_trans = bit32.band(col, bit32.bnot(4278190080 --[[IM_COL32_A_MASK]])) + local idx_count = (points_count - 2) * 3 + points_count * 6 + local vtx_count = (points_count * 2) + PrimReserve(self, idx_count, vtx_count) + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local vw = self._VtxWritePtr + local iw = self._IdxWritePtr + + -- Add indexes for fill + local vtx_inner_idx = self._VtxCurrentIdx + local vtx_outer_idx = self._VtxCurrentIdx + 1 + + triangulator:Init(points, points_count) + while triangulator._TrianglesLeft > 0 do + local t0, t1, t2 = triangulator:GetNextTriangle() + ib[iw + 0] = vtx_inner_idx + t0 * 2; ib[iw + 1] = vtx_inner_idx + t1 * 2; ib[iw + 2] = vtx_inner_idx + t2 * 2 + iw = iw + 3 + end + + -- Compute normals + -- [Luau] scratch arrays (see AddPolyline) instead of _Data->TempBuffer. Indexes below are 1-based. + local temp_normals_x, temp_normals_y = poly_normals_x, poly_normals_y + local i0 = points_count + for i1 = 1, points_count do + local p0 = points[i0] + local p1 = points[i1] + local dx, dy = IM_NORMALIZE2F_OVER_ZERO(p1.x - p0.x, p1.y - p0.y) + temp_normals_x[i0] = dy + temp_normals_y[i0] = -dx + i0 = i1 + end + + i0 = points_count + for i1 = 1, points_count do + -- Average normals + local dm_x, dm_y = IM_FIXNORMAL2F((temp_normals_x[i0] + temp_normals_x[i1]) * 0.5, (temp_normals_y[i0] + temp_normals_y[i1]) * 0.5) + dm_x = dm_x * AA_SIZE * 0.5 + dm_y = dm_y * AA_SIZE * 0.5 + + -- Add vertices + local p1 = points[i1] + WV(vb, vw, p1.x - dm_x, p1.y - dm_y, uv_x, uv_y, col) -- Inner + WV(vb, vw + 1, p1.x + dm_x, p1.y + dm_y, uv_x, uv_y, col_trans) -- Outer + vw = vw + 2 + + -- Add indexes for fringes + local e0, e1 = (i0 - 1) * 2, (i1 - 1) * 2 + ib[iw + 0] = vtx_inner_idx + e1; ib[iw + 1] = vtx_inner_idx + e0; ib[iw + 2] = vtx_outer_idx + e0 + ib[iw + 3] = vtx_outer_idx + e0; ib[iw + 4] = vtx_outer_idx + e1; ib[iw + 5] = vtx_inner_idx + e1 + iw = iw + 6 + i0 = i1 + end + self._VtxWritePtr = vw + self._IdxWritePtr = iw + self._VtxCurrentIdx = self._VtxCurrentIdx + vtx_count + else + -- Non Anti-aliased Fill + local idx_count = (points_count - 2) * 3 + local vtx_count = points_count + PrimReserve(self, idx_count, vtx_count) + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local vw = self._VtxWritePtr + local iw = self._IdxWritePtr + for i = 1, vtx_count do + local p = points[i] + WV(vb, vw, p.x, p.y, uv_x, uv_y, col) + vw = vw + 1 + end + local base_idx = self._VtxCurrentIdx + triangulator:Init(points, points_count) + while triangulator._TrianglesLeft > 0 do + local t0, t1, t2 = triangulator:GetNextTriangle() + ib[iw + 0] = base_idx + t0; ib[iw + 1] = base_idx + t1; ib[iw + 2] = base_idx + t2 + iw = iw + 3 + end + self._VtxWritePtr = vw + self._IdxWritePtr = iw + self._VtxCurrentIdx = base_idx + vtx_count end - if #idx == 3 then self:AddTriangleFilled(points[idx[1]], points[idx[2]], points[idx[3]], col) end end function MT.ImDrawList:PathFillConcave(col) @@ -52244,6 +52538,18 @@ function ImMul(lhs, rhs) return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y) end +--- @param a ImVec2 +--- @param b ImVec2 +--- @param c ImVec2 +--- @return float +function ImTriangleArea(a, b, c) return ImFabs((a.x * (b.y - c.y)) + (b.x * (c.y - a.y)) + (c.x * (a.y - b.y))) * 0.5 end + +--- @param a ImVec2 +--- @param b ImVec2 +--- @param c ImVec2 +--- @return boolean +function ImTriangleIsClockwise(a, b, c) return ((b.x - a.x) * (c.y - b.y)) - ((c.x - b.x) * (b.y - a.y)) > 0.0 end + --- @param str char[] --- @param begin? int function ImStd.ImStrlen(str, begin) @@ -78237,6 +78543,8 @@ return { ImTextureRef = ImTextureRef, ImTextureStatus = ImTextureStatus, ImToUpper = ImToUpper, + ImTriangleArea = ImTriangleArea, + ImTriangleIsClockwise = ImTriangleIsClockwise, ImTrunc = ImTrunc, ImTrunc64 = ImTrunc64, ImU16 = ImU16, diff --git a/build/imgui_love.linemap.json b/build/imgui_love.linemap.json index 5422169..bc6b4db 100644 --- a/build/imgui_love.linemap.json +++ b/build/imgui_love.linemap.json @@ -1 +1 @@ -[[183, "imgui.lua"], [21603, "imgui_demo.lua"], [33455, "imgui_draw.lua"], [40964, "imgui_h.lua"], [43870, "imgui_impl_love.lua"], [44410, "imgui_internal.lua"], [48120, "imgui_tables.lua"], [53179, "imgui_widgets.lua"], [65155, "imstb_rectpack.lua"], [65819, "imstb_textedit.lua"], [67361, "imstb_truetype.lua"], [70149, "imstd_minstdio.lua"]] \ No newline at end of file +[[183, "imgui.lua"], [21603, "imgui_demo.lua"], [33455, "imgui_draw.lua"], [41258, "imgui_h.lua"], [44164, "imgui_impl_love.lua"], [44704, "imgui_internal.lua"], [48426, "imgui_tables.lua"], [53485, "imgui_widgets.lua"], [65461, "imstb_rectpack.lua"], [66125, "imstb_textedit.lua"], [67667, "imstb_truetype.lua"], [70455, "imstd_minstdio.lua"]] \ No newline at end of file diff --git a/build/imgui_love.lua b/build/imgui_love.lua index 256b3fc..b600a38 100644 --- a/build/imgui_love.lua +++ b/build/imgui_love.lua @@ -138,7 +138,7 @@ if not utf8 then end -local ImGui, ImVec2, ImVec4, ImRect, HelpMarker, ImVec2_Copy, IMGUI_DEMO_MARKER, ImGuiTableFlags, ImStd, ImVector, ImGuiKey, ImDrawFlags, ImGuiInputFlags, ImRect_Copy, ImGuiDataType, ImStb, IM_TRUNC, ImVec2_CopyV, ImGuiStyleVarInfo, IM_ASSERT_USER_ERROR, ImGuiHoveredFlags, ImFormatString, IM_ASSERT_USER_ERROR_RET, ImHashStr, ImS32, ImGuiColorEditFlags, ImIsPowerOfTwo, ImAbs, ImVec4_Copy, ExampleAppConsoleMT, IM_ROUND, ImGuiTableColumnFlags, ImGuiTreeNodeFlags, PopStyleCompact, PushStyleCompact, IM_ASSERT_PARANOID, ImU16, ExampleNames, ImGuiWindowFlags, ImGuiItemFlags, stbte, ImColor, ImCos, ImGuiButtonFlags, ImGuiDebugLogFlags, ImGuiLocKey, ImU32, ExampleTreeFuncs, ImGuiListClipper, ImS16, _PathArcToFastExXY, ImGuiMultiSelectFlags, ImSin, ImGuiTabBarFlags, ImUpperPowerOfTwo, IM_F32_TO_INT8_SAT, ImFontAtlasPackGetRect, ImGuiMouseCursor, DockSettingsRenameNodeReferences, ExampleDualListBoxMT, IM_DRAWLIST_TEX_LINES_WIDTH_MAX, ImCeil, ImGuiCol, ImGuiSelectionBasicStorage, ImLengthSqr, ImS8, ImU8, ExampleAppPropertyEditorMT, ImFabs, ImGuiScrollFlags, ImGui_ImplLOVE_GetBackendData, ImLog, ExampleSelectionWithDeletionMT, CustomConstraints, ExampleAppLogMT, ImFontAtlasBuildInit, ImGuiComboFlags, ImGuiPopupFlags, ImGuiSliderFlags, ImTextClassifierSetCharClass, MyDocument, ShowExampleMenuFile, CalcCornerArcStepAndCount, ImFontAtlasBakedAddFontGlyph, ImFontAtlasPackAddRect, ImFontBaked_BuildLoadGlyph, ImGuiInputTextFlags, ImGuiStyle_Copy, ExampleAsset_s_current_sort_specs, ExampleMemberInfo, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC, ImFontAtlasBuildRenderBitmapFromString, ImFontAtlasFontDestroyOutput, ImFontAtlasFontSourceAddToFont, ImFontAtlasFontSourceInit, ImFontAtlasTextureAdd, ImFontAtlasTextureBlockQueueUpload, ImFontBaked_BuildGrowIndex, ImGuiTableColumn, stb_textedit_key, stbtt_GetGlyphBitmapBoxSubpixel, IM_FREE, ImDrawListSplitter, ImGuiDragDropFlags, ImGuiFocusedFlags, stb_text_locate_coord, stb_text_makeundo_insert, stb_text_makeundo_replace, stb_textedit_find_charpos, stbtt_GetGlyphShape, IM_ALLOC, ImGuiTableColumnSortSpecs, ImGuiTableInstanceData, ImGuiTableSettings, stb_text_makeundo_delete, stb_text_redo, stb_text_undo, stb_textedit_click, stb_textedit_drag, stbtt_FindGlyphIndex, stbtt_GetFontOffsetForIndex, stbtt_GetFontVMetrics, stbtt_GetGlyphBitmapBox, stbtt_GetGlyphHMetrics, stbtt_InitFont, stbtt_MakeGlyphBitmapSubpixelPrefilter, stbtt_ScaleForPixelHeight, stbtt__GetGlyphInfoT2, stbtt__GetGlyphShapeT2, stbtt_fontinfo, ImGuiOldColumnData, ImGuiOldColumnFlags, ImGuiOldColumns, ImGuiTable, ImGuiTableColumnSettings, ImGuiTableSortSpecs, ImGuiTableTempData, ImGui_ImplLOVE_CreateTexture, ImGui_ImplLOVE_DestroyTexture, ImGui_ImplLOVE_Init, ImGui_ImplLOVE_NewFrame, ImGui_ImplLOVE_RenderDrawData, ImGui_ImplLOVE_Shutdown, ImGui_ImplLOVE_UpdateTexture, stbrp_init_target, IMGUI_TABLE_MAX_COLUMNS, IMSTB_TEXTEDIT_UNDOSTATECOUNT, IM_COL32_DISABLE, ImGui_ImplLove, STB_TEXTEDIT_K_BACKSPACE, STB_TEXTEDIT_K_DELETE, STB_TEXTEDIT_K_DOWN, STB_TEXTEDIT_K_LEFT, STB_TEXTEDIT_K_LINEEND, STB_TEXTEDIT_K_LINESTART, STB_TEXTEDIT_K_PGDOWN, STB_TEXTEDIT_K_PGUP, STB_TEXTEDIT_K_REDO, STB_TEXTEDIT_K_RIGHT, STB_TEXTEDIT_K_SHIFT +local ImGui, ImVec2, ImVec4, ImRect, HelpMarker, ImVec2_Copy, IMGUI_DEMO_MARKER, ImGuiTableFlags, ImStd, ImVector, ImGuiKey, ImDrawFlags, ImGuiInputFlags, ImRect_Copy, ImGuiDataType, ImStb, IM_TRUNC, ImVec2_CopyV, ImGuiStyleVarInfo, IM_ASSERT_USER_ERROR, ImGuiHoveredFlags, ImFormatString, IM_ASSERT_USER_ERROR_RET, ImHashStr, ImS32, ImGuiColorEditFlags, ImIsPowerOfTwo, ImAbs, ImVec4_Copy, ExampleAppConsoleMT, IM_ROUND, ImGuiTableColumnFlags, ImGuiTreeNodeFlags, PopStyleCompact, PushStyleCompact, IM_ASSERT_PARANOID, ImU16, ExampleNames, ImGuiWindowFlags, ImGuiItemFlags, stbte, ImColor, ImCos, ImGuiButtonFlags, ImGuiDebugLogFlags, ImGuiLocKey, ImU32, ExampleTreeFuncs, ImGuiListClipper, ImS16, _PathArcToFastExXY, ImGuiMultiSelectFlags, ImSin, ImGuiTabBarFlags, ImUpperPowerOfTwo, IM_F32_TO_INT8_SAT, ImFontAtlasPackGetRect, ImGuiMouseCursor, DockSettingsRenameNodeReferences, ExampleDualListBoxMT, IM_DRAWLIST_TEX_LINES_WIDTH_MAX, ImCeil, ImFabs, ImGuiCol, ImGuiSelectionBasicStorage, ImLengthSqr, ImS8, ImU8, ExampleAppPropertyEditorMT, ImGuiScrollFlags, ImGui_ImplLOVE_GetBackendData, ImLog, ExampleSelectionWithDeletionMT, CustomConstraints, ExampleAppLogMT, ImFontAtlasBuildInit, ImGuiComboFlags, ImGuiPopupFlags, ImGuiSliderFlags, ImTextClassifierSetCharClass, MyDocument, ShowExampleMenuFile, CalcCornerArcStepAndCount, ImFontAtlasBakedAddFontGlyph, ImFontAtlasPackAddRect, ImFontBaked_BuildLoadGlyph, ImGuiInputTextFlags, ImGuiStyle_Copy, ExampleAsset_s_current_sort_specs, ExampleMemberInfo, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC, ImFontAtlasBuildRenderBitmapFromString, ImFontAtlasFontDestroyOutput, ImFontAtlasFontSourceAddToFont, ImFontAtlasFontSourceInit, ImFontAtlasTextureAdd, ImFontAtlasTextureBlockQueueUpload, ImFontBaked_BuildGrowIndex, ImGuiTableColumn, stb_textedit_key, stbtt_GetGlyphBitmapBoxSubpixel, IM_FREE, ImDrawListSplitter, ImGuiDragDropFlags, ImGuiFocusedFlags, stb_text_locate_coord, stb_text_makeundo_insert, stb_text_makeundo_replace, stb_textedit_find_charpos, stbtt_GetGlyphShape, IM_ALLOC, ImGuiTableColumnSortSpecs, ImGuiTableInstanceData, ImGuiTableSettings, stb_text_makeundo_delete, stb_text_redo, stb_text_undo, stb_textedit_click, stb_textedit_drag, stbtt_FindGlyphIndex, stbtt_GetFontOffsetForIndex, stbtt_GetFontVMetrics, stbtt_GetGlyphBitmapBox, stbtt_GetGlyphHMetrics, stbtt_InitFont, stbtt_MakeGlyphBitmapSubpixelPrefilter, stbtt_ScaleForPixelHeight, stbtt__GetGlyphInfoT2, stbtt__GetGlyphShapeT2, stbtt_fontinfo, ImGuiOldColumnData, ImGuiOldColumnFlags, ImGuiOldColumns, ImGuiTable, ImGuiTableColumnSettings, ImGuiTableSortSpecs, ImGuiTableTempData, ImGui_ImplLOVE_CreateTexture, ImGui_ImplLOVE_DestroyTexture, ImGui_ImplLOVE_Init, ImGui_ImplLOVE_NewFrame, ImGui_ImplLOVE_RenderDrawData, ImGui_ImplLOVE_Shutdown, ImGui_ImplLOVE_UpdateTexture, stbrp_init_target, IMGUI_TABLE_MAX_COLUMNS, IMSTB_TEXTEDIT_UNDOSTATECOUNT, IM_COL32_DISABLE, ImGui_ImplLove, STB_TEXTEDIT_K_BACKSPACE, STB_TEXTEDIT_K_DELETE, STB_TEXTEDIT_K_DOWN, STB_TEXTEDIT_K_LEFT, STB_TEXTEDIT_K_LINEEND, STB_TEXTEDIT_K_LINESTART, STB_TEXTEDIT_K_PGDOWN, STB_TEXTEDIT_K_PGUP, STB_TEXTEDIT_K_REDO, STB_TEXTEDIT_K_RIGHT, STB_TEXTEDIT_K_SHIFT local type, mathMin, mathMax, mathFloor, mathCeil, bitOr, bitLShift = type, math.min, math.max, math.floor, math.ceil, bit32.bor, bit32.lshift local function IM_COL32(R, G, B, A) return (bitOr(bitLShift(A, 24 --[[IM_COL32_A_SHIFT]]), bitLShift(B, 16 --[[IM_COL32_B_SHIFT]]), bitLShift(G, 8 --[[IM_COL32_G_SHIFT]]), bitLShift(R, 0 --[[IM_COL32_R_SHIFT]]))) end @@ -40909,47 +40909,341 @@ function MT.ImDrawList:PathBezierQuadraticCurveTo(p2, p3, num_segments) end end --- ponytail: ear-clipping, no anti-aliased fringe (upstream's AA triangulator not ported). O(n^2) is fine for UI polygons. +-------------------------------------------------------------------------------- +-- [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() +-------------------------------------------------------------------------------- + +local ImTriangulatorNodeType = +{ + Convex = 0, + Ear = 1, + Reflex = 2, +} + +-- [Luau] No pointers: a node is an int handle (1-based slot) into the parallel arrays of its ImTriangulator +-- (tri.Type[n], tri.Index[n], tri.Pos[n], tri.Next[n], tri.Prev[n]). node->Index (0-based, as upstream) == n - 1. +local function ImTriangulatorNode_Unlink(tri, n) tri.Prev[tri.Next[n]] = tri.Prev[n]; tri.Next[tri.Prev[n]] = tri.Next[n] end + +-- [Luau] Deviation: upstream classifies a node as reflex when '!ImTriangleIsClockwise()', i.e. also when its corner is +-- degenerate (collinear or duplicate points). A duplicate point then sits as a reflex on top of every candidate ear's vertex +-- and blocks it, ending in the degenerate fallback with overlapping triangles. Only strictly counter-clockwise corners are +-- reflex here: degenerate corners are clipped as zero-area ears. +local function ImTriangulator_IsReflex(a, b, c) return ((b.x - a.x) * (c.y - b.y)) - ((c.x - b.x) * (b.y - a.y)) < 0.0 end + +local function ImTriangulatorNodeSpan() + return { Data = {}, Size = 0 } -- Data: node handles, 1-based +end +MT.ImTriangulatorNodeSpan = {} +MT.ImTriangulatorNodeSpan.__index = MT.ImTriangulatorNodeSpan +function MT.ImTriangulatorNodeSpan:push_back(node) self.Size = self.Size + 1; self.Data[self.Size] = node end +function MT.ImTriangulatorNodeSpan:find_erase_unsorted(idx) for i = self.Size, 1, -1 do if self.Data[i] - 1 == idx then self.Data[i] = self.Data[self.Size]; self.Size = self.Size - 1; return end end end + +-- [Luau] The node/ears/reflexes storage (upstream: carved out of _Data->TempBuffer via scratch_buffer) is owned by the +-- triangulator itself and only ever grows. AddConcavePolyFilled() reuses a single module-level instance (no per-call allocations). +local function ImTriangulator() + return setmetatable({ + _TrianglesLeft = 0, + _Nodes = 0, -- node handle (head of the circular list) + _Ears = setmetatable(ImTriangulatorNodeSpan(), MT.ImTriangulatorNodeSpan), + _Reflexes = setmetatable(ImTriangulatorNodeSpan(), MT.ImTriangulatorNodeSpan), + Type = {}, Index = {}, Pos = {}, Next = {}, Prev = {}, -- [Luau] ImTriangulatorNode fields, one array each + }, MT.ImTriangulator) +end +MT.ImTriangulator = {} +MT.ImTriangulator.__index = MT.ImTriangulator + +function MT.ImTriangulator.EstimateTriangleCount(points_count) return (points_count < 3) and 0 or points_count - 2 end +-- [Luau] In elements (nodes + ears + reflexes) rather than bytes. Unused by the port: arrays grow on demand. +function MT.ImTriangulator.EstimateScratchBufferSize(points_count) return points_count + points_count * 2 end + +-- 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) --- @param points ImVec2[] # 1-based -function MT.ImDrawList:AddConcavePolyFilled(points, points_count, col) - if points_count < 3 or bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then return end - local idx = {} - local area = 0.0 +function MT.ImTriangulator:Init(points, points_count) + assert(points_count >= 3) + self._TrianglesLeft = MT.ImTriangulator.EstimateTriangleCount(points_count) + self._Nodes = 1 + self._Ears.Size = 0 + self._Reflexes.Size = 0 + self:BuildNodes(points, points_count) + + -- [Luau] Deviation: upstream only flips the node list when no ear is found, so a counter-clockwise *concave* polygon + -- (whose reflex corners look like ears) yields triangles outside of it. Normalize to clockwise order up-front (O(N)). + local area2 = 0.0 + local p0 = points[points_count] for i = 1, points_count do - idx[i] = i - local a, b = points[i], points[i % points_count + 1] - area = area + (a.x * b.y - b.x * a.y) + local p1 = points[i] + area2 = area2 + (p0.x * p1.y - p1.x * p0.y) + p0 = p1 end - local sign = (area >= 0) and 1 or -1 - local function cross(o, a, b) return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x) end - local guard = 0 - while #idx > 3 and guard < points_count * points_count do - guard = guard + 1 - local n = #idx - local clipped = false - for i = 1, n do - local ia, ib, ic = idx[(i - 2) % n + 1], idx[i], idx[i % n + 1] - local a, b, c = points[ia], points[ib], points[ic] - if cross(a, b, c) * sign > 0 then - local inside = false - for j = 1, n do - local p = idx[j] - if p ~= ia and p ~= ib and p ~= ic then - local q = points[p] - if cross(a, b, q) * sign >= 0 and cross(b, c, q) * sign >= 0 and cross(c, a, q) * sign >= 0 then inside = true; break end - end - end - if not inside then - self:AddTriangleFilled(a, b, c, col) - table.remove(idx, i) - clipped = true - break - end + if area2 < 0.0 then + self:FlipNodeList() + end + + self:BuildReflexes() + self:BuildEars() +end + +function MT.ImTriangulator:BuildNodes(points, points_count) + local Type, Index, Pos, Next, Prev = self.Type, self.Index, self.Pos, self.Next, self.Prev + for i = 1, points_count do + Type[i] = ImTriangulatorNodeType.Convex + Index[i] = i - 1 + Pos[i] = points[i] -- [Luau] reference, not a copy (points are not modified while triangulating) + Next[i] = i + 1 + Prev[i] = i - 1 + end + Prev[1] = points_count + Next[points_count] = 1 +end + +function MT.ImTriangulator:BuildReflexes() + local Pos, Next, Prev = self.Pos, self.Next, self.Prev + local n1 = self._Nodes + for _ = self._TrianglesLeft, 0, -1 do + if ImTriangulator_IsReflex(Pos[Prev[n1]], Pos[n1], Pos[Next[n1]]) then + self.Type[n1] = ImTriangulatorNodeType.Reflex + self._Reflexes:push_back(n1) + end + n1 = Next[n1] + end +end + +function MT.ImTriangulator:BuildEars() + local Index, Pos, Next, Prev = self.Index, self.Pos, self.Next, self.Prev + local n1 = self._Nodes + for _ = self._TrianglesLeft, 0, -1 do + if self.Type[n1] == ImTriangulatorNodeType.Convex then + local n0, n2 = Prev[n1], Next[n1] + if self:IsEar(Index[n0], Index[n1], Index[n2], Pos[n0], Pos[n1], Pos[n2]) then + self.Type[n1] = ImTriangulatorNodeType.Ear + self._Ears:push_back(n1) end end - if not clipped then break end + n1 = Next[n1] + end +end + +-- [Luau] out_triangle[3] is returned as 3 values (relative 0-based indexes) +function MT.ImTriangulator:GetNextTriangle() + if self._Ears.Size == 0 then + self:FlipNodeList() + + local node = self._Nodes + for _ = self._TrianglesLeft, 0, -1 do + self.Type[node] = ImTriangulatorNodeType.Convex + node = self.Next[node] + end + self._Reflexes.Size = 0 + self:BuildReflexes() + self:BuildEars() + + -- If we still don't have ears, it means geometry is degenerated. + if self._Ears.Size == 0 then + -- Return first triangle available, mimicking the behavior of convex fill. + assert(self._TrianglesLeft > 0) -- Geometry is degenerated + self._Ears.Data[1] = self._Nodes + self._Ears.Size = 1 + end + end + + local ear = self._Ears.Data[self._Ears.Size] + self._Ears.Size = self._Ears.Size - 1 + local Index, Next, Prev = self.Index, self.Next, self.Prev + local t0, t1, t2 = Index[Prev[ear]], Index[ear], Index[Next[ear]] + + ImTriangulatorNode_Unlink(self, ear) + if ear == self._Nodes then + self._Nodes = Next[ear] + end + + self:ReclassifyNode(Prev[ear]) + self:ReclassifyNode(Next[ear]) + self._TrianglesLeft = self._TrianglesLeft - 1 + return t0, t1, t2 +end + +function MT.ImTriangulator:FlipNodeList() + local Next, Prev = self.Next, self.Prev + local nodes = self._Nodes + local prev = nodes + local temp = nodes + local current = Next[nodes] + Next[prev] = prev + Prev[prev] = prev + while current ~= nodes do + temp = Next[current] + + Next[current] = prev + Prev[prev] = current + Next[nodes] = current + Prev[current] = nodes + + prev = current + current = temp + end + self._Nodes = prev +end + +-- A triangle is an ear is no other vertex is inside it. We can test reflexes vertices only (see reference algorithm) +function MT.ImTriangulator:IsEar(i0, i1, i2, v0, v1, v2) + local Index, Pos = self.Index, self.Pos + local data = self._Reflexes.Data + for p = 1, self._Reflexes.Size do + local reflex = data[p] + local idx = Index[reflex] + if idx ~= i0 and idx ~= i1 and idx ~= i2 then + if ImStd.ImTriangleContainsPoint(v0, v1, v2, Pos[reflex]) then + return false + end + end + end + return true +end + +function MT.ImTriangulator:ReclassifyNode(n1) + -- Classify node + local type + local n0 = self.Prev[n1] + local n2 = self.Next[n1] + local Index, Pos = self.Index, self.Pos + if ImTriangulator_IsReflex(Pos[n0], Pos[n1], Pos[n2]) then + type = ImTriangulatorNodeType.Reflex + elseif self:IsEar(Index[n0], Index[n1], Index[n2], Pos[n0], Pos[n1], Pos[n2]) then + type = ImTriangulatorNodeType.Ear + else + type = ImTriangulatorNodeType.Convex + end + + -- Update lists when a type changes + local n1_type = self.Type[n1] + if type == n1_type then + return + end + if n1_type == ImTriangulatorNodeType.Reflex then + self._Reflexes:find_erase_unsorted(Index[n1]) + elseif n1_type == ImTriangulatorNodeType.Ear then + self._Ears:find_erase_unsorted(Index[n1]) + end + if type == ImTriangulatorNodeType.Reflex then + self._Reflexes:push_back(n1) + elseif type == ImTriangulatorNodeType.Ear then + self._Ears:push_back(n1) + end + self.Type[n1] = type +end + +local concave_triangulator = ImTriangulator() -- [Luau] reused scratch (see ImTriangulator()) + +-- 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') +--- @param points ImVec2[] # 1-based +--- @param points_count int +--- @param col ImU32 + +function MT.ImDrawList:AddConcavePolyFilled(points, points_count, col) + if points_count < 3 or bit32.band(col, 4278190080 --[[IM_COL32_A_MASK]]) == 0 then + return + end + + local uv = self._Data.TexUvWhitePixel + local uv_x, uv_y = uv.x, uv.y + local triangulator = concave_triangulator + if bit32.band(self.Flags, 4096 --[[ImDrawFlags.AAFill]]) ~= 0 then + -- Anti-aliased Fill + local AA_SIZE = self._FringeScale + local col_trans = bit32.band(col, bit32.bnot(4278190080 --[[IM_COL32_A_MASK]])) + local idx_count = (points_count - 2) * 3 + points_count * 6 + local vtx_count = (points_count * 2) + PrimReserve(self, idx_count, vtx_count) + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local vw = self._VtxWritePtr + local iw = self._IdxWritePtr + + -- Add indexes for fill + local vtx_inner_idx = self._VtxCurrentIdx + local vtx_outer_idx = self._VtxCurrentIdx + 1 + + triangulator:Init(points, points_count) + while triangulator._TrianglesLeft > 0 do + local t0, t1, t2 = triangulator:GetNextTriangle() + ib[iw + 0] = vtx_inner_idx + t0 * 2; ib[iw + 1] = vtx_inner_idx + t1 * 2; ib[iw + 2] = vtx_inner_idx + t2 * 2 + iw = iw + 3 + end + + -- Compute normals + -- [Luau] scratch arrays (see AddPolyline) instead of _Data->TempBuffer. Indexes below are 1-based. + local temp_normals_x, temp_normals_y = poly_normals_x, poly_normals_y + local i0 = points_count + for i1 = 1, points_count do + local p0 = points[i0] + local p1 = points[i1] + local dx, dy = IM_NORMALIZE2F_OVER_ZERO(p1.x - p0.x, p1.y - p0.y) + temp_normals_x[i0] = dy + temp_normals_y[i0] = -dx + i0 = i1 + end + + i0 = points_count + for i1 = 1, points_count do + -- Average normals + local dm_x, dm_y = IM_FIXNORMAL2F((temp_normals_x[i0] + temp_normals_x[i1]) * 0.5, (temp_normals_y[i0] + temp_normals_y[i1]) * 0.5) + dm_x = dm_x * AA_SIZE * 0.5 + dm_y = dm_y * AA_SIZE * 0.5 + + -- Add vertices + local p1 = points[i1] + WV(vb, vw, p1.x - dm_x, p1.y - dm_y, uv_x, uv_y, col) -- Inner + WV(vb, vw + 1, p1.x + dm_x, p1.y + dm_y, uv_x, uv_y, col_trans) -- Outer + vw = vw + 2 + + -- Add indexes for fringes + local e0, e1 = (i0 - 1) * 2, (i1 - 1) * 2 + ib[iw + 0] = vtx_inner_idx + e1; ib[iw + 1] = vtx_inner_idx + e0; ib[iw + 2] = vtx_outer_idx + e0 + ib[iw + 3] = vtx_outer_idx + e0; ib[iw + 4] = vtx_outer_idx + e1; ib[iw + 5] = vtx_inner_idx + e1 + iw = iw + 6 + i0 = i1 + end + self._VtxWritePtr = vw + self._IdxWritePtr = iw + self._VtxCurrentIdx = self._VtxCurrentIdx + vtx_count + else + -- Non Anti-aliased Fill + local idx_count = (points_count - 2) * 3 + local vtx_count = points_count + PrimReserve(self, idx_count, vtx_count) + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local vw = self._VtxWritePtr + local iw = self._IdxWritePtr + for i = 1, vtx_count do + local p = points[i] + WV(vb, vw, p.x, p.y, uv_x, uv_y, col) + vw = vw + 1 + end + local base_idx = self._VtxCurrentIdx + triangulator:Init(points, points_count) + while triangulator._TrianglesLeft > 0 do + local t0, t1, t2 = triangulator:GetNextTriangle() + ib[iw + 0] = base_idx + t0; ib[iw + 1] = base_idx + t1; ib[iw + 2] = base_idx + t2 + iw = iw + 3 + end + self._VtxWritePtr = vw + self._IdxWritePtr = iw + self._VtxCurrentIdx = base_idx + vtx_count end - if #idx == 3 then self:AddTriangleFilled(points[idx[1]], points[idx[2]], points[idx[3]], col) end end function MT.ImDrawList:PathFillConcave(col) @@ -44870,6 +45164,18 @@ function ImMul(lhs, rhs) return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y) end +--- @param a ImVec2 +--- @param b ImVec2 +--- @param c ImVec2 +--- @return float +function ImTriangleArea(a, b, c) return ImFabs((a.x * (b.y - c.y)) + (b.x * (c.y - a.y)) + (c.x * (a.y - b.y))) * 0.5 end + +--- @param a ImVec2 +--- @param b ImVec2 +--- @param c ImVec2 +--- @return boolean +function ImTriangleIsClockwise(a, b, c) return ((b.x - a.x) * (c.y - b.y)) - ((c.x - b.x) * (b.y - a.y)) > 0.0 end + --- @param str char[] --- @param begin? int function ImStd.ImStrlen(str, begin) @@ -70856,6 +71162,8 @@ return { ImTextureRef = ImTextureRef, ImTextureStatus = ImTextureStatus, ImToUpper = ImToUpper, + ImTriangleArea = ImTriangleArea, + ImTriangleIsClockwise = ImTriangleIsClockwise, ImTrunc = ImTrunc, ImTrunc64 = ImTrunc64, ImU16 = ImU16, diff --git a/docs/PORT_STATUS.txt b/docs/PORT_STATUS.txt index 9580bb7..694781a 100644 --- a/docs/PORT_STATUS.txt +++ b/docs/PORT_STATUS.txt @@ -14,18 +14,18 @@ polyline stroker (AddPolyline), Lua-native replacements (ImGuiStorage, ImStr*, I upstream file funcs ported by size similarity stubs imgui.cpp 786 87.4% 93.5% 75.1% 2 imgui_widgets.cpp 308 93.8% 98.7% 78.4% 1 -imgui_draw.cpp 258 81.8% 92.3% 61.1% 2 +imgui_draw.cpp 258 86.8% 94.4% 61.2% 2 imgui_tables.cpp 113 96.5% 99.0% 74.0% 0 imgui_demo.cpp 56 100.0% 100.0% 55.8% 0 imgui.h 110 53.6% 37.4% 60.0% 0 -imgui_internal.h 269 62.5% 65.7% 67.6% 1 -TOTAL 1900 83.1% 95.3% 70.4% 6 +imgui_internal.h 269 63.2% 67.5% 68.5% 1 +TOTAL 1900 83.9% 95.6% 70.4% 6 -upstream code reproduced (similarity x ported size / all upstream size): 67.0% +upstream code reproduced (similarity x ported size / all upstream size): 67.2% Public API (imgui.h, namespace ImGui) 368 / 376 97.9% Internal API (imgui_internal.h, namespace ImGui) 417 / 443 94.1% -Enum values (imgui.h + imgui_internal.h) 1034 / 1078 95.9% -Struct fields (imgui.h + imgui_internal.h) 1833 / 1926 95.2% +Enum values (imgui.h + imgui_internal.h) 1037 / 1078 96.2% +Struct fields (imgui.h + imgui_internal.h) 1839 / 1926 95.5% Legend: [x] ported (sim %) [~] stub [ ] not ported [-] excluded (c++-only / obsolete / os-specific / #if 0 / test-engine) @@ -41,7 +41,7 @@ imgui.cpp [ ] FreeWrapper -- USER FACING STRUCTURES (ImGuiStyle, ImGuiIO, ImGuiPlatformIO) - [x] 33% ImGuiStyle::ImGuiStyle imgui_internal.lua:1518 + [x] 33% ImGuiStyle::ImGuiStyle imgui_internal.lua:1530 [x] 83% ImGuiStyle::ScaleAllSizes imgui.lua:4779 [x] 12% ImGuiIO::ImGuiIO imgui_h.lua:1187 [x] 83% ImGuiIO::AddInputCharacter imgui.lua:7983 @@ -200,7 +200,7 @@ imgui.cpp [ ] ImGui::GetAllocatorFunctions [x] 87% ImGui::CreateContext imgui.lua:5580 [x] 83% ImGui::DestroyContext imgui.lua:5592 - [x] 6% ImGuiContext::ImGuiContext imgui_internal.lua:2239 + [x] 6% ImGuiContext::ImGuiContext imgui_internal.lua:2251 [-] ImGuiContext::~ImGuiContext c++-only [x] 78% ImGui::Initialize imgui.lua:5538 [x] 44% ImGui::Shutdown imgui.lua:14315 @@ -210,7 +210,7 @@ imgui.cpp [ ] ImGui::CallContextHooks -- MAIN CODE (most of the code! lots of stuff, needs tidying up!) - [x] 11% ImGuiWindow::ImGuiWindow imgui_internal.lua:2843 + [x] 11% ImGuiWindow::ImGuiWindow imgui_internal.lua:2855 [-] ImGuiWindow::~ImGuiWindow c++-only [x] 74% SetCurrentWindow imgui.lua:10805 [x] 85% ImGui::GcCompactTransientMiscBuffers imgui.lua:10826 @@ -1076,7 +1076,7 @@ imgui_widgets.cpp [x] 79% STB_TEXTEDIT_DELETECHARS imgui_widgets.lua:4712 [x] 87% STB_TEXTEDIT_INSERTCHARS imgui_widgets.lua:4726 [x] 81% stb_textedit_replace imgui_widgets.lua:4769 - [x] 12% ImGuiInputTextState::ImGuiInputTextState imgui_internal.lua:1705 + [x] 12% ImGuiInputTextState::ImGuiInputTextState imgui_internal.lua:1717 [-] ImGuiInputTextState::~ImGuiInputTextState c++-only [x] 93% ImGuiInputTextState::OnKeyPressed imgui_widgets.lua:4797 [x] 63% ImGuiInputTextState::OnCharPressed imgui_widgets.lua:4812 @@ -1219,7 +1219,7 @@ imgui_widgets.cpp -- Widgets: BeginTabBar, EndTabBar, etc. [x] 19% ImGuiTabBarSection::ImGuiTabBarSection imgui_widgets.lua:10678 - [x] 16% ImGuiTabBar::ImGuiTabBar imgui_internal.lua:2210 + [x] 16% ImGuiTabBar::ImGuiTabBar imgui_internal.lua:2222 [x] 65% TabItemGetSectionIdx imgui_widgets.lua:10645 [ ] TabItemComparerBySection [ ] TabItemComparerByBeginOrder @@ -1273,7 +1273,7 @@ imgui_draw.cpp [x] 82% ImGui::StyleColorsLight imgui_draw.lua:270 -- ImDrawList - [x] 30% ImDrawListSharedData::ImDrawListSharedData imgui_internal.lua:1120 + [x] 30% ImDrawListSharedData::ImDrawListSharedData imgui_internal.lua:1132 [-] ImDrawListSharedData::~ImDrawListSharedData c++-only [x] 57% ImDrawListSharedData::SetCircleTessellationMaxError imgui_draw.lua:3918 [x] 6% ImDrawList::ImDrawList imgui_h.lua:657 @@ -1351,20 +1351,20 @@ imgui_draw.cpp [x] 84% ImDrawList::AddImageRounded imgui_draw.lua:6549 -- ImTriangulator, ImDrawList concave polygon fill - [ ] ImTriangulatorNode::Unlink - [ ] ImTriangulatorNodeSpan::push_back - [ ] ImTriangulatorNodeSpan::find_erase_unsorted - [ ] ImTriangulator::EstimateTriangleCount - [ ] ImTriangulator::EstimateScratchBufferSize - [ ] ImTriangulator::Init - [ ] ImTriangulator::BuildNodes - [ ] ImTriangulator::BuildReflexes - [ ] ImTriangulator::BuildEars - [ ] ImTriangulator::GetNextTriangle - [ ] ImTriangulator::FlipNodeList - [ ] ImTriangulator::IsEar - [ ] ImTriangulator::ReclassifyNode - [x] 17% ImDrawList::AddConcavePolyFilled imgui_draw.lua:7460 + [x] 35% ImTriangulatorNode::Unlink imgui_draw.lua:7478 + [x] 50% ImTriangulatorNodeSpan::push_back imgui_draw.lua:7491 + [x] 66% ImTriangulatorNodeSpan::find_erase_unsorted imgui_draw.lua:7492 + [x] 84% ImTriangulator::EstimateTriangleCount imgui_draw.lua:7508 + [x] 71% ImTriangulator::EstimateScratchBufferSize imgui_draw.lua:7510 + [x] 38% ImTriangulator::Init imgui_draw.lua:7516 + [x] 45% ImTriangulator::BuildNodes imgui_draw.lua:7541 + [x] 32% ImTriangulator::BuildReflexes imgui_draw.lua:7554 + [x] 38% ImTriangulator::BuildEars imgui_draw.lua:7566 + [x] 53% ImTriangulator::GetNextTriangle imgui_draw.lua:7582 + [x] 65% ImTriangulator::FlipNodeList imgui_draw.lua:7620 + [x] 56% ImTriangulator::IsEar imgui_draw.lua:7643 + [x] 58% ImTriangulator::ReclassifyNode imgui_draw.lua:7658 + [x] 39% ImDrawList::AddConcavePolyFilled imgui_draw.lua:7701 -- ImDrawListSplitter [x] 58% ImDrawListSplitter::ClearFreeMemory imgui_draw.lua:4201 @@ -1964,13 +1964,13 @@ imgui.h [x] 92% ImDrawListSplitter::Clear imgui_draw.lua:4196 [ ] ImDrawList::GetClipRectMin [ ] ImDrawList::GetClipRectMax - [x] 80% ImDrawList::AddPolyline imgui_internal.lua:1077 - [x] 100% ImDrawList::PathClear imgui_internal.lua:1029 - [x] 16% ImDrawList::PathLineTo imgui_internal.lua:1033 - [x] 48% ImDrawList::PathLineToMergeDuplicate imgui_internal.lua:1043 - [x] 94% ImDrawList::PathFillConvex imgui_internal.lua:1053 - [x] 94% ImDrawList::PathFillConcave imgui_draw.lua:7501 - [x] 75% ImDrawList::PathStroke imgui_internal.lua:1061 + [x] 80% ImDrawList::AddPolyline imgui_internal.lua:1089 + [x] 100% ImDrawList::PathClear imgui_internal.lua:1041 + [x] 16% ImDrawList::PathLineTo imgui_internal.lua:1045 + [x] 48% ImDrawList::PathLineToMergeDuplicate imgui_internal.lua:1055 + [x] 94% ImDrawList::PathFillConvex imgui_internal.lua:1065 + [x] 94% ImDrawList::PathFillConcave imgui_draw.lua:7795 + [x] 75% ImDrawList::PathStroke imgui_internal.lua:1073 [x] 89% ImDrawList::ChannelsSplit imgui_draw.lua:4377 [x] 80% ImDrawList::ChannelsMerge imgui_draw.lua:4378 [x] 89% ImDrawList::ChannelsSetCurrent imgui_draw.lua:4380 @@ -2040,8 +2040,8 @@ imgui_internal.h [x] 35% ImUpperPowerOfTwo imgui_internal.lua:280 [x] 82% ImCountSetBits imgui_internal.lua:295 [x] 62% ImToUpper imgui_internal.lua:403 - [x] 75% ImCharIsBlankA imgui_internal.lua:692 - [x] 83% ImCharIsBlankW imgui_internal.lua:695 + [x] 75% ImCharIsBlankA imgui_internal.lua:704 + [x] 83% ImCharIsBlankW imgui_internal.lua:707 [ ] ImCharIsXdigitA [x] 0% ImFileOpen imgui.lua:4702 [x] 0% ImFileClose imgui.lua:4719 @@ -2080,41 +2080,41 @@ imgui_internal.h [x] 71% ImExponentialMovingAverage imgui_internal.lua:308 [x] 60% ImIsTruncated imgui_internal.lua:264 [x] 60% ImIsTruncated4 imgui_internal.lua:266 - [ ] ImTriangleArea - [ ] ImTriangleIsClockwise - [x] 0% IM_MSVC_RUNTIME_CHECKS_OFF::ImVec1 imgui_internal.lua:797 + [x] 100% ImTriangleArea imgui_internal.lua:468 + [x] 100% ImTriangleIsClockwise imgui_internal.lua:474 + [x] 0% IM_MSVC_RUNTIME_CHECKS_OFF::ImVec1 imgui_internal.lua:809 [ ] ImVec2i::ImVec2i [ ] ImVec2ih::ImVec2ih - [x] 0% ImRect::ImRect imgui_internal.lua:827 - [x] 90% ImRect::GetCenter imgui_internal.lua:857 - [x] 89% ImRect::GetSize imgui_internal.lua:862 - [x] 94% ImRect::GetWidth imgui_internal.lua:858 - [x] 94% ImRect::GetHeight imgui_internal.lua:859 - [x] 91% ImRect::GetArea imgui_internal.lua:951 - [x] 33% ImRect::GetTL imgui_internal.lua:865 - [x] 89% ImRect::GetTR imgui_internal.lua:867 - [x] 89% ImRect::GetBL imgui_internal.lua:869 - [x] 33% ImRect::GetBR imgui_internal.lua:871 - [x] 43% ImRect::Contains imgui_internal.lua:834 - [x] 96% ImRect::ContainsWithPad imgui_internal.lua:846 - [x] 14% ImRect::Overlaps imgui_internal.lua:852 - [x] 59% ImRect::Add imgui_internal.lua:896 - [x] 96% ImRect::AddX imgui_internal.lua:912 - [x] 96% ImRect::AddY imgui_internal.lua:913 - [x] 38% ImRect::Expand imgui_internal.lua:917 - [x] 64% ImRect::Translate imgui_internal.lua:934 - [x] 55% ImRect::TranslateX imgui_internal.lua:940 - [x] 55% ImRect::TranslateY imgui_internal.lua:945 - [x] 22% ImRect::ClipWith imgui_internal.lua:876 - [x] 20% ImRect::ClipWithFull imgui_internal.lua:886 - [x] 91% ImRect::IsInverted imgui_internal.lua:949 - [x] 94% ImRect::ToVec4 imgui_internal.lua:928 - [x] 36% ImRect::AsVec4 imgui_internal.lua:954 + [x] 0% ImRect::ImRect imgui_internal.lua:839 + [x] 90% ImRect::GetCenter imgui_internal.lua:869 + [x] 89% ImRect::GetSize imgui_internal.lua:874 + [x] 94% ImRect::GetWidth imgui_internal.lua:870 + [x] 94% ImRect::GetHeight imgui_internal.lua:871 + [x] 91% ImRect::GetArea imgui_internal.lua:963 + [x] 33% ImRect::GetTL imgui_internal.lua:877 + [x] 89% ImRect::GetTR imgui_internal.lua:879 + [x] 89% ImRect::GetBL imgui_internal.lua:881 + [x] 33% ImRect::GetBR imgui_internal.lua:883 + [x] 43% ImRect::Contains imgui_internal.lua:846 + [x] 96% ImRect::ContainsWithPad imgui_internal.lua:858 + [x] 14% ImRect::Overlaps imgui_internal.lua:864 + [x] 59% ImRect::Add imgui_internal.lua:908 + [x] 96% ImRect::AddX imgui_internal.lua:924 + [x] 96% ImRect::AddY imgui_internal.lua:925 + [x] 38% ImRect::Expand imgui_internal.lua:929 + [x] 64% ImRect::Translate imgui_internal.lua:946 + [x] 55% ImRect::TranslateX imgui_internal.lua:952 + [x] 55% ImRect::TranslateY imgui_internal.lua:957 + [x] 22% ImRect::ClipWith imgui_internal.lua:888 + [x] 20% ImRect::ClipWithFull imgui_internal.lua:898 + [x] 91% ImRect::IsInverted imgui_internal.lua:961 + [x] 94% ImRect::ToVec4 imgui_internal.lua:940 + [x] 36% ImRect::AsVec4 imgui_internal.lua:966 [ ] ImBitArrayGetStorageSizeInBytes [ ] ImBitArrayClearAllBits [ ] ImBitArrayTestBit [ ] ImBitArrayClearBit - [x] 34% ImBitArraySetBit imgui_internal.lua:989 + [x] 34% ImBitArraySetBit imgui_internal.lua:1001 [ ] ImBitArraySetBitRange [ ] ImBitVector::Create [ ] ImBitVector::Clear @@ -2166,10 +2166,10 @@ imgui_internal.h [ ] template::offset_from_ptr [ ] template::ptr_from_offset [ ] template::swap - [x] 83% ImGuiTextIndex::clear imgui_internal.lua:1819 - [x] 100% ImGuiTextIndex::size imgui_internal.lua:1821 - [x] 71% ImGuiTextIndex::get_line_begin imgui_internal.lua:1823 - [x] 76% ImGuiTextIndex::get_line_end imgui_internal.lua:1827 + [x] 83% ImGuiTextIndex::clear imgui_internal.lua:1831 + [x] 100% ImGuiTextIndex::size imgui_internal.lua:1833 + [x] 71% ImGuiTextIndex::get_line_begin imgui_internal.lua:1835 + [x] 76% ImGuiTextIndex::get_line_end imgui_internal.lua:1839 [ ] ImGuiTextFilterItem::ImGuiTextFilterItem [ ] ImGuiPackedDate::ImGuiPackedDate [ ] ImGuiPackedDate::IsValid @@ -2177,37 +2177,37 @@ imgui_internal.h [ ] ImGuiPackedDate::SubtractMonths -- ImDrawList support - [x] 27% ImDrawDataBuilder::ImDrawDataBuilder imgui_internal.lua:3016 + [x] 27% ImDrawDataBuilder::ImDrawDataBuilder imgui_internal.lua:3028 -- Style support [ ] ImGuiStyleVarInfo::GetVarPtr - [x] 39% ImGuiStyleMod::ImGuiStyleMod imgui_internal.lua:1268 + [x] 39% ImGuiStyleMod::ImGuiStyleMod imgui_internal.lua:1280 -- Widgets support: flags, enums, data structures - [x] 21% ImGuiComboPreviewData::ImGuiComboPreviewData imgui_internal.lua:1665 - [x] 12% ImGuiMenuColumns::ImGuiMenuColumns imgui_internal.lua:2683 - [x] 20% ImGuiInputTextDeactivatedState::ImGuiInputTextDeactivatedState imgui_internal.lua:1751 + [x] 21% ImGuiComboPreviewData::ImGuiComboPreviewData imgui_internal.lua:1677 + [x] 12% ImGuiMenuColumns::ImGuiMenuColumns imgui_internal.lua:2695 + [x] 20% ImGuiInputTextDeactivatedState::ImGuiInputTextDeactivatedState imgui_internal.lua:1763 [ ] ImGuiInputTextDeactivatedState::ClearFreeMemory [ ] ImGuiInputTextState::ClearText [ ] ImGuiInputTextState::ClearFreeMemory [ ] ImGuiInputTextState::GetText - [x] 3% ImGuiNextWindowData::ImGuiNextWindowData imgui_internal.lua:1433 + [x] 3% ImGuiNextWindowData::ImGuiNextWindowData imgui_internal.lua:1445 [ ] ImGuiNextWindowData::ClearFlags - [x] 20% ImGuiNextItemData::ImGuiNextItemData imgui_internal.lua:1385 + [x] 20% ImGuiNextItemData::ImGuiNextItemData imgui_internal.lua:1397 [ ] ImGuiNextItemData::ClearFlags - [x] 11% ImGuiLastItemData::ImGuiLastItemData imgui_internal.lua:1305 + [x] 11% ImGuiLastItemData::ImGuiLastItemData imgui_internal.lua:1317 [x] 8% ImGuiErrorRecoveryState::ImGuiErrorRecoveryState imgui.lua:19808 [ ] ImGuiPtrOrIndex::ImGuiPtrOrIndex -- Popup support - [x] 36% ImGuiPopupData::ImGuiPopupData imgui_internal.lua:3570 + [x] 36% ImGuiPopupData::ImGuiPopupData imgui_internal.lua:3582 -- Inputs support - [x] 9% ImGuiInputEvent::ImGuiInputEvent imgui_internal.lua:3364 - [x] 73% ImGuiKeyRoutingData::ImGuiKeyRoutingData imgui_internal.lua:1772 - [x] 10% ImGuiKeyRoutingTable::ImGuiKeyRoutingTable imgui_internal.lua:1799 + [x] 9% ImGuiInputEvent::ImGuiInputEvent imgui_internal.lua:3376 + [x] 73% ImGuiKeyRoutingData::ImGuiKeyRoutingData imgui_internal.lua:1784 + [x] 10% ImGuiKeyRoutingTable::ImGuiKeyRoutingTable imgui_internal.lua:1811 [ ] ImGuiKeyRoutingTable::Clear - [x] 64% ImGuiKeyOwnerData::ImGuiKeyOwnerData imgui_internal.lua:647 + [x] 64% ImGuiKeyOwnerData::ImGuiKeyOwnerData imgui_internal.lua:659 -- Clipper support [ ] ImGuiListClipperRange::FromIndices @@ -2216,25 +2216,25 @@ imgui_internal.h [ ] ImGuiListClipperData::Reset -- Navigation support - [x] 13% ImGuiNavItemData::ImGuiNavItemData imgui_internal.lua:1905 + [x] 13% ImGuiNavItemData::ImGuiNavItemData imgui_internal.lua:1917 [ ] ImGuiNavItemData::Clear -- Typing-select support - [x] 7% ImGuiTypingSelectState::ImGuiTypingSelectState imgui_internal.lua:1949 - [x] 71% ImGuiTypingSelectState::Clear imgui_internal.lua:1952 + [x] 7% ImGuiTypingSelectState::ImGuiTypingSelectState imgui_internal.lua:1961 + [x] 71% ImGuiTypingSelectState::Clear imgui_internal.lua:1964 -- Columns support [x] 19% ImGuiOldColumnData::ImGuiOldColumnData imgui_tables.lua:340 [x] 5% ImGuiOldColumns::ImGuiOldColumns imgui_tables.lua:345 -- Box-select support - [x] 6% ImGuiBoxSelectState::ImGuiBoxSelectState imgui_internal.lua:1976 + [x] 6% ImGuiBoxSelectState::ImGuiBoxSelectState imgui_internal.lua:1988 -- Multi-select support - [x] 9% ImGuiMultiSelectTempData::ImGuiMultiSelectTempData imgui_internal.lua:2022 - [x] 10% ImGuiMultiSelectTempData::Clear imgui_internal.lua:2028 - [x] 67% ImGuiMultiSelectTempData::ClearIO imgui_internal.lua:2046 - [x] 72% ImGuiMultiSelectState::ImGuiMultiSelectState imgui_internal.lua:2059 + [x] 9% ImGuiMultiSelectTempData::ImGuiMultiSelectTempData imgui_internal.lua:2034 + [x] 10% ImGuiMultiSelectTempData::Clear imgui_internal.lua:2040 + [x] 67% ImGuiMultiSelectTempData::ClearIO imgui_internal.lua:2058 + [x] 72% ImGuiMultiSelectState::ImGuiMultiSelectState imgui_internal.lua:2071 -- Docking support [x] 100% ImGuiDockNode::IsRootNode imgui.lua:1095 @@ -2252,23 +2252,23 @@ imgui_internal.h [x] 20% ImGuiDockContext::ImGuiDockContext imgui.lua:1040 -- Viewport support - [x] 36% ImGuiViewportP::ImGuiViewportP imgui_internal.lua:3103 + [x] 36% ImGuiViewportP::ImGuiViewportP imgui_internal.lua:3115 [-] ImGuiViewportP::~ImGuiViewportP c++-only - [x] 78% ImGuiViewportP::ClearRequestFlags imgui_internal.lua:3058 - [x] 94% ImGuiViewportP::CalcWorkRectPos imgui_internal.lua:3065 - [x] 98% ImGuiViewportP::CalcWorkRectSize imgui_internal.lua:3069 - [x] 87% ImGuiViewportP::UpdateWorkRect imgui_internal.lua:3074 - [x] 92% ImGuiViewportP::GetMainRect imgui_internal.lua:3081 - [x] 92% ImGuiViewportP::GetWorkRect imgui_internal.lua:3088 - [x] 96% ImGuiViewportP::GetBuildWorkRect imgui_internal.lua:3095 + [x] 78% ImGuiViewportP::ClearRequestFlags imgui_internal.lua:3070 + [x] 94% ImGuiViewportP::CalcWorkRectPos imgui_internal.lua:3077 + [x] 98% ImGuiViewportP::CalcWorkRectSize imgui_internal.lua:3081 + [x] 87% ImGuiViewportP::UpdateWorkRect imgui_internal.lua:3086 + [x] 92% ImGuiViewportP::GetMainRect imgui_internal.lua:3093 + [x] 92% ImGuiViewportP::GetWorkRect imgui_internal.lua:3100 + [x] 96% ImGuiViewportP::GetBuildWorkRect imgui_internal.lua:3107 -- Settings support - [x] 18% ImGuiWindowSettings::ImGuiWindowSettings imgui_internal.lua:2650 + [x] 18% ImGuiWindowSettings::ImGuiWindowSettings imgui_internal.lua:2662 [ ] ImGuiWindowSettings::GetName [x] 13% ImGuiSettingsHandler::ImGuiSettingsHandler imgui_h.lua:2898 -- Metrics, Debug Tools - [x] 7% ImGuiDebugAllocInfo::ImGuiDebugAllocInfo imgui_internal.lua:1853 + [x] 7% ImGuiDebugAllocInfo::ImGuiDebugAllocInfo imgui_internal.lua:1865 [ ] ImGuiStackLevelInfo::ImGuiStackLevelInfo [ ] ImGuiDebugItemPathQuery::ImGuiDebugItemPathQuery [ ] ImGuiIDStackTool::ImGuiIDStackTool @@ -2277,12 +2277,12 @@ imgui_internal.h [ ] ImGuiContextHook::ImGuiContextHook -- ImGuiWindowTempData, ImGuiWindow - [x] 92% ImGuiWindow::Rect imgui_internal.lua:2824 - [x] 84% ImGuiWindow::TitleBarRect imgui_internal.lua:2830 - [x] 91% ImGuiWindow::MenuBarRect imgui_internal.lua:2836 + [x] 92% ImGuiWindow::Rect imgui_internal.lua:2836 + [x] 84% ImGuiWindow::TitleBarRect imgui_internal.lua:2842 + [x] 91% ImGuiWindow::MenuBarRect imgui_internal.lua:2848 -- Tab bar, Tab item support - [x] 50% ImGuiTabItem::ImGuiTabItem imgui_internal.lua:2191 + [x] 50% ImGuiTabItem::ImGuiTabItem imgui_internal.lua:2203 -- Table support + internal API [x] 28% ImGuiTableColumn::ImGuiTableColumn imgui_tables.lua:240 @@ -2302,32 +2302,32 @@ imgui_internal.h [x] 100% GetCurrentWindowRead imgui.lua:13592 [x] 100% GetCurrentWindow imgui.lua:13599 [ ] SetWindowParentWindowForFocusRoute - [x] 99% WindowRectAbsToRel imgui_internal.lua:664 - [x] 99% WindowRectRelToAbs imgui_internal.lua:669 - [x] 98% WindowPosAbsToRel imgui_internal.lua:682 - [x] 98% WindowPosRelToAbs imgui_internal.lua:688 + [x] 99% WindowRectAbsToRel imgui_internal.lua:676 + [x] 99% WindowRectRelToAbs imgui_internal.lua:681 + [x] 98% WindowPosAbsToRel imgui_internal.lua:694 + [x] 98% WindowPosRelToAbs imgui_internal.lua:700 [ ] GetPixelDensity - [x] 55% GetRoundedFontSize imgui_internal.lua:658 + [x] 55% GetRoundedFontSize imgui_internal.lua:670 [x] 15% GetForegroundDrawList imgui.lua:13675 - [x] 80% LocalizeGetMsg imgui_internal.lua:3674 + [x] 80% LocalizeGetMsg imgui_internal.lua:3686 [ ] ScrollToBringRectIntoView [ ] GetItemStatusFlags [ ] GetActiveID [ ] GetFocusID [x] 2% ItemSize imgui.lua:6492 - [x] 100% IsNamedKey imgui_internal.lua:572 - [x] 75% IsNamedKeyOrMod imgui_internal.lua:603 + [x] 100% IsNamedKey imgui_internal.lua:584 + [x] 75% IsNamedKeyOrMod imgui_internal.lua:615 [ ] IsLegacyKey - [x] 60% IsKeyboardKey imgui_internal.lua:577 - [x] 60% IsGamepadKey imgui_internal.lua:582 - [x] 60% IsMouseKey imgui_internal.lua:587 - [x] 60% IsAliasKey imgui_internal.lua:592 - [x] 100% IsLRModKey imgui_internal.lua:597 - [x] 76% ConvertSingleModFlagToKey imgui_internal.lua:609 + [x] 60% IsKeyboardKey imgui_internal.lua:589 + [x] 60% IsGamepadKey imgui_internal.lua:594 + [x] 60% IsMouseKey imgui_internal.lua:599 + [x] 60% IsAliasKey imgui_internal.lua:604 + [x] 100% IsLRModKey imgui_internal.lua:609 + [x] 76% ConvertSingleModFlagToKey imgui_internal.lua:621 [x] 17% GetKeyData imgui.lua:8440 - [x] 97% MouseButtonToKey imgui_internal.lua:569 - [x] 81% IsActiveIdUsingNavDir imgui_internal.lua:673 - [x] 88% GetKeyOwnerData imgui_internal.lua:635 + [x] 97% MouseButtonToKey imgui_internal.lua:581 + [x] 81% IsActiveIdUsingNavDir imgui_internal.lua:685 + [x] 88% GetKeyOwnerData imgui_internal.lua:647 [x] 100% DockNodeGetRootNode imgui.lua:4488 [x] 100% DockNodeIsInHierarchyOf imgui.lua:4493 [x] 86% DockNodeGetDepth imgui.lua:4501 @@ -2335,26 +2335,26 @@ imgui_internal.h [ ] GetWindowDockNode [x] 97% DockBuilderGetCentralNode imgui.lua:3661 [ ] GetCurrentFocusScope - [x] 91% GetBoxSelectState imgui_internal.lua:2065 - [x] 95% GetMultiSelectState imgui_internal.lua:2068 + [x] 91% GetBoxSelectState imgui_internal.lua:2077 + [x] 95% GetMultiSelectState imgui_internal.lua:2080 [ ] GetCurrentTabBar [x] 35% TabBarGetTabOrder imgui_widgets.lua:11197 [-] RenderNavHighlight obsolete - [x] 100% TempInputIsActive imgui_internal.lua:3681 - [x] 88% GetInputTextState imgui_internal.lua:3689 + [x] 100% TempInputIsActive imgui_internal.lua:3693 + [x] 88% GetInputTextState imgui_internal.lua:3701 [ ] IsItemActiveAsInputText - [x] 80% SetNextItemColorMarker imgui_internal.lua:1083 + [x] 80% SetNextItemColorMarker imgui_internal.lua:1095 -- ImFontLoader - [x] 6% ImFontLoader::ImFontLoader imgui_internal.lua:3165 + [x] 6% ImFontLoader::ImFontLoader imgui_internal.lua:3177 -- ImFontAtlas internal API [-] operator== c++-only [-] operator!= c++-only - [x] 75% ImFontAtlasRectId_GetIndex imgui_internal.lua:3217 - [x] 62% ImFontAtlasRectId_GetGeneration imgui_internal.lua:3221 - [x] 77% ImFontAtlasRectId_Make imgui_internal.lua:3226 - [x] 18% ImFontAtlasBuilder::ImFontAtlasBuilder imgui_internal.lua:1184 + [x] 75% ImFontAtlasRectId_GetIndex imgui_internal.lua:3229 + [x] 62% ImFontAtlasRectId_GetGeneration imgui_internal.lua:3233 + [x] 77% ImFontAtlasRectId_Make imgui_internal.lua:3238 + [x] 18% ImFontAtlasBuilder::ImFontAtlasBuilder imgui_internal.lua:1196 ==================================================================================================== API declarations not ported (by upstream line) @@ -2489,7 +2489,7 @@ Enums (values ported / total; missing listed) ImGuiWindowRefreshFlags_ 4 / 4 ImTextureFormat 2 / 2 ImTextureStatus 5 / 5 - ImTriangulatorNodeType 0 / 3 missing: ImTriangulatorNodeType_Convex, ImTriangulatorNodeType_Ear, ImTriangulatorNodeType_Reflex + ImTriangulatorNodeType 3 / 3 ImWcharClass 3 / 3 MyItemColumnID 5 / 5 @@ -2635,8 +2635,8 @@ Struct fields (name found in the port / total; missing listed). Field names are ImRect 2 / 2 ImTextureData 16 / 17 missing: QueueUserData ImTextureRect 4 / 4 - ImTriangulator 0 / 4 missing: _TrianglesLeft, _Nodes, _Ears, _Reflexes - ImTriangulatorNode 3 / 5 missing: Next, Prev + ImTriangulator 4 / 4 + ImTriangulatorNode 5 / 5 ImTriangulatorNodeSpan 2 / 2 ImVec2i 2 / 2 ImVec2ih 2 / 2 diff --git a/imgui_draw.lua b/imgui_draw.lua index 9710ff7..fc44323 100644 --- a/imgui_draw.lua +++ b/imgui_draw.lua @@ -7455,47 +7455,341 @@ function MT.ImDrawList:PathBezierQuadraticCurveTo(p2, p3, num_segments) end end --- ponytail: ear-clipping, no anti-aliased fringe (upstream's AA triangulator not ported). O(n^2) is fine for UI polygons. +-------------------------------------------------------------------------------- +-- [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() +-------------------------------------------------------------------------------- + +local ImTriangulatorNodeType = +{ + Convex = 0, + Ear = 1, + Reflex = 2, +} + +-- [Luau] No pointers: a node is an int handle (1-based slot) into the parallel arrays of its ImTriangulator +-- (tri.Type[n], tri.Index[n], tri.Pos[n], tri.Next[n], tri.Prev[n]). node->Index (0-based, as upstream) == n - 1. +local function ImTriangulatorNode_Unlink(tri, n) tri.Prev[tri.Next[n]] = tri.Prev[n]; tri.Next[tri.Prev[n]] = tri.Next[n] end + +-- [Luau] Deviation: upstream classifies a node as reflex when '!ImTriangleIsClockwise()', i.e. also when its corner is +-- degenerate (collinear or duplicate points). A duplicate point then sits as a reflex on top of every candidate ear's vertex +-- and blocks it, ending in the degenerate fallback with overlapping triangles. Only strictly counter-clockwise corners are +-- reflex here: degenerate corners are clipped as zero-area ears. +local function ImTriangulator_IsReflex(a, b, c) return ((b.x - a.x) * (c.y - b.y)) - ((c.x - b.x) * (b.y - a.y)) < 0.0 end + +local function ImTriangulatorNodeSpan() + return { Data = {}, Size = 0 } -- Data: node handles, 1-based +end +MT.ImTriangulatorNodeSpan = {} +MT.ImTriangulatorNodeSpan.__index = MT.ImTriangulatorNodeSpan +function MT.ImTriangulatorNodeSpan:push_back(node) self.Size = self.Size + 1; self.Data[self.Size] = node end +function MT.ImTriangulatorNodeSpan:find_erase_unsorted(idx) for i = self.Size, 1, -1 do if self.Data[i] - 1 == idx then self.Data[i] = self.Data[self.Size]; self.Size = self.Size - 1; return end end end + +-- [Luau] The node/ears/reflexes storage (upstream: carved out of _Data->TempBuffer via scratch_buffer) is owned by the +-- triangulator itself and only ever grows. AddConcavePolyFilled() reuses a single module-level instance (no per-call allocations). +local function ImTriangulator() + return setmetatable({ + _TrianglesLeft = 0, + _Nodes = 0, -- node handle (head of the circular list) + _Ears = setmetatable(ImTriangulatorNodeSpan(), MT.ImTriangulatorNodeSpan), + _Reflexes = setmetatable(ImTriangulatorNodeSpan(), MT.ImTriangulatorNodeSpan), + Type = {}, Index = {}, Pos = {}, Next = {}, Prev = {}, -- [Luau] ImTriangulatorNode fields, one array each + }, MT.ImTriangulator) +end +MT.ImTriangulator = {} +MT.ImTriangulator.__index = MT.ImTriangulator + +function MT.ImTriangulator.EstimateTriangleCount(points_count) return (points_count < 3) and 0 or points_count - 2 end +-- [Luau] In elements (nodes + ears + reflexes) rather than bytes. Unused by the port: arrays grow on demand. +function MT.ImTriangulator.EstimateScratchBufferSize(points_count) return points_count + points_count * 2 end + +-- 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) --- @param points ImVec2[] # 1-based -function MT.ImDrawList:AddConcavePolyFilled(points, points_count, col) - if points_count < 3 or bit32.band(col, IM_COL32_A_MASK) == 0 then return end - local idx = {} - local area = 0.0 +function MT.ImTriangulator:Init(points, points_count) + IM_ASSERT(points_count >= 3) + self._TrianglesLeft = MT.ImTriangulator.EstimateTriangleCount(points_count) + self._Nodes = 1 + self._Ears.Size = 0 + self._Reflexes.Size = 0 + self:BuildNodes(points, points_count) + + -- [Luau] Deviation: upstream only flips the node list when no ear is found, so a counter-clockwise *concave* polygon + -- (whose reflex corners look like ears) yields triangles outside of it. Normalize to clockwise order up-front (O(N)). + local area2 = 0.0 + local p0 = points[points_count] for i = 1, points_count do - idx[i] = i - local a, b = points[i], points[i % points_count + 1] - area = area + (a.x * b.y - b.x * a.y) + local p1 = points[i] + area2 = area2 + (p0.x * p1.y - p1.x * p0.y) + p0 = p1 end - local sign = (area >= 0) and 1 or -1 - local function cross(o, a, b) return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x) end - local guard = 0 - while #idx > 3 and guard < points_count * points_count do - guard = guard + 1 - local n = #idx - local clipped = false - for i = 1, n do - local ia, ib, ic = idx[(i - 2) % n + 1], idx[i], idx[i % n + 1] - local a, b, c = points[ia], points[ib], points[ic] - if cross(a, b, c) * sign > 0 then - local inside = false - for j = 1, n do - local p = idx[j] - if p ~= ia and p ~= ib and p ~= ic then - local q = points[p] - if cross(a, b, q) * sign >= 0 and cross(b, c, q) * sign >= 0 and cross(c, a, q) * sign >= 0 then inside = true; break end - end - end - if not inside then - self:AddTriangleFilled(a, b, c, col) - table.remove(idx, i) - clipped = true - break - end + if area2 < 0.0 then + self:FlipNodeList() + end + + self:BuildReflexes() + self:BuildEars() +end + +function MT.ImTriangulator:BuildNodes(points, points_count) + local Type, Index, Pos, Next, Prev = self.Type, self.Index, self.Pos, self.Next, self.Prev + for i = 1, points_count do + Type[i] = ImTriangulatorNodeType.Convex + Index[i] = i - 1 + Pos[i] = points[i] -- [Luau] reference, not a copy (points are not modified while triangulating) + Next[i] = i + 1 + Prev[i] = i - 1 + end + Prev[1] = points_count + Next[points_count] = 1 +end + +function MT.ImTriangulator:BuildReflexes() + local Pos, Next, Prev = self.Pos, self.Next, self.Prev + local n1 = self._Nodes + for _ = self._TrianglesLeft, 0, -1 do + if ImTriangulator_IsReflex(Pos[Prev[n1]], Pos[n1], Pos[Next[n1]]) then + self.Type[n1] = ImTriangulatorNodeType.Reflex + self._Reflexes:push_back(n1) + end + n1 = Next[n1] + end +end + +function MT.ImTriangulator:BuildEars() + local Index, Pos, Next, Prev = self.Index, self.Pos, self.Next, self.Prev + local n1 = self._Nodes + for _ = self._TrianglesLeft, 0, -1 do + if self.Type[n1] == ImTriangulatorNodeType.Convex then + local n0, n2 = Prev[n1], Next[n1] + if self:IsEar(Index[n0], Index[n1], Index[n2], Pos[n0], Pos[n1], Pos[n2]) then + self.Type[n1] = ImTriangulatorNodeType.Ear + self._Ears:push_back(n1) end end - if not clipped then break end + n1 = Next[n1] + end +end + +-- [Luau] out_triangle[3] is returned as 3 values (relative 0-based indexes) +function MT.ImTriangulator:GetNextTriangle() + if self._Ears.Size == 0 then + self:FlipNodeList() + + local node = self._Nodes + for _ = self._TrianglesLeft, 0, -1 do + self.Type[node] = ImTriangulatorNodeType.Convex + node = self.Next[node] + end + self._Reflexes.Size = 0 + self:BuildReflexes() + self:BuildEars() + + -- If we still don't have ears, it means geometry is degenerated. + if self._Ears.Size == 0 then + -- Return first triangle available, mimicking the behavior of convex fill. + IM_ASSERT(self._TrianglesLeft > 0) -- Geometry is degenerated + self._Ears.Data[1] = self._Nodes + self._Ears.Size = 1 + end + end + + local ear = self._Ears.Data[self._Ears.Size] + self._Ears.Size = self._Ears.Size - 1 + local Index, Next, Prev = self.Index, self.Next, self.Prev + local t0, t1, t2 = Index[Prev[ear]], Index[ear], Index[Next[ear]] + + ImTriangulatorNode_Unlink(self, ear) + if ear == self._Nodes then + self._Nodes = Next[ear] + end + + self:ReclassifyNode(Prev[ear]) + self:ReclassifyNode(Next[ear]) + self._TrianglesLeft = self._TrianglesLeft - 1 + return t0, t1, t2 +end + +function MT.ImTriangulator:FlipNodeList() + local Next, Prev = self.Next, self.Prev + local nodes = self._Nodes + local prev = nodes + local temp = nodes + local current = Next[nodes] + Next[prev] = prev + Prev[prev] = prev + while current ~= nodes do + temp = Next[current] + + Next[current] = prev + Prev[prev] = current + Next[nodes] = current + Prev[current] = nodes + + prev = current + current = temp + end + self._Nodes = prev +end + +-- A triangle is an ear is no other vertex is inside it. We can test reflexes vertices only (see reference algorithm) +function MT.ImTriangulator:IsEar(i0, i1, i2, v0, v1, v2) + local Index, Pos = self.Index, self.Pos + local data = self._Reflexes.Data + for p = 1, self._Reflexes.Size do + local reflex = data[p] + local idx = Index[reflex] + if idx ~= i0 and idx ~= i1 and idx ~= i2 then + if ImStd.ImTriangleContainsPoint(v0, v1, v2, Pos[reflex]) then + return false + end + end + end + return true +end + +function MT.ImTriangulator:ReclassifyNode(n1) + -- Classify node + local type + local n0 = self.Prev[n1] + local n2 = self.Next[n1] + local Index, Pos = self.Index, self.Pos + if ImTriangulator_IsReflex(Pos[n0], Pos[n1], Pos[n2]) then + type = ImTriangulatorNodeType.Reflex + elseif self:IsEar(Index[n0], Index[n1], Index[n2], Pos[n0], Pos[n1], Pos[n2]) then + type = ImTriangulatorNodeType.Ear + else + type = ImTriangulatorNodeType.Convex + end + + -- Update lists when a type changes + local n1_type = self.Type[n1] + if type == n1_type then + return + end + if n1_type == ImTriangulatorNodeType.Reflex then + self._Reflexes:find_erase_unsorted(Index[n1]) + elseif n1_type == ImTriangulatorNodeType.Ear then + self._Ears:find_erase_unsorted(Index[n1]) + end + if type == ImTriangulatorNodeType.Reflex then + self._Reflexes:push_back(n1) + elseif type == ImTriangulatorNodeType.Ear then + self._Ears:push_back(n1) + end + self.Type[n1] = type +end + +local concave_triangulator = ImTriangulator() -- [Luau] reused scratch (see ImTriangulator()) + +-- 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') +--- @param points ImVec2[] # 1-based +--- @param points_count int +--- @param col ImU32 +@native +function MT.ImDrawList:AddConcavePolyFilled(points, points_count, col) + if points_count < 3 or bit32.band(col, IM_COL32_A_MASK) == 0 then + return + end + + local uv = self._Data.TexUvWhitePixel + local uv_x, uv_y = uv.x, uv.y + local triangulator = concave_triangulator + if bit32.band(self.Flags, ImDrawFlags.AAFill) ~= 0 then + -- Anti-aliased Fill + local AA_SIZE = self._FringeScale + local col_trans = bit32.band(col, bit32.bnot(IM_COL32_A_MASK)) + local idx_count = (points_count - 2) * 3 + points_count * 6 + local vtx_count = (points_count * 2) + PrimReserve(self, idx_count, vtx_count) + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local vw = self._VtxWritePtr + local iw = self._IdxWritePtr + + -- Add indexes for fill + local vtx_inner_idx = self._VtxCurrentIdx + local vtx_outer_idx = self._VtxCurrentIdx + 1 + + triangulator:Init(points, points_count) + while triangulator._TrianglesLeft > 0 do + local t0, t1, t2 = triangulator:GetNextTriangle() + ib[iw + 0] = vtx_inner_idx + t0 * 2; ib[iw + 1] = vtx_inner_idx + t1 * 2; ib[iw + 2] = vtx_inner_idx + t2 * 2 + iw = iw + 3 + end + + -- Compute normals + -- [Luau] scratch arrays (see AddPolyline) instead of _Data->TempBuffer. Indexes below are 1-based. + local temp_normals_x, temp_normals_y = poly_normals_x, poly_normals_y + local i0 = points_count + for i1 = 1, points_count do + local p0 = points[i0] + local p1 = points[i1] + local dx, dy = IM_NORMALIZE2F_OVER_ZERO(p1.x - p0.x, p1.y - p0.y) + temp_normals_x[i0] = dy + temp_normals_y[i0] = -dx + i0 = i1 + end + + i0 = points_count + for i1 = 1, points_count do + -- Average normals + local dm_x, dm_y = IM_FIXNORMAL2F((temp_normals_x[i0] + temp_normals_x[i1]) * 0.5, (temp_normals_y[i0] + temp_normals_y[i1]) * 0.5) + dm_x = dm_x * AA_SIZE * 0.5 + dm_y = dm_y * AA_SIZE * 0.5 + + -- Add vertices + local p1 = points[i1] + WV(vb, vw, p1.x - dm_x, p1.y - dm_y, uv_x, uv_y, col) -- Inner + WV(vb, vw + 1, p1.x + dm_x, p1.y + dm_y, uv_x, uv_y, col_trans) -- Outer + vw = vw + 2 + + -- Add indexes for fringes + local e0, e1 = (i0 - 1) * 2, (i1 - 1) * 2 + ib[iw + 0] = vtx_inner_idx + e1; ib[iw + 1] = vtx_inner_idx + e0; ib[iw + 2] = vtx_outer_idx + e0 + ib[iw + 3] = vtx_outer_idx + e0; ib[iw + 4] = vtx_outer_idx + e1; ib[iw + 5] = vtx_inner_idx + e1 + iw = iw + 6 + i0 = i1 + end + self._VtxWritePtr = vw + self._IdxWritePtr = iw + self._VtxCurrentIdx = self._VtxCurrentIdx + vtx_count + else + -- Non Anti-aliased Fill + local idx_count = (points_count - 2) * 3 + local vtx_count = points_count + PrimReserve(self, idx_count, vtx_count) + local vb = self.VtxBuffer.Buf + local ib = self.IdxBuffer.Data + local vw = self._VtxWritePtr + local iw = self._IdxWritePtr + for i = 1, vtx_count do + local p = points[i] + WV(vb, vw, p.x, p.y, uv_x, uv_y, col) + vw = vw + 1 + end + local base_idx = self._VtxCurrentIdx + triangulator:Init(points, points_count) + while triangulator._TrianglesLeft > 0 do + local t0, t1, t2 = triangulator:GetNextTriangle() + ib[iw + 0] = base_idx + t0; ib[iw + 1] = base_idx + t1; ib[iw + 2] = base_idx + t2 + iw = iw + 3 + end + self._VtxWritePtr = vw + self._IdxWritePtr = iw + self._VtxCurrentIdx = base_idx + vtx_count end - if #idx == 3 then self:AddTriangleFilled(points[idx[1]], points[idx[2]], points[idx[3]], col) end end function MT.ImDrawList:PathFillConcave(col) diff --git a/imgui_internal.lua b/imgui_internal.lua index 3c7b522..4d7781a 100644 --- a/imgui_internal.lua +++ b/imgui_internal.lua @@ -461,6 +461,18 @@ function ImMul(lhs, rhs) return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y) end +--- @param a ImVec2 +--- @param b ImVec2 +--- @param c ImVec2 +--- @return float +function ImTriangleArea(a, b, c) return ImFabs((a.x * (b.y - c.y)) + (b.x * (c.y - a.y)) + (c.x * (a.y - b.y))) * 0.5 end + +--- @param a ImVec2 +--- @param b ImVec2 +--- @param c ImVec2 +--- @return boolean +function ImTriangleIsClockwise(a, b, c) return ((b.x - a.x) * (c.y - b.y)) - ((c.x - b.x) * (b.y - a.y)) > 0.0 end + --- @param str char[] --- @param begin? int function ImStd.ImStrlen(str, begin)