local zprite = require("zprite") local tex = love.graphics.newImage("atlas.png") tex:setFilter("nearest", "nearest") local gx = 0 local gy = 0 local angle = 0 local scale = 0.35 local dist = 3 local function layered_noise(x, y, layers, persistence) local freq = 1 local ampl = 1 local maxval = 0 local val = 0 for _ = 1, layers do val = val + love.math.simplexNoise(x * freq, y * freq) * ampl maxval = maxval + ampl ampl = ampl * persistence freq = freq * 2 end return val / maxval end local function ease(n) return math.min((1.1 * n ^ 2 + 1 - math.cos(n * math.pi / 2)) / 2, 1) end local function heightmap(x, y) return ease(layered_noise(x * 0.0005, y * 0.0005, 4, 0.8)) end local atlas = { water = love.graphics.newQuad(1, 1, 8, 8, tex), sand = love.graphics.newQuad(11, 1, 8, 8, tex), dirt = love.graphics.newQuad(21, 1, 8, 8, tex), grass = love.graphics.newQuad(31, 1, 8, 8, tex), stone = love.graphics.newQuad(1, 11, 8, 8, tex), snow = love.graphics.newQuad(11, 11, 8, 8, tex), } local alpha = 0.8 local shadow_alpha local colors = {} local time = 0 local function calculate_shadow_alpha(x, y) local delta = 0.01 local dhdx = (heightmap(x + delta / 2, y) - heightmap(x - delta / 2, y)) / delta local dhdy = (heightmap(x, y + delta / 2) - heightmap(x, y - delta / 2)) / delta local value = math.max(math.abs(dhdx), math.abs(dhdy)) * 100 return (1 - 1 / (1 + math.exp(value))) / 8 end function colors.water(layer) if layer % 2 == 1 then return 0, 0, 0, shadow_alpha end if layer < 14 then local r, g, b, a = colors.sand(layer) return r, g, b, a * 1.2 end layer = layer / 2 return layer / 32, layer / 16, layer / 8, alpha end function colors.sand(layer) if layer % 2 == 1 then return 0, 0, 0, shadow_alpha end layer = layer / 2 return layer / 24 + 0.3, layer / 24 + 0.2, layer / 24 + 0.1, alpha end function colors.dirt(layer) if layer % 2 == 1 then return 0, 0, 0, shadow_alpha end layer = layer / 2 if layer < 24 then return layer / 32 + 0.1, layer / 48 + 0.05, layer / 64, alpha else return layer / 50 - 0.1, layer / 50 + 0.1, 0.1, alpha end end function colors.stone(layer) if layer % 2 == 1 then return 0, 0, 0, shadow_alpha end layer = layer / 2 local value = layer / 48 - 0.2 return value / 1.1, value, value * 1.1, alpha end function colors.snow(layer) if layer % 2 == 1 then return 0, 0, 0, shadow_alpha end layer = layer / 2 local value = layer / 48 + 0.25 return value / 1.1, value, value * 1.1, alpha end local function scale_map(layer) return 0 end local function offset_map(layer) if layer % 2 == 1 then return math.cos(time) / 16, math.sin(time) / 16 + 0.5 else return 0, 0.5 end end local z = zprite.zchunk.new(tex, 128, 80, dist * 2 + 1) local function pick_color(height) if height < 0.25 then return colors.water end if height < 0.4 then return colors.sand end if height < 0.67 then return colors.dirt end if height < 0.85 then return colors.stone end return colors.snow end local function generate(x, y) local height = heightmap(x, y) shadow_alpha = calculate_shadow_alpha(x, y) z:put(x, y, atlas.stone, 2 * math.ceil(height * 32) + 16, pick_color(height), offset_map, scale_map) end local generated = {} local function generate_chunk(cx, cy) local idx = z:_chunk_index(cx * z._chunk_size, cy * z._chunk_size) if generated[idx] then return end generated[idx] = { cx, cy } z:clear(cx * z._chunk_size, cy * z._chunk_size) for i = 1, z._chunk_size, 8 do for j = 1, z._chunk_size, 8 do local x = cx * z._chunk_size + i - 1 local y = cy * z._chunk_size + j - 1 generate(x, y) end end end function love.keyreleased(key) if key == "space" then generated = {} end end function love.update(dt) if love.keyboard.isDown("space") then time = math.fmod(time + dt, 2 * math.pi) end local speed = 512 if love.keyboard.isDown("d") then local dx = speed * math.cos(-angle) local dy = speed * math.sin(-angle) gx = gx - dx * dt gy = gy - dy * dt end if love.keyboard.isDown("a") then local dx = speed * math.cos(-angle) local dy = speed * math.sin(-angle) gx = gx + dx * dt gy = gy + dy * dt end if love.keyboard.isDown("s") then local dx = speed * math.cos(-angle + math.pi / 2) local dy = speed * math.sin(-angle + math.pi / 2) gx = gx - dx * dt gy = gy - dy * dt end if love.keyboard.isDown("w") then local dx = speed * math.cos(-angle + math.pi / 2) local dy = speed * math.sin(-angle + math.pi / 2) gx = gx + dx * dt gy = gy + dy * dt end if love.keyboard.isDown("q") then angle = angle + dt end if love.keyboard.isDown("e") then angle = angle - dt end if love.keyboard.isDown("f") then scale = scale * (1 - dt) if scale < 0.2 then scale = 0.2 end end if love.keyboard.isDown("r") then scale = scale * (1 + dt) if scale > 5 then scale = 5 end end local center_cx = -math.floor(gx / z._chunk_size) local center_cy = -math.floor(gy / z._chunk_size) for key, gen in pairs(generated) do if math.abs(gen[1] - center_cx) > dist or math.abs(gen[2] - center_cy) > dist then generated[key] = nil end end for cx = center_cx - dist, center_cx + dist do for cy = center_cy - dist, center_cy + dist do generate_chunk(cx, cy) end end end local t = love.math.newTransform() function love.draw() z._height_scale = scale * 20 t:reset() t:translate(love.graphics.getWidth() / 2, love.graphics.getHeight() / 2) t:scale(scale, scale) t:rotate(angle) t:translate(gx - z._chunk_size / 2, gy - z._chunk_size / 2) z:draw(t) end