/ src / render / renderer.lua
renderer.lua
  1  -- src/render/renderer.lua
  2  -- Pixel renderer for a Kind-based world grid (chunked).
  3  
  4  local Kind = require("src.core.kind")
  5  
  6  local Renderer = {}
  7  Renderer.__index = Renderer
  8  
  9  local function chunk_index(self, cx, cy)
 10    return (cy - 1) * self.chunk_cols + cx
 11  end
 12  
 13  function Renderer.new(world, palette, scale)
 14    assert(world and world.w and world.h)
 15    local self = setmetatable({}, Renderer)
 16  
 17    self.world = world
 18    self.palette = palette
 19    self.scale = scale or 4
 20    self.chunk_size = world.chunk_size or 16
 21    self.chunk_cols = math.ceil(world.w / self.chunk_size)
 22    self.chunk_rows = math.ceil(world.h / self.chunk_size)
 23    self.chunks = {}
 24  
 25    for cy = 1, self.chunk_rows do
 26      for cx = 1, self.chunk_cols do
 27        local x0 = (cx - 1) * self.chunk_size + 1
 28        local y0 = (cy - 1) * self.chunk_size + 1
 29        local w = math.min(self.chunk_size, world.w - x0 + 1)
 30        local h = math.min(self.chunk_size, world.h - y0 + 1)
 31        local imagedata = love.image.newImageData(w, h)
 32        local image = love.graphics.newImage(imagedata)
 33        image:setFilter("nearest", "nearest")
 34  
 35        local i = chunk_index(self, cx, cy)
 36        self.chunks[i] = {
 37          cx = cx,
 38          cy = cy,
 39          x0 = x0,
 40          y0 = y0,
 41          w = w,
 42          h = h,
 43          imagedata = imagedata,
 44          image = image,
 45        }
 46      end
 47    end
 48  
 49    self:full_rebuild()
 50  
 51    return self
 52  end
 53  
 54  function Renderer:set_scale(scale)
 55    self.scale = math.max(1, math.floor(scale))
 56  end
 57  
 58  function Renderer:color_for_kind(kind)
 59    return self.palette[kind] or self.palette.FALLBACK
 60  end
 61  
 62  function Renderer:rebuild_chunk(chunk)
 63    for y = 0, chunk.h - 1 do
 64      local wy = chunk.y0 + y
 65      for x = 0, chunk.w - 1 do
 66        local wx = chunk.x0 + x
 67        local kind = self.world:get(wx, wy) or Kind.EMPTY
 68        local c = self:color_for_kind(kind)
 69        chunk.imagedata:setPixel(x, y, c[1], c[2], c[3], c[4])
 70      end
 71    end
 72    chunk.image:replacePixels(chunk.imagedata)
 73  end
 74  
 75  function Renderer:full_rebuild()
 76    for i = 1, #self.chunks do
 77      self:rebuild_chunk(self.chunks[i])
 78    end
 79  end
 80  
 81  function Renderer:apply_dirty_chunks(dirty_chunks)
 82    if not dirty_chunks or #dirty_chunks == 0 then
 83      return false
 84    end
 85  
 86    for i = 1, #dirty_chunks do
 87      local entry = dirty_chunks[i]
 88      local index = chunk_index(self, entry.cx, entry.cy)
 89      local chunk = self.chunks[index]
 90      if chunk then
 91        self:rebuild_chunk(chunk)
 92      end
 93    end
 94  
 95    return true
 96  end
 97  
 98  function Renderer:apply_dirty(dirty)
 99    if not dirty or #dirty == 0 then
100      return false
101    end
102  
103    local touched = {}
104  
105    for i = 1, #dirty do
106      local p = dirty[i]
107      local index = self.world:chunk_index(p.x, p.y)
108      if index then
109        local chunk = self.chunks[index]
110        if chunk then
111          local kind = self.world:get(p.x, p.y) or Kind.EMPTY
112          local c = self:color_for_kind(kind)
113          local lx = p.x - chunk.x0
114          local ly = p.y - chunk.y0
115          chunk.imagedata:setPixel(lx, ly, c[1], c[2], c[3], c[4])
116          touched[index] = true
117        end
118      end
119    end
120  
121    for index in pairs(touched) do
122      local chunk = self.chunks[index]
123      if chunk then
124        chunk.image:replacePixels(chunk.imagedata)
125      end
126    end
127  
128    return true
129  end
130  
131  function Renderer:draw(x, y, scale)
132    local sx = scale or self.scale
133    x = x or 0
134    y = y or 0
135  
136    love.graphics.setColor(1, 1, 1, 1)
137    for i = 1, #self.chunks do
138      local chunk = self.chunks[i]
139      local dx = x + (chunk.x0 - 1) * sx
140      local dy = y + (chunk.y0 - 1) * sx
141      love.graphics.draw(chunk.image, dx, dy, 0, sx, sx)
142    end
143  end
144  
145  return Renderer