diff --git a/Astrolabe/Astrolabe.lua b/Astrolabe/Astrolabe.lua index e0b5368..e9ca4fd 100644 --- a/Astrolabe/Astrolabe.lua +++ b/Astrolabe/Astrolabe.lua @@ -1,19 +1,18 @@ --[[ Name: Astrolabe -Revision: $Rev: 48 $ -$Date: 2007-05-01 19:24:57 -0400 (Tue, 01 May 2007) $ +Revision: $Rev: 52 $ +$Date: 2008-01-29 20:45:51 +0000 (Tue, 29 Jan 2008) $ Author(s): Esamynn (esamynn@wowinterface.com) Inspired By: Gatherer by Norganna MapLibrary by Kristofer Karlsson (krka@kth.se) -Website: http://esamynn.wowinterface.com/ -Documentation: http://www.esamynn.org/wiki/Astrolabe -SVN: http://esamynn.org/svn/astrolabe/ +Documentation: http://wiki.esamynn.org/Astrolabe +SVN: http://svn.esamynn.org/astrolabe/ Description: This is a library for the World of Warcraft UI system to place icons accurately on both the Minimap and the Worldmaps accurately and maintain the accuracy of those positions. -Copyright (C) 2006-2007 James Carrothers +Copyright (C) 2006-2008 James Carrothers License: This library is free software; you can redistribute it and/or @@ -42,7 +41,7 @@ Note: -- DO NOT MAKE CHANGES TO THIS LIBRARY WITHOUT FIRST CHANGING THE LIBRARY_VERSION_MAJOR -- STRING (to something unique) OR ELSE YOU MAY BREAK OTHER ADDONS THAT USE THIS LIBRARY!!! local LIBRARY_VERSION_MAJOR = "Astrolabe-0.4" -local LIBRARY_VERSION_MINOR = tonumber(string.match("$Revision: 48 $", "(%d+)") or 1) +local LIBRARY_VERSION_MINOR = tonumber(string.match("$Revision: 52 $", "(%d+)") or 1) if not DongleStub then error(LIBRARY_VERSION_MAJOR .. " requires DongleStub.") end if not DongleStub:IsNewerVersion(LIBRARY_VERSION_MAJOR, LIBRARY_VERSION_MINOR) then return end @@ -50,14 +49,27 @@ if not DongleStub:IsNewerVersion(LIBRARY_VERSION_MAJOR, LIBRARY_VERSION_MINOR) t local Astrolabe = {}; -- define local variables for Data Tables (defined at the end of this file) -local WorldMapSize, MinimapSize; +local WorldMapSize, MinimapSize, ValidMinimapShapes; function Astrolabe:GetVersion() return LIBRARY_VERSION_MAJOR, LIBRARY_VERSION_MINOR; end + +-------------------------------------------------------------------------------------------------------------- +-- Config Constants +-------------------------------------------------------------------------------------------------------------- + +local configConstants = { + MinimapUpdateMultiplier = true, +} + +-- this constant is multiplied by the current framerate to determine +-- how many icons are updated each frame +Astrolabe.MinimapUpdateMultiplier = 1; + -------------------------------------------------------------------------------------------------------------- --- Working Tables and Config Constants +-- Working Tables -------------------------------------------------------------------------------------------------------------- Astrolabe.LastPlayerPosition = { 0, 0, 0, 0 }; @@ -65,8 +77,6 @@ Astrolabe.MinimapIcons = {}; Astrolabe.IconsOnEdge = {}; Astrolabe.IconsOnEdge_GroupChangeCallbacks = {}; - -Astrolabe.MinimapUpdateTime = 0.2; Astrolabe.UpdateTimer = 0; Astrolabe.ForceNextUpdate = false; Astrolabe.IconsOnEdgeChanged = false; @@ -75,6 +85,8 @@ Astrolabe.IconsOnEdgeChanged = false; -- The state of this variable is controlled by the AstrolabeMapMonitor library. Astrolabe.WorldMapVisible = false; +local AddedOrUpdatedIcons = {} +local MinimapIconsMetatable = { __index = AddedOrUpdatedIcons } -------------------------------------------------------------------------------------------------------------- -- Internal Utility Functions @@ -331,14 +343,18 @@ end -- local variables specifically for use in this section local minimapRotationEnabled = false; +local minimapShape = false; local MinimapCompassRing = MiniMapCompassRing; local twoPi = math.pi * 2; local atan2 = math.atan2; local sin = math.sin; local cos = math.cos; +local abs = math.abs; +local sqrt = math.sqrt; +local min = math.min +local yield = coroutine.yield local function placeIconOnMinimap( minimap, minimapZoom, mapWidth, mapHeight, icon, dist, xDist, yDist ) - --TODO: add support for non-circular minimaps local mapDiameter; if ( Astrolabe.minimapOutside ) then mapDiameter = MinimapSize.outdoor[minimapZoom]; @@ -350,6 +366,7 @@ local function placeIconOnMinimap( minimap, minimapZoom, mapWidth, mapHeight, ic local yScale = mapDiameter / mapHeight; local iconDiameter = ((icon:GetWidth() / 2) + 3) * xScale; local iconOnEdge = nil; + local isRound = true; if ( minimapRotationEnabled ) then -- for the life of me, I cannot figure out why the following @@ -359,6 +376,20 @@ local function placeIconOnMinimap( minimap, minimapZoom, mapWidth, mapHeight, ic yDist = dist * cos(dir); end + if ( minimapShape and not (xDist == 0 or yDist == 0) ) then + isRound = (xDist < 0) and 1 or 3; + if ( yDist < 0 ) then + isRound = minimapShape[isRound]; + else + isRound = minimapShape[isRound + 1]; + end + end + + -- for non-circular portions of the Minimap edge + if not ( isRound ) then + dist = (abs(xDist) > abs(yDist)) and abs(xDist) or abs(yDist); + end + if ( (dist + iconDiameter) > mapRadius ) then -- position along the outside of the Minimap iconOnEdge = true; @@ -366,6 +397,7 @@ local function placeIconOnMinimap( minimap, minimapZoom, mapWidth, mapHeight, ic xDist = xDist * factor; yDist = yDist * factor; end + if ( Astrolabe.IconsOnEdge[icon] ~= iconOnEdge ) then Astrolabe.IconsOnEdge[icon] = iconOnEdge; Astrolabe.IconsOnEdgeChanged = true; @@ -390,11 +422,11 @@ function Astrolabe:PlaceIconOnMinimap( icon, continent, zone, xPos, yPos ) --icon's position has no meaningful position relative to the player's current location return -1; end - local iconData = self.MinimapIcons[icon]; - if not ( iconData ) then - iconData = GetWorkingTable(icon); - self.MinimapIcons[icon] = iconData; - end + local iconData = GetWorkingTable(icon); + iconData = GetWorkingTable(icon); + self.MinimapIcons[icon] = nil; + AddedOrUpdatedIcons[icon] = iconData + iconData.continent = continent; iconData.zone = zone; iconData.xPos = xPos; @@ -409,6 +441,9 @@ function Astrolabe:PlaceIconOnMinimap( icon, continent, zone, xPos, yPos ) minimapRotationEnabled = false; end + -- check Minimap Shape + minimapShape = GetMinimapShape and ValidMinimapShapes[GetMinimapShape()]; + -- place the icon on the Minimap and :Show() it local map = Minimap placeIconOnMinimap(map, map:GetZoom(), map:GetWidth(), map:GetHeight(), icon, dist, xDist, yDist); @@ -421,6 +456,7 @@ function Astrolabe:RemoveIconFromMinimap( icon ) if not ( self.MinimapIcons[icon] ) then return 1; end + AddedOrUpdatedIcons[icon] = nil self.MinimapIcons[icon] = nil; self.IconsOnEdge[icon] = nil; icon:Hide(); @@ -428,6 +464,7 @@ function Astrolabe:RemoveIconFromMinimap( icon ) end function Astrolabe:RemoveAllMinimapIcons() + self:DumpNewIconsCache() local minimapIcons = self.MinimapIcons; local IconsOnEdge = self.IconsOnEdge; for k, v in pairs(minimapIcons) do @@ -437,105 +474,197 @@ function Astrolabe:RemoveAllMinimapIcons() end end -local lastZoom; -function Astrolabe:UpdateMinimapIconPositions() - local C, Z, x, y = self:GetCurrentPlayerPosition(); - if not ( C and C >= 0 ) then - if not ( self.WorldMapVisible ) then - self.processingFrame:Hide(); +local lastZoom; -- to remember the last seen Minimap zoom level +local fullUpdateInProgress = nil +local resetIncrementalUpdate = false + +local function UpdateMinimapIconPositions( self ) + yield() + + while ( true ) do + local C, Z, x, y = self:GetCurrentPlayerPosition(); + if not ( C and C >= 0 ) then + if not ( self.WorldMapVisible ) then + self.processingFrame:Hide(); + end + return; + end + local Minimap = Minimap; + local lastPosition = self.LastPlayerPosition; + local lC, lZ, lx, ly = unpack(lastPosition); + + if ( GetCVar("rotateMinimap") ~= "0" ) then + minimapRotationEnabled = true; + else + minimapRotationEnabled = false; + end + + -- check current frame rate + local numPerCycle = min(50, GetFramerate() * (self.MinimapUpdateMultiplier or 1)) + + -- check Minimap Shape + minimapShape = GetMinimapShape and ValidMinimapShapes[GetMinimapShape()]; + + if ( lC == C and lZ == Z and lx == x and ly == y ) then + -- player has not moved since the last update + if ( lastZoom ~= Minimap:GetZoom() or self.ForceNextUpdate or minimapRotationEnabled ) then + local currentZoom = Minimap:GetZoom(); + lastZoom = currentZoom; + local mapWidth = Minimap:GetWidth(); + local mapHeight = Minimap:GetHeight(); + numPerCycle = numPerCycle * 2 + local count = 0 + for icon, data in pairs(self.MinimapIcons) do + placeIconOnMinimap(Minimap, currentZoom, mapWidth, mapHeight, icon, data.dist, data.xDist, data.yDist); + + count = count + 1 + if ( count > numPerCycle ) then + count = 0 + yield() + end + end + self.ForceNextUpdate = false; + end + else + local dist, xDelta, yDelta = self:ComputeDistance(lC, lZ, lx, ly, C, Z, x, y); + if ( dist ) then + local currentZoom = Minimap:GetZoom(); + lastZoom = currentZoom; + local mapWidth = Minimap:GetWidth(); + local mapHeight = Minimap:GetHeight(); + local count = 0 + for icon, data in pairs(self.MinimapIcons) do + local xDist = data.xDist - xDelta; + local yDist = data.yDist - yDelta; + local dist = sqrt(xDist*xDist + yDist*yDist); + placeIconOnMinimap(Minimap, currentZoom, mapWidth, mapHeight, icon, dist, xDist, yDist); + + data.dist = dist; + data.xDist = xDist; + data.yDist = yDist; + + count = count + 1 + if ( count > numPerCycle ) then + count = 0 + yield() + if ( resetIncrementalUpdate ) then + break; + end + end + end + else + self:RemoveAllMinimapIcons() + end + + if not ( resetIncrementalUpdate ) then + lastPosition[1] = C; + lastPosition[2] = Z; + lastPosition[3] = x; + lastPosition[4] = y; + end + end + + -- put new/update icons into the main datacache + self:DumpNewIconsCache() + + if not ( resetIncrementalUpdate ) then + yield() end - return; end - local Minimap = Minimap; - local lastPosition = self.LastPlayerPosition; - local lC, lZ, lx, ly = unpack(lastPosition); - - if ( GetCVar("rotateMinimap") ~= "0" ) then - minimapRotationEnabled = true; - else - minimapRotationEnabled = false; +end + +local incrementalUpdateCrashed = true +local incrementalUpdateThread = coroutine.wrap(UpdateMinimapIconPositions) +function Astrolabe:UpdateMinimapIconPositions() + if ( incrementalUpdateCrashed ) then + incrementalUpdateThread = coroutine.wrap(UpdateMinimapIconPositions) + incrementalUpdateThread(self) + end + if ( fullUpdateInProgress ) then + fullUpdateInProgress() end + incrementalUpdateCrashed = true + incrementalUpdateThread() + incrementalUpdateCrashed = false +end + +function CalculateMinimapIconPositions( self ) + yield() - if ( lC == C and lZ == Z and lx == x and ly == y ) then - -- player has not moved since the last update - if ( lastZoom ~= Minimap:GetZoom() or self.ForceNextUpdate or minimapRotationEnabled ) then - local currentZoom = Minimap:GetZoom(); - lastZoom = currentZoom; - local mapWidth = Minimap:GetWidth(); - local mapHeight = Minimap:GetHeight(); - for icon, data in pairs(self.MinimapIcons) do - placeIconOnMinimap(Minimap, currentZoom, mapWidth, mapHeight, icon, data.dist, data.xDist, data.yDist); + while ( true ) do + local C, Z, x, y = self:GetCurrentPlayerPosition(); + if not ( C and C >= 0 ) then + if not ( self.WorldMapVisible ) then + self.processingFrame:Hide(); end - self.ForceNextUpdate = false; + return; end - else - local dist, xDelta, yDelta = self:ComputeDistance(lC, lZ, lx, ly, C, Z, x, y); - if ( dist ) then - local currentZoom = Minimap:GetZoom(); - lastZoom = currentZoom; - local mapWidth = Minimap:GetWidth(); - local mapHeight = Minimap:GetHeight(); - for icon, data in pairs(self.MinimapIcons) do - local xDist = data.xDist - xDelta; - local yDist = data.yDist - yDelta; - local dist = sqrt(xDist*xDist + yDist*yDist); + + -- put new/update icons into the main datacache + self:DumpNewIconsCache() + + if ( GetCVar("rotateMinimap") ~= "0" ) then + minimapRotationEnabled = true; + else + minimapRotationEnabled = false; + end + + -- check current frame rate + local numPerCycle = GetFramerate() * (self.MinimapUpdateMultiplier or 1) * 2 + + -- check Minimap Shape + minimapShape = GetMinimapShape and ValidMinimapShapes[GetMinimapShape()]; + + local currentZoom = Minimap:GetZoom(); + lastZoom = currentZoom; + local Minimap = Minimap; + local mapWidth = Minimap:GetWidth(); + local mapHeight = Minimap:GetHeight(); + local count = 0 + for icon, data in pairs(self.MinimapIcons) do + local dist, xDist, yDist = self:ComputeDistance(C, Z, x, y, data.continent, data.zone, data.xPos, data.yPos); + if ( dist ) then placeIconOnMinimap(Minimap, currentZoom, mapWidth, mapHeight, icon, dist, xDist, yDist); data.dist = dist; data.xDist = xDist; data.yDist = yDist; + else + self:RemoveIconFromMinimap(icon) + end + + count = count + 1 + if ( count > numPerCycle ) then + count = 0 + yield() end - else - self:RemoveAllMinimapIcons() end + local lastPosition = self.LastPlayerPosition; lastPosition[1] = C; lastPosition[2] = Z; lastPosition[3] = x; lastPosition[4] = y; + + fullUpdateInProgress = nil + yield() end end +local updateCrashed = true +local updateThread = coroutine.wrap(CalculateMinimapIconPositions) function Astrolabe:CalculateMinimapIconPositions() - local C, Z, x, y = self:GetCurrentPlayerPosition(); - if not ( C and C >= 0 ) then - if not ( self.WorldMapVisible ) then - self.processingFrame:Hide(); - end - return; - end - - if ( GetCVar("rotateMinimap") ~= "0" ) then - minimapRotationEnabled = true; - else - minimapRotationEnabled = false; - end - - local currentZoom = Minimap:GetZoom(); - lastZoom = currentZoom; - local Minimap = Minimap; - local mapWidth = Minimap:GetWidth(); - local mapHeight = Minimap:GetHeight(); - for icon, data in pairs(self.MinimapIcons) do - local dist, xDist, yDist = self:ComputeDistance(C, Z, x, y, data.continent, data.zone, data.xPos, data.yPos); - if ( dist ) then - placeIconOnMinimap(Minimap, currentZoom, mapWidth, mapHeight, icon, dist, xDist, yDist); - - data.dist = dist; - data.xDist = xDist; - data.yDist = yDist; - else - self:RemoveIconFromMinimap(icon) - end + if ( updateCrashed ) then + updateThread = coroutine.wrap(CalculateMinimapIconPositions) + updateThread(self) end - - local lastPosition = self.LastPlayerPosition; - lastPosition[1] = C; - lastPosition[2] = Z; - lastPosition[3] = x; - lastPosition[4] = y; + updateCrashed = true + fullUpdateInProgress = updateThread -- save the thread so it will be finished + updateThread() + updateCrashed = false end + function Astrolabe:GetDistanceToIcon( icon ) local data = self.MinimapIcons[icon]; if ( data ) then @@ -566,6 +695,15 @@ function Astrolabe:Register_OnEdgeChanged_Callback( func, ident ) self.IconsOnEdge_GroupChangeCallbacks[func] = ident; end +-- for use ONLY by the activate function +function Astrolabe:DumpNewIconsCache() + local MinimapIcons = self.MinimapIcons + for icon, data in pairs(AddedOrUpdatedIcons) do + MinimapIcons[icon] = data + AddedOrUpdatedIcons[icon] = nil + end +end + -------------------------------------------------------------------------------------------------------------- -- World Map Icon Placement @@ -637,6 +775,7 @@ function Astrolabe:OnEvent( frame, event ) end elseif ( event == "ZONE_CHANGED_NEW_AREA" ) then + frame:Hide(); frame:Show(); end @@ -650,7 +789,7 @@ function Astrolabe:OnUpdate( frame, elapsed ) pcall(func); end end - + --[[ -- icon position updates local updateTimer = self.UpdateTimer - elapsed; if ( updateTimer > 0 ) then @@ -658,11 +797,15 @@ function Astrolabe:OnUpdate( frame, elapsed ) return; end self.UpdateTimer = self.MinimapUpdateTime; + ]] self:UpdateMinimapIconPositions(); end function Astrolabe:OnShow( frame ) -- set the world map to a zoom with a valid player position + if not ( self.WorldMapVisible ) then + SetMapToCurrentZone(); + end local C, Z = Astrolabe:GetCurrentPlayerPosition(); if ( C and C >= 0 ) then SetMapZoom(C, Z); @@ -676,7 +819,8 @@ end -- called by AstrolabMapMonitor when all world maps are hidden function Astrolabe:AllWorldMapsHidden() - self:CalculateMinimapIconPositions(); + self.processingFrame:Hide(); + self.processingFrame:Show(); end @@ -686,8 +830,11 @@ end local function activate( newInstance, oldInstance ) if ( oldInstance ) then -- this is an upgrade activate + if ( oldInstance.DumpNewIconsCache ) then + oldInstance:DumpNewIconsCache() + end for k, v in pairs(oldInstance) do - if ( type(v) ~= "function" ) then + if ( type(v) ~= "function" and (not configConstants[k]) ) then newInstance[k] = v; end end @@ -707,6 +854,7 @@ local function activate( newInstance, oldInstance ) end end end + configConstants = nil -- we don't need this anymore local frame = newInstance.processingFrame; frame:SetParent("Minimap"); @@ -731,6 +879,8 @@ local function activate( newInstance, oldInstance ) end ); + setmetatable(Astrolabe.MinimapIcons, MinimapIconsMetatable) + -- register this library with AstrolabeMapMonitor local AstrolabeMapMonitor = DongleStub("AstrolabeMapMonitor"); AstrolabeMapMonitor:RegisterAstrolabeLibrary(Astrolabe, LIBRARY_VERSION_MAJOR); @@ -764,6 +914,23 @@ MinimapSize = { }, } +ValidMinimapShapes = { + -- { upper-left, lower-left, upper-right, lower-right } + ["SQUARE"] = { false, false, false, false }, + ["CORNER-TOPLEFT"] = { true, false, false, false }, + ["CORNER-TOPRIGHT"] = { false, false, true, false }, + ["CORNER-BOTTOMLEFT"] = { false, true, false, false }, + ["CORNER-BOTTOMRIGHT"] = { false, false, false, true }, + ["SIDE-LEFT"] = { true, true, false, false }, + ["SIDE-RIGHT"] = { false, false, true, true }, + ["SIDE-TOP"] = { true, false, true, false }, + ["SIDE-BOTTOM"] = { false, true, false, true }, + ["TRICORNER-TOPLEFT"] = { true, true, true, false }, + ["TRICORNER-TOPRIGHT"] = { true, false, true, true }, + ["TRICORNER-BOTTOMLEFT"] = { true, true, false, true }, + ["TRICORNER-BOTTOMRIGHT"] = { false, true, true, true }, +} + -- distances across and offsets of the world maps -- in game yards WorldMapSize = { diff --git a/Astrolabe/AstrolabeMapMonitor.lua b/Astrolabe/AstrolabeMapMonitor.lua index ae81c27..58b83cc 100644 --- a/Astrolabe/AstrolabeMapMonitor.lua +++ b/Astrolabe/AstrolabeMapMonitor.lua @@ -1,7 +1,7 @@ --[[ Name: AstrolabeMapMonitor Revision: $Rev: 44 $ -$Date: 2007-03-30 14:56:21 -0400 (Fri, 30 Mar 2007) $ +$Date: 2007-03-30 19:56:21 +0100 (Fri, 30 Mar 2007) $ Author(s): Esamynn (esamynn@wowinterface.com) Inspired By: Gatherer by Norganna MapLibrary by Kristofer Karlsson (krka@kth.se)