local _, MDT = ... local twipe, tinsert, tremove, tgetn = table.wipe, table.insert, table.remove, table.getn -- return true if a is more lower-left than b local function is_lower_left(a, b) if a[1] < b[1] then return true end if a[1] > b[1] then return false end if a[2] < b[2] then return true end if a[2] > b[2] then return true end return false end -- return true if c is left of line a-b local function is_left_of(a, b, c) local u1 = b[1] - a[1] local v1 = b[2] - a[2] local u2 = c[1] - a[1] local v2 = c[2] - a[2] return u1 * v2 - v1 * u2 < 0 end local function convex_hull(pts) local lower_left = 1 for i = 2, #pts do if is_lower_left(pts[i], pts[lower_left]) then lower_left = i end end local hull = {} local final = 1 local tries = 0 repeat table.insert(hull, lower_left) final = 1 for j = 2, #pts do if lower_left == final or is_left_of(pts[lower_left], pts[final], pts[j]) then final = j end end lower_left = final tries = tries + 1 until final == hull[1] or tries > 100 --deadlocked here otherwise?!? local hullpts = {} for _, index in ipairs(hull) do table.insert(hullpts, pts[index]) end return hullpts end local function cross(a, b) return a[1] * b[2] - a[2] * b[1] end local function area(points) local res = cross(points[#points], points[1]) for i = 1, #points - 1 do res = res + cross(points[i], points[i + 1]) end return math.abs(res) / 2 end local function centroid(pts) local rx = 0 local ry = 0 for k, v in pairs(pts) do rx = rx + v[1] ry = ry + v[2] end rx = rx / #pts ry = ry / #pts return { rx, ry } end local function expand_polygon(poly, numCirclePoints) local res = {} local resIndex = 1 for i = 1, #poly do local x = poly[i][1] local y = poly[i][2] local r = poly[i][3] * 10 local adjustedNumPoints = math.max(1, math.floor(numCirclePoints * poly[i][3])) for j = 1, adjustedNumPoints do local cx = x + r * math.cos(2 * math.pi / adjustedNumPoints * j) local cy = y + r * math.sin(2 * math.pi / adjustedNumPoints * j) res[resIndex] = { cx, cy, r } resIndex = resIndex + 1 end end return res end ---TexturePool local activeTextures = {} local texturePool = {} local function getTexture() local size = tgetn(texturePool) if size == 0 then return MDT.main_frame.mapPanelFrame:CreateTexture(nil, "OVERLAY", nil, 0) else local tex = texturePool[size] tremove(texturePool, size) tex:SetRotation(0) tex:SetTexCoord(0, 1, 0, 1) tex:ClearAllPoints() tex.coords = nil tex.points = nil return tex end end local function releaseTexture(tex) tex:Hide() tinsert(texturePool, tex) end ---ReleaseAllActiveTextures function MDT:ReleaseHullTextures() for k, tex in pairs(activeTextures) do releaseTexture(tex) end twipe(activeTextures) end --make a pool for fontStrings local activeFontStrings = {} local fontStringPool = {} local frameIndex = 0 local function getFontString() local size = tgetn(fontStringPool) if size == 0 then local fsFrame = CreateFrame("Frame", "MDTFontStringContainerFrame" .. frameIndex, MDT.main_frame.mapPanelFrame) frameIndex = frameIndex + 1 fsFrame:SetFrameStrata("HIGH") fsFrame:SetFrameLevel(100) fsFrame:SetWidth(40) fsFrame:SetHeight(40) local fs = fsFrame:CreateFontString(nil, "OVERLAY", nil, 0) fs:SetPoint("CENTER", 0, 0) fs:SetJustifyH("CENTER") fs:SetJustifyV("MIDDLE") fs:SetTextColor(1, 1, 1, 1) fs:SetFontObject("GameFontNormalMed3Outline") fsFrame.fs = fs return fsFrame else local fsFrame = fontStringPool[size] tremove(fontStringPool, size) fsFrame.fs:SetText("") return fsFrame end end local function releaseFontString(fsFrame) fsFrame:Hide() tinsert(fontStringPool, fsFrame) end function MDT:ReleaseHullFontStrings() for k, fsFrame in pairs(activeFontStrings) do releaseFontString(fsFrame) end twipe(activeFontStrings) end function MDT:DrawHullFontString(hull, pullIdx) --2. get centroid of each pull local center if hull and hull[#hull] then if #hull > 2 then center = centroid(hull) center[1] = center[1] center[2] = center[2] elseif #hull == 2 then local x1 = hull[1][1] local y1 = hull[1][2] local x2 = hull[2][1] local y2 = hull[2][2] center = { (x1 + x2) / 2, (y1 + y2) / 2 } elseif #hull == 1 then local x1 = hull[1][1] local y1 = hull[1][2] center = { x1, y1 + 15 } end end if not center then return end local fsFrame = getFontString() fsFrame.fs:SetText(pullIdx) fsFrame:ClearAllPoints() fsFrame:SetSize(40, 40) fsFrame:SetPoint("CENTER", MDT.main_frame.mapPanelTile1, "TOPLEFT", center[1], center[2]) fsFrame:Show() tinsert(activeFontStrings, fsFrame) end function MDT:DrawHullCircle(x, y, size, color, layer, layerSublevel) local circle = getTexture() circle:SetDrawLayer(layer, layerSublevel) circle:SetTexture("Interface\\AddOns\\MythicDungeonTools\\Textures\\Circle_White") circle:SetVertexColor(color.r, color.g, color.b, color.a) circle:SetWidth(1.1 * size) circle:SetHeight(1.1 * size) circle:ClearAllPoints() circle:SetPoint("CENTER", MDT.main_frame.mapPanelTile1, "TOPLEFT", x, y) circle:Show() tinsert(activeTextures, circle) end function MDT:DrawHullLine(x, y, a, b, size, color, smooth, layer, layerSublevel, lineFactor) local line = getTexture() line:SetTexture("Interface\\AddOns\\MythicDungeonTools\\Textures\\Square_White") line:SetVertexColor(color.r, color.g, color.b, color.a) DrawLine(line, MDT.main_frame.mapPanelTile1, x, y, a, b, size, lineFactor and lineFactor or 1.1, "TOPLEFT") line:SetDrawLayer(layer, layerSublevel) line:Show() line.coords = { x, y, a, b } tinsert(activeTextures, line) if smooth == true then MDT:DrawHullCircle(x, y, size * 0.9, color, layer, layerSublevel) end end function MDT:DrawHull(vertices, pullColor, pullIdx) --if true then return end local hull = convex_hull(vertices) if hull then -- expand_polygon: higher value = more points = more expensive = smoother outlines hull = expand_polygon(hull, 30) hull = convex_hull(hull) for i = 1, #hull do local a = hull[i] local b = hull[1] if i ~= #hull then b = hull[i + 1] end --layerSublevel go from -8 to 7 --we rotate through the layerSublevel to avoid collisions MDT:DrawHullLine(a[1], a[2], b[1], b[2], 3 * (MDT.scaleMultiplier[MDT:GetDB().currentDungeonIdx] or 1), pullColor, true, "ARTWORK", pullIdx % 16 - 8, 1) end end end local function getPullVertices(p, blips) local vertices = {} for enemyIdx, clones in pairs(p) do if tonumber(enemyIdx) then for _, cloneIdx in pairs(clones) do if MDT:IsCloneIncluded(enemyIdx, cloneIdx) then for _, blip in pairs(blips) do if (blip.enemyIdx == enemyIdx) and (blip.cloneIdx == cloneIdx) then local endPoint, endRelativeTo, endRelativePoint, endX, endY = blip:GetPoint() table.insert(vertices, { endX, endY, blip.normalScale }) break end end end end end end return vertices end function MDT:DrawAllHulls(pulls) MDT:ReleaseHullTextures() MDT:ReleaseHullFontStrings() local preset = MDT:GetCurrentPreset() local blips = MDT:GetDungeonEnemyBlips() local vertices pulls = pulls or preset.value.pulls for pullIdx, p in pairs(pulls) do local r, g, b = MDT:DungeonEnemies_GetPullColor(pullIdx, pulls) vertices = getPullVertices(p, blips) MDT:DrawHull(vertices, { r = r, g = g, b = b, a = 1 }, pullIdx) MDT:DrawHullFontString(vertices, pullIdx) end end function MDT:FindClosestPull(x, y) local preset = MDT:GetCurrentPreset() local blips = MDT:GetDungeonEnemyBlips() local vertices, hull, center local centers = {} --1. construct all hulls of pulls in this sublevel for pullIdx, p in pairs(preset.value.pulls) do vertices = getPullVertices(p, blips) hull = convex_hull(vertices) --2. get centroid of each pull if hull and hull[#hull] then if #hull > 2 then center = centroid(hull) centers[pullIdx] = center elseif #hull == 2 then local x1 = hull[1][1] local y1 = hull[1][2] local x2 = hull[2][1] local y2 = hull[2][2] centers[pullIdx] = { (x1 + x2) / 2, (y1 + y2) / 2 } elseif #hull == 1 then local x1 = hull[1][1] local y1 = hull[1][2] centers[pullIdx] = { x1, y1 } end end end --3. find closest centroid local centerDist = math.huge local centerIndex for k, center in pairs(centers) do local squaredDist = (x - center[1]) ^ 2 + (y - center[2]) ^ 2 if squaredDist < centerDist then centerDist = squaredDist centerIndex = k end end if centerIndex then return centerIndex, centers[centerIndex][1], centers[centerIndex][2] end end