Pazyryk
Deity
- Joined
- Jun 13, 2008
- Messages
- 3,584
Iterators are hard to make but easy to use. This iterator returns x,y plot coordinates out to a given radius from given center coordinates (edit: including the center). It knows about map edges and map wrapping and deals with these appropriately. It is optimized for speed including the use of memoization (offsets are calculated once for a given radius; subsequent calls with that radius use stored result). It also has two optional args (nearX, nearY) that define a sorting order for the returned coordinates. Sorting is expensive so don't supply nearX, nearY if you don't need sorting. Radius should work up to half the width or height (whichever is smaller) of the current map (for much more than radius 10 I would suggest iteration over all plots with a distance test).
Usage:
Here's the code:
Here's a bit of debug code I used to test it. This will highlight hexes out to supplied radius from the currently selected unit (with a different colored one indicating the first iteration coordinates, which is useful if you supply nearX, nearY for sorting). You'll need include( "FLuaVector" ) for this debug function.
Edit (7/26/12): Contrary to my original post, this function also returns the center plot. My apologies if this inaccuracy messed anyone up. When I have time, I'll modify this with an optional "bExcludeCenter" arg, since I sometimes want the center but other times don't. But for now you'll just have to add this logic yourself.
Usage:
Code:
for x, y in PlotToRadiusIterator(centerX, centerY, 1) do
--x, y for plots at radius 1 and center
--there will be 7 iterations unless we are next to a map edge without wrap
end
for x, y in PlotToRadiusIterator(centerX, centerY, 5) do
--x, y for plots in radius 0 to 5
end
for x, y in PlotToRadiusIterator(centerX, centerY, 3, nearX, nearY) do
--x, y for plots in radius 0 to 3,
--sorted so that they iterate in order by closest to nearX,nearY
--(sorting is expensive so don't supply nearX, nearY if you don't need it)
end
Here's the code:
Code:
local iW, iH = Map.GetGridSize()
local bWrapX, bWrapY = Map.IsWrapX(), Map.IsWrapY()
local yOffsets, xOffsetsEvenY, xOffsetsOddY = {}, {}, {} --contain tables indexed by radius; created and kept as needed
local tempIdx, sortedOffsetsX, sortedOffsetsY = {}, {}, {} --used for sorting when approach plot given (indexed by radius; created and kept as needed)
function PlotToRadiusIterator(x, y, radius, myX, myY)
-- last 2 args (optional & expensive) specify an "approach" coordinate that will cause return values to be sorted by nearest-first
local Distance = Map.PlotDistance
local Floor = math.floor
local yOffset = yOffsets[radius]
if not yOffset then --calculate and keep offsets for this radius (radius up to half map size)
local centerX = Floor(iW / 2)
local centerYeven = Floor(iH / 4) * 2
local centerYodd = centerYeven + 1
local evenPos, oddPos = 1, 1
yOffsets[radius], xOffsetsEvenY[radius], xOffsetsOddY[radius] = {}, {}, {}
for testYoffset = -radius, radius do
local testYeven = centerYeven + testYoffset
local testYodd = centerYodd + testYoffset
for textXoffset = -radius, radius do
local testX = centerX + textXoffset
if Distance(centerX, centerYeven, testX, testYeven) <= radius then
xOffsetsEvenY[radius][evenPos] = textXoffset
yOffsets[radius][evenPos] = testYoffset
evenPos = evenPos + 1
end
if Distance(centerX, centerYodd, testX, testYodd) <= radius then
xOffsetsOddY[radius][oddPos] = textXoffset
oddPos = oddPos + 1
end
end
end
yOffset = yOffsets[radius]
end
local xOffset
if y % 2 == 0 then -- y is even
xOffset = xOffsetsEvenY[radius]
else -- y is odd
xOffset = xOffsetsOddY[radius]
end
local number = #yOffset
if myX then --sort returned values by direction I am comming from
local Sort = table.sort
local sortIdx, sortX, sortY = tempIdx[radius], sortedOffsetsX[radius], sortedOffsetsY[radius]
if not sortIdx then
sortIdx, sortX, sortY = {}, {}, {}
tempIdx[radius], sortedOffsetsX[radius], sortedOffsetsY[radius] = sortIdx, sortX, sortY
for i = 1, number do
sortIdx[i] = i
end
end
Sort(sortIdx, function(a, b)
local Distance = Map.PlotDistance
local myX, myY, xOffset, yOffset = myX, myY, xOffset, yOffset
return Distance(myX, myY, x + xOffset[a], y + yOffset[a]) < Distance(myX, myY, x + xOffset[b], y + yOffset[b])
end)
for i = 1, number do
local sortIndex = sortIdx[i]
sortX[i] = xOffset[sortIndex]
sortY[i] = yOffset[sortIndex]
end
xOffset, yOffset = sortX, sortY --use sorted table instead of unsorted table
end
local i = 0
return function()
local x, y, bWrapX, bWrapY, number, xOffest, yOffset = x, y, bWrapX, bWrapY, number, xOffest, yOffset --for speed
while i < number do
i = i + 1
local xAdj = x + xOffset[i]
local yAdj = y + yOffset[i]
if bWrapX then
if xAdj < 0 then
xAdj = xAdj + iW
elseif xAdj >= iW then
xAdj = xAdj - iW
end
end
if bWrapY then
if yAdj < 0 then
yAdj = yAdj + iH
elseif yAdj >= iH then
yAdj = yAdj - iH
end
end
if yAdj >= 0 and yAdj < iH and xAdj >= 0 and xAdj < iW then --only return a valid map coordinant
return xAdj, yAdj
end
end
end
end
Here's a bit of debug code I used to test it. This will highlight hexes out to supplied radius from the currently selected unit (with a different colored one indicating the first iteration coordinates, which is useful if you supply nearX, nearY for sorting). You'll need include( "FLuaVector" ) for this debug function.
Code:
function DebugPlotIterator(radius, nearX, nearY) --nearX, nearY optional
local unit = UI.GetHeadSelectedUnit()
Events.ClearHexHighlights()
local bStarted = false
print("calling PlotToRadiusIterator", unit:GetX(), unit:GetY(), radius, nearX, nearY)
for x, y in PlotToRadiusIterator(unit:GetX(), unit:GetY(), radius, nearX, nearY) do
print(x, y)
if bStarted then
Events.SerialEventHexHighlight( ToHexFromGrid( Vector2( x, y ) ), true, Vector4( 0, 1, 0, 1 ))
else
Events.SerialEventHexHighlight( ToHexFromGrid( Vector2( x, y ) ), true, Vector4( 1, 0, 1, 1 ))
bStarted = true
end
end
end
Edit (7/26/12): Contrary to my original post, this function also returns the center plot. My apologies if this inaccuracy messed anyone up. When I have time, I'll modify this with an optional "bExcludeCenter" arg, since I sometimes want the center but other times don't. But for now you'll just have to add this logic yourself.