
frontclip= 32.0

lighttunnel= {

  move_u= 0.0,
  rot_x= 0.0,
  rot_y= 0.0,

-- camera pos
  cam_x= 0.0,
  cam_y= 0.0,
  cam_z= 0.0,

  texture1= nil,
  texture2= nil,
  mesh= nil,

  text_img= nil,
  text_pal= nil,
  text_line= 0,
  text_x= 0,
  text_y= 0
}


lighttunnel.load= function(this)
  local tun_pos= {
    [0]= { -18.0, 190.0, -1000.0 }, { -92.0,   0.0, -1000.0 }, { -92.0,   0.0,  1000.0 }, { -18.0, 190.0,  1000.0 },
         {  18.0, 190.0, -1000.0 }, {  92.0,   0.0, -1000.0 }, {  92.0,   0.0,  1000.0 }, {  18.0, 190.0,  1000.0 },
         { -92.0,   0.0, -1000.0 }, { -92.0,   0.0,  1000.0 }, {  92.0,   0.0,  1000.0 }, {  92.0,   0.0, -1000.0 },
         { -18.0,-190.0, -1000.0 }, { -92.0,   0.0, -1000.0 }, { -92.0,   0.0,  1000.0 }, { -18.0,-190.0,  1000.0 },
         {  18.0,-190.0, -1000.0 }, {  92.0,   0.0, -1000.0 }, {  92.0,   0.0,  1000.0 }, {  18.0,-190.0,  1000.0 }
  }

  local tun_uv={ 
    [0]= { -5.0, 1.0-0.97 }, { -5.0, 1.0-0.01 }, {  5.0, 1.0-0.01 }, {  5.0, 1.0-0.97 }, 
         { -5.0, 1.0-0.97 }, { -5.0, 1.0-0.01 }, {  5.0, 1.0-0.01 }, {  5.0, 1.0-0.97 }, 
         { -5.0, 1.0-0.01 }, {  5.0, 1.0-0.01 }, {  5.0, 1.0-0.99 }, { -5.0, 1.0-0.99 }, 
         { -5.0, 1.0-0.97 }, { -5.0, 1.0-0.01 }, {  5.0, 1.0-0.01 }, {  5.0, 1.0-0.97 }, 
         { -5.0, 1.0-0.97 }, { -5.0, 1.0-0.01 }, {  5.0, 1.0-0.01 }, {  5.0, 1.0-0.97 }
  } 

  local np= 5    -- 5 quads
  local nv= np*4 -- 4 vertices per quad

  local mesh= {
    pos= userdata("f64", 3, nv),
    screen_pos= userdata("f64", 3, nv),
    uv= userdata("f64", 2, nv)
  }

  for i=0,nv-1 do
      local pos= tun_pos[i]
      mesh.pos:set(0, i, pos[1], pos[2], pos[3])

      local uv= tun_uv[i]
      mesh.uv:set(0, i, uv[1], uv[2])
  end

  this.mesh= mesh

  -- load and preprocess texture 
  -- TODO: gleich so abspeichern?
  local temp= unpod(fetch("wall.pod")) -- 256x256
  this.texture1= userdata("i16", 256,257)
  for y=0,256 do
    for x=0,255 do
      local c1= temp:get(x,y&255)
      local c2= temp:get(x,(y+1)&255)
      c1= max(c1-128, 0)
      c2= max(c2-128, 0)
      this.texture1:set(x,y, (c2<<8)|c1)
    end
  end

  local temp= unpod(fetch("floor.pod")) -- 256x256
  this.texture2= userdata("i16", 256,257)
  for y=0,256 do
    for x=0,255 do
      local c1= temp:get(y&255,x&255)
      local c2= temp:get(y&255,(x+1)&255)
      c1= max(c1-128, 0)
      c2= max(c2-128, 0)
--      c1=63-c1
--      c2=63-c2
      this.texture2:set(x,y, (c2<<8)|c1)
    end
  end

  -- load text overlay image and palette
  this.text_img= unpod(fetch("tunneltext.pod"))  -- 256x256 indexed

  local pal_raw= fetch("tunneltext.pod.pal")
  local pal_entries= {}
  local pal_text= pal_raw
  if type(pal_raw) == "table" then pal_text= table.concat(pal_raw, "\n") end
  if type(pal_text) == "string" then
    for token in pal_text:gmatch("[#]?([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])") do
      local v= tonumber(token, 16)
      if v then pal_entries[#pal_entries+1]= v end
    end
  end
  this.text_pal= pal_entries
end


lighttunnel.init= function(this)
  palt(0,false)
  fillp()
end


lighttunnel.update= function(this)
  local f= this.fade

  if (f<=1.0) then
    f= flr(f * 256.0 * 8 / 3)
    if (f<0) f=0

    -- 0x808080 .. 0xffffff (mid grey to white)
    for i=0,47 do 
      pal(i, (f*(i+48)>>8)*0x010101, 2)
    end
  else
    f= flr( (f-1)*256.0*3 )
    for i=0,47 do
      local c= (((i+48)<<3)+f) // 3
      if (c>255) c=255 
      pal(i, c*0x010101, 2)
    end
  end

  -- text palette for color indices 48..63
  local tp= this.text_pal
  if tp then
    for i=48, min(#tp, 64)-1 do
      pal(i, tp[i+1] or 0x800080, 2)
    end
  else
    for i=48,63 do
      pal(i, 0x800080, 2)
    end
  end
end

lighttunnel.setparams= function(this, params)
-- params[0] = row (float)
-- params[1] = part
  this.move_u= params[2]
  this.rot_x=  params[3]/360.0
  this.rot_y=  params[4]/360.0
  this.cam_x=  params[5]
  this.cam_y=  params[6]
  this.cam_z=  params[7]
  this.fade=   params[8]
  this.glow=   params[9]
  this.text_line= params[10]
  this.text_x=   params[11]
  this.text_y=   params[12]
end

lighttunnel.draw= function(this)

  -- performs matrix multiplication on source vertices (xyz)
  local function transform(dst, vin, mat)
    local m3x3= userdata("f64", 3, 3)
    m3x3:copy(mat, true, 0,0, 3, 4,3, 3) -- copy 3x3 from 4x4
--    local pos= vec(mat[12],mat[13],mat[14]) --TODO: use mat directly with offset 12
    
    local cnt= vin:height()
    vin:matmul(m3x3, dst, cnt)
    dst:add(mat, true, 12,0, 3, 0,3, cnt)
  end

  local quads= { 
    [0]= { [0]=  3, 2, 1, 0 },  -- left
         { [0]=  4, 5, 6, 7 },  -- right 
         { [0]= 12,13,14,15 },  -- right mirror
         { [0]= 19,18,17,16 },  -- left_mirror
         { [0]= 11,10, 9, 8 }   -- floor (draw last because transparent)
  }

  cls(127)
  
  -- some movement for the object
  local move_u= this.move_u % 1.0
  local rot1= matrixRotY(this.rot_y)
  local rot2= matrixRotX(this.rot_x)
  local obj = rot1:matmul(rot2)
  local fov = matrix(1, 1.66, 1)  -- aspect ratio correction for the projection (fov)
  local pos = matrixTranslation(this.cam_x, this.cam_y, this.cam_z) 
  local cam= obj:matmul(fov)
  cam:matmul(pos, true)

  local mesh= this.mesh
  transform(mesh.screen_pos, mesh.pos, cam)  

  local poly1= userdata("f64", 8, 20)
  local poly2= userdata("f64", 8, 20)

  for f=0,#quads do 
    local quad_idx= quads[f]

    -- build source polygon
    local cnt= 4 -- always quads
    for i=0,cnt-1 do
      local idx= quad_idx[i]
      local x,y,z= mesh.screen_pos:get(0,idx,3)
      local u,v= mesh.uv:get(0,idx,2)
      poly1:set(0,i, x,y,z, (u+move_u)*256.0, v*256.0, 0,0,0)
    end

    cnt= clip_front(poly2, poly1, cnt)
    cnt= clip_top(poly1, poly2, cnt)
    cnt= clip_bottom(poly2, poly1, cnt)
    cnt= clip_left(poly1, poly2, cnt)
    cnt= clip_right(poly2, poly1, cnt)

    -- anything left from the polygon?
    if (cnt>=3) then
      poly2:mutate("f64", 8*20, 1)
      local poly= poly2

      -- perspective projection
      poly:copy(1.0, true, 0,7, 1, 0, 8, cnt) -- dst[7]= fov
      poly:div(poly, true, 2,7, 1, 8, 8, cnt) -- dst[7]= fov / dst.z
      poly:copy(poly, true, 7,2, 1, 8, 8, cnt) -- dst.z= fov / dst.z
      poly:mul(poly, true, 7,0, 1, 8, 8, cnt) -- dst.x *= dst[7]
      poly:mul(poly, true, 7,1, 1, 8, 8, cnt) -- dst.y *= dst[7]

      poly:mul(poly, true, 7,3, 1, 8, 8, cnt) -- dst.u *= dst[7]
      poly:mul(poly, true, 7,4, 1, 8, 8, cnt) -- dst.v *= dst[7]

      poly:mul(120,  true, 0,0, 1, 0, 8, cnt) -- dst.u *= 120
      poly:mul(68,   true, 0,1, 1, 0, 8, cnt) -- dst.v *= 100

      poly:add(119.5,  true, 0,0, 1, 0, 8, cnt) --dst.x += 120 (centerx)
      poly:add(67.5,   true, 0,1, 1, 0, 8, cnt) --dst.y += 67 (centery)

      -- prepare ceil(y)
      poly:copy(0.0, true,  0,7, 1, 0,8, cnt)  -- dst[7]= 0
      poly:sub(poly, true,  1,7, 1, 8,8, cnt)  -- dst[7]= -dst.y
      poly:mod(1.0,  true,  0,7, 1, 0,8, cnt)  -- dst[7]= (-dst.y) % 1.0
      poly:add(poly, true,  1,7, 1, 8,8, cnt)  -- dst[7]+= dst.y  -> dst[7]= ceil(dst.y)

      if (f==4) then
        -- transparent
        lighttunnel_draw_polygon2(poly, 0, cnt, this.texture2)
      else
        lighttunnel_draw_polygon(poly, 0, cnt, this.texture1)
      end

      poly2:mutate("f64", 8, 20)
    end
  end

  -- frame buffer contains grey values from 0..127
  get_display():shr(1, true, 0,0, 240*135, 0,0, 1) -- 0..127 -> 0..63
  get_display():mul(3, true, 0,0, 240*135, 0,0, 1) -- 0..63 -> 0..191
  -- add a bit of dither to avoid banding
  for y=0,134,2 do 
    get_display():add(2, true, 0,(y+0)*240+0, 1, 0,2, 240/2) -- add odd pixels in odd scanlines
    get_display():add(2, true, 0,(y+1)*240+1, 1, 0,2, 240/2) -- add even pixels in even scanlines
  end
  get_display():shr(2, true, 0,0, 240*135, 0,0, 1) -- 0..191 -> 0..47
  get_display():min(47, true, 0,0, 240*135, 0,0, 1) -- remove possible overflows


  -- text overlay using color indices 48..63
  local tl= flr(this.text_line)
  if tl >= 1 and tl <= 6 and this.text_img then
    -- line definitions: {y_offset_in_image, height}
    local lines= {
      {0,   16},  -- 1: "Acryl"
      {16,  16},  -- 2: "Hellfire"
      {32,  16},  -- 3: "Jazz"
      {48,  16},  -- 4: "Goodbye."
      {64,  64},  -- 5: "Revision 2026"
      {128, 48},  -- 6: "Konsumer"
    }
    local line= lines[tl]
    local src_y= line[1]
    local h= line[2]

    -- center on 240x135 screen, then apply offset
    local dx= flr((240 - 256) / 2 + this.text_x)
    local dy= flr((135 - h) / 2 + this.text_y)

    -- clip source rect to screen bounds
    local sx, sw = 0, 256
    if dx < 0 then  sx= -dx  sw= sw + dx  dx= 0  end
    if dx + sw > 240 then  sw= 240 - dx  end

    palt(48, true)
    if sw > 0 then
      sspr(this.text_img, sx, src_y, sw, h, dx, dy, sw, h)
    end
    palt(48, false)
  end
end
