Scaling up a tile-map smoothly?

62 views Asked by At

I'm making a mod for some game, and I'm using a base tile-map that I want to be scaleable to a bigger map. However, when I just use a "nearest-neighbour" kind of scaling the map will have hard square edges. I want to prevent this.

So I have a tilemap, something like this:

- - X -
- X X X
X X X X
X X - -

With my current scaling I get something like:

- - - - X X - -
- - - - X X - -
- - X X X X X X 
- - X X X X X X
X X X X X X X X
X X X X X X X X
X X X X - - - -
X X X X - - - - 

Which has some hard edges as you can see. I would like them to be more smooth:

- - - - X X - -
- - - X X X X -
- - X X X X X X 
- X X X X X X X
X X X X X X X X
X X X X X X X X
X X X X X X - -
X X X X - - - - 

I wasn't sure what to call this, so my search didn't turn up much.

How can I do something like this?

Note that there are several different kinds of tiles, and no in-between tile types.

1

There are 1 answers

1
The Oddler On BEST ANSWER

So I played around a bit myself, and found something that seems to work quite well.

Here's what I do (Lua):

--First get the cells you're between (x and y are real numbers, not ints)
local top = math.floor(y)
local bottom = (top + 1)
local left = math.floor(x)
local right = (left + 1)
--Then calculate weights. These are basically 1 - the distance. The distance is scaled to be between 0 and 1.
local sqrt2 = math.sqrt(2)
local w_top_left = 1 - math.sqrt((top - y)*(top - y) + (left - x)*(left - x)) / sqrt2
local w_top_right = 1 - math.sqrt((top - y)*(top - y) + (right - x)*(right - x)) / sqrt2
local w_bottom_left = 1 - math.sqrt((bottom - y)*(bottom - y) + (left - x)*(left - x)) / sqrt2
local w_bottom_right = 1 - math.sqrt((bottom - y)*(bottom - y) + (right - x)*(right - x)) / sqrt2
--Then square these weights, which makes it look better
w_top_left = w_top_left * w_top_left
w_top_right = w_top_right * w_top_right
w_bottom_left = w_bottom_left * w_bottom_left
w_bottom_right = w_bottom_right * w_bottom_right
--Now get the codes (or types) of the surrounding tiles
local c_top_left = decompressed_map_data[top % height][left % width]
local c_top_right = decompressed_map_data[top % height][right % width]
local c_bottom_left = decompressed_map_data[bottom % height][left % width]
local c_bottom_right = decompressed_map_data[bottom % height][right % width]
--Next calculate total weights for codes
-- So add together the weights of surrounding tiles if they have the same type
local totals = {}
add_to_total(totals, w_top_left, c_top_left) --see below for this helper func
add_to_total(totals, w_top_right, c_top_right)
add_to_total(totals, w_bottom_left, c_bottom_left)
add_to_total(totals, w_bottom_right, c_bottom_right)
--Lastly choose final code, which is the tile-type with the highest weight
local code = nil
local weight = 0
for _, total in pairs(totals) do
    if total.weight > weight then
        code = total.code
        weight = total.weight
    end
end
return terrain_codes[code]

-- Helper function
local function add_to_total(totals, weight, code)
    if totals[code] == nil then
        totals[code] = {code=code, weight=weight}
    else
        totals[code].weight = totals[code].weight + weight
    end
end

And voila. This select an exact tile-type for any x/y value even when they are not integers, thus making it possible to scale your grid. I'm not sure if there are better ways, but it works and looks good. In the end I also added some random number to the weights, to make the edges a little less straight which looks better in Factorio when scaling very high.