klogo= {

  rotation_x = 0.5,
  rotation_y = 0.9,
  fov =        128.0,
  distance   = 5.0,
  fade       = 1.0,
  shake      = 0,
  glitch     = 0,
  _pal_dirty = false,

  palette= { [0]= 
0x000000, 0x3b1f07, 0x623316, 0x915725, 0xa7672d, 0xaf8a63, 0xbf996d, 0xcea97b, 
0xdbbe69, 0xe4c766, 0xecd263, 0xf0de73, 0xf2e388, 0xf5e7a5, 0xf6e7c6, 0xfaf2d5, 
0x3b220e, 0x432610, 0x4b2c14, 0x532f15, 0x58371b, 0x643e1e, 0x72421d, 0x824a20, 
0x92501e, 0xa4571f, 0xb45f20, 0xba6a24, 0xc57225, 0xcd8129, 0xd3902e, 0xd8a438, 
0x2f140c, 0x351e0a, 0x57432f, 0x614d38, 0x6c563f, 0x72604e, 0x7d6e5c, 0x907f6a, 
0xa8967f, 0xb9a78e, 0xc9b79d, 0xdcc9aa, 0xead6b9, 0x25120c, 0x29150d, 0x2b180f, 
0x2f1c11, 0x331f12, 0x372314, 0x392716, 0x3d2918, 0x412d1a, 0x45311d, 0x4c3621, 
0x543d26, 0x61462d, 0x6e4f31, 0x7c5c3d, 0x886745, 0x95724e, 0xa27d57, 0xb68542
  },

  mesh= nil,
  texture= nil,
  envmap= nil,
  colortable= nil,

  backface_tmp= nil,
  backface_visidx= nil,
  backface_quadidx= nil,

-- data from disk  
  packed_texture= nil,
  packed_envmap= nil,
  packed_logo= nil
}


klogo.load= function(this)
  this.packed_logo=    fetch("klogo.pod")
  -- deterministic RNG for shake/glitch
  this._rng = 0x12345678
--[[
  this.noise= userdata("u8", 1280,19)
  for y=0,18 do
    for x=0,1279 do
      local v= flr(rnd(y*19/15)+0.5)
      if (v>0) v=v+63-19
      if (v>63) v=63
      this.noise:set(x,y, v)
    end
  end
 ]]
  this.texture= unpod( fetch("k-ao.pod") )
  this.texture:bor(0x40, true, 0,0, 256*256, 0,0, 1)   -- +64 for 2. color table

--poke(0x550e, 16,16)

  local temp= unpod( fetch("envmap.pod") )
  this.envmap= userdata("u8", 256,256)
  -- move envmap
  for y=0,255 do
    this.envmap:set(0,y, temp:get(128,(y+128)&255,128))
    this.envmap:set(128,y, temp:get(0,(y+128)&255,128))
  end

  this.backface_tmp= userdata("f64", 2000*3, 1) 
  this.backface_visidx= userdata("f64", 2000+1, 1) 
  this.backface_quadidx= userdata("i32", 2000*4, 1)

  this.mesh= klogo_meshload(fetch("klogo.pod"))

  local function findbest(refr,refg,refb, colors)
    local best= 0
    local best_dist= 255*255*3

    for i=0,63 do
      local c= colors[i] or 0
      local r= (c>>16 & 255) - refr
      local g= (c>>8 & 255)  - refg
      local b= (c>>0 & 255)  - refb
      local dist= (r*r) + (g*g) + (b*b)
      if dist < best_dist then
        best_dist= dist
        best= i
      end
    end
    return best
  end

  local colors= this.palette
  this.colortable= userdata("u8", 64, 64)
  for src=0,63 do
    local level= src/32+0.1
    for dst=0,63 do
      local c= colors[dst] or 0
      local r= (c>>16 & 255)* level
      local g= (c>>8 & 255) * level
      local b= (c>>0 & 255) * level
      if (r>255) r=255
      if (g>255) g=255
      if (b>255) b=255

      local best= findbest(r,g,b, colors)
      this.colortable:set(dst, src, best)
    end
  end

end

local function irnd(this, n)
  this._rng = (this._rng * 1103515245 + 12345) & 0x7fffffff
  return this._rng % n
end

klogo.setparams= function(this, params)
  this.rotation_x= params[2] / 360.0 + 0.5
  this.rotation_y= params[3] / 360.0
  this.fov=        params[4]
  this.distance=   params[5]
  this.fade=       params[6] == nil and 1.0 or params[6]
  this.shake=      params[7] or 0
  this.glitch=     params[8] or 0
end

klogo.update= function(this)
  pal(this.palette, 2)

  poke(0x5508, 127,63,63,0)--
end

klogo.init= function(this)

  for src=0,63 do
      memset(0x8000+src*64, src, 64)
      poke(0x9000+src*64, this.colortable:get(0,src,64))
  end
end


klogo.draw = function(this)

  -- shake: offset camera
  local shk = this.shake
  local ox, oy = 0, 0
  if shk > 0 then
    ox = irnd(this, flr(shk * 2 + 1)) - flr(shk)
    oy = irnd(this, flr(shk * 2 + 1)) - flr(shk)
    camera(-ox, -oy)
  end

  -- performs matrix multiplication on source vertices (xyz)
  local function transform(dst, vin, mat)
    local m= userdata("f64", 3, 3)
    m:copy(mat, true, 0,0, 3, 4,3, 3) -- copy 3x3 from 4x4

    local cnt= vin:height()
    vin:matmul(m, dst, cnt)
    dst:add(mat, true, 12,0, 3, 0,3, cnt)
  end

  -- performs matrix multiplication on source vertices (xyz)
  local function transform3x3(dst, vin, mat)
    local m= userdata("f64", 3, 3)
    m:copy(mat, true, 0,0, 3, 4,3, 3) -- copy 3x3 from 4x4

    vin:matmul(m, dst, vin:height())
  end

  local function project(dst, src, nrm, uv, fov)
    local cnt= src:height()

    dst:copy(src, true, 0,0, 2, 3, 8, cnt)  -- dst.xy = src.xy

    -- dst.z= 128/src.z
    dst:copy(fov, true, 0,2, 1, 0, 8, cnt) -- dst.z= fov
    dst:div(src,  true, 2,2, 1, 3, 8, cnt) -- dst.z= fov / src.z

    -- todo: can we do xy*z in one pass?
    dst:mul(dst,  true, 2,0, 1, 8, 8, cnt) -- dst.x *= dst.z
    dst:mul(dst,  true, 2,1, 1, 8, 8, cnt) -- dst.y *= dst.z

    dst:add(240/2,  true, 0,0, 1, 0, 8, cnt) --dst.x += 120 (centerx)
    dst:add(135/2,  true, 0,1, 1, 0, 8, cnt) --dst.y += 67 (centery)

    dst:copy(uv,  true, 0,3, 2, 3, 8, cnt)  -- dst.uv= uv.xy
  --  dst:mul(64,   true, 0,3, 2, 0, 8, cnt) -- dst.uv *= 64,64  -- todo: scale normals in advance?
  --  dst:add(64,   true, 0,3, 2, 0, 8, cnt) -- dst.uv += 64,64

    dst:copy(nrm, true, 0,5, 2, 3, 8, cnt)  -- dst.uv2= nrm.xy
  end

  local function find_binary(data, count)
      local low, high = 0, count - 1
      local result = count -- Default: keine positiven Werte gefunden
      
      while low <= high do
          local mid = (low + high) >> 1
          if data[mid] < 0 then
              result = mid      -- Könnte der erste sein, aber links weitersuchen
              high = mid - 1
          else
              low = mid + 1     -- Rechts weitersuchen
          end
      end
      return result
  end

  local function cull_quads(dst, mesh, visible)
    local pos=  mesh.screen_pos

    local vtx=  mesh.proj_verts
    local fnrm= mesh.screen_facenormals
    local cnt=  mesh.numpolys

    -- pick one vertex from every polygon
    local tmp= userdata("f64", 2000*3, 1)  -- TODO: allocate global  
    local visidx= this.backface_visidx
    local quadidx= this.backface_quadidx

  --  pos:take(mesh.quad_indices, tmp, 0,0, 3, 4,3, cnt)
  --  tmp:mutate("f64", 2000*3, 1) -- tmp,  true, 0,0, 1, 3,1, cnt) -- x<0:-1 x>=0:+1
    pos:take(mesh.cull_idx, tmp, 0,0, 3, 1,3, cnt)

    -- view dot3 face-normal
    tmp:mul(fnrm, true, 0,0, 3, 3,3, cnt) -- tmp= vx*nx, vy*ny, vz*nz
    tmp:add(tmp,  true, 1,0, 1, 3,3, cnt) -- x+=y
    tmp:add(tmp,  true, 2,0, 1, 3,3, cnt) -- x+=z
    tmp:sgn(tmp,  true, 0,0, 1, 3,1, cnt) -- x<0:-1 x>=0:+1
    tmp:mutate("f64", 1, cnt) -- tmp,  true, 0,0, 1, 3,1, cnt) -- x<0:-1 x>=0:+1
    
    visidx:set(0, 0, -0)
    visidx:set(cnt,0, -cnt<<2)
    visidx:lerp(0, cnt)

    tmp:mul(visidx, true, 0,0, 1, 1,1, cnt)

  --  tmp:copy(visidx, true, 0,0, cnt)
    tmp:mutate("f64", 1, cnt) -- tmp,  true, 0,0, 1, 3,1, cnt) -- x<0:-1 x>=0:+1
    tmp:sort(0,true)
    local index= find_binary(tmp, cnt)
    tmp:mutate("f64", index, 1) -- tmp,  true, 0,0, 1, 3,1, cnt) -- x<0:-1 x>=0:+1
  --  print("0: "..tmp[0])
  --  print(index..": "..tmp[index])
  --  print((index+1)..": "..tmp[index+1])
    local vis= tmp:convert("i32") -- mutate("f64", cnt, 1) -- tmp,  true, 0,0, 1, 3,1, cnt) -- x<0:-1 x>=0:+1
    tmp:mutate("f64", 2000*3, 1) -- tmp,  true, 0,0, 1, 3,1, cnt) -- x<0:-1 x>=0:+1
    mesh.quad_indices:take(vis, quadidx,  0,0, 4, 1,4, index)
    vtx:take(quadidx, dst,  p, visible<<5,  8,  1,8, index*4)

    visible+=index
    return visible
  end

  for y=0,134 do
    local d= abs(y-69)
    local intensity=  14-(d/4*sin(this.rotation_x)*8)
    if (intensity>0) then
      get_display():copy(intensity, true, 0, y*240, 240, 0, 0, 1) 
    end   
  end

  set_spr(1, this.texture) 
  set_spr(2, this.envmap) 

  -- some movement for the object
  local rot1= matrixRotY(this.rotation_y)
  local rot2= matrixRotX(this.rotation_x)
  local obj = rot2:matmul(rot1)
  local cam = matrixTranslation(0, 0, this.distance)
--  cam = matrixMul(cam, obj)
  obj:matmul(cam, cam)

  local vis_faces= userdata("f64", 8*4, 2000) -- 1.5% cpu!

  local mesh= this.mesh

  transform3x3(mesh.screen_nrm,         mesh.nrm,         obj)  
  transform3x3(mesh.screen_facenormals, mesh.facenormals, obj)  
  transform(   mesh.screen_pos,         mesh.pos,         cam)  

  project(mesh.proj_verts, mesh.screen_pos, mesh.screen_nrm, mesh.uv, this.fov)   

	vis_faces:mutate("f64", 8*4, 2000)
  local total= 0
  total= cull_quads(vis_faces, mesh, total)   -- ?
  vis_faces:mutate("f64", 8*4, total)

--  print("verts:"..mesh.pos:height().."  total:"..total)

  vis_faces:sort(2, false)  -- sort by z

   -- TODO: earlier!
  -- dst.z= ceil(dst.y)
  vis_faces:copy(0.0,      true,  0,7, 1, 0,8, total*4)  -- dst[7]= 0
  vis_faces:sub(vis_faces, true,  1,7, 1, 8,8, total*4)  -- dst[7]= -dst.y
  vis_faces:mod(1.0,       true,  0,7, 1, 0,8, total*4)  -- dst[7]= (-dst.y) % 1.0
  vis_faces:add(vis_faces, true,  1,7, 1, 8,8, total*4)  -- dst[7]+= dst.y  -> dst[7]= ceil(dst.y)

  for i=0,total-1 do
    klogo_draw_polygon( vis_faces, i*8*4, 4 )
  end

  -- reset shake camera
  if shk > 0 then camera(0, 0) end

  -- fade (0=black, 1=normal, 2=white)
  local f = this.fade
  if f ~= 1.0 then
    for i = 0, 63 do
      local c = this.palette[i] or 0
      local r = (c >> 16) & 0xff
      local g = (c >> 8) & 0xff
      local b = c & 0xff
      if f < 1 then
        r = flr(r * f + 0.5)
        g = flr(g * f + 0.5)
        b = flr(b * f + 0.5)
      else
        local f2 = f - 1
        r = flr(r + (255 - r) * f2 + 0.5)
        g = flr(g + (255 - g) * f2 + 0.5)
        b = flr(b + (255 - b) * f2 + 0.5)
      end
      pal(i, (r << 16) | (g << 8) | b, 2)
    end
    this._pal_dirty = true
  elseif this._pal_dirty then
    pal(this.palette, 2)
    this._pal_dirty = false
  end

  -- glitch: horizontal slice displacement
  if this.glitch > 0 then
    local disp = get_display()
    local amp = this.glitch * 18
    local slices = 24
    local iw, ih = 240, 135
    local snap = userdata("u8", iw, ih)
    snap:copy(disp, true, 0, 0, iw * ih, 0, 0, 1)
    for s = 0, slices - 1 do
      local sy0 = flr(s * ih / slices)
      local sy1 = flr((s + 1) * ih / slices)
      local sh = sy1 - sy0
      if sh > 0 then
        local off = flr((irnd(this, 2000) / 1000 - 1) * amp)
        if irnd(this, 9) == 0 then off += (irnd(this, 2) * 2 - 1) * flr(amp * 0.9) end
        if off != 0 then
          for y = sy0, sy1 - 1 do
            local row_off = y * iw
            if off > 0 then
              disp:copy(snap, true, row_off, row_off + off, iw - off, 0, 0, 1)
              disp:copy(0, true, 0, row_off, off, 0, 0, 1)
            else
              local a = -off
              disp:copy(snap, true, row_off + a, row_off, iw - a, 0, 0, 1)
              disp:copy(0, true, 0, row_off + iw - a, a, 0, 0, 1)
            end
          end
        end
      end
    end
  end
end

