Search code examples
smoothing

Scaling up a tile-map smoothly?


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.


Solution

  • 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.