local bbox = {}
local tempMat, tempMat2, tempMat3, tempMat4 = mat4(), mat4(), mat4(), mat4()
local tempVec = vec3(0, 0, 0)
local zeroVec = { 0, 0, 0, 0 }
local tempPlane = { origin = vec3(), normal = vec3(0, 0, -1) }
local defMat, defMat2, defMat3 = mat4(), mat4(), mat4()
local sliceVec, sliceOffset, slicePosition = vec3(0, 1, 0), vec3(), vec3()
local parent = { transform = mat4():identity() }
local mappedPolys={}

local function setValues(t, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10)
  t[1], t[2], t[3], t[4], t[5], t[6], t[7], t[8], t[9], t[10] = v1, v2, v3, v4, v5, v6, v7, v8, v9, v10
  return t
end

local function funpack(t)
  return t[2], t[3], t[4], t[5], t[6], t[7], t[8], t[9]
end

local function releaseGbuffer(view)
  if not view.gbuffer then return end
  if view.gbuffer.diffuse then
    view.gbuffer.diffuse:release()
    view.gbuffer.position:release()
    view.gbuffer.normal:release()
    view.gbuffer.light:release()
    view.gbuffer.diffuse = nil
    view.gbuffer.position = nil
    view.gbuffer.normal = nil
    view.gbuffer.light = nil
    -- view.gbuffer.depth    :release()
  end

  view.gbuffer = nil
end

local function newViewCanvas(view)
  gfx.setDefaultFilter("nearest")
  print("NEW VIEW CANVAS")
  if ctx.isAnim then
    -- view.canvas = view.canvas or engine.canvas
    -- return
  end
  if view.canvas and not view.preview then
    if gfx.isCanvas(view.canvas) then view.canvas:release() end
    view.canvas = newCanvas(view.bounds.w, view.bounds.h, view.flags)
  else
    view.canvas = view.canvas or (view.bounds and newCanvas(view.bounds.w, view.bounds.h, view.flags) or newCanvas(screenW, screenH, view.flags))
  end

  if light_roomIsLit(view.room) then
    view.gbuffer = view.gbuffer or {}
    view.gbuffer.diffuse = newCanvas(view.bounds.w, view.bounds.h, { format = "rgba8" })
    view.gbuffer.position = newCanvas(view.bounds.w, view.bounds.h, { format = "rgba16f" })
    view.gbuffer.normal = newCanvas(view.bounds.w, view.bounds.h, { format = "rgba8" }) --rgb565
    view.gbuffer.light = newCanvas(view.bounds.w, view.bounds.h, { format = "rgba8" })
    view.gbuffer.depth = newCanvas(view.bounds.w, view.bounds.h, { format = "depth16", readable = true })
  else
    releaseGbuffer(view)
  end
end

local function setViewCanvas(view)
  bench.push("setViewCanvas")
  local changeRenderPath = false
  if (view.gbuffer and not light_roomIsLit(view.room)) or (not view.gbuffer and light_roomIsLit(view.room)) then
    -- changeRenderPath = true
  end

  if engine.resizingWindow or not view.canvas or changeRenderPath then 
    newViewCanvas(view) 
  end

  if view.gbuffer then
    -- trace("Render: Deferred")
    return gfx.setCanvas({
      view.gbuffer.diffuse,
      view.gbuffer.normal,
      view.gbuffer.position,
      depth = true,
      depthstencil = view.gbuffer.depth,
    })
  else
    -- trace("Render: Forward")
    --TODO forward render setviewcanvas
    -- if ctx.isAnim or useEngineCanvas then -- and animatePanel.antiAlias and engine.resizingWindow then
    --   gfx.setCanvas(engine.canvas)
    --   engine.flipView = true
    -- else
    engine.flipView = false
    if ctx.isAnim then
      view.canvas = engine.canvas
      -- gfx.setCanvas(view.canvas)
    -- gfx.setCanvas({view.canvas,depth=true})
    else
      gfx.setCanvas({view.canvas, depth = true })
    end
    -- print(view.canvas:getMSAA())
  end
  -- end
  bench.pop("setViewCanvas")
end

local function mapViewPoly(poly, viewport, main,static)
  mappedPolys[poly] = mappedPolys[poly] or {}
  local rpoly = mappedPolys[poly]
  local xtotal = 0
  local ytotal = 0
  bbox[1], bbox[2], bbox[3], bbox[4] = poly[1], poly[2], poly[1], poly[2]

  for i, p in ipairs(poly) do
    rpoly[i]=p
    xtotal = ((i + 1) % 2 == 0) and xtotal + p or xtotal
    ytotal = ((i + 1) % 2 == 0) and ytotal or ytotal + p

    if i % 2 == 0 then
      bbox[2] = (poly[i] < bbox[2]) and poly[i] or bbox[2]
      bbox[4] = (poly[i] > bbox[4]) and poly[i] or bbox[4]
    else
      bbox[1] = (poly[i] < bbox[1]) and poly[i] or bbox[1]
      bbox[3] = (poly[i] > bbox[3]) and poly[i] or bbox[3]
    end
  end


  local xavg, yavg = xtotal / (#poly / 2), ytotal / (#poly / 2)
  local toZoom = static and 1 or ctx.roomZoom
  local size = (1 + 1 - ((bbox[4] - bbox[2]) * (bbox[3] - bbox[1])))*4
  if main then 
    for i, p in ipairs(rpoly) do
      rpoly[i] = ((i + 1) % 2 == 0) 
       and lerp((p - main.viewport.center.x) * size + 0.5 or p, p, toZoom) * renderW
        or lerp((p - main.viewport.center.y) * size + 0.5 or p, p, toZoom) * renderH
        if main.viewport == viewport then 
           rpoly[i] = ((i + 1) % 2 == 0) 
           and math.clamp(0,renderW,rpoly[i])
          or math.clamp(0,renderH,rpoly[i])
        end
    end
  end

  return rpoly, xavg, yavg
end

function view_updateViewport(view,static)
  if not view.viewport then return end

  bench.push("viewport")
  local newCenter = { x = 0, y = 0 }
  local wob = ctx.editing and { 0, 0 } or { view.viewport.wobble.speed, view.viewport.wobble.dist }
  view.viewport.center = view.viewport.center or {}
  view.viewport.center.x = view.viewport.center.x or 0
  view.viewport.center.y = view.viewport.center.y or 0
  view.viewport.centerContained = false

  ctx.viewport.viewport.size = ctx.viewport.viewport.size or 1

  view.flags = view.flags or {}
  view.flags.msaa = (ctx.isAnim and animatePanel.antiAlias and animatePanel.antiAliasing) or ((hiSpec and editor.debug.anti_alias) and 8 or 0)

  local filter = animatePanel.antiAlias and "linear" or "nearest"

  if ctx.isAnim then 
    animatePanel.viewport = view 
  end

  if
    view.bounds and view.bounds.w and
    (animatePanel.antiAliasing ~= animatePanel.pantiAliasing)
    or animatePanel.antiAlias ~= animatePanel.pantiAlias
    or editor.debug.anti_alias ~= editor.debug.panti_alias
    or view.room.lighting.dynamic.enabled ~= view.room.lighting.dynamic.penabled
  then
    newViewCanvas(view)
  end

  animatePanel.pantiAliasing = animatePanel.antiAliasing
  animatePanel.pantiAlias = animatePanel.antiAlias
  editor.debug.panti_alias = editor.debug.anti_alias
  view.room.lighting.dynamic.penabled = view.room.lighting.dynamic.enabled

  if ctx.isAnim or (ctx.roomZoom==0 and ctx.viewport == view) then 
    view.bounds = view.bounds or {}
    view.bounds.l = 0
    view.bounds.r = renderW
    view.bounds.t = 0
    view.bounds.b = renderH
    view.bounds.x = view.bounds.l
    view.bounds.y = view.bounds.t
    view.bounds.changed = (view.bounds.w ~= (view.bounds.r - view.bounds.l) or view.bounds.h ~= (view.bounds.b - view.bounds.t))
    view.bounds.w = (view.bounds.r - view.bounds.l)
    view.bounds.h = (view.bounds.b - view.bounds.t)
  else
    

    for p, poly in ipairs(view.viewport.polys) do
      bench.push("map")
      local mapped, xavg, yavg = mapViewPoly(poly, ctx.viewport.viewport,editor.viewport,static)

      if view.viewport.center and point_in_poly(mapped, { x = view.viewport.center.x * renderW, y = view.viewport.center.y * renderH }) then
        view.viewport.centerContained = true
      end
      bench.pop("map")
      
      if
        not mouse_getRelativeMode()
        and not view.viewport.scissor
        and ctx.roomZoom > 0.9
        and ctx.editing
        and (editor.stageHovered)
        and point_in_poly(mapped, mouse, true)
      then
        editor.viewHovered = view
        editor.lastviewHovered = view

        if not (mouse.down.L or animatePanel.open or contextMenu.open) then
          scene_setNextView(editor.viewHovered)
        end
      end

      newCenter.x = newCenter.x + xavg
      newCenter.y = newCenter.y + yavg

      if not view.viewport.scissor then
        bench.push("wobble")
        view.viewpoly = scene_wobble(mapped, view.viewport.wobble.speed, view.viewport.wobble.dist)

        view.viewVerts = view.viewVerts or {}
        view.prevpoly = view.prevpoly or {}
        view.changed = false

        if #view.prevpoly ~= #view.viewpoly then
          lume.clear(view.viewVerts)
          view.viewVerts.nocopy = true
        end

        bench.pop("wobble")

        for i = 1, #poly, 1 do
          if view.prevpoly[i] ~= poly[i] then view.changed = true end
          view.prevpoly[i] = poly[i]
        end

        view.bounds = view.bounds or {}
        view.bounds.l, view.bounds.r, view.bounds.t, view.bounds.b = 99999, -99999, 99999, -99999

        for i = 2, #mapped, 2 do
          view.bounds.l = math.min(view.bounds.l, mapped[i - 1])
          view.bounds.r = math.max(view.bounds.r, mapped[i - 1])
          view.bounds.t = math.min(view.bounds.t, mapped[i])
          view.bounds.b = math.max(view.bounds.b, mapped[i])
        end

        if ctx.roomZoom==0 and ctx.viewport == view then
          view.bounds.l = math.max(view.bounds.l, 0)
          view.bounds.r = math.min(view.bounds.r, renderW)
          view.bounds.t = math.max(view.bounds.t, 0)
          view.bounds.b = math.min(view.bounds.b, renderH)
        end

        view.bounds.x = view.bounds.l
        view.bounds.y = view.bounds.t
        view.bounds.changed = (view.bounds.w ~= (view.bounds.r - view.bounds.l) or view.bounds.h ~= (view.bounds.b - view.bounds.t))
        view.bounds.w = (view.bounds.r - view.bounds.l)
        view.bounds.h = (view.bounds.b - view.bounds.t)

        bench.push("canvas")
        if not view.canvas then
          if ctx.roomZoom < 0.5 then
            view.bounds.w = view.bounds.w / 4
            view.bounds.h = view.bounds.h / 4
            newViewCanvas(view)
            view.bounds.w = view.bounds.w / 4
            view.bounds.h = view.bounds.h / 4
          end
        elseif not engine.resizingWindow then
          local newed = false
          if
            (ctx.viewport == view and ctx.roomZoom == 0)
            or (view.bounds.l == 0 and view.bounds.t == 0 and view.bounds.w == renderW and view.bounds.h == renderH)
          then
            if math.abs(view.canvas:getWidth() - renderW) > 4 and not animatePanel.exporting then
              newViewCanvas(view)
              newed = true
            end
          end
          if not newed and ctx.roomZoom == 1 and view.bounds.w == renderW and not animatePanel.exporting then newViewCanvas(view) end
        end
        bench.pop("canvas")

        -- if view.changed then
        bench.push("triangulate")
        local success, tris = pcall(mth.triangulate, view.viewpoly)
        if not success then
          print(ansicolors("%{redbg} bad viewpoly %{reset}"), #view.viewpoly)
          tris = mth.triangulate({ 0, 0, 100, 0, 100, 100, 0, 100 })
        end

        local v = 1
        local bl, bt, bw, bh = view.bounds.l, view.bounds.t, view.bounds.w, view.bounds.h
        for t, tri in ipairs(tris) do
          view.viewVerts[v] = {
            tri[1],
            tri[2],
            0,
            1,
            (tri[1] - bl) / bw,
            (tri[2] - bt) / bh,
            1,
            1,
            1,
            1,
          }
          v = v + 1
          view.viewVerts[v] = {
            tri[3],
            tri[4],
            0,
            1,
            (tri[3] - bl) / bw,
            (tri[4] - bt) / bh,
            1,
            1,
            1,
            1,
          }
          v = v + 1
          view.viewVerts[v] = {
            tri[5],
            tri[6],
            0,
            1,
            (tri[5] - bl) / bw,
            (tri[6] - bt) / bh,
            1,
            1,
            1,
            1,
          }
          v = v + 1
        end

        bench.pop("triangulate")

        bench.push("mesh")
        if #view.viewVerts > 0 and view.canvas then
          if view.viewMesh and view.viewMesh.setVertices and #view.viewVerts == view.numVerts then
            -- if not view.vertsset then 
            bench.push("mesh1")
            -- trace("view.viewVerts", #view.viewVerts,view.viewVerts[1][2])
            view.viewMesh:setVertices(view.viewVerts, 1)
            gfx.setTexture(view.viewMesh, view.canvas)
            bench.pop("mesh1")
            -- end
            -- view.vertsset = true
          else
            bench.push("mesh2")
            view.viewMesh = gfx.newMesh(zformat, view.viewVerts, "triangles")
            gfx.setTexture(view.viewMesh, view.canvas)
            view.numVerts = #view.viewVerts
            bench.pop("mesh2")
          end
        end
        bench.pop("mesh")
      -- end
      else
        trace("preview", newCenter.x, newCenter.y)
      end
    end
  end
  
  view.aspectRatio = view.bounds.w / view.bounds.h
  view.bounds.c = view.bounds.c or vec2()
  view.bounds.c.x = view.bounds.x + view.bounds.w / 2
  view.bounds.c.y = view.bounds.y + view.bounds.h / 2

  view.viewport.center.x = scene_wobble(newCenter.x / #view.viewport.polys, wob[1] * 0.2, wob[2] * 0.005)
  view.viewport.center.y = scene_wobble(newCenter.y / #view.viewport.polys, wob[1] * 0.2, wob[2] * 0.005)

  view.camera.center = {
    x = lerp(renderW / 2, view.viewport.center.x * renderW, ctx.viewport == view and ctx.roomZoom or 1),
    y = lerp(renderH / 2, view.viewport.center.y * renderH, ctx.viewport == view and ctx.roomZoom or 1),
  }

  -- gfx.push()
  -- if roomZoom>0 then
  --  gfx.translate(
  --    -view.bounds.l+view.viewport.wobble.dist,
  --    -view.bounds.t+view.viewport.wobble.dist
  --  )
  -- end

  editor.linkHovered = false
  bench.pop("viewport")
  return view
end

local function prepareView(view, light, cache)
  bench.push("prepareView")

  if not view.bounds then view.bounds = { l = 0, t = 0, l = view.w, r = view.h, w = view.w, h = view.h } end

  gfx.push("all")

  setViewCanvas(view)
  
  camera_adjust(view.camera, view)
  gfx.setCamera(view.camera)

  if ctx.isAnim then
    gfx.clear(((animatePanel.exporting and animatePanel.exportAlpha) and { 0, 0, 0, 1 } or colorTable(animatePanel.stageColor)))
  else
    gfx.clear(colorTable(view.room.sky.color))
  end
  gfx.setDepthMode("lequal", true)

  gfx.setColor(1, 1, 1, 1)
  gfx.setShader(shaders.forwardRender)
  view.drawShadowed = not editor.debug.no_lights and not editor.debug.wireframe
  shaders.forwardRender:send("drawShadowed", view.drawShadowed)

  bench.pop("prepareView")
end

local function unprepareView(view)
  bench.push("unprepareView")
  gfx.pop()
  gfx.setShader()
  shaders.forwardRender:send("drawShadowed", false)
  -- gfx.setColor(1,1,1,mouse_getRelativeMode() and 1 or 0)
  -- gfx.circle("line",renderWidth/2-16,renderHeight/2-16,1,16)
  gfx.setWireframe(false)
  bench.pop("unprepareView")
end

local function releaseView(view)
  if not view.release then return end
  releaseGbuffer(view)
end

local function drawPasteBoard(over)
  local panel = animatePanel
  if ctx.isAnim and panel.root and panel.root.stageWidth and not panel.exporting then
    gfx.push("all")

    local sw, sh = panel.root.stageWidth, panel.root.stageHeight
    local x1, y1 = -sw / 2, -sh / 2
    local x2, y2 = -x1, -y1
    RectMesh:setTexture()

    gfx.setTransform(gfx.camera.vp)
    gfx.setDepthMode("always")
    if not panel.showPasteboard then
      local out = 999999
      shaders.forwardRender:send("layer", 100)

      local x3, y3, x4, y4 = x1 - out, y1 - out, x2 + out, y2 + out
      local scaled = mat4():scale(mat4(), vec3(out, out, 1))
      gfx.setColor(1, 0, 0, 1)
      gfx.setMeshCullMode("none")

      -- gfx.setLineWidth(2)
      -- gfx.rectangle("fill",-1*sw/2,-1*sh/2,1*sw,1*sh)
      -- gfx.rectangle("line",-1*sw/2,-1*sh/2,1*sw,1*sh)
      gfx.rectangle("fill", -out, -1 * sh / 2, out - 1 * sw / 2, 1 * sh)
      gfx.rectangle("fill", 1 * sw / 2, -1 * sh / 2, out, 1 * sh)

      gfx.rectangle("fill", -out, -out, out * 2 + sw, out - sh / 2)
      gfx.rectangle("fill", -out, sh / 2, out * 2 + sw, out)

      -- gfx.rectangle("line",-1*sw/2,-1*sh/2,1*sw,1*sh)
      shaders.forwardRender:send("layer", 0)
    else
      -- gfx.setTexture(RectMesh,ClearTex)
      local r, g, b, a = unpackColor(panel.stageColor)
      local l = (r + g + b) > 1 and 0 or 1
      gfx.setColor(l, l, l, 1)
      gfx.setLineWidth(2)
      -- gfx.setShader(shaders.forwardRender)
      shaders.forwardRender:send("layer", 100)
      gfx.rectangle("line", -1 * sw / 2, -1 * sh / 2, 1 * sw, 1 * sh)
      shaders.forwardRender:send("layer", 0)
    end
    gfx.pop()
  end
end

local function addEntityModels(models, entity, level, matrix)
  trace(string.format("%s> (%s) %i models, %i children", string.rep(" ", level * 2), entity.key, #entity.models, #entity.children))
  matrix:mul(entity.transform, matrix)
  for m, model in ipairs(entity.models) do
    table.insert(models, model)
    model.transform = model.transform or mat4()
    model.matrix = model.matrix or mat4()
    model.matrix:mul(model.transform, matrix)
    model.selected = entity.selected
    if ctx.viewport == editor.viewport and entity.visible and not editor.transforming then
      -- print(entity.transform)
      scene_testCursorModel(model)
    end
  end
  for c, child in ipairs(entity.children) do
    -- addEntityModels(models, child, level + 1, matrix)
  end
  return models
end

local function queueForward(room, t)
  table.insert(room.queued, t)
end

local function getModelToCamDist(model)
  local pos = scene_getTransform(model) * tempVec
  if model.sort then
    tempPlane.origin:set(pos)
    model.camDist = cpml.mesh.signed_distance(ctx.viewport.camera.pos, tempPlane) + model.sort
  else
    model.camDist = vec3.dist(-ctx.viewport.camera.pos, pos)
  end
end

local function drawModel(model)
  model.parent = model.parent or parent

  if (not ctx.editing and model.parent.visible == false) or (model.visible ~= nil and model.visible == false) then return end
  local selectedE = model.parent.selected
  local hoveredE = model.parent == editor.entityHovered
  local hoveredM = model == editor.modelHovered

  local offsetMesh
  local cr, cg, cb, ca = 1, 1, 1, 1

  if ((model.parent.light and model.parent.light.enabled) or model.noCastShadow or model.parent.surface) and game.drawingShadow then return end

  gfx.push("all")

  model.transform = model.transform or blankMat

  if ctx.editing then
    local wobble = (animatePanel.open or model.isTerrain or model.isEdge) and 0
      or (selectedE and time * 16 or (((hoveredE or hoveredM) and not pathsPanel.open) and time * 4 or 0))
    shaders.forwardRender:send("wobble", wobble)

    if model.isEdge and room[model.mode][model.id] then
      model.walled = room[model.mode][model.id].walled == "closed"
        or (room[model.mode][model.id].walled ~= "open" and room[model.mode][model.id].points[model.index].walled)
    end

    local brightness = 1

    if editor.state ~= "Cutting" then
      brightness = (model.isEdge and (model.walled and 0.8 or 0.9) or 1)
        * (animatePanel.open and 1 or (hoveredM and 0.95 or (selectedE and 0.7 or (hoveredE and 0.8 or 1))))
    end

    if model.color then
      -- if model.parent and model.parent.color then
      --  cr,cg,cb,ca = (model.color[1] or 1)*(model.parent.color.r or 1),(model.color[2] or 1)*(model.parent.color.g or 1),(model.color[3] or 1)*(model.parent.color.b or 1),(model.color[4] or 1)*(model.parent.color.a or 1)
      -- else
      cr, cg, cb, ca = model.color[1], model.color[2], model.color[3], model.color[4]
    -- end
    else
      cr, cg, cb, ca = 1, 1, 1, 1
    end

    model.hoverBrightness = model.hoverBrightness and easeTo(model.hoverBrightness, brightness, 4) or 1

    shaders.forwardRender:send("highlight", 0.2)

    if
      model.mesh
      and model.isFrame
      and (editor.debug.draw_frame_bounds or (animatePanel.open and (model.parent.drawBounds or model.drawBounds) and not model.onion))
    then
      if editor.debug.draw_frame_bounds then shaders.forwardRender:send("layer", -1) end

      if not ctx.isAnim and animatePanel and animatePanel.f and not animatePanel.playing then
        local p1, p2 = -model.parent.transform * vec3(0, 0, 0), -model.transform * -model.parent.transform * vec3(0, 0, 0)
        local base = animatePanel.f.blank and 0 or 1
        offsetMesh = gfx.newMesh(zformat, { { p1.x, p1.y, p1.z, 1 }, { p2.x, p2.y, p2.z, 1 }, { p1.x, p1.y, p1.z, 1 } }, "fan")
      end
    end
  else
    shaders.forwardRender:send("highlight", model.hoverBrightness)
  end

  if model.skelly and ((model.anim and model.anim.current_pose) or model.current_pose) then
    shaders.forwardRender:send("skellyman", 1)
    shaders.forwardRender:send("poseMatrixArray", "column", unpack(model.current_pose or model.anim.current_pose))
    model.front = true
  end

  if model.outline then shaders.forwardRender:send("outline", model.outline or 0) end

  if model.fadeDist then shaders.forwardRender:send("fadeDist", model.fadeDist) end

  if model.wireframe or editor.debug.wireframe then gfx.setWireframe(true) end

  if model.stripe then
    shaders.forwardRender:send("stripe", model.stripe)
  elseif model.hoverBrightness then
    shaders.forwardRender:send("stripe", (1 - model.hoverBrightness) * 20)
  end

  if model.polka then shaders.forwardRender:send("polka", model.polka + 0.05) end

  local base = ((model.parent.layer or 0) + (model.layer or 0)) * -1
  gfx.setBlendMode("alpha", "alphamultiply")

  if model.cullMode then 
    -- gfx.setMeshCullMode(model.cullMode)
  end

  if ctx.isAnim and model.isFrame then

    -- shaders.forwardRender:send("renderwhite",1)
  end
  -- gl.Clear(gle.DEPTH_BUFFER_BIT)
  -- RectMesh:setTexture(load_imageTex("checkers"))
  -- gfx.draw3D(RectMesh,mat4():scale(mat4():translate(mat4(),vec3(0,0,100)),vec3(1000,1000,1)))
  -- gfx.setDepthMode("always", true)

  if (model.mesh or model.vector) and not (model.hoverOnly and not hoveredM) then
    shaders.forwardRender:send("layer", base)
    if model.dither then shaders.forwardRender:send("dither", type(model.dither) == "number" and model.dither or 1) end
    if model.unlit then shaders.forwardRender:send("drawShadowed", false) end
    if model.alpha then shaders.forwardRender:send("alpha", model.alpha) end
    if model.onion then
      shaders.forwardRender:send("onion", 1)
      shaders.forwardRender:send("tint", { model.tint.r, model.tint.g, model.tint.b, model.tint.a })
    end

    gfx.setColor(cr, cg, cb, ca)
      

    if model.vector then
      shaders.forwardRender:send("vector", 1)
      local debug = animatePanel.debugView and animatePanel.debugWireframe
      gfx.setWireframe(debug)
      if animatePanel.exportingAlpha then
        -- gfx.setBlendMode("replace","premultiplied")
        -- gl.BlendEquationSeparate(gle.FUNC_ADD,gle.FUNC_ADD);
        -- gl.BlendFuncSeparate(gle.ONE,gle.ONE_MINUS_SRC_ALPHA,gle.ONE,gle.ONE_MINUS_SRC_ALPHA)
      end

      if model.onion then
        if model.strokeMesh and model.groupCounts[-1] then
          gfx.setWireframe(debug or (model.strokeWire and game.vectorLayers[i] ~= "fill"))
          model.strokeMesh:setDrawRange(1, model.groupCounts[-1])
          gfx.draw3D(model.strokeMesh, model.transform)
        end
      else
        local startVert = 1
        if lume.count(model.groupCounts) > 1 or model.strokePreview then
          for i = 1, #game.vectorLayers, 1 do
            if model.strokeMesh and model.groupCounts[i] then
              -- local pushed,skip
              -- if i==2 and model.editLayer and animatePanel.brush.paint then
              --  if animatePanel.drawn==1 then
              --    pushed=true
              --    gfx.push("all")
              --    gfx.setCanvas(animatePanel.strokePreviewUnderlay)
              --    gfx.clear(0,0,0,0)
              --    gfx.setBlendMode("replace")
              --    gfx.setColor(1,1,1,1)
              --    -- gfx.pop()
              --  elseif animatePanel.drawn>1 then
              --    skip=true
              --  end
              -- end
              -- if not skip then
              gfx.setWireframe(debug or (model.strokeWire and game.vectorLayers[i] ~= "fill"))
              shaders.forwardRender:send("brush", (model.textureStroke and game.vectorLayers[i] ~= "fill") and 1 or 0)
              model.strokeMesh:setDrawRange(startVert, model.groupCounts[i])
              -- print("HIIIII")
              gfx.draw3D(model.strokeMesh, model.transform)
              _DEBUG_STRKMSH= model.strokeMesh
              startVert = model.groupCounts[i] + 1 or startVert
              --  if pushed then
              --    gfx.draw3D(RectMesh,model.transform)
              --    gfx.pop()
              --  end
              -- else
              --  print("Skip")
              -- end
            end

            if model.strokePreview and model.strokePreviewLayer == i then
              if animatePanel.brush.paint then --and animatePanel.strokePreviewUnderlay then
                gfx.push("all")

                gfx.setCanvas(animatePanel.strokePreviewCanvas)
                gfx.clear(0, 0, 0, 0)
                gfx.setColor(1, 1, 1, 1)
                gfx.draw3D(model.strokePreview, model.transform)

                gfx.pop()

                shaders.forwardRender:send("vector", 0)

                gfx.setColor(1, 1, 1, animatePanel.color.a)
                -- gfx.setBlendMode("replace")
                -- shaders.forwardRender:send("underlay",animatePanel.strokePreviewUnderlay)
                -- shaders.forwardRender:send("sampleUnderlay",1)
                gfx.draw(animatePanel.strokePreviewCanvas)
                -- shaders.forwardRender:send("sampleUnderlay",0)

                shaders.forwardRender:send("vector", 1)
              else
                gfx.draw3D(model.strokePreview, model.transform)
              end
            end
            if model.strokeSelected and model.strokeSelectedLayer == i then 
              gfx.draw3D(model.strokeSelected, model.transform) 
            end
          end
        end
      end
    else
      if model.indexed then
        shaders.forwardRender:send("drawIndexed", true)
        shaders.forwardRender:send("palette", unpack(model.palette))
      end

      gfx.draw3D(model.mesh, model.matrix)

      if model.brushCanvas then gfx.draw3D(model.brushMesh, model.transform) end

      if animatePanel.debugView then
        gfx.setColor(1, 1, 1, 0.2)
        model.mesh:setTexture(load_imageTex("checkers"))
        gfx.draw3D(model.mesh, model.transform)
        if animatePanel.debugCompaction and animatePanel.f and animatePanel.f.packedCanvas and not mouse.down.L then
          gfx.setColor(1, 1, 1, 0.2)
          model.mesh:setTexture(animatePanel.f.packedCanvas.unpacker)
          gfx.draw3D(model.mesh, model.transform)
        end
      end
      model.mesh:setTexture()
      if model.indexed then shaders.forwardRender:send("drawIndexed", false) end
    end

    shaders.forwardRender:send("brush", 0)
    shaders.forwardRender:send("onion", 0)
    shaders.forwardRender:send("vector", 0)

    if model.alpha then shaders.forwardRender:send("alpha", 1) end

    if model.unlit then shaders.forwardRender:send("drawShadowed", view.drawShadowed) end
    if model.dither then shaders.forwardRender:send("dither", 0) end

    if not game.drawingShadow and model.dotlines and (model.dots or model.lines) then
      if model.dots then
        gfx.setPointSize(4)
        shaders.forwardRender:send("layer", -1)
        gfx.draw(model.dots)
      end
      if model.lines then
        shaders.forwardRender:send("layer", -1)
        gfx.setWireframe(true)
        gfx.draw(model.lines)
      end
      gfx.setWireframe(false)
    end
  end

  shaders.forwardRender:send("layer", 0)

  -- shaders.forwardRender:send("renderwhite",0)

  if model.stripe or model.hoverBrightness then shaders.forwardRender:send("stripe", 0) end
  if model.polka then shaders.forwardRender:send("polka", 0) end
  if model.outline then shaders.forwardRender:send("outline", 0) end
  if model.fadeDist then shaders.forwardRender:send("fadeDist", 0) end
  if model.skelly then shaders.forwardRender:send("skellyman", 0) end

  shaders.forwardRender:send("highlight", 1)

  if offsetMesh then
    gfx.setWireframe(true)
    gfx.draw3D(offsetMesh)
    gfx.setWireframe(false)
  end

  if model.poseMesh then
    gfx.setWireframe(true)
    gfx.draw3D(model.poseMesh)
    gfx.setWireframe(false)
  end

  gfx.pop()
end

local function drawEntityReloader(entity,view)
  if entity.isReloading and not load_isLoading(entity.key) then
    entity.img = load_imageTex(entity.key)
    entity.models = {}
  end
  entity.isReloading = load_isLoading(entity.key)
  if load_isLoading(entity.key) then
    table.insert(view.room.delayed, {
      entity = entity.position,
      draw = function()
        gfx.setColor(0, 0, 0, entity.render.alpha / 2)
        gfx.rectangle("line", entity.render.x - 30, entity.render.y - 30, 60, 60, 8)
        gfx.setColor(1, 1, 1, 1)
        gfx.draw(load_imageTex("reload"), entity.render.x, entity.render.y, time * 8, 0.6, 0.6, 64, 64)
      end,
    })
  end
end

local function drawEntityVisible(entity,view)
  if entity.visible or entity.visible == nil then return end
  table.insert(view.room.delayed, {
    entity = entity.position,
    draw = function()
      gfx.setColor(0, 0, 0, entity.render.alpha / 2)
      gfx.rectangle("line", entity.render.x - 32, entity.render.y - 30, 30, 30, 2)
      gfx.setColor(1, 1, 1, entity.render.alpha)
      gfx.draw(load_imageTex("hidden"), entity.render.x - 28, entity.render.y - 24, 0, 0.5, 0.5, 0, 0)
    end,
  })
end

local function drawEntityScripts(entity,view)
  if not entity.scripts or #entity.scripts == 0 then return end
  table.insert(view.room.delayed, {
    entity = entity.position,
    draw = function()
      gfx.setColor(0, 0, 0, entity.render.alpha / 2)
      --for s,script in pairs(entity.scripts) do
      gfx.rectangle("fill", entity.render.x, entity.render.y - 30, 40, 30, 2)
      gfx.setColor(1, 1, 1, entity.render.alpha)
      gfx.draw(load_imageTex("script"), entity.render.x, entity.render.y - 25, 0, 0.1, 0.1, 0, 0)
      gfx.printf(#entity.scripts, entity.render.x + 25, entity.render.y - 23, 20)
      --end
    end,
  })
end

local function drawSymbol(symbol, transform, anim, frame)
  for m, model in ipairs(scene_getEntityFrameData(symbol, anim, frame)) do
    model.transform = model.transform and (transform * model.transform) or transform
    drawModel(model)
  end
end

local function drawRoomStuff(view)
  bench.push("drawRoomStuff")
  local room = view.room

  bench.push("prepModels")
  view.room.models = view.room.models or { nocopy = true }
  bench.push("ents")
  view.room.queued = {}
  editor.modelsSelected = {}
  local matrix = mat4()
  for e, entity in ipairs(view.room.entities) do
    if entity.light.isLight then
    -- queueForward(view.room,{drawSimple,entity,true})
    elseif not entity.visible then
    elseif entity.simple then
      if entity.visible and not entity.ikbone then 
        queueForward(view.room, { view_drawSimple, entity }) 
      end
    elseif not entity.img then
    -- queueForward(view.room,{drawSimple,entity,true})
    else
      trace("")
      trace("Syms Used:")
      addEntityModels(view.room.models, entity, 0, matrix)
      if view == editor.viewport then
        local unp = camera_project(entity.position)
        if unp then
          entity.render = entity.render or {}
          entity.render.x, entity.render.y = mf(unp.x), mf(unp.y)
          -- local dist = vec2.dist(view.bounds.c,entity.render)
          -- entity.render.alpha = 1-(dist*(3/view.bounds.w))*(vec3.dist(entity,editor.viewport.camera.pos)/500)

          if ctx.editing and not animatePanel.open then
            drawEntityReloader(entity,view)
            if not entity.selected and not editor.debug.no_icons then
              drawEntityVisible(entity, view)
              drawEntityScripts(entity, view)
            end
          end
        end
      end
    end
  end

  bench.pop("ents")

  if not game.drawingShadow then
    if view == editor.viewport then
      bench.push("int")
      if not animatePanel.open and editor.intersect and editor.intersectNormal and not editor.orbiting then
        local size = 5
        gfx.setMeshCullMode("none")
        editor.intersectMesh = editor.intersectMesh or gfx.newMesh(zformat, 3, "triangles")
        editor.intersectMesh:setVertices({
          {editor.intersect.x,editor.intersect.y,editor.intersect.z,1,0,0,1,1,1,1},
            {editor.intersect.x+editor.intersectNormal.x*size,editor.intersect.y+editor.intersectNormal.y*size,editor.intersect.z+editor.intersectNormal.z*size,1,0,0,1,1,1,1},
            {editor.intersect.x,editor.intersect.y,editor.intersect.z,1,0,0,1,1,1,1}
        })
        drawModel({
          wireframe = false,
          mesh = editor.intersectMesh,
        })
      end
      if
        animatePanel.open
        and animatePanel.l.remodel
        and animatePanel.selectMode[3]
        and editor.entityHovered == animatePanel.instance
        and editor.intersectTri
        and editor.intersectMat
      then
        local tri = editor.intersectTri
        drawModel({
          wireframe = true,
          layer = 1,
          transform = editor.intersectMat,
          mesh = gfx.newMesh(zformat, {
            { tri[1].x, tri[1].y, tri[1].z, 1, 0, 0, 1, 1, 1, 1 },
            { tri[2].x, tri[2].y, tri[2].z, 1, 0, 0, 1, 1, 1, 1 },
            { tri[3].x, tri[3].y, tri[3].z, 1, 0, 0, 1, 1, 1, 1 },
          }, "triangles"),
        })
      end
      bench.pop("int")
    end
  end
  bench.pop("prepModels")

  if models and #models > 0 then
    bench.push("dist")
    for m, model in ipairs(models) do
      if model.transform then
        getModelToCamDist(model) --trace("model.camDist",model.camDist)
      end
      -- else
      --  model.camDist = vec3.dist2(ctx.viewport.camera.pos, model.x and model or vec3(0))
      --  --applyTransform(model)
      --  --model.matrix = scene_getMatrix(model)
      -- end
    end
    bench.pop("dist")
    table.sort(room.models, function(a, b)
      return (a.camDist and b.camDist) and a.camDist < b.camDist or false
    end)
    table.sort(room.delayed, function(a, b)
      return (a.camDist and b.camDist) and a.camDist > b.camDist or false
    end)
  end

  bench.push("drawModels")
  gfx.push("all")

  gfx.setShader(shaders.forwardRender)
  gfx.setColor(1, 0, 0, 0.5)
  gfx.setMeshCullMode("none")

  if animatePanel and animatePanel.instance and animatePanel.instance.children then
    for e,entity in ipairs(animatePanel.instance.children) do
    -- drawSymbol(entity.symbol,mat4():scale(mat4():translate(mat4(),vec3(500*e,500,0)),vec3(0.5)),entity.anim,entity.frame)
    end
  end

  for m, model in ipairs(room.models) do
    -- if model.selected then 
    --   model.color = {1,0.9,0.2,1}
    --   drawModel(model)
    --   model.color = {1,1,1,1}
    -- end
    drawModel(model)

    if game.state=="Edit" then 
      local pos = model.matrix * vec3(0)
      local proj = camera_project(pos)
      if model.selected and proj then
        -- 0 point axes
        local x, y = proj.x, proj.y
        local dir = (-view.camera.pos):normalize2()
        local L = 5
        local proj2 = camera_project(model.matrix*vec3(L, 0, 0))
        if proj2 then
          local x2, y2 = proj2.x, proj2.y
          gfx.setColor(1, 0, 0, 1)
          gfx.line(x, y, x2, y2)
        end
        local proj2 = camera_project(model.matrix*vec3(0, L, 0))
        if proj2 then
          local x2, y2 = proj2.x, proj2.y
          gfx.setColor(0, 1, 0, 1)
          gfx.line(x, y, x2, y2)
        end
        local proj2 = camera_project(model.matrix*vec3(0, 0, L))
        if proj2 then
          local x2, y2 = proj2.x, proj2.y
          gfx.setColor(0, 0, 1, 1)
          gfx.line(x, y, x2, y2)
        end
        -- gfx.setColor(1, 0, 0, 1)
        -- gfx.circle("line", proj.x, proj.y, 4)
      end
      
    end
  end

  if not game.drawingShadow then
    for d, delayed in ipairs(room.delayed) do
      delayed.draw()
    end
  end

  gfx.setMeshCullMode("none")
  drawPasteBoard(true)
  gfx.pop()

  bench.pop("drawModels")

  lume.clear(room.models)
  if not game.drawingShadow then lume.clear(room.delayed) end
  room.models.nocopy = true

  for d, draw in ipairs(view.room.queued) do
    draw[1](funpack(draw))
  end

  if view == editor.viewport and editor.gizmo.new then
    bench.push("toools")

    if editor.gizmo.active and editor.gizmo.radius then 
      gfx.push("all")
      gfx.setShader()
      gfx.setLineWidth(editor.gizmo.items.rotate.hovered and 3 or 1)
      gfx.setColor(0.5,0.5,0.5,1)
      gfx.circle("line",editor.gizmo.screenCurrent.x,editor.gizmo.screenCurrent.y,editor.gizmo.radius)
      gfx.pop()
    end

    gfx.push("all")
    gfx.setDepthMode("always", false)
    for t, tool in ipairs(editor.toolModels) do
      drawModel(tool)
    end
    gfx.pop()

    lume.clear(editor.toolModels)
    bench.pop("toools")
  end
  bench.pop("drawRoomStuff")
end

local function boxFromBounds(bounds, mesh)
  local mn, mx = bounds.min, bounds.max

  local verts = {
    { mn.x, mn.y, mn.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mn.x, mx.y, mn.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mn.x, mn.y, mn.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mx.x, mn.y, mn.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mx.x, mx.y, mn.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mx.x, mn.y, mn.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mn.x, mn.y, mx.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mn.x, mx.y, mx.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mn.x, mn.y, mx.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mx.x, mn.y, mx.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mx.x, mx.y, mx.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mx.x, mn.y, mx.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mn.x, mn.y, mn.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mx.x, mn.y, mn.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mn.x, mn.y, mn.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mn.x, mn.y, mx.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mx.x, mn.y, mx.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mn.x, mn.y, mx.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mn.x, mx.y, mn.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mx.x, mx.y, mn.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mn.x, mx.y, mn.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mn.x, mx.y, mx.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mx.x, mx.y, mx.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mn.x, mx.y, mx.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mn.x, mn.y, mn.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mn.x, mn.y, mx.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mn.x, mn.y, mn.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mx.x, mn.y, mn.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mx.x, mn.y, mx.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mx.x, mn.y, mn.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mn.x, mx.y, mn.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mn.x, mx.y, mx.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mn.x, mx.y, mn.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mx.x, mx.y, mn.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mx.x, mx.y, mx.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { mx.x, mx.y, mn.z, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
  }
  if mesh then
    mesh:setVertices(verts, 1)
    return mesh
  end

  local mesh = gfx.newMesh(zformat, verts, "triangles", "dynamic")

  return mesh
end


------------------------------------------------------------------------------
------------------------------------------------------------------------------
-- GLOBALS            
------------------------------------------------------------------------------
------------------------------------------------------------------------------


function view_drawSimple(entity, worldSpace, circle)
  if not game.drawingShadow and ctx.editing then
    -- and (entity.visible or type(entity.visible)=="nil") then
    -- print("simp")

    shaders.forwardRender:send("wobble", 0)
    gfx.push("all")
    gfx.setShader()

    if entity.mode == "ramps" and entity.pointIndex < #pathsPanel.points then
      gfx.setColor(1, 1, 1, pathsPanel.lineHovered == entity.pointIndex and 0.6 or 0.1)
      gfx.setLineWidth(pathsPanel.lineHovered == entity.pointIndex and 4 or 2)
      gfx.line(
        pathsPanel.points[entity.pointIndex + 1].render.x,
        pathsPanel.points[entity.pointIndex + 1].render.y,
        pathsPanel.points[entity.pointIndex].render.x,
        pathsPanel.points[entity.pointIndex].render.y
      )
    end

    gfx.setColor(1, 1, 1, 1)
    -- gfx.setLineStyle("rough")
    entity.render = entity.render or {}
    -- if entity.render.ox then
    --  entity.render.x,  entity.render.y   = entity.render.ox, entity.render.oy
    -- else
    local projected = camera_project((entity.selected and editor.gizmo.dragging) and entity.nextPosition or entity.position)

    if not projected then 
      return 
    end
    entity.render.x, entity.render.y = projected.x, projected.y

    -- end
    local size = (worldSpace and pathsPanel.pointSize / 1.5 or pathsPanel.pointSize) * game.resolution ^ 0.7
    local x, y = projected.x or 0, projected.y or 0

    if worldSpace then
      -- x,y  = entity.render.x,  entity.render.y
      local pos = ((entity.selected and editor.transforming) and entity.nextPosition or entity.position)
      local sc
      if entity.light.enabled then
        print("drow light")
        gfx.setColor(entity.light.color.r, entity.light.color.g, entity.light.color.b, 1)
        gfx.setLineWidth(entity.light.strength * 2 * math.pow(game.resolution, 0.5))
      elseif circle then
        gfx.setColor(1, 1, 1, 1)
      else
        if not entity.img then
          gfx.setColor(1, 1, 1, 1)
          gfx.draw(load_imageTex("missing"), 0, 0, 0, 0.4, 0.4, 47, 80)
        end
      end
    else
      x, y = entity.render.x, entity.render.y
    end

    if entity.light.enabled then
      size = size / game.resolution
      local rays = 8
      local a, b
      for i = 1, rays, 1 do
        a, b =
          vec2.from_cartesian(size, (i / rays) * 2 * pi), vec2.from_cartesian(size * 1.5 + math.pow(entity.light.range, 0.5), (i / rays) * 2 * pi)
        gfx.line(x + a.x, y + a.y, x + b.x, y + b.y)
      end
    end

    gfx.setLineWidth(2)
    gfx.circle("line", x, y, size, 16)
    --if not (entity.selected and editor.gizmo.dragging) then
    if entity.selected then gfx.circle("fill", x, y, size * 1.3, 16) end
    if entity.hovered then
      gfx.circle("fill", x, y, size * 0.6, 10) --gfx.circle("line",entity.render.x,entity.render.y,size*1.1,10)
    end
    --end

    gfx.pop()
  end
end

function view_drawPaths(view)
  if ctx.isAnim then return end
  bench.push("drawPaths")
  bench.push("processPath")

  local room = view.room
  local snap
  local dist, dist2
  local point
  local nextPoint
  local startScreen
  local endScreen
  local x1, y1, x2, y2
  local w3
  local wall
  local lineBox
  local lineHovered
  local hoveredPath

  -- trace("snapping disabled")
  if game.state == "Edit" and (pathsPanel.open or game.time < 1) then
    lineHovered = 0
    hoveredPath = pathsPanel.hoveredPath or pathsPanel.current
    for m, mode in pairs({ "floors", "ramps", "paths" }) do
      for p, path in pairs(room[mode]) do
        for i, point in ipairs(path.points) do
          --dist        = path.dists[i] or 50
          nextPoint = i < #path.points and path.points[i + 1] or ((mode ~= "ramps" and path.loop) and path.points[1] or point)
          startScreen = camera_project(point)
          endScreen = camera_project(nextPoint)

          w3 = 1
          wall = false

          if startScreen and endScreen and not editor.drawingLink and view == ctx.viewport then
          x1, y1, x2, y2 = startScreen.x, startScreen.y, endScreen.x, endScreen.y
          lineBox = { math.min(x1, x2), math.min(y1, y2), math.max(x1, x2), math.max(y1, y2) }
            if room.key == ctx.viewport.key and pointInBB({ mouse.x * game.resolution, mouse.y * game.resolution }, lineBox) then
              dist = distPointToLineSegment({ mouse.x * game.resolution, mouse.y * game.resolution }, { x1, y1 }, { x2, y2 })
              if dist < 20 then
                snap = true
                dist2 = 1 - math.dist(mouse.x * game.resolution, mouse.y * game.resolution, x2, y2) / math.dist(x1, y1, x2, y2)

                if mode == "floors" then
                  pathsPanel.snapPoint = pathsPanel.snapPoint or {}
                  pathsPanel.snapPoint.x = -lerp(point.x, nextPoint.x, 0.5)
                  pathsPanel.snapPoint.y = -lerp(point.y, nextPoint.y, 0.5)
                  pathsPanel.snapPoint.z = -lerp(point.z, nextPoint.z, 0.5)
                  pathsPanel.snapPoint.id = point.id
                  pathsPanel.snapPoint.room = room.isEntiy and room.uid or nil
                  pathsPanel.snapPoint.floor = p
                  pathsPanel.snapPoint.i = i
                  pathsPanel.snapPoint.room = room.key
                end
                if not editor.transforming and  not editor.gizmo.hovered and dist < 10 then
                  trace("FFFF")
                  lineHovered = i
                  w3 = 1.5
                  pathsPanel.newPointPosition = {
                    x = lerp(point.x - room.bx, nextPoint.x - room.bx, dist2),
                    y = lerp(point.y - room.by, nextPoint.y - room.by, dist2),
                    z = lerp(point.z - room.bz, nextPoint.z - room.bz, dist2),
                    i = i,
                  }
                end
              end
            end
          end

          if mode == "floors" then
            if type(point.walled) ~= nil and point.walled == false then
              w3 = w3 * 0.5
              wall = true
            end
          end
        end
      end
    end
    if room.key == ctx.viewport.key then pathsPanel.lineHovered = (editor.hoveredEntity == nil) and lineHovered or 0 end
  end

  if not snap then pathsPanel.snapPoint = nil end
  bench.pop("processPath")

  if room.isRoom and room.terrain and room.terrain.models and not editor.debug.hide_terrain then
    gfx.push("all")

    debug.terrain_tris = 0


    local BBOX = {}
    local shader
    if room.lighting.probes.rendering then
      shader = shaders.forwardRender 
    elseif game.drawingShadow then
      shader = shaders.shadowStatic 
    elseif view.gbuffer then
      shader = shaders.deferredStatic 
    else
      shader = shaders.forwardRender 
    end

    gfx.setShader(shader)

    if not game.drawingShadow then shader:send("time", time) end

    local drawn = 0
    bench.push("draw paths")
    for m = #room.terrain.models, 1, -1 do
      local model = room.terrain.models[m]

      if room[model.mode][model.id] then
        if model.isGrass then
          if room[model.mode][model.id].enabled then drawModel(model) end
        else
          if model.visible == true or model.visible == nil then
            model.m = m / #room.terrain.models

            gfx.setWireframe((editor.debug.wireframe or not room[model.mode][model.id].enabled))

            if not (game.drawingShadow or not ctx.editing) or not model.isEdge then
              if not (model.isEdge and (animatePanel.open or editor.state == "Cutting" or (editor.modelHovered and editor.modelHovered.isWall))) then
                local brightness = 1
                if ctx.editing then
                  if not game.drawingShadow and not mouse_getRelativeMode() and not animatePanel.open and editor.state ~= "Cutting" then
                    --brightness = (model.isEdge and (model.walled and 0.8 or 0.9) or 1)
                    brightness = (model == editor.modelHovered and 0.95 or 1)
                    -- shader:send("stripe", (brightness<1 and not editor.pickingColor) and 1 or 0)
                  end
                  model.hoverBrightness = model.hoverBrightness and easeTo(model.hoverBrightness, brightness, 4) or 1
                  gfx.setColor(1,1,1, (model.brightness or 1) * (model.hoverBrightness or 1), true)
                else
                  gfx.setColor(1, 1, 1, 1)
                end

                local mode = (room.lighting.probes.rendering or gfx.isWireframe()) and "none"
                  or (game.drawingShadow and "front" or (room.lighting.vct.back and "back" or "front"))
                mode = mode == "front" and "back" or (mode == "back" and "front" or mode)
                gfx.setMeshCullMode(mode)
                if model.mesh then
                  gfx.setColor(1, 1, 1, 1)
                  if room.lighting.vct.rendering then
                    if cpml.intersect.aabb_aabb(model.bbox, room.lighting.vct.sliceBox) then
                      drawn = drawn + 1
                      gfx.draw3D(model.mesh)
                    end
                  else
                    if editor.modelHovered == model then
                      shader:send("dither", 1)
                      shader:send("polka", 0)
                      shader:send("stripe", 0)
                      gfx.setColor(1.2, 1.2, 1.2, 1)
                    end
                    if model.rayBB then
                      -- gfx.setColor(1.2, 1.2, 1.2, 1)
                    end
                    gfx.draw3D(model.mesh)
                    -- shader:send("polka", 0)
                    -- shader:send("stripe", 0)
                    -- shader:send("dither", 0)
                  end
                end

              end
            end

            if not room.bbox then table.insert(BBOX, model.bbox) end
            gfx.setWireframe(false)
          end
        end
      else
        table.remove(room.terrain.models, m)
      end
    end
    bench.pop("draw paths")

    bench.push("pick_path_test")
    for m = 1, #room.terrain.models, 1 do
      local model = room.terrain.models[m]
      if room[model.mode][model.id] then
        if model.isGrass and room[model.mode][model.id].enabled then drawModel(model)
        else
          if model.visible == true or model.visible == nil then
            if view == ctx.viewport then
              scene_testCursorModel(model)
            end
          end
        end
      end
    end
    bench.pop("pick_path_test")
 
    if room.lighting.vct.rendering then room.lighting.vct.drawCalls = room.lighting.vct.drawCalls + drawn end
    -- end
    -- gfx.draw3D(game.lightSphere)

    gfx.pop()

    if (pathsPanel.wallCutting or pathsPanel.gridFade > 0) and not game.drawingShadow then
      trace("cutwall", pathsPanel.wallCutting, pathsPanel.gridFade > 0)
      shaders.forwardRender:send("fadePoint", { -pathsPanel.cutMouse3D.x, -pathsPanel.cutMouse3D.y, -pathsPanel.cutMouse3D.z })
      shaders.forwardRender:send("drawShadowed", false)
      drawModel({
        color = { 1, 1, 1, pathsPanel.gridFade },
        mesh = editor.debugGrid,
        transform = pathsPanel.wallMat,
        wireframe = true,
        layer = -25,
        fadeDist = 200,
      })
      shaders.forwardRender:send("drawShadowed", (view.drawShadowed ~= nil))
    end
  end

  bench.pop("drawPaths")
end

function view_pushView(view)
  view_updateViewport(view)
  table.insert(game.views, view)
end

function view_drawViewContents(view)
  bench.push("drawRoom")
  gfx.push("all")

  light_updateLights(view)

  if _pathTraceOnly then
    gfx.pop()
    bench.pop("drawRoom")
    return
  end

  prepareView(view)
  editor_drawGrid(view)
  view_drawPaths(view)
  drawTests(view)
  plan_drawLinks(view)
  drawRoomStuff(view)
  unprepareView(view)
  light_render(view)

  if editor.debug.bounding_boxes then
    -- gfx.push("all")
    -- gfx.setShader(shaders.forwardRender)
    -- gfx.setMeshCullMode("none")
    -- gfx.setWireframe(true)
    -- gfx.setDepthMode("less", true)
    -- gfx.draw3D(box.boundingMesh)
    -- gfx.pop()
  end

  releaseView(view)
  gfx.pop()
  bench.pop("drawRoom")
  return view
end

function view_drawViewToScreen(view)
  gfx.reset()

  gfx.push("all")

  gfx.setColor(1, 1, 1, 1)

  if view.viewMesh and ctx.roomZoom > 0 then
    gfx.setDepthMode()
    gfx.setShader()

    gfx.setWireframe(false)
    gfx.setMeshCullMode("none")
    gfx.push()
    gfx.setColor(1, 1, 1, 1)
    gfx.draw(view.viewMesh)
    gfx.pop()

    if altDown then
      gfx.push("all")
      gfx.setColor(1, 1, 1, 0.2)
      gfx.print(lume.find(ctx.views, view), view.bounds.x + view.bounds.w / 2, view.bounds.y + view.bounds.h / 2)
      gfx.setColor(0, 0, 0, 0.2)
      gfx.print(lume.find(ctx.views, view), view.bounds.x + view.bounds.w / 2 + 2, view.bounds.y + view.bounds.h / 2 + 2)
      gfx.pop()
    end

    if ctx.editing and ctx.roomZoom==1 and editor.viewport == view and not animatePanel.open and not mouse_getRelativeMode() then
      editor.viewOutlineColor = {
        (1 - ctx.page.sky.color.r) / 2,
        (1 - ctx.page.sky.color.g) / 2,
        (1 - ctx.page.sky.color.b) / 2,
        0.5,
      }
      if not view.viewport.shapes then
        view.viewport.shapes = {}
        editor_parseShapes(view.viewport.polys, view.viewport.shapes)
      end
      editor_shapeEditor(view.viewport, 1, {
        x = 0,
        y = 0,
        w = screenW,
        h = screenH,
        dotSize = 10,
        out = true,
        skipOutline = true,
        res = game.resolution,
      })
    end
  elseif view.canvas then
    gfx.push()
    local panel = animatePanel
    if panel.exporting then
      local scale = math.min(view.bounds.w / panel.root.stageWidth, 1)
      gfx.scale(scale, scale)
      gfx.translate((renderW - scale * panel.root.stageWidth) * 0.5, (renderH - scale * panel.root.stageHeight) * 0.5)
    end
    view.canvas:setFilter("nearest", "nearest")
    
    gfx.draw(view.canvas,0,0,0,
      engine.window.size.width/view.canvas:getWidth(),
      engine.window.size.height/view.canvas:getHeight()
    )

    -- crosshairs
    gfx.setLineWidth(1)
    local l = 4
    if mouse_getRelativeMode() then
      local x, y = view.bounds.w / 2, view.bounds.h / 2
      gfx.line(x - l, y, x + l, y)
      gfx.line(x, y - l, x, y + l)
    elseif editor.orbiting then
      local proj = camera_project(editor.orbiting.target)
      if proj then
        local x, y = proj.x, proj.y
        gfx.line(x - l, y, x + l, y)
        gfx.line(x, y - l, x, y + l)
      end
    end

    -- 0 point axes
    local proj = camera_project(vec3(0))
    if proj and not ctx.isAnim then
      local x, y = proj.x, proj.y
      local dir = (-view.camera.pos):normalize2()
      local L = 50
      local proj2 = camera_project(vec3(L, 0, 0))
      if proj2 then
        local x2, y2 = proj2.x, proj2.y
        gfx.setColor(1, 0, 0, 1)
        gfx.line(x, y, x2, y2)
      end
      local proj2 = camera_project(vec3(0, L, 0))
      if proj2 then
        local x2, y2 = proj2.x, proj2.y
        gfx.setColor(0, 1, 0, 1)
        gfx.line(x, y, x2, y2)
      end
      local proj2 = camera_project(vec3(0, 0, L))
      if proj2 then
        local x2, y2 = proj2.x, proj2.y
        gfx.setColor(0, 0, 1, 1)
        gfx.line(x, y, x2, y2)
      end
    end

    if panel.f then 
      gfx.setColor(0, 0, 1, 1)
      gfx.translate(-(view.camera.pos.x-screenW/2),-(view.camera.pos.y-screenH/2))
      gfx.scale(1/(view.camera.fov/(screenH/2)),1/(view.camera.fov/(screenH/2)))
      for s,stroke in ipairs(panel.debugStrokes) do
        local line = stroke.line
        gfx.longline(line)
      --   local x,y

      --   for i=1,line[0][0],1 do
      --     if x then 
      --       -- print("line",i,x,y)
      --       gfx.line(x,y,line[i][1],line[i][2])
      --     end
      --     x,y=line[i][1],line[i][2]
      --   end
      --   -- if _DEBUG_STRKMSH then 
      --   --   gfx.draw3D(_DEBUG_STRKMSH,mat4.identity())
      --   --   print("_DEBUG_STRKMSH",_DEBUG_STRKMSH)
      --   -- end
      end
      -- if panel.instance.transform and panel.mouse then 
      --   gfx.draw3D(editor.sphereMesh,mat4.translate(mat4(),panel.instance.transform,panel.mouse))
      -- end
    end

    gfx.pop()
  else
    print("cant draw view")
  end

  gfx.pop()
end
