--[[pod_format="raw",created="2026-04-04 22:10:52",modified="2026-04-04 22:10:52",revision=0]]

mechkoi = {
  -- parameters
  camx= 0.0,
  camy= 0.0,
  camz= 10.0,
  zoom= 128.0,
  cam_rotx = 0.5,
  cam_roty = 0.25,
  anim= {[0]=0,1.0},
  fishpos= { [0]= vec(0,0,0), vec(6,4,-2) },
  fade= 100,

  palette={},
  scene= {},
  backface_tmp= nil,
  backface_visidx= nil,
  backface_quadidx= nil,
  colortable= nil,

  noise= nil,

  -- colormap fish
  colors1= { [0]= 
    0x000000, 0x1c2a27, 0x2d3f3b, 0x3c5551, 0x5a5e5b, 0x4d6e6e, 0x251e1c, 0x332f2b, 
    0x433b33, 0x4b473e, 0x55524a, 0x615b50, 0x75695f, 0x656460, 0x71716f, 0x7d7c7a, 
    0x8a8987, 0x9f9c98, 0xbab6b1, 0xd3d2d1, 0xe4e4e4, 0x15110f, 0x4e170c, 0x751a05, 
    0x8d2606, 0x733625, 0xa14627, 0xcf4a14, 0xe06131, 0xc06e52, 0xe28c6f, 0x040404 },

  -- colormap envmap
  colors2= { [0]= 
    0x050503, 0x1a1811, 0x28241b, 0x2e2a21, 0x332e26, 0x34312c, 0x38312a, 0x3a342f, 
    0x3e3731, 0x463b31, 0x504436, 0x5a4a3a, 0x644a34, 0x6c4f36, 0x70543a, 0x71563e, 
    0x73583e, 0x775a3f, 0x7a5c40, 0x7d5f41, 0x896541, 0x946e45, 0xa2794a, 0xa2845e, 
    0xb08550, 0xae8e62, 0xbd9257, 0xbb9967, 0xc9a061, 0xc6a673, 0xcfa969, 0xd1ad70, 
    0xcdb387, 0x64523f, 0x715e4a, 0x826b50, 0x8c7869, 0x947855, 0x998a7e, 0xb2a18f, 
    0x3e3b38, 0x45433f, 0x524e46, 0x5d564e, 0x6b6562, 0x767274, 0x828287, 0x929499, 
    0xa4a6a7, 0xb5b9b8, 0x5c7290, 0x627d98, 0x6c87a0, 0x768fa8, 0x8097ae, 0x8ba0b5, 
    0x95a9bb, 0x9fb2c1, 0xa9bac7, 0xb3c2cc, 0xbec9d0, 0xc7d2d7, 0xd3dade, 0xdde4e6 },

}


mechkoi.setparams= function(this, params)
  this.camx= params[2]
  this.camy= params[3]
  this.camz= params[4]
  this.zoom= params[5]
  this.cam_rotx = params[6]/360.0
  this.cam_roty = params[7]/360.0
  this.anim[0]= params[8]
  this.anim[1]= params[9]
  this.fishpos[0].x= params[10]
  this.fishpos[0].y= params[11]
  this.fishpos[0].z= params[12]

  this.fishpos[1].x= params[13]
  this.fishpos[1].y= params[14]
  this.fishpos[1].z= params[15]

  this.fade= params[16]
end


mechkoi.load = function(this)
  local function rgbBlend(c1, c2, alpha)
    alpha= alpha or 0
    alpha= flr(alpha)
    local r1,g1,b1= c1>>16&255, c1>>8&255, c1&255
    local r2,g2,b2= c2>>16&255, c2>>8&255, c2&255
    local r= r1 + ((r2-r1)*alpha>>8)
    local g= g1 + ((g2-g1)*alpha>>8)
    local b= b1 + ((b2-b1)*alpha>>8)
    return (r<<16) | (g<<8) | b
  end

  local colors= this.colors1
  for i=0,7 do 
    local t= i*32
    this.palette[i]=    rgbBlend(colors[0],  colors[7],  t)
    this.palette[8+i]=  rgbBlend(colors[7],  colors[11], t)
    this.palette[16+i]= rgbBlend(colors[11], colors[16], t)
    this.palette[24+i]= rgbBlend(colors[16], colors[20], t)

    this.palette[32+i]= rgbBlend(colors[0],  colors[23], t)
    this.palette[40+i]= rgbBlend(colors[23], colors[27], t)
    this.palette[48+i]= rgbBlend(colors[27], colors[30], t)
    this.palette[56+i]= rgbBlend(colors[30], colors[20], t)
  end

--  this.packed_normal= fetch("mechkoi_normal.pod") -- 3x u8
--  this.packed_envmap= fetch("mechkoi_envmap2.pod")
--  this.packed_color=  fetch("mechkoi_color3.pod")
  local temp= unpod( fetch("mechkoi_envmap2.pod") )
  this.envmap= userdata("u8", 256,256)
  this.envmap:copy(temp, true, 0,0, 128, 128, 256, 128)
  this.envmap:copy(temp, true, 0,128, 128, 128, 256, 128)
  this.envmap:copy(this.envmap, true, 0,256*128, 256,256,256,128)
  this.envmap:bor(64, true, 0,0, 256*256, 0,0,1)
  

  local temp= unpod( fetch("mechkoi_normal.pod") ) -- 3x u8
  this.normalmap= userdata("i16", 3, 256*256)
  this.normalmap:mutate("u8")
  this.normalmap:copy(temp, true, 0,0, 1, 1,2, 3*256*256)
  this.normalmap:mutate("i16")
  this.normalmap:sub(127, true, 0,0, 1, 0,1, 3*256*256)

--  this.normalenv= userdata("u8", 256,256)

--  store("test.png", temp)
  this.texture= unpod( fetch("mechkoi_color3.pod") )
--[[
  this.colortable= userdata("u8", 64, 64)
  for src=0,63 do
    for dst=0,63 do
      local c= dst & 31
      if (src<32) then
        c= c*(src+8)\(32+8)
      else
        c+=(src-32)
      end
      if (c>31) c=31

      this.colortable:set(dst, src, c|(dst & 32))
    end
  end
]]

  for i=0,4 do
    local filename= "morph_mech_koi_00"..i..".obj.pod"
    local temp= unpod( fetch(filename) )
    local mesh= mechkoi_meshload(temp, (i~=0))
    add( this.scene, mesh )
  end

  this.noise= userdata("u8", 1280,16)
  for y=0,15 do
    for x=0,1279 do
      this.noise:set(x,y, flr(rnd(y)+0.5))
    end
  end



  this.backface_tmp= userdata("f64", 2, 2000*4)  -- TODO: allocate global  
  this.backface_visidx= userdata("f64", 2000+1, 1)  -- TODO: allocate global  
  this.backface_quadidx= userdata("i32", 2000*4, 1)
--  backface_tmpidx= userdata("i32", 1000*4, 1)
end

mechkoi.init = function(this)
  endless_zoom:deinit()
  pal()
  fillp()

    poke2(0x5530, 0)
    poke2(0x5532, 0)
    poke2(0x5534, 255)
    poke2(0x5536, 255)

  for src=0,63 do
    memset(0x8000 + src*64, src, 64)
    for dst=0,63 do

      local c= dst & 31
      if (src<32) then
        c= c*(src+8)\(32+8)
      else
        c+=(src-32)
      end
      if (c>31) c=31

      poke(0x9000 + src*64 + dst, c|(dst & 32))
    end
  end

end

mechkoi.deinit= function(this)
  this.backface_tmp= nil
  this.backface_visidx= nil
  this.backface_quadidx= nil

  this.normalenv= nil
  this.texture= nil
  this.envmap= nil
  this.normalmap= nil
end  

mechkoi.update= function(this)

  local function rgbScale(col, scale)
    local r= flr(col >> 16 & 0xff) * scale >> 8
    local g= flr(col >> 8 & 0xff)  * scale >> 8
    local b= flr(col & 0xff)       * scale >> 8
    if (r>255) r=255
    if (g>255) g=255
    if (b>255) b=255
    return (r<<16) + (g<<8) + b
  end
  
  poke(0x5508, 127,63,63,0)--

  local l1= flr(this.fade*2.56)
  local colors= this.palette
  for i=0,63 do
    pal(   i, rgbScale(colors[i], l1), 2)
  end
end


mechkoi.draw = function(this)
 
  -- 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)
    local cnt= src:height()

    dst:copy(src, true, 0,0, 3, 3, 8, cnt)  -- dst.xy = src.xy
    dst:mul(this.zoom, true, 0,0, 1, 0, 8, cnt) -- dst.x*= fov
    dst:mul(this.zoom, true, 0,1, 1, 0, 8, cnt) -- dst.y*= fov
    dst:div(dst,  true, 2,0, 1, 8, 8, cnt) -- dst.x/= z
    dst:div(dst,  true, 2,1, 1, 8, 8, cnt) -- dst.y/= 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(uv, 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

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

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

  --  backface_tmp:mutate("f64", 2, 1000*4)  -- mutate back

    -- pick 4 xy-coords from every polygon
    local tmp= userdata("f64", 2, 1000*4)  -- 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
    vtx:take(mesh.quad_indices, tmp, 0,0, 2, 1,2, cnt*4) -- copy xy for quad

  --  v[0].x = ((v[0].x-v[2].x)*(v[1].y-v[3].y)) 
  --         - ((v[0].y-v[2].y)*(v[1].x-v[3].x))

    tmp:sub(tmp, true, 2*2,   0*2,   4, 4*2, 4*2, cnt) -- v[0].xy-= v[2].xy
  --  tmp:sub(tmp, true, 3*2,   1*2,   2, 4*2, 4*2, cnt) -- v[1].xy-= v[3].xy
    tmp:mul(tmp, true, 1*2+1, 0*2,   1, 4*2, 4*2, cnt) -- v[0].x*= v[1].y
    tmp:mul(tmp, true, 1*2,   0*2+1, 1, 4*2, 4*2, cnt) -- v[0].y*= v[1].x
    tmp:sub(tmp, true, 1,     0,     1, 4*2, 4*2, cnt) -- v[0].x-= v[0].y
    tmp:copy(tmp,true, 0,     0,     1, 4*2, 1,   cnt) -- v[0],v[4],v[8],v[12],...
    tmp:sgn(tmp, true, 0,     0,     1, 1,   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)

    -- visible face indices are positive,
    -- backfacing polys have negative index
    -- after sorting all visible polygon indices are at the front
    tmp:mutate("f64", 1, cnt) -- tmp,  true, 0,0, 1, 3,1, cnt) -- x<0:-1 x>=0:+1
    tmp:sort(0,true)

    -- find first negative index
    local index= find_binary(tmp, cnt)
    tmp:mutate("f64", 1, index) -- tmp,  true, 0,0, 1, 3,1, cnt) -- x<0:-1 x>=0:+1
    local vis= tmp:convert("i32") -- mutate("f64", cnt, 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,  0, visible<<5,  8,  1,8, index*4)

    return visible+index
  end


  -- some movement for the object
  local rot1= matrixRotY(this.cam_roty)
  local rot2= matrixRotX(this.cam_rotx)
  local obj = rot2:matmul(rot1)
  local cam = matrixTranslation(this.camx, this.camy, this.camz)
--  cam = matrixMul(cam, obj)
  obj:matmul(cam, cam)

  for y=0,134 do
    local offsety1= y/82
    local offsety2= y/68
    local intensity=  flr(
                      9
                    + sin(this.cam_rotx+offsety1)*4.0 
                    + cos(this.cam_roty+offsety2)*3.0
                    )
    if (intensity<0) intensity=0
    if (intensity>15) intensity=15
    local offsetx= (rnd(1024)\1)&1023
    get_display():copy(this.noise, true, intensity*1280+offsetx, y*240, 240, 0, 0, 1)    
  end
  
-- prepare normal map
  -- transform normals r,g,b in i16
--  local temp2= userdata("i16", 3, this.normalmap:height())
  local mat1= userdata("i16", 3, 1)
  local mat2= userdata("i16", 3, 1)
--  local mat3= userdata("i16", 3, 1)
  local index= userdata("i32", 256, 256)

  mat1:set(0,0, obj[0]*127, obj[4]*127, obj[8]*127)
  mat2:set(0,0, obj[1]*127, obj[5]*127, obj[9]*127)
--  mat3:set(0,0, obj[2]*127, obj[6]*127, obj[10]*127)

--  temp2:mutate("u8")
  index:mutate("u8")

  local temp1= userdata("i16", 3, 256*256)
  local temp2= userdata("i16", 3, 256*256)
  this.normalmap:mul(mat1, temp1, 0,0, 3, 0,3, 256*256)
  this.normalmap:mul(mat2, temp2, 0,0, 3, 0,3, 256*256)
--  temp1:add(temp2, true, 0,0, 3, 3,3, this.normalmap:height())
--  this.normalmap:mul(mat3, temp2, 0,0, 3, 0,3, this.normalmap:height())
  temp1:add(temp1, true, 1,0, 1, 3,3, 256*256)
  temp1:add(temp1, true, 2,0, 1, 3,3, 256*256)
  temp1:add(16384, true, 0,0, 1, 0,3, 256*256)
--  temp1:shr(  8, true, 0,0, 3*this.normalmap:height(), 0,0, 1)
  temp1:mutate("u8")
  index:copy(temp1, true, 1,0, 1, 6,4, 256*256)
  temp1:mutate("i16")

  this.normalmap:mul(mat2, temp2, 0,0, 3, 0,3, 256*256)
  temp2:add(temp2, true, 1,0, 1, 3,3, 256*256)
  temp2:add(temp2, true, 2,0, 1, 3,3, 256*256)
  temp2:add(16384, true, 0,0, 1, 0,3, 256*256)
--  temp2:shr(  8, true, 0,0, 3*this.normalmap:height(), 0,0, 1)

  temp2:mutate("u8")
  index:copy(temp2, true, 1,1, 1, 6,4, 256*256)
  temp2:mutate("i16")
  index:mutate("i32")

  local normalenv= userdata("u8", 256,256)

  this.envmap:take(index, normalenv, 0,0, 1, 1,1, 256*256)

  local total= 0

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

  for i=0,1 do
  
    --  for i=1,#scene do
    local fishfr= this.anim[i]
    local u= (fishfr%1)

    local frame1= flr(fishfr%4)+2
    local frame2= flr((fishfr+1)%4)+2
    local frame3= flr((fishfr+2)%4)+2
    local frame4= flr((fishfr+3)%4)+2

    local mesh= this.scene[1]
    local src0= this.scene[frame1].pos
    local src1= this.scene[frame2].pos
    local src2= this.scene[frame3].pos
    local src3= this.scene[frame4].pos
    local dst=  mesh.pos

    local temp_blend= userdata("f64", 3, src1:height())

    local u2= u*u
    local u3= u2*u

    local c0= 0.5 * (-u3 + 2*u2 - u)
    local c1= 0.5 * (3*u3 - 5*u2) + 1.0
    local c2= 0.5 * (-3*u3 + 4*u2 + u)
    local c3= 0.5 * (u3 - u2)

    src0:mul(c0, dst,         0,0, 3, 0,3,  src0:height())
    src1:mul(c1, temp_blend,  0,0, 3, 0,3,  src1:height())
    dst:add(temp_blend, true, 0,0, 3, 3,3,  src1:height())
    src2:mul(c2, temp_blend,  0,0, 3, 0,3,  src2:height())
    dst:add(temp_blend, true, 0,0, 3, 3,3,  src2:height())
    src3:mul(c3, temp_blend,  0,0, 3, 0,3,  src3:height())
    dst:add(temp_blend, true, 0,0, 3, 3,3,  src3:height())

    dst:add(this.fishpos[i], true, 0,0, 3, 0,3,  dst:height())      
--[[
    for i=0,src1:height()-1 do
      local p0= src0:row(i)
      local p1= src1:row(i)
      local p2= src2:row(i)
      local p3= src3:row(i)
      dst:set(
        0,i, 
        p0.x*c0 + p1.x*c1 + p2.x*c2 + p3.x*c3,
        p0.y*c0 + p1.y*c1 + p2.y*c2 + p3.y*c3,
        p0.z*c0 + p1.z*c1 + p2.z*c2 + p3.z*c3
-- linear
--        v1.x + (v2.x-v1.x)*u, 
--        v1.y + (v2.y-v1.y)*u, 
--        v1.z + (v2.z-v1.z)*u 

      )

    end
]]
--    print(i..": "..mesh.screen_pos:height())


    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)   

	 

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

--  end


  vis_faces:mutate("f64", 8*4, total)
  vis_faces:sort(2, true)

-- sort: 1.1%

   -- 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)

--  vis_faces:ceil(vis_faces, true, 1,2,   1, 5,5, total*4) -- copy 2

--  scrbuf:copy(4,true)
  set_spr(1, this.texture)
  set_spr(2, normalenv) 

  --  local poly= userdata("f64", 5*4, 1)
    for i=0,total-1 do
      if (    vis_faces:get(0+2,i)>0
          and vis_faces:get(8+2,i)>0
          and vis_faces:get(16+2,i)>0
          and vis_faces:get(24+2,i)>0) then

          mechkoi_draw_polygon( vis_faces, i*8*4, 4 )
      end
    end


--  get_display():copy(this.normalenv, true, 0,0, 240, 256, 240, 135)    
  --[[
  this.normalmap:mutate("u8")
  for y=0,134 do
    get_display():copy(this.normalmap, true, y*256*3,y*240, 1, 6,1, 240)
    get_display():shr(1, true, 0,0, 240*135,0,0,1)
  end
  this.normalmap:mutate("i16")
]]
end

