local blankCanvas
local w = 1
local cube = {
  { --right
    { w, -1, 1, 0, 1, 1 },
    { w, -1, -1, 0, 0, 1 },
    { w, 1, -1, 0, 0, 0 },
    { w, 1, -1, 0, 0, 0 }, -- dup
    { w, 1, 1, 0, 1, 0 },
    { w, -1, 1, 0, 1, 1 }, -- dup
  },
  { --left
    { -w, -1, -1, 0, 1, 1 },
    { -w, -1, 1, 0, 0, 1 },
    { -w, 1, 1, 0, 0, 0 },
    { -w, 1, 1, 0, 0, 0 }, -- dup
    { -w, 1, -1, 0, 1, 0 },
    { -w, -1, -1, 0, 1, 1 }, -- dup
  },
  { --top
    { 1, w, -1, 0, 1, 1 },
    { -1, w, -1, 0, 0, 1 },
    { -1, w, 1, 0, 0, 0 },
    { -1, w, 1, 0, 0, 0 }, -- dup
    { 1, w, 1, 0, 1, 0 },
    { 1, w, -1, 0, 1, 1 }, -- dup
  },
  { --bottom
    { -1, -w, -1, 0, 1, 1 },
    { 1, -w, -1, 0, 0, 1 },
    { 1, -w, 1, 0, 0, 0 },
    { 1, -w, 1, 0, 0, 0 }, -- dup
    { -1, -w, 1, 0, 1, 0 },
    { -1, -w, -1, 0, 1, 1 }, -- dup
  },
  { --front
    { -1, -1, w, 0, 1, 1 },
    { 1, -1, w, 0, 0, 1 },
    { 1, 1, w, 0, 0, 0 },
    { 1, 1, w, 0, 0, 0 }, -- dup
    { -1, 1, w, 0, 1, 0 },
    { -1, -1, w, 0, 1, 1 }, -- dup
  },
  { --back
    { 1, -1, -w, 0, 1, 1 },
    { -1, -1, -w, 0, 0, 1 },
    { -1, 1, -w, 0, 0, 0 },
    { -1, 1, -w, 0, 0, 0 }, -- dup
    { 1, 1, -w, 0, 1, 0 },
    { 1, -1, -w, 0, 1, 1 }, -- dup
  },
}
local cubeDirs = {
  { vec3(1, 0, 0), vec3(0, 1, 0) },
  { vec3(-1, 0, 0), vec3(0, 1, 0) },
  { vec3(0, -1, 0), vec3(0, 0, -1) },
  { vec3(0, 1, 0), vec3(0, 0, -1) },
  { vec3(0, 0, 1), vec3(0, 1, 0) },
  { vec3(0, 0, -1), vec3(0, 1, 0) },
}

local norms = {
  { -1, 0, 0 },
  { 0, -1, 0 },
  { 0, 0, 1 },
  { 1, 0, 0 },
  { 0, 1, 0 },
  { 0, 0, -1 },
}

local shadowTypeNums = { point = 1, spot = 2, direction = 3 }
local tv1, tv2, tv3 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
local tempRay = {}
local tempNormal = vec3()
local tempAlbedo = vec3()
local directLight = vec3()
local shadowMat = mat4()
local lightMat = mat4()

local function rotateMatrix(m, r)
  m:rotate(m, r.y, vec3.unit_x)
  m:rotate(m, r.x, vec3.unit_y)
  m:rotate(m, r.z, vec3.unit_z)
  return m
end

local function updateRoomBounds(room)
  for m, model in ipairs(room.terrain.models) do
    if not model.bbox then model.bbox = scene_calcBBOX(model) end
    local bbox = model.bbox
    room.bounds = m == 1 and { min = bbox.min:clone(), max = bbox.max:clone() } or room.bounds
    room.bounds.min.x = math.min(room.bounds.min.x, bbox.min.x)
    room.bounds.min.y = math.min(room.bounds.min.y, bbox.min.y)
    room.bounds.min.z = math.min(room.bounds.min.z, bbox.min.z)
    room.bounds.max.x = math.max(room.bounds.max.x, bbox.max.x)
    room.bounds.max.y = math.max(room.bounds.max.y, bbox.max.y)
    room.bounds.max.z = math.max(room.bounds.max.z, bbox.max.z)
  end
  room.bounds.min = room.bounds.min - vec3(50)
  room.bounds.max = room.bounds.max + vec3(50)
  room.bounds.center = (room.bounds.min + room.bounds.max) / 2
end

local function setStaticProbes(room)
  local abs, floor, ceil = math.abs, math.floor, math.ceil
  local size = room.lighting.probes.gridsize
  local size2 = size * size
  local spacing = room.lighting.probes.spacing
  local bmin, bmax = room.bounds.min, room.bounds.max
  local probes = room.lightProbes
  local pos, upper, lower = vec3(), vec3(), vec3()
  local index
  local triangles = {}

  for m, model in ipairs(room.terrain.models) do
    for i = 1, model.mesh:getVertexCount(), 1 do
      if not model.isEdge then
        pos = -vec3(model.mesh:getVertex(i))
        pos = (vec3(-bmax.x, -bmax.y, -bmin.z) - pos) / spacing
        local n = size - 1

        upper:set(n - abs(floor(pos.x)), n - abs(floor(pos.y)), abs(floor(pos.z)))
        lower:set(n - abs(ceil(pos.x)), n - abs(ceil(pos.y)), abs(ceil(pos.z)))

        probes[lower.x * size2 + lower.y * size + lower.z + 1].active = true
        probes[upper.x * size2 + lower.y * size + lower.z + 1].active = true
        probes[lower.x * size2 + upper.y * size + lower.z + 1].active = true
        probes[lower.x * size2 + lower.y * size + upper.z + 1].active = true
        probes[upper.x * size2 + lower.y * size + upper.z + 1].active = true
        probes[lower.x * size2 + upper.y * size + upper.z + 1].active = true
        probes[upper.x * size2 + upper.y * size + lower.z + 1].active = true
        probes[upper.x * size2 + upper.y * size + upper.z + 1].active = true

        if (i - 1) % 3 == 0 then
          table.insert(triangles, {
            vec3(model.mesh:getVertex(i)),
            vec3(model.mesh:getVertex(i + 1)),
            vec3(model.mesh:getVertex(i + 2)),
            m,
            i,
          })
        end
      end
    end
  end

  room.terrain.triangles = triangles
  room.terrain.bvh = cpml.bvh(triangles)

  local active = 0
  for p, probe in ipairs(probes) do
    if probe.active then
      active = active + 1
      for i = 1, 36, 1 do
        room.lighting.probes.mesh:setVertexAttribute((p - 1) * 36 + i, 3, 1, 1, 1, 1)
      end
    end
  end
  print("active probes", active)
end

local function bvhRoom(room)
  local triangles = {}
  for m, model in ipairs(room.terrain.models) do
    for i = 1, model.mesh:getVertexCount(), 1 do
      if not model.isEdge then
        if (i - 1) % 3 == 0 then
          table.insert(triangles, {
            vec3(model.mesh:getVertex(i)),
            vec3(model.mesh:getVertex(i + 1)),
            vec3(model.mesh:getVertex(i + 2)),
            m,
            i,
          })
        end
      end
    end
  end

  room.terrain.triangles = triangles
  room.terrain.bvh = cpml.bvh(triangles)
end

local function setValues(t, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17)
  t[1], t[2], t[3], t[4], t[5], t[6], t[7], t[8], t[9], t[10], t[11], t[12], t[13], t[14], t[15], t[16], t[17] =
    v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17
  return t
end

local function random_in_unit_sphere()
  local point = vec3()
  while true do
    point:set(math.random() * 2 - 1, math.random() * 2 - 1, math.random() * 2 - 1)
    if point:len2() >= 1 then return point:normalize2() end
  end
end

local function random_in_hemisphere(normal)
  local in_unit_sphere = random_in_unit_sphere()
  -- print(in_unit_sphere,normal)
  if vec3.dot(in_unit_sphere, normal) > 0 then -- In the same hemisphere as the normal
    return in_unit_sphere
  else
    return -in_unit_sphere
  end
end

local function ray_color(origin, direction, room, depth, diffuse)
  -- If we've exceeded the ray bounce limit, no more light is gathered.
  if depth <= 0 then
    directLight:set(0.5, 0.5, 1)
    local size = 50
    local lightPosition = vec3(room.lightPosition[1])
      + vec3(math.random() * size - size / 2, math.random() * size - size / 2, math.random() * size - size / 2)
    local lightDirection = lightPosition - origin
    local lightDistance = (lightDirection):len()
    local lightDistance2 = (lightDirection):len2()
    tempRay.origin = origin + direction
    tempRay.direction = lightDirection
    if vec3.dot(lightDirection, direction) < 0 then
      directLight:set(0, 0, 0)
    else
      local lightArea = size
      local lightCosine = math.abs(lightDirection.y)
      local pdf = lightDistance2 / (lightCosine * lightArea)
      for t = 1, #room.terrain.triangles, 1 do
        local point, distance = cpml.intersect.ray_triangle(tempRay, room.terrain.triangles[t])
        if point and distance < 1 then
          directLight:set(0, 0, 0)
          break
        end
      end
      return directLight / pdf
    end
    return directLight
  end

  if diffuse then
    return ray_color(origin + direction, random_in_hemisphere(direction), room, depth - 1)
  else
    local lastDistance = math.huge
    local intP, intD, intT, tri
    tempRay.origin = origin
    tempRay.direction = direction
    for t = 1, #room.terrain.triangles, 1 do
      local point, distance = cpml.intersect.ray_triangle(tempRay, room.terrain.triangles[t])
      if point and distance < lastDistance then
        lastDistance = distance
        intP = point
        intD = distance
        intT = t
      end
    end
    if intP and intD and intT then
      local tri = room.terrain.triangles[intT]
      local model = room.terrain.models[tri[4]]
      setValues(tv1, model.mesh:getVertex(tri[5]))
      tempNormal:set(tv1[11], tv1[12], tv1[13])
      tempAlbedo:set(tv1[7], tv1[8], tv1[9])
      return tempAlbedo * ray_color(intP + tempNormal, random_in_hemisphere(tempNormal), room, depth - 1)
    end

    -- local hit=room.terrain.bvh:intersectRay(ray.origin,ray.direction)
    -- if hit and hit.triangleIndex then
    --   local tri=room.terrain.triangles[hit.triangleIndex]
    --   local model = room.terrain.models[tri[4]]
    --   setValues(tv1,model.mesh:getVertex(tri[5]))
    --   -- vec3(tv1[7],tv1[8],tv1[9]) * 0.5
    --   local normal = vec3(tv1[11],tv1[12],tv1[13])
    --   local target = hit.intersectionPoint  + random_in_hemisphere(normal);
    --   return ray_color( {origin=hit.intersectionPoint, direction=target - hit.intersectionPoint},  room,  depth-1)*0.5
    -- end
  end

  local col = vec3(room.sky.color.r, room.sky.color.g, room.sky.color.b)
  direction:normalize2()
  t = 0.5 * (direction.y + 1)
  -- col:scale3(1.0-t)
  return col
end

local function sendVolumes(shader, name, volumes, from_zero)
  for i = from_zero and 0 or 1, 6, 1 do
    shader:send(string.format("%s%i", name, i), type(volumes) == "table" and volumes[i] or volumes)
  end
end

local function setVolumeFilter(vct, filter)
  for b, buffer in ipairs({ "diffuse", "normal", "light" }) do
    for i = 0, 6, 1 do
      vct[buffer][i]:setFilter(filter, filter)
      if i > 0 then vct[buffer][i]:setMipmapFilter(filter, 0) end
    end
  end
  vct.occlusion:setFilter(filter, filter)
  vct.occlusion:setMipmapFilter(filter, 0)
end

local function mipmapVoxels(vct, buffer, normal, alpha)
  local mainShader = alpha and shaders.voxelNormalmip or shaders.voxelMipmap
  local shader = normal and shaders.voxelLightmip or mainShader
  gfx.setShader(shader)
  gfx.setBlendMode("alpha")
  sendVolumes(shader, "volume", buffer[0])
  if normal then sendVolumes(shader, "normal", normal) end
  for mip = 1, 6, 1 do
    if mip == 2 then
      if alpha then
        shader = mainShader
        gfx.setShader(shader)
      end
      sendVolumes(shader, "volume", buffer)
    end
    shader:send("mip", mip)
    local slices = vct.resolution / (2 ^ mip)
    for slice = 1, slices, 1 do
      shader:send("z", slice - 1)
      gfx.setCanvas({
        { buffer[1], layer = slice, mipmap = mip },
        { buffer[2], layer = slice, mipmap = mip },
        { buffer[3], layer = slice, mipmap = mip },
        { buffer[4], layer = slice, mipmap = mip },
        { buffer[5], layer = slice, mipmap = mip },
        { buffer[6], layer = slice, mipmap = mip },
      })
      gfx.clear(0, 0, 0, 0)
      gfx.draw(vct.dummy, 1 / mip)
    end
  end
end

local function drawShadow(view, room, light, cache)
  if not light.position then return end
  game.drawingShadow = true
  gfx.setCanvas({ depth = true, depthstencil = light.canvas })
  gfx.setDepthMode("lequal", true)
  gfx.clear(1, 1, 1, 1, 0, 1)
  shaders.shadowStatic:send("lightPosition", mth.vec3Table2(light.position))
  shaders.shadowStatic:send("lightRange", light.range)
  for i = 1, light.type == "point" and 6 or 1, 1 do
    light.matrix = mat4(light.mats[i])
    if light.type ~= "point" then
      light.matrix:mul(light.matrix, shadowMat:look_at(nil, vec3(0), -light.direction, vec3(0, 1, 0)))
      -- pointMatrix(light.matrix,light.direction)
    end
    light.matrix:mul(light.matrix, shadowMat:translate(mat4(), -light.position))
    gfx.setDepthMode("always", true)
    gl.Viewport(light.resolution * (i - 1), 0, light.resolution, light.resolution)
    gfx.setColor(1, 1, 1, 1)

    gfx.setDepthMode("lequal", true)

    -- draw statics
    if not light.cache or (light.cache and cache) or ctx.editing then view_drawPaths(view, light.matrix) end

    -- draw dynamics
    -- if (not light.cache) or (light.cache and not cache) then
    --  shaders.forwardRender:send("game.drawingShadow",true)
    --    gfx.setMeshCullMode( "front" )
    --    drawRoomStuff   (view,room)
    --  shaders.forwardRender:send("game.drawingShadow",false)
    -- end
  end
  game.drawingShadow = false
end

local function pointMatrix(m, v)
  local ang1 = vec2.angle_between(vec2(0, 1), vec2(v.x, v.z))
  local x = vec2(v.x, v.z):len()
  local ang2 = vec2.angle_between(vec2(1, 0), vec2(x, v.y))
  rotateMatrix(m, vec3(pi + (v.x > 0 and ang1 or -ang1), (v.y > 0 and ang2 or -ang2), 0))
  return m
end

local function drawLight(view, room, l)
  local light = room.lights[l]
  local dist = vec3.dist(-view.camera.pos, vec3(room.lightPosition[l]))

  gfx.setColor(room.lightColor[l])
  lightMat = lightMat:identity()
  local proxy

  if room.lightType[l] < 3 then
    if room.lightType[l] == 1 then
      gfx.setMeshCullMode(dist < room.lightRange[l] and "back" or "front")
      lightMat:scale(lightMat, vec3(room.lightRange[l]))
      if light then light.mesh = game.lightSphere end
      proxy = game.lightSphere
    else
      gfx.setMeshCullMode("back")
      proxy = light and light.mesh or game.lightSphere
      pointMatrix(lightMat, light.direction)
    end
    lightMat:translate(lightMat, vec3(room.lightPosition[l]))
  -- lightMat:transpose(lightMat)
  else
    proxy = patterns.atlas
  end
  local shadowed = (room.lightShadowed[l] == true and light and light.canvas ~= nil)
  room.lightShader:send("modelMatrix", lightMat)
  room.lightShader:send("lightPosition", room.lightPosition[l])
  room.lightShader:send("lightColor", room.lightColor[l])
  room.lightShader:send("lightPower", room.lightPower[l])
  room.lightShader:send("lightRange", room.lightRange[l])
  shaders.deferredLight:send("lightAperture", room.lightAperture[l])
  shaders.deferredLight:send("lightFalloff", room.lightFalloff[l])
  shaders.deferredLight:send("lightType", room.lightType[l])
  shaders.deferredLight:send("lightDirection", room.lightDirection[l])
  shaders.deferredLight:send("lightShadowed", shadowed)
  shaders.deferredLight:send("lightSoftness", light and light.soft and (light.softness or 0) + 0.0001 or 0)
  shaders.deferredLight:send("lightSamples", light and light.soft and (light.softness or 0) * 65 + 1 or 0)
  if shadowed then
    shaders.deferredLight:send("lightMatrix", "column", light.matrix)
    if light.soft then
      shaders.deferredLight:send("lightShadow2", light.canvas)
    else
      shaders.deferredLight:send("lightShadow", light.canvas)
    end
  end
  gfx.draw(proxy)

  shaders.deferredLight:send("lightType", 1)
end

function light_roomIsLit(room)
  return room.lighting.enabled and room.lighting.dynamic.enabled
end

function light_renderDebug()
  local l = editor and ctx.viewport and ctx.viewport.room.lighting
  if l and l.enabled and l.probes.enabled and l.probes.showProbes and l.probes.showAtlas and l.probes.canvas then
    scale = l.probes.atlasSize --/ (l.probes.convolve and l.probes.downsample or 1)

    if not l.probes.convolve then gfx.setShader(shaders.depth) end
    gfx.draw(l.probes.convolve and l.probes.canvas or l.probes.depth, screenW - l.probes.dimensions * l.probes.atlasSize, 0, 0, scale, scale)
    -- gfx.setWireframe(true)
    -- gfx.draw(room.lighting.probes.convolver)
    -- gfx.setWireframe(false)
  end
  local l = editor and ctx.viewport and ctx.viewport.room.lighting
  if
    l
    and ctx.editing
    and not ctx.pageView
    and l.enabled
    and l.render.enabled
    and l.render.run
    and l.render.rendering
    -- and l.render.showAtlas
    and l.render.canvas
  then
    -- scale=l.probes.atlasSize --/ (l.probes.convolve and l.probes.downsample or 1)
    -- if not l.probes.convolve then
    --  gfx.setShader(shaders.depth)
    -- end
    -- gfx.setWireframe(true)

    -- shaders.simple_blur2:send("image_size",{renderWidth/l.render.scale,renderHeight/l.render.scale})
    -- shaders.simple_blur2:send("intensity",0.5)
    -- shaders.simple_blur2:send("alpha",1)
    -- gfx.setShader(shaders.simple_blur2)
    gfx.setShader()
    gfx.setBlendMode("replace")
    -- gfx.setBlendMode("alpha","alphamultiply")
    local lockedScale = l.render.lock and 0.5 or 1
    gfx.draw(l.render.canvas2, 0, 0, 0, (l.render.scale / game.resolution) * lockedScale, (l.render.scale / game.resolution) * lockedScale)
    l.render.trisTex:setFilter("nearest", "nearest")
    gfx.draw(l.render.trisTex, 0, 0, 0, 1, 1)
    gfx.setShader()

    gfx.setShader()
    -- gfx.draw(room.lighting.probes.convolver)
    -- gfx.setWireframe(false)
  end
end

function light_render(view)
  if true then return end
  local room = view.room
  local vct = room.lighting.vct
  local probes = room.lighting.probes
  local render = room.lighting.render

  if (render.enabled and render.run) or not view.gbuffer or not room.lightShader then return end

  bench.push("lights")

  if editor.debug.no_lights or not room.lighting.enabled or not room.lighting.dynamic.enabled then
    gfx.setCanvas(view.canvas)
    gfx.clear(room.sky.color.r, room.sky.color.g, room.sky.color.b, 1)
    gfx.draw(view.gbuffer.diffuse)
  else
    editor.debug.num_test_lights = editor.debug.num_test_lights or 20
    local lights = editor.debug.test_lights and math.min(#room.lightPosition, editor.debug.num_test_lights) or lume.count(room.lights)

    if not game.lightSphere then
      game.lightSphere = gfx.newMesh(zformat, obj_loader("models/sphere.obj"), "triangles")
      game.lightSphere:setTexture(load_imageTex("checkers"))
    end

    -- setViewCanvas(view)

    gfx.setCanvas(view.gbuffer.light)
    gfx.clear(0, 0, 0, 1, false, false)
    gfx.setDepthMode("less", false)
    gfx.setColor(1, 1, 1, 1)
    gfx.setShader(room.lightShader)

    shaders.deferredLight:send("gbuffer_diffuse", view.gbuffer.diffuse)
    room.lightShader:send("gbuffer_position", view.gbuffer.position)
    room.lightShader:send("gbuffer_normal", view.gbuffer.normal)
    room.lightShader:send("projectionMatrix", ctx.viewport.camera.projMatrix)
    room.lightShader:send("viewMatrix", ctx.viewport.camera.viewMatrix)
    room.lightShader:send("eyePosition", mth.vec3Table2(-ctx.viewport.camera.pos))

    -- shaders.deferredLight:send("shadowAtlasSize",4096)
    if altDown and not (vct.enabled and vct.showVoxels) then
      local names = { "diffuse", "normal", "light", "proxy geometry", "traced" }
      for i = 1, vct.enabled and 5 or 4, 1 do
        if input_keyDown(tostring(i)) then
          trace(names[i])
          editor_tempTip(names[i])
        end
      end
    end

    --temp
    if altDown and input_keyDown("4") then
      gfx.setDepthMode("less", false)
      gfx.setCanvas(view.gbuffer.diffuse)
      room.lightShader:send("debug", true)
      gfx.setWireframe(true)
    else
      room.lightShader:send("debug", false)
      gfx.setBlendMode("add")
    end

    --   if vct.enabled and editor.lastIntersect and not contextMenu.open then
    --    mouse_pushClick(function()
    --     room.testLightPosition={-editor.lastIntersect.x,-editor.lastIntersect.y-60,-editor.lastIntersect.z}
    --   end)
    --  end
    --  if room.lights[1] and room.testLightPosition then
    --   room.lights[1]:setPosition(room.testLightPosition)
    -- end

    if vct.enabled then
      setVolumeFilter(vct, "linear")
      for s, shader in ipairs({ shaders.voxelTrace, shaders.voxelOcclusion, shaders.voxelLight }) do
        shader:send("volumeScale", vct.scale)
        shader:send("volumePosition", vct.position)
        shader:send("voxelVolumeSize", vct.resolution)
        shader:send("voxelVolumeInverseSize", 1 / vct.resolution)
        shader:send("gbuffer_position", view.gbuffer.position)
        shader:send("gbuffer_normal", view.gbuffer.normal)
        if shader ~= shaders.voxelTrace then
          shader:send("vbuffer_occlusion", vct.occlusion)
        else
          shader:send("eyePosition", { -ctx.viewport.camera.pos.x, -ctx.viewport.camera.pos.y, -ctx.viewport.camera.pos.z })
        end
      end
    end

    for l = 1, lights, 1 do
      drawLight(view, room, l)
    end

    gfx.setMeshCullMode("back")
    gfx.setWireframe(false)

    gfx.reset()
    gfx.setColor(1, 1, 1, 1)
    -- gfx.setDepthMode("always",false)
    gfx.setCanvas(view.canvas)

    local ambc, amba = room.lighting.ambient.color, room.lighting.ambient.amount
    local r, g, b = ambc.r * amba, ambc.g * amba, ambc.b * amba
    local m = 1
    gfx.clear(r * m, g * m, b * m, 1)
    --  if true then
    --    gfx.draw(view.gbuffer.diffuse)
    --    return
    --  end

    --   if room.sky.skybox.mesh then
    --     gfx.push("all")
    --     gfx.setDepthMode("less", true)
    --     room.lightShader:send("debug",   true)
    --     gfx.setShader(room.lightShader)
    --     local lightMat = lightMat:identity()
    --     lightMat:scale(lightMat,vec3(1/5))
    --     lightMat:translate(lightMat,vec3(ctx.viewport.camera.pos))
    --     lightMat:transpose(lightMat)
    --     room.lightShader:send("modelMatrix",   lightMat)
    --     gfx.setCanvas({view.canvas,depthstencil=view.gbuffer.depth} )
    --    camera_adjust   (view.camera,view)
    --  gfx.setCamera ( view.camera )
    --  -- gfx.setShader(shaders.forwardRender)
    --     local a =room.lighting.ambient.amount
    --  gfx.setColor(r,g,b,1)
    --  gfx.setMeshCullMode("none")
    --  gfx.draw3D(room.sky.skybox.mesh)
    --     gfx.pop()
    -- end

    local combine = vct.enabled and shaders.voxelCombine or shaders.deferredCombine

    local function send_gbuffer()
      combine:send("ambientLight", room.lighting.ambient.amount)
      combine:send("ambientColor", colorTable(room.lighting.ambient.color))
      combine:send("gbuffer_diffuse", view.gbuffer.diffuse)
      combine:send("gbuffer_position", view.gbuffer.position)
      combine:send("gbuffer_normal", view.gbuffer.normal)
      combine:send("gbuffer_depth", view.gbuffer.depth)
    end

    if vct.enabled and vct.showVoxels then
      game_setResolution(0.5)
      gfx.setCanvas(view.canvas)
      gfx.setDepthMode("less", true)
      gfx.setShader(shaders.voxelRender)
      setVolumeFilter(vct, "nearest")
      shaders.voxelRender:send("volumeScale", vct.scale)
      shaders.voxelRender:send("volumePosition", vct.position)
      shaders.voxelRender:send("projectionMatrix", ctx.viewport.camera.projMatrix)
      shaders.voxelRender:send("viewMatrix", ctx.viewport.camera.viewMatrix)
      shaders.voxelRender:send("lod", lume.round(vct.lod or 0, 1))
      shaders.voxelRender:send("occlusion", vct.occlusion)
      shaders.voxelRender:send("average", vct.showAverage == false)
      shaders.voxelRender:send("renderOcclusion", vct.show == "occlusion")
      sendVolumes(shaders.voxelRender, "volume", vct[vct.show == "occlusion" and "diffuse" or vct.show], true)
      shaders.voxelRender:send("eyePosition", { -ctx.viewport.camera.pos.x, -ctx.viewport.camera.pos.y, -ctx.viewport.camera.pos.z })
      gfx.setMeshCullMode("none")
      gfx.draw(vct.mesh)
    else
      if altDown and (editor.pickingColor or input_keyDown("1")) then
        gfx.draw(view.gbuffer.diffuse)
      elseif altDown and input_keyDown("2") then
        gfx.draw(view.gbuffer.normal)
      elseif altDown and input_keyDown("3") then
        gfx.draw(view.gbuffer.light)
      elseif altDown and input_keyDown("5") and probes.enabled then
        gfx.draw(probes.buffer)
      else
        -- sample probes -------------------------------------------------
        if probes.enabled then
          shaders.deferredCombine:send("gbuffer_probed", room.lighting.probes.buffer)
          shaders.probeLights:send("gbuffer_normal", view.gbuffer.normal)
          shaders.probeLights:send("probeMap", room.lighting.probes.convolved)
          -- shaders.probeLights:send("probeDepth",       room.lighting.probes.depth)
          shaders.probeLights:send("probeGridSize", room.lighting.probes.gridsize)
          shaders.probeLights:send("probeResolution", room.lighting.probes.resolution / room.lighting.probes.downsample)
          shaders.probeLights:send("probeDimensions", room.lighting.probes.dimensions / room.lighting.probes.downsample)
          shaders.probeLights:send("probeAmount", room.lighting.probes.amount)
          shaders.probeLights:send("probeTesting", room.lighting.probes.testing)
          shaders.probeLights:send("probeSpacing", { room.lighting.probes.spacing:unpack() })
          shaders.probeLights:send("probeMin", { room.bounds.min:unpack() })
          gfx.push("all")
          gfx.setShader(shaders.probeLights)
          gfx.setCanvas(room.lighting.probes.buffer)
          gfx.draw(view.gbuffer.diffuse) -- dummy
          gfx.pop()
        end

        -- trace voxels --------------------------------------------------
        if vct.enabled then
          trace("tracing voxels")
          gfx.push("all")
          gfx.setShader(vct.showOcclusion and shaders.voxelOcclusion or shaders.voxelTrace)
          sendVolumes(shaders.voxelTrace, "vbuffer_light", vct.diffuse, true) -- temp
          gfx.setCanvas(vct.buffer)
          gfx.clear(0, 0, 0, 0)
          gfx.draw(view.gbuffer.diffuse) -- used as dummy
          gfx.pop()
          if (altDown and input_keyDown("5") and vct.enabled) or vct.showTraced then
            gfx.draw(vct.buffer)
          else
            gfx.setShader(shaders.voxelCombine)
            send_gbuffer()
            shaders.voxelCombine:send("eyePosition", { -ctx.viewport.camera.pos.x, -ctx.viewport.camera.pos.y, -ctx.viewport.camera.pos.z })
            shaders.voxelCombine:send("volumeScale", vct.scale)
            shaders.voxelCombine:send("volumePosition", vct.position)
            shaders.voxelCombine:send("gbuffer_traced", vct.buffer)
            shaders.voxelCombine:send("gbuffer_normal", view.gbuffer.normal)

            --pass samples to the shader
            local quality = 32
            local f = {}
            local range = 32
            for i = 1, quality do
              local r = i / quality * math.pi * 2
              local d = (0.5 + i % 4) / 4
              f[#f + 1] = {
                math.cos(r) * d * range / screenW,
                math.sin(r) * d * range / screenH,
                (1 - d) ^ 2 / quality,
              }
            end
            shaders.voxelCombine:send("samples", unpack(f))

            gfx.draw(view.gbuffer.light)
          end
        else
          gfx.setShader(combine)
          send_gbuffer()
          combine:send("toon", room.lighting.toon.enabled and 1 or 0)
          combine:send("toonWidth", room.lighting.toon.amount or 2)
          gfx.draw(view.gbuffer.light)
        end
      end

      gfx.push("all")
      gfx.setShader(room.lightShader)
      gfx.setCanvas({ view.canvas, depthstencil = view.gbuffer.depth })

      -- print("lights",#room.lights)
      -- show where the lights are/ are pointing
      for l, light in ipairs(room.lights) do
        -- print("light")
        local entity = scene_getEntityByID(light.entity)
        if entity then
          local target = scene_getEntityByID(entity.light.targetEntity)
          gfx.push("all")
          gfx.setDepthMode("less", false)
          gfx.setShader(shaders.forwardRender)
          view_drawSimple(entity, true)
          if target then view_drawSimple(target, true, true) end
          gfx.pop()

          gfx.push("all")
          gfx.setMeshCullMode("back")
          gfx.setDepthMode("less", false)
          shaders.deferredLight:send("debug", true)
          gfx.setWireframe(true)
          gfx.setColor(1, 1, 1, 0.01)
          lightMat = lightMat:identity()
          pointMatrix(lightMat, light.direction)

          local selected = (entity.selected or (target and target.selected))
          if selected and light.type ~= "direction" then
            if light.type == "point" then
              local dist = vec3.dist(-view.camera.pos, light.position)
              gfx.setMeshCullMode(dist < light.range and "back" or "front")
              lightMat:scale(lightMat, vec3(light.range))
            end
            lightMat:translate(lightMat, light.position)
            lightMat:transpose(lightMat)
            shaders.deferredLight:send("modelMatrix", lightMat)
            gfx.draw3D(light.mesh)
          end

          if light.type ~= "point" then
            -- light.line:setVertexAttribute(2,1,  0,0,vec3.dist(light.position,light.target),1)
            gfx.setMeshCullMode("none")
            gfx.setColor(1, 1, 1, selected and 0.1 or 0.01)
            gfx.draw3D(light.line)
          end
          gfx.pop()
        end
      end

      gfx.setDepthMode("less", true)
      room.lightShader:send("debug", true)

      -- if editor.debug.test_lights then
      gfx.setColor(1, 1, 1, 1)
      for l = 1, lights, 1 do
        lightMat = lightMat:identity()
        lightMat:scale(lightMat, vec3(room.lightRange[l] / (room.lights[l] and 200 or 100)))
        lightMat:translate(lightMat, vec3(room.lightPosition[l]))
        lightMat:transpose(lightMat)
        room.lightShader:send("modelMatrix", lightMat)
        gfx.setColor(room.lightColor[l])
        gfx.draw(game.lightSphere)
      end
      -- end

      lightMat = lightMat:identity()
      lightMat:transpose(lightMat)

      -- show probes
      if room.lighting.probes.enabled and room.lighting.probes.showProbes then
        room.lightShader:send("modelMatrix", lightMat)
        room.lightShader:send("drawDepth", not room.lighting.probes.convolve)
        gfx.draw(room.lighting.probes.mesh)
        room.lightShader:send("drawDepth", false)
      end

      gfx.pop()
    end

    gfx.push("all")
    gfx.setShader(shaders.shadowPreview)
    trace("editor.debug.draw_shadow_maps", editor.debug.draw_shadow_maps)
    if not shadowMapTester then
      shadowMapTester = newCanvas(512, 512)
      shadowMapTester:renderTo(function()
        gfx.clear(1, 1, 1, 1)
      end)
    end
    if (altDown and input_keyDown("l")) or editor.debug.draw_shadow_maps then
      editor_pushDraw(function()
        for l, light in ipairs(room.lights) do
          if light.selected and light.canvas then
            local s = 0.3
            light.canvas:setDepthSampleMode()
            shaders.shadowPreview:send("shadow", light.canvas)
            gfx.draw(shadowMapTester, 0, 0, 0, (light.type == "point" and 6 or 1) * s, s)
            gfx.setShader()
            gfx.print("resolution:" .. light.resolution, 0, 512 * s)
          end
        end
      end)
    end
    gfx.pop()

  end

  bench.pop("lights")
end

function light_updateLights(view)
  local room = view.room
  if not room.sky.skybox.mesh then
    local cubeVerts = {}
    local scale = 5000
    for f, face in ipairs(cube) do
      for v, vert in ipairs(face) do
        table.insert(cubeVerts, { vert[1] * scale, vert[2] * scale, vert[3] * scale, 1, 0, 0, 1, 1, 1, 1 })
      end
    end
    room.sky.skybox.mesh = gfx.newMesh(zformat, cubeVerts, "triangles")
    room.sky.skybox.mesh:setTexture(load_imageTex("checkers"))
  end

  if (not room.lights and not editor.debug.no_lights) or not room.lighting.enabled then return end
  trace("Update Lights")

  local render = room.lighting.render

  room.lightShader =
    -- (room.lighting.vct.enabled and not room.lighting.vct.useShadowmap) and shaders.voxelLight or
    shaders.deferredLight

  updateRoomBounds(room)

  if room.lighting.probes.enabled then
    room.lighting.probes.gridsize = 8
    room.lighting.probes.resolution = 16
    room.lighting.probes.amount = room.lighting.probes.amount or 0
    room.lighting.probes.samples = 1
    room.lighting.probes.downsample = 2
    room.lighting.probes.dimensions = 1024
    room.lighting.probes.atlasSize = room.lighting.probes.atlasSize or 0.2
    if not room.lightProbes then
      room.lighting.probes.lastprobe = 1
      room.lighting.probes.lastray = 1

      room.lightProbes = { nocopy = true }
      local probes = room.lightProbes
      local downsample = room.lighting.probes.downsample
      local dimensions = room.lighting.probes.dimensions
      local resolution = room.lighting.probes.resolution
      local samples = room.lighting.probes.samples
      local size = room.lighting.probes.gridsize
      local num = size ^ 3
      local max = room.bounds.max
      local min = room.bounds.min
      local spacing = (max - min) / (size - 1)
      local umax, vmax = mf(dimensions / (resolution * 6)), mf(dimensions / resolution)
      local ustep, vstep = (resolution * 6) / dimensions, resolution / dimensions
      local verts1 = {}
      local n = 0
      local u, v = 0, 0
      local scale = 8
      local inset = resolution / 4
      local a = 0

      room.bounds.size = (max - min)
      room.lighting.probes.spacing = (max - min) / (size - 1)
      print("spacing", inspect(room.lighting.probes.spacing))
      spacing = vec3(spacing.x, spacing.y, spacing.z)

      room.lighting.probes.buffer = newCanvas(view.bounds.w, view.bounds.h)
      room.lighting.probes.canvas = newCanvas(dimensions, dimensions)
      room.lighting.probes.projected = newCanvas(dimensions, dimensions)
      room.lighting.probes.convolved = newCanvas(dimensions / downsample, dimensions / downsample)
      room.lighting.probes.depth = newCanvas(dimensions, dimensions, { format = "depth16", readable = true })

      room.lighting.probes.buffer:renderTo(function()
        gfx.clear(0, 0, 0, 0)
      end)
      -- room.lighting.probes.canvas:setFilter("nearest","nearest")
      -- room.lighting.probes.projected:setFilter("nearest","nearest")
      -- room.lighting.probes.projected:setFilter("nearest","nearest")

      gfx.push("all")
      gfx.setCanvas(room.lighting.probes.canvas)
      gfx.clear(0, 0, 0, 1)
      room.lighting.probes.num = num

      colors = { { 1, 0, 0, 1 }, { 0, 1, 0, 1 }, { 0, 0, 1, 1 }, { 0, 0, 0, 1 }, { 1, 1, 1, 1 }, { 0, 1, 1, 1 } }

      for x = 0, size - 1, 1 do
        for y = 0, size - 1, 1 do
          for z = 0, size - 1, 1 do
            n = n + 1
            local xx, yy, zz = x * spacing.x + max.x - room.bounds.size.x, y * spacing.y + max.y - room.bounds.size.y, z * spacing.z + min.z
            for f = 0, 5, 1 do
              local u0, u1 = u + f * vstep, u + (f + 1) * vstep
              -- local u0,u1 = u , u + vstep
              local v0, v1 = v, v + vstep
              local face = cube[f + 1]
              table.insert(verts1, { xx + scale * face[1][1], yy + scale * face[1][2], zz + scale * face[1][3], 1, u1, v1, 1, 1, 1, a })
              table.insert(verts1, { xx + scale * face[2][1], yy + scale * face[2][2], zz + scale * face[2][3], 1, u0, v1, 1, 1, 1, a })
              table.insert(verts1, { xx + scale * face[3][1], yy + scale * face[3][2], zz + scale * face[3][3], 1, u0, v0, 1, 1, 1, a })
              table.insert(verts1, { xx + scale * face[4][1], yy + scale * face[4][2], zz + scale * face[4][3], 1, u0, v0, 1, 1, 1, a })
              table.insert(verts1, { xx + scale * face[5][1], yy + scale * face[5][2], zz + scale * face[5][3], 1, u1, v0, 1, 1, 1, a })
              table.insert(verts1, { xx + scale * face[6][1], yy + scale * face[6][2], zz + scale * face[6][3], 1, u1, v1, 1, 1, 1, a })
              -- gfx.setColor(math.random(),math.random(),math.random(),1)
              -- gfx.setColor(unpackColor(colors[f+1]))
              -- gfx.setColor(x/size,y/size,z/size,1)
              -- gfx.rectangle("fill",u0*dimensions,v0*dimensions,resolution,resolution)
            end
            probes[n] = probes[n] or {}
            probes[n].position = vec3(xx, yy, zz)
            probes[n].uv = vec2(u * dimensions, v * dimensions)
            probes[n].active = false
            probes[n].id = n
            probes[n].face = mf(math.random() * 5) + 1
            u = u + ustep
            if u + ustep > 1 then
              u = 0
              v = v + vstep
            end
          end
        end
      end

      gfx.pop()

      room.lighting.probes.concube = gfx.newMesh(zformat, lume.slice(verts1, 1, 36), "triangles")
      for i = 1, 36, 1 do
        room.lighting.probes.concube:setVertexAttribute(
          i,
          1,
          verts1[i][1] - probes[1].position.x,
          verts1[i][2] - probes[1].position.y,
          verts1[i][3] - probes[1].position.z,
          1
        )
        room.lighting.probes.concube:setVertexAttribute(i, 3, 1, 1, 1, 1)
      end
      room.lighting.probes.mesh = gfx.newMesh(zformat, verts1, "triangles")

      -- room.lighting.probes.mesh:setTexture(room.lighting.probes.canvas)
      room.lighting.probes.concube:setTexture(room.lighting.probes.canvas)

      setStaticProbes(room)

      room.lighting.probes.pixel = gfx.newMesh(zformat, 4, "fan")

      local rays = {}
      local faces = {}
      local sampleres = resolution * samples
      for f = 0, 5, 1 do
        local face = cube[f + 1]
        local mat = mat4.from_perspective(90, 1, 0.1, 10000)
        local mat2 = mat4.from_perspective(90, 1, 0.1, 100)
        table.insert(faces, {
          dir = cubeDirs[f + 1][1],
          mat = mat4():mul(mat, mat4.look_at(mat4(), vec3(0), cubeDirs[f + 1][1], cubeDirs[f + 1][2])),
          mat2 = mat4():mul(
            mat4():mul(mat2, mat4.look_at(mat4(), vec3(0), cubeDirs[f + 1][1], cubeDirs[f + 1][2])),
            mat4():translate(mat4(), (cubeDirs[f + 1][1] * scale * 0.95))
          ),
        })

        for x = 1, sampleres, 1 do
          for y = 1, sampleres, 1 do
            table.insert(rays, {
              uv = vec2(f * resolution + x, y),
              direction = vec3.lerp(
                vec3.lerp(vec3(face[3]), vec3(face[5]), (x - 0.5) / sampleres),
                vec3.lerp(vec3(face[2]), vec3(face[1]), (x - 0.5) / sampleres),
                (y - 0.5) / sampleres
              ),
            })
          end
        end
      end

      room.lighting.probes.rays = lume.shuffle(rays)
      room.lighting.probes.rays.nocopy = true
      room.lighting.probes.faces = faces
      room.lighting.probes.faces.nocopy = true

      room.activeProbes = { nocopy = true }
      for p, probe in ipairs(room.lightProbes) do
        if probe.active then table.insert(room.activeProbes, probe) end
      end

      room.shuffledProbes = lume.shuffle(room.activeProbes)
      room.shuffledProbes.nocopy = true
    end

    room.lighting.probes.mesh:setTexture(room.lighting.probes.convolve and room.lighting.probes.canvas or room.lighting.probes.depth)

    gfx.push("all")
    gfx.setShader()
    gfx.setCanvas({ room.lighting.probes.canvas, depth = true, depthstencil = room.lighting.probes.depth })
    gfx.setDepthMode("lequal", true)

    local probes = room.shuffledProbes
    local p = room.lighting.probes
    local dimensions = p.dimensions
    local downsample = p.downsample
    local res = p.resolution
    local pixel = p.pixel
    local rays = p.rays
    local faces = p.faces

    local numtris, maxtris, numprobes, numrays = 0, 0, 0, 0
    local v1, v2, v3 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
    local count = 8
    local limit = p.lastprobe + count

    local rasterize = true
    local r, g, b = view.room.sky.color.r, view.room.sky.color.g, view.room.sky.color.b

    if gfx.camera and gfx.camera.vp then --keyDown("r") and keyDown("t") then
      collectgarbage("stop")
      if rasterize then -- fill probes by rasterization
        room.lighting.probes.rendering = true
        local probesUpdated = {}
        local temp = mat4()
        local n = 0

        while p.lastprobe < limit do
          n = n + 1
          numrays = numrays + 1
          p.lastprobe = p.lastprobe + 1
          if p.lastprobe > #probes then
            p.lastprobe = 1
            limit = limit - #probes
          end
          p.lastray = (p.lastray == 5) and 1 or p.lastray + 1
          local probe = probes[p.lastprobe]
          probesUpdated[n] = probe
          probe.face = probe.face == 6 and 1 or probe.face + 1
          local f = probe.face
          local face = faces[f]
          temp:mul(face.mat, temp:translate(mat4(), -probe.position))
          gfx.setDepthMode("always", true)
          gl.Viewport(probe.uv.x + res * (f - 1), probe.uv.y, res, res)
          gfx.setColor(1, 1, 1, 1)
          gfx.setShader(shaders.forwardRender)
          shaders.forwardRender:send("MVP", "column", temp)
          gfx.setMeshCullMode("back")
          gfx.draw3D(room.sky.skybox.mesh)
          gfx.setDepthMode("lequal", true)
          view_drawPaths(view, temp)
        end

        if 1 > 2 and room.lighting.probes.convolve then
          gfx.setCanvas(room.lighting.probes.projected)
          gfx.setShader(shaders.forwardRender)
          gfx.setDepthMode()
          gfx.setColor(1, 1, 1, 1)
          gfx.setMeshCullMode("none")
          for p, probe in ipairs(probesUpdated) do
            for i = 1, 36, 1 do
              local j = (i - 1) % 6 + 1
              local x = (j > 1 and j < 5) and 0 or 1
              local y = (j > 2 and j < 6) and 0 or 1
              room.lighting.probes.concube:setVertexAttribute(
                i,
                2,
                (probe.uv.x + (x + mf((i - 1) / 6)) * res) / dimensions,
                (probe.uv.y + y * res) / dimensions
              )
            end

            -- room.lighting.probes.concube:setDrawRange(1,36)

            for f = 1, 6, 1 do
              gl.Viewport(probe.uv.x + res * (f - 1), probe.uv.y, res, res)
              shaders.forwardRender:send("MVP", "column", faces[f].mat2)
              gfx.draw3D(room.lighting.probes.concube)
            end
          end

          gfx.setCanvas(room.lighting.probes.convolved)
          gfx.setShader(shaders.blur13)
          shaders.blur13:send("Size", res / downsample)
          shaders.blur13:send("Resolution", { dimensions, dimensions })
          gfx.setDepthMode()
          local zoom = 2
          for p, probe in ipairs(probesUpdated) do
            for f = 1, 6, 1 do
              -- gl.Viewport(probe.uv.x+res*(f-1),probe.uv.y,res,res)
              local x, y, scale = (probe.uv.x + res * (f - 1)), probe.uv.y, res / downsample
              local xx, yy = x + res / 2, y + res / 2, gfx.setScissor(x / downsample, y / downsample, scale, scale)
              gfx.push()
              gfx.scale(1 / downsample, 1 / downsample)
              -- gfx.scale(zoom,zoom)
              -- gfx.translate(x*zoom,y*zoom)
              gfx.draw(room.lighting.probes.projected, xx, yy, 0, zoom, zoom, xx, yy)
              gfx.pop()
            end
          end
        end
        room.lighting.probes.rendering = false
      else -- fill probes by casting rays
        while p.lastprobe < limit do
          numrays = numrays + 1
          p.lastprobe = p.lastprobe + 1
          if p.lastprobe > #probes then
            p.lastprobe = 1
            limit = limit - #probes
          end
          p.lastray = (p.lastray == #rays) and 1 or p.lastray + 1
          local probe = probes[p.lastprobe]
          local r = math.max((p.lastray + p.lastprobe) % #rays, 1)
          local ray = rays[r]
          local hit = room.terrain.bvh:intersectRay(probe.position, ray.direction)
          if hit and hit.triangleIndex then
            local tri = room.terrain.triangles[hit.triangleIndex]
            local model = room.terrain.models[tri[4]]
            setValues(v1, model.mesh:getVertex(tri[5]))
            gfx.setColor(v1[7], v1[8], v1[9], 1 / room.lighting.probes.samples)
            gfx.points(probe.uv.x + ray.uv.x, probe.uv.y + ray.uv.y)
          end
        end
      end
    end

    trace(string.format("active probes: %i/%i", #room.activeProbes, room.lighting.probes.num))
    gfx.pop()

    if room.lighting.probes.test and editor and editor.lastIntersect then
      room.lighting.probes.testing = 0
      local dist = 99999999
      for p, probe in ipairs(room.activeProbes) do
        local d = vec3.dist(probe.position, -editor.lastIntersect)
        if d < dist then
          dist = d
          room.lighting.probes.testing = probe.id
        end
      end
      if room.lighting.probes.testing > 0 then
        -- print( room.lighting.probes.testing)
        room.lighting.probes.mesh:setDrawRange((room.lighting.probes.testing - 1) * 36 + 1, 36)
      end
    end
    if room.lighting.probes.testing == 0 or not room.lighting.probes.test then
      room.lighting.probes.mesh:setDrawRange(1, room.lighting.probes.mesh:getVertexCount())
    end

    gfx.setShader()
  end

  _pathTraceOnly = render.enabled and render.run and render.lock
  if
    not ctx.pageView
    and room.lighting.static.enabled
    and room.lighting.render.enabled
    and room.lighting.render.run
    and view.gbuffer
    and view.camera.pos
    and ctx.editing
  then
    collectgarbage("stop")

    local refresh = false
    render.gpu = render.gpu or true
    render.atlasSize = 256

    if not room.terrain.triangles then bvhRoom(room) end

    if render.gpu then
      -- print(#room.terrain.triangles)
      if not render.trisTex then
        render.trisData = gfx.newImageData(render.atlasSize, render.atlasSize, "rgba32f")
        -- render.gridData = gfx.newImageData(render.atlasSize,render.atlasSize,"rgba16f")
        local tris = room.terrain.triangles
        local abs, floor, ceil = math.abs, math.floor, math.ceil
        local bmin, bmax = room.bounds.min, room.bounds.max
        local aabb
        for t = 1, #room.terrain.triangles, 1 do
          local tt = t - 1
          local tri = tris[t]
          local pixelX, pixelY = tt % render.atlasSize, math.floor(tt / render.atlasSize) * 9
          local model = room.terrain.models[tri[4]]
          local x1, y1, z1, w1, u1, v1, r1, g1, b1, a1, nx1, ny1, nz1 = model.mesh:getVertex(tri[5])
          local x2, y2, z2, w2, u2, v2, r2, g2, b2, a2, nx2, ny2, nz2 = model.mesh:getVertex(tri[5] + 1)
          local x3, y3, z3, w3, u3, v3, r3, g3, b3, a3, nx3, ny3, nz3 = model.mesh:getVertex(tri[5] + 2)

          render.trisData:setPixel(pixelX, pixelY, x1, y1, z1, 1)
          render.trisData:setPixel(pixelX, pixelY + 1, x2, y2, z2, 1)
          render.trisData:setPixel(pixelX, pixelY + 2, x3, y3, z3, 1)

          render.trisData:setPixel(pixelX, pixelY + 3, r1, g1, b1, 1)
          render.trisData:setPixel(pixelX, pixelY + 4, r2, g2, b2, 1)
          render.trisData:setPixel(pixelX, pixelY + 5, r3, g3, b3, 1)

          render.trisData:setPixel(pixelX, pixelY + 6, nx1, ny1, nz1, 1)
          render.trisData:setPixel(pixelX, pixelY + 7, nx2, ny2, nz2, 1)
          render.trisData:setPixel(pixelX, pixelY + 8, nx3, ny3, nz3, 1)
        end

        -- for t=1,#room.terrain.triangles,1 do
        --   local tt = t-1
        --   local tri=tris[t]
        --   for x=0,8,1 do
        --     for y=0,8,1 do
        --       for z=0,8,1 do
        --         triangle_aabb(tri,aabb)
        --       end
        --     end
        --   end
        -- end

        print("new tri tex")
        render.trisTex = gfx.newImage(render.trisData)
        -- render.gridTex  = gfx.newImage(render.gridData)
        -- shaders.pathTrace:send('numTris',#room.terrain.triangles)
        -- shaders.pathTrace:send('triangles',render.trisTex)
      end
    end

    local v1, v2, v3 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }

    render.block = render.block or vec2()
    render.numrays = render.numrays or 32
    render.depth = render.depth or 4
    render.scale = render.scale or 1
    render.blurSize = render.blurSize or 2
    render.samples = 1
    render.exposure = render.exposure or 1
    local resx, resy = view.bounds.w / render.scale, view.bounds.h / render.scale
    render.tile = 128
    if not render.angle or not render.pos or not render.fov then
      render.pos = view.camera.pos:clone()
      render.angle = vec3(view.camera.angle)
      render.fov = view.camera.fov
    end
    if not render.res or resx ~= render.res.x or resy ~= render.res.y then
      render.res = vec2(resx, resy)
      render.rays = nil
      refresh = true
      render.dummy = newCanvas(render.tile, render.tile, { mipmaps = "none" })
      render.dummy2 = newCanvas(render.tile, render.tile, { mipmaps = "none" })
      render.canvas = newCanvas(resx, resy, { format = "rgba16f" })
      render.canvas2 = newCanvas(resx, resy)
      render.canvas:setFilter("linear", "linear")
      render.canvas2:setFilter("nearest", "nearest")
    end
    if not render.gpu and not render.rays then
      render.rrays = { nocopy = true }
      for x = 1, resx, 1 do
        for y = 1, resy, 1 do
          local ray = { uv = vec2(resx - x, y), direction = vec3(x / resx - 0.5, (y / resy - 0.5) / game.aspectRatio, 1) }
          table.insert(render.rrays, ray)
        end
      end
      render.rays = lume.shuffle(render.rrays)
      render.rays.nocopy = true

      render.data = render.canvas:newImageData()
      render.image = gfx.newImage(render.data)

      render.lastray = 1
    end

    local ambc, amba = room.lighting.ambient.color, room.lighting.ambient.amount

    local diff = not render.lock
      and (
        (view.camera.pos - render.pos):len() > 0
        or (vec3(view.camera.angle) - render.angle):len() > 0
        or math.abs(render.fov - view.camera.fov) > 0
        or render.amb ~= (ambc.r % 0.03 + ambc.g ^ 2 + ambc.b) * amba + math.sin(render.exposure * 77)
      )

    if not render.lock then
      render.pos = view.camera.pos:clone()
      render.angle = vec3(view.camera.angle)
      render.fov = view.camera.fov
      render.amb = (ambc.r % 0.03 + ambc.g ^ 2 + ambc.b) * amba + math.sin(render.exposure * 77)
    end

    if refresh or (view.camera.stopped and diff) then
      print("refresh pathtracing buffers")
      render.lastray = 1
      render.block.x, render.block.y = 0, 0
      render.passes = 0
      gfx.push("all")
      gfx.setCanvas(render.canvas)
      gfx.clear(0, 0, 0, 0)
      gfx.setCanvas(render.canvas2)
      gfx.clear(0, 0, 0, 0)

      if not render.gpu then
        render.position_buffer = view.gbuffer.position:newImageData()
        render.normal_buffer = view.gbuffer.normal:newImageData()
        render.diffuse_buffer = view.gbuffer.diffuse:newImageData()
        render.data = render.canvas:newImageData()
      end

      gfx.pop()
    end
    local rays = render.shuffle and render.rays or render.rrays
    -- print("stopped",view.camera.stopped,diff)
    -- local start = ffi.new("float[8]")

    render.rendering = render.lock or (view.camera.stopped and not diff)
    print("render.rendering", render.rendering)
    if render.rendering then
      ----------------------------- HARDWARE
      if render.gpu then
        gfx.push("all")
        gfx.setShader(shaders.pathTrace)

        for i = 1, 1, 1 do
          render.block.y = (render.block.x + render.tile > resx) and (render.block.y + render.tile > resy and 0 or render.block.y + render.tile)
            or render.block.y
          render.block.x = render.block.x + render.tile > resx and 0 or render.block.x + render.tile
          render.newPass = (render.block.y == 0 and render.block.x == 0)
          render.passes = render.newPass and render.passes + 1 or render.passes
          -- print(render.block.x,render.block.y ,render.passes)

          shaders.pathTrace:send("numTris", #room.terrain.triangles)
          shaders.pathTrace:send("time", engine.dt)
          shaders.pathTrace:send("blockSize", render.tile)
          shaders.pathTrace:send("blockCoord", { render.block.x, render.block.y + render.tile })
          shaders.pathTrace:send("gbuffer_diffuse", view.gbuffer.diffuse)
          shaders.pathTrace:send("gbuffer_position", view.gbuffer.position)
          shaders.pathTrace:send("gbuffer_normal", view.gbuffer.normal)
          shaders.pathTrace:send("triangles", render.trisTex)
          shaders.pathTrace:send("ambientColor", { ambc.r * amba, ambc.g * amba, ambc.b * amba })
          -- shaders.pathTrace:send("skyColor",colorTable(room.sky.color))

          gfx.setBlendMode("add")
          gfx.setCanvas(render.canvas)
          gfx.draw(render.dummy, render.block.x, render.block.y)
          gfx.setCanvas()

          print("pathtrace")
        end

        if render.newPass then
          gfx.setShader(shaders.ptmap)
          shaders.ptmap:send("image_size", { resx, resy })
          shaders.ptmap:send("samples", render.passes)
          shaders.ptmap:send("exposure", render.exposure)
          shaders.ptmap:send("blur", render.blurEnabled and render.blurSize or 0)
          shaders.ptmap:send("gbuffer_position", view.gbuffer.position)
          shaders.ptmap:send("gbuffer_normal", view.gbuffer.normal)
          shaders.ptmap:send("eye_position", mth.vec3Table2(-ctx.viewport.camera.pos))

          gfx.setBlendMode("alpha")
          gfx.setCanvas(render.canvas2)
          gfx.draw(render.canvas)
        end

        gfx.pop()
      ----------------------------- SOFTWARE
      else
        local r2, g2, b2, a2

        local i = 1
        while true do
          render.lastray = (render.lastray == #rays) and 1 or render.lastray + 1
          local pray = rays[render.lastray]
          local x, y, z = render.position_buffer:getPixel(
            math.clamp(0, renderW - 1, mf(pray.uv.x * render.scale)),
            math.clamp(0, renderH - 1, mf(pray.uv.y * render.scale))
          )
          local u, v, w = render.normal_buffer:getPixel(
            math.clamp(0, renderW - 1, mf(pray.uv.x * render.scale)),
            math.clamp(0, renderH - 1, mf(pray.uv.y * render.scale))
          )
          local r, g, b = render.diffuse_buffer:getPixel(
            math.clamp(0, renderW - 1, mf(pray.uv.x * render.scale)),
            math.clamp(0, renderH - 1, mf(pray.uv.y * render.scale))
          )
          local col = vec3(r, g, b)
          local dr, dg, db, da = render.data:getPixel(pray.uv.x, pray.uv.y - 1)
          if x == 0 then
            col = vec3(room.sky.color.r, room.sky.color.g, room.sky.color.b)
            if da == 0 then
              local direction = vec3.rotate(
                vec3.rotate(pray.direction + vec3(0, 0, -lerp(0, 0.5, (render.fov - 35) / 35)), render.angle.y, vec3.unit_x),
                pi - render.angle.x,
                vec3.unit_y
              )
              direction:normalize2()
              t = 0.5 * (direction.y + 1)
              -- col:scale3(1.0-t)
              render.data:setPixel(pray.uv.x, pray.uv.y - 1, dr + col.x, dg + col.y, db + col.z, da + 1)
            end
          -- i=i+1
          else
            i = i + 1
            col = ray_color(vec3(x, y, z), vec3(u * 2 - 1, v * 2 - 1, w * 2 - 1), room, render.depth, col)
            render.data:setPixel(pray.uv.x, pray.uv.y - 1, dr + col.x, dg + col.y, db + col.z, da + 1)
          end

          if i == render.numrays then break end
          -- gfx.points(ray.uv.x+0.0001,ray.uv.y+0.0001)
        end

        -- shaders.interp:send("image_size",{renderWidth/2,renderHeight/2})
        -- gfx.setShader(shaders.interp)
        render.image:replacePixels(render.data)
        gfx.push("all")
        gfx.setShader(shaders.rtmap)
        gfx.setCanvas(render.canvas2)
        gfx.draw(render.image)
        gfx.pop()
      end
    end
    -- collectgarbage("restart")
  end

  if room.lighting.vct.enabled then
    local vct = room.lighting.vct
    local anisotropicBase = false
    vct.resolution = 128 --vct.resolution or 64
    vct.bounces = vct.bounces or 1
    room.lighting.vct.show = room.lighting.vct.show or "diffuse"
    local buffers = { "diffuse", "normal", "light" }
    local formats = { "srgba8", "rgba8", "rgba16f" }
    local res = vct.resolution
    local rand = math.random

    if not vct.buffer then
      vct.ortho = mat4()
      vct.inverse = mat4()
      vct.sliceBox = { min = vec3(), max = vec3() }
      vct.staticRendered = false
      vct.drawCalls = 0
      vct.slice = 0
      vct.dir = 1
      vct.screen = { w = view.bounds.w, h = view.bounds.h }

      local cubeVerts = {}
      local scale = 1
      for f, face in ipairs(cube) do
        for v, vert in ipairs(face) do
          table.insert(cubeVerts, { vert[1] * scale, vert[2] * scale, vert[3] * scale, 1, 0, 0, 1, 1, 1, 1 })
        end
      end

      vct.mesh = gfx.newMesh(zformat, cubeVerts, "triangles")

      for b, buffer in ipairs(buffers) do
        vct[buffer] = {}
        for i = 1, 6, 1 do
          table.insert(vct[buffer], gfx.newCanvas(res / 2, res / 2, res / 2, { format = formats[buffer], type = "volume", mipmaps = "manual" }))
        end
        vct[buffer][0] = gfx.newCanvas(res, res, res, { type = "volume", mipmaps = "none" })
      end

      vct.occlusion = gfx.newCanvas(res, res, res, { type = "volume", format = "r8", mipmaps = "auto" })

      vct.diffuse3D = gfx.newCanvas(res, res, res, { type = "volume", mipmaps = "none" })
      vct.diffuse3D:setFilter("nearest", "nearest")

      vct.normal3D = gfx.newCanvas(res, res, res, { format = "rgba8", type = "volume", mipmaps = "none" })
      vct.normal3D:setFilter("nearest", "nearest")

      vct.buffer = newCanvas(view.bounds.w, view.bounds.h)
      vct.buffer:setFilter("nearest", "nearest")
      vct.dummy = newCanvas(res, res)
      vct.dummy:renderTo(function()
        gfx.clear(1, 1, 1, 1)
      end)
    end

    if vct.screen.w ~= view.bounds.w or vct.screen.h ~= view.bounds.h then
      vct.buffer = newCanvas(view.bounds.w, view.bounds.h)
      vct.buffer:setFilter("nearest", "nearest")
    end

    vct.scale = {
      (room.bounds.max.x - room.bounds.min.x) / 2,
      (room.bounds.max.y - room.bounds.min.y) / 2,
      (room.bounds.max.z - room.bounds.min.z) / 2,
    }
    vct.position = {
      -(room.bounds.max.x - room.bounds.min.x) / 2 + room.bounds.max.x,
      -(room.bounds.max.y - room.bounds.min.y) / 2 + room.bounds.max.y,
      (room.bounds.max.z - room.bounds.min.z) / 2 + room.bounds.min.z,
    }

    local xStep, yStep, zStep =
      (room.bounds.max.x - room.bounds.min.x) / res, (room.bounds.max.y - room.bounds.min.y) / res, (room.bounds.max.z - room.bounds.min.z) / res

    -- trace(room.bounds.min.x,room.bounds.max.x)
    -- trace(room.bounds.min.y,room.bounds.max.y)
    -- trace(room.bounds.min.z,room.bounds.max.z)

    setVolumeFilter(vct, "nearest")
    gfx.push("all")

    gfx.setColor(1, 1, 1, 1)
    gfx.setShader(shaders.forwardRender)

    gfx.setDepthMode("less", true)

    local dirmax = 6
    for i = 1, vct.resolution == 64 and 16 or 8, 1 do
      vct.dir = vct.slice == res and (vct.dir == dirmax and 1 or vct.dir + 1) or vct.dir
      vct.slice = vct.slice == res and 1 or vct.slice + 1

      -- trace("vdir",vct.dir)
      -- trace("vslice",vct.slice)

      ----------------------------------
      -- possibly draw from each axis once with both sides as outputs?
      ----------------------------------
      if vct.dir == 1 or vct.dir == 4 then
        vct.ortho:mul(
          mat4.from_ortho(
            -room.bounds.min.z,
            -room.bounds.max.z,
            room.bounds.max.y,
            room.bounds.min.y,
            -room.bounds.max.x + xStep * (vct.slice - 1),
            -room.bounds.max.x + xStep * vct.slice
          ),
          mat4():rotate(mat4(), -pi / 2, vec3(0, 1, 0))
        )
      elseif vct.dir == 2 or vct.dir == 5 then
        vct.ortho:mul(
          mat4.from_ortho(
            room.bounds.min.x,
            room.bounds.max.x,
            room.bounds.max.z,
            room.bounds.min.z,
            room.bounds.min.y + yStep * (vct.slice - 1),
            room.bounds.min.y + yStep * vct.slice
          ),
          mat4():rotate(mat4(), -pi / 2, vec3(1, 0, 0))
        )
      elseif vct.dir == 3 or vct.dir == 6 then
        vct.ortho = mat4.from_ortho(
          room.bounds.min.x,
          room.bounds.max.x,
          room.bounds.max.y,
          room.bounds.min.y,
          -room.bounds.min.z - zStep * (vct.slice - 1),
          -room.bounds.min.z - zStep * vct.slice
        )
      end

      vct.inverse:invert(vct.ortho)
      vct.sliceBox.min, vct.sliceBox.max = vct.inverse * vec3(-1), vct.inverse * vec3(2)

      -- draw geometry to temp gbuffer slice
      -- gfx.setMeshCullMode(vct.dir>3 and "back" or "front")
      gfx.setMeshCullMode("none")
      gl.Disable(gle.CULL_FACE)
      vct.back = vct.dir > 3
      shaders.forwardRender:send("MVP", "column", vct.ortho)

      if not vct.staticRendered then
        vct.rendering = true
        trace("voxelizing statics")
        gfx.setCanvas({
          { vct.diffuse3D, layer = vct.slice },
          { vct.normal3D, layer = vct.slice },
        })
        gfx.clear(0, 0, 0, 0)
        view_drawPaths(view, vct.ortho)
        gfx.setShader()
        vct.rendering = false
      end

      if vct.slice == res and not vct.staticRendered then
        -- flip directional volumes and gen mips
        gfx.setShader(shaders.voxelFlip)
        shaders.voxelFlip:send("dir", vct.dir)
        for b, buffer in ipairs({ "diffuse", "normal" }) do
          gfx.push()
          gfx.setBlendMode("alpha", "premultiplied")
          shaders.voxelFlip:send("volume", vct[buffer .. "3D"])
          for slice = 1, res, 1 do
            shaders.voxelFlip:send("z", (slice - 1) / res)
            gfx.setCanvas(vct[buffer][0], slice)
            gfx.draw(vct.dummy)
          end
          gfx.pop()
        end

        if vct.dir == dirmax then
          -- use diffuse alpha for occlusion base
          for slice = 1, res, 1 do
            gfx.setShader(shaders.voxelAlpha)
            gfx.setCanvas(vct.occlusion, slice)
            shaders.voxelAlpha:send("z", (slice - 1) / res)
            shaders.voxelAlpha:send("volume", vct.diffuse[0])
            gfx.draw(vct.dummy)
          end

          mipmapVoxels(vct, vct.diffuse)
          mipmapVoxels(vct, vct.normal, nil, true)
          vct.staticRendered = true
          print("vct.drawCalls", vct.drawCalls)
          vct.drawCalls = 0
        end
      end

      vct.back = false

      -- lights/emissives
      if vct.staticRendered then
        shaders.voxelInject:send("volumeScale", vct.scale)
        shaders.voxelInject:send("volumePosition", vct.position)
        shaders.voxelInject:send("voxelVolumeSize", vct.resolution)
        shaders.voxelInject:send("voxelVolumeInverseSize", 1 / vct.resolution)
        shaders.voxelInject:send("lightPosition", unpack(room.lightPosition))
        shaders.voxelInject:send("lightColor", unpack(room.lightColor))
        shaders.voxelInject:send("lightPower", unpack(room.lightPower))
        shaders.voxelInject:send("lightRange", unpack(room.lightRange))
        gfx.push("all")
        gfx.setShader(shaders.voxelInject)

        shaders.voxelInject:send("occlusion", vct.occlusion)
        shaders.voxelInject:send("z", (vct.slice - 1) / res)
        shaders.voxelInject:send("normal", vct.normal[0])
        shaders.voxelInject:send("diffuse", vct.diffuse[0])
        gfx.setCanvas(vct.light[0], vct.slice)
        gfx.clear(0, 0, 0, 0)
        gfx.draw(vct.dummy)
        gfx.pop()
        if vct.slice == res then mipmapVoxels(vct, vct.light, vct.normal) end
      end
    end

    -- bounce light
    if vct.staticRendered then
    end

    -- fade

    gfx.pop()
  end

  if #room.lightPosition > 0 then
    local n = 1
    local factor = 0.2
    for l = 1, #room.lightPosition, 1 do
      n = n + 3
      local d = l * 40
      local t = editor.debug.move_testlights and time * 0.1 or 1
      local r, g, b = hsvToRgb(math.sin(t + n * l ^ 2) / 2, 0.6, 1)
      -- room.lightColor[l]   = {r,g,b}
      if not (room.lights[l] and room.lights[l].entity) then
        room.lightPosition[l] = {
          -250 + factor * math.cos(t + 1 + d + d ^ 2) * d,
          math.cos(math.sin(l ^ 2 + t + frandom(d))) * 800 - 900,
          factor * math.sin(t - d) * d + 400,
        }
        room.lightRange[l] = 600
        room.lightColor[l] = { r, g, b }
      end
    end
  end

  blankCanvas = blankCanvas or newCanvas(10, 10)
  local dynamicShadow, shadowStatic = blankCanvas, blankCanvas

  for i, entity in ipairs(room.entities) do
    if entity.light.enabled and entity.light.isLight then
      if not lume.find(room.lights, entity.light) then
        entity.light.entity = entity.uid
        table.insert(room.lights, entity.light)
      end
    end
  end

  for l, light in lume.ripairs(room.lights) do
    local entity = scene_getEntityByID(light.entity)
    if not (entity and light.isLight) then table.remove(room.lights, l) end
  end
  trace("num lights", #room.lights)

  for l, light in ipairs(room.lights) do
    local entity = scene_getEntityByID(light.entity)
    light.position = entity.selected and entity.nextPosition or entity.position
    light.cache = entity.light.cache
    light.shadow = entity.light.shadow
    light.type = light.type or "point"
    light.selected = entity.selected
    if not (light.mats and light.mats[1]) then
      -- print("lighttarget 1",inspect(light.target))
    end
    light.target = light.target or mth.vec3Table(light.position + vec3(0, light.range, 0))
    light.direction = light.direction or vec3(vec3.sub2(light.position, light.target))
    light.aperture = light.aperture or 45
    light.falloff = light.falloff or 0
    local typeChanged = light.ptype ~= light.type

    if light.targetEntity then
      local targetEntity = scene_getEntityByID(light.targetEntity)
      trace("TARGET 1")
      if targetEntity then
        trace("TARGET 22")
        local either = entity.selected or targetEntity.selected
        local both = entity.selected and targetEntity.selected
        if light.type == "point" or not either or (#editor.selectedEntities > 1) or both then -- and not both) or (#selectedEntities>2) then
          scene_deleteEntity(targetEntity)
          light.targetEntity = nil
        else
          trace("TARGET 333")
          if light.lock then
            vec3.set(light.target, targetEntity.selected and targetEntity.nextPosition or targetEntity.position)
          else
            if targetEntity.selected then
              vec3.set(light.target, targetEntity.selected and targetEntity.nextPosition or targetEntity.position)
              light.direction:set(vec3.sub2(light.position, light.target))
            else
              targetEntity:setPosition(light.position - light.direction * light.range)
            end
          end
        end
      else
        light.targetEntity = nil
      end
    else
      -- print("LIGHT TARG 1")
      if light.type == "spot" and entity.selected and #editor.selectedEntities == 1 then
        print("LIGHT TARG 2")
        local targetEntity = editor_newEntity(nil, {
          position = light.lock and vec3(light.target) or (light.position - light.direction * light.range),
          simple = true,
        })
        light.targetEntity = targetEntity.uid
      end
    end

    if light.lock then light.direction:set(vec3.sub2(light.position, light.target))       -- light.direction.y=-light.direction.y
end
    light.direction = vec3.normalize(light.direction)
    -- light.direction = vec3(math.sin(time),math.cos(time),math.sin(time*0.8)+math.cos(time+2.1))

    if light.range ~= light.prange or light.paperture ~= light.aperture or typeChanged or not light.mats then
      -- cone :)
      if light.type == "spot" then
        local verts = {}
        local w = math.tan(math.rad(light.aperture / 2)) * light.range * 1.1
        -- print("aperture",math.rad(light.aperture))
        local y1, y2, y3 = 0, light.range, light.range --+math.sin(math.rad(light.aperture))*light.range*0.5
        local step = pi / 16

        -- TODO convert to ffi
        for a = 0, 2 * pi - step, step do
          local x1, z1 = math.sin(a) * w, math.cos(a) * w
          local x2, z2 = math.sin(a + step) * w, math.cos(a + step) * w
          table.insert(verts, { 0, 0, y1, 1, 1, 1, 1, 1 })
          table.insert(verts, { x2, z2, y2, 1, 1, 1, 1, 1 })
          table.insert(verts, { x1, z1, y2, 1, 1, 1, 1, 1 })

          table.insert(verts, { x1, z1, y3, 1, 1, 1, 1, 1 })
          table.insert(verts, { x1, z1, y2, 1, 1, 1, 1, 1 })
          table.insert(verts, { x2, z2, y2, 1, 1, 1, 1, 1 })

          table.insert(verts, { x1, z1, y3, 1, 1, 1, 1, 1 })
          table.insert(verts, { x2, z2, y2, 1, 1, 1, 1, 1 })
          table.insert(verts, { x2, z2, y3, 1, 1, 1, 1, 1 })

          table.insert(verts, { 0, 0, y3, 1, 1, 1, 1, 1 })
          table.insert(verts, { x1, z1, y3, 1, 1, 1, 1, 1 })
          table.insert(verts, { x2, z2, y3, 1, 1, 1, 1, 1 })
        end
        light.mesh = gfx.newMesh(zformat, verts, "triangles")
      end
      if light.type ~= "point" then
        light.line = gfx.newMesh(zformat, {
          { 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 },
          { 0, 0, light.range, 1, 0, 0, 1, 1, 1, 1 },
          { 0, 0, 0, 1, 0, 0, 1, 1, 1, 1 },
        }, "triangles")
      end
      light.remake = true
    end

    if light.shadow then
      if (light.remake and not contextMenu.open) or typeChanged or not light.mats then
        light.mats = { nocopy = true }
        light.remake = false
        light.resolution = light.type == "direction" and 1024 or mf(math.sqrt(light.range) * 40)

        if light.canvas then light.canvas:release() end

        light.cam = nil
        print("light.resolution", light.resolution)
        light.canvas = newCanvas(light.resolution * (light.type == "point" and 6 or 1), light.resolution, { format = "depth16", readable = true })

        if light.type == "point" then
          local mat = mat4.from_perspective(90, 1, 0.1, light.range)
          for f = 1, 6, 1 do
            table.insert(light.mats, mat4():mul(mat, mat4.look_at(mat4(), vec3(0), cubeDirs[f][1], cubeDirs[f][2])))
          end
        end

        local num = 500
        if light.type == "spot" then light.mats[1] = mat4.from_perspective(light.aperture, 1, 1, light.range) end
        if light.type == "direction" then light.mats[1] = mat4.from_ortho(num, -num, num, -num, 0.1, light.range) end
      end

      if light.canvas then
        if light.soft then
          light.canvas:setDepthSampleMode()
          light.canvas:setFilter("linear", "linear")
        else
          light.canvas:setDepthSampleMode("less", false)
        end

        dynamicShadow = light.canvas
        if ctx.editing then shadowStatic = light.canvas end

        drawShadow(view, room, light)
      end

      if light.cache and not ctx.editing then
        if not light.canvasCache then
          light.canvasCache = newCanvas(light.resolution, light.resolution, { format = "depth16", readable = true })
          if LOVE then light.canvasCache:setDepthSampleMode("less", false) end
          light.canvasCache:setFilter("linear", "linear")
          drawShadow(view, room, light, true)
        end
        shadowStatic = light.canvasCache
      else
        if light.canvasCache then
          light.canvasCache:release()
          light.canvasCache = nil
        end
      end

      light.ptype = light.type
      light.prange = light.range
      light.paperture = light.aperture
    end

    light.color = light.color or { r = 1, g = 1, b = 1 }
    room.lightPosition[l] = mth.vec3Table2(light.position)
    room.lightColor[l] = { light.color.r, light.color.g, light.color.b, 1 }
    room.lightPower[l] = entity.light.strength
    room.lightRange[l] = light.range
    room.lightShadowed[l] = light.shadow
    room.lightType[l] = shadowTypeNums[light.type] or 1
    room.lightQuad[l] = light.quad or { 0, 0, 64, 64 }
    room.lightAperture[l] = light.aperture
    room.lightFalloff[l] = light.falloff
    room.lightDirection[l] = mth.vec3Table2(light.direction)
  end

  if view.bounds and view.bounds.w then shaders.forwardRender:send("canvasSize", { view.bounds.w, view.bounds.h }) end

  if forwardShading then
    room.lightShader:send("lightPosition", unpack(room.lightPosition))
    room.lightShader:send("lightColor", unpack(room.lightColor))
    room.lightShader:send("lightPower", unpack(room.lightPower))
    room.lightShader:send("lightRange", unpack(room.lightRange))
    room.lightShader:send("lightShadowed", unpack(room.lightShadowed))
  end
end
