planes = {

  pitch= 0.0,
  yaw= 0.0,

  posx1= 0.0,
  posy1= 0.0,
  posz1= 0.0,

  posx2= 0.0,
  posy2= 0.0,
  posz2= 0.0,

  flash1= 0,
  flash2= 0,

  org_texture= {},
  texture= {},
  particles={ {},{} },
  planes= {},

-- packed data
--  packed_texture= {}, -- [1] and [2] are used
--  packed_flare,

  palette= { [0]= 
    0x000000, 0x000001, 0x000102, 0x000105, 0x000207, 0x00020a, 0x00030d, 0x00040f, 
    0x010513, 0x010717, 0x010819, 0x01081a, 0x010b20, 0x010c22, 0x020e25, 0x02122b, 
    0x031532, 0x031735, 0x041838, 0x041b3c, 0x051e42, 0x062146, 0x062249, 0x07234b, 
    0x07254e, 0x082751, 0x082853, 0x092955, 0x092b59, 0x0a2c5b, 0x0a2d5e, 0x0b3162, 
    0x0c3265, 0x0c3368, 0x0c346a, 0x0d356c, 0x0d366e, 0x0d3973, 0x0e3d7c, 0x0f4285, 
    0x10468c, 0x104790, 0x104893, 0x114c99, 0x114d9c, 0x114e9f, 0x114fa1, 0x1252a5, 
    0x1356ae, 0x155ebd, 0x1b68cd, 0x2170da, 0x2978e4, 0x418af1, 0x5a9af6, 0x71a7f5, 
    0x82b2f6, 0x94bdf7, 0xa6c8f8, 0xb8d3fa, 0xc9defb, 0xdbe9fc, 0xedf4fd, 0xffffff,

    0x000000, 0x010000, 0x020001, 0x030002, 0x050003, 0x070004, 0x0a0006, 0x0d0008, 
    0x0f000a, 0x13010d, 0x180111, 0x200118, 0x220119, 0x23021b, 0x25021c, 0x2b0221, 
    0x300325, 0x330328, 0x350329, 0x37042b, 0x3a042d, 0x3e0531, 0x420534, 0x460638, 
    0x49063a, 0x4b073c, 0x4e073e, 0x520842, 0x550944, 0x590947, 0x5c0a4a, 0x600b4d, 
    0x630b50, 0x650b51, 0x670c53, 0x680c53, 0x6a0c55, 0x6c0c57, 0x6e0c59, 0x730c5d, 
    0x7c0c63, 0x840c6b, 0x8e0b72, 0x960a79, 0x9e0980, 0xa10982, 0xa30884, 0xa60887, 
    0xab088b, 0xb1088f, 0xbd0a99, 0xce13a7, 0xe429ba, 0xf247c8, 0xf865d1, 0xf97dd5, 
    0xf98dda, 0xfa9ddf, 0xfbade4, 0xfcbeea, 0xfcceef, 0xfddef4, 0xfeeef9, 0xffffff
  }

}

local dst_w= 240
local dst_h= 135


function particle_move(particle)
  local path= particle.path
  local prev= flr(particle.segment)
  particle.segment+=particle.delta
  local key= flr(particle.segment)

  if (prev != key) then    
    if (key>=#path-1) then
--      particle_init(particle, paths)
      particle.segment= 0
      key= 0
    end
    local p1,p2= path[key+1], path[key+2]
    local dx,dy= p2[1]-p1[1], p2[2]-p1[2]
    particle.delta= particle.speed / sqrt( dx*dx + dy*dy )
  end

  local p1,p2= path[key+1],path[key+2]
  local t= particle.segment % 1.0
  particle.x= p1[1] + (p2[1]-p1[1])*t
  particle.y= p1[2] + (p2[2]-p1[2])*t

  return particle.x, particle.y
end


function particle_init(part, path)
--    local id= flr(rnd(#paths-1))
--    local path= paths[id]
    local p1,p2= path[1], path[2]
    local dx,dy= p2[1]-p1[1], p2[2]-p1[2]

    part.path= path
    part.segment= 0.0
    part.speed = (rnd(7)+5)/5.0
    part.delta= part.speed / sqrt( dx*dx + dy*dy )
    part.x= p1[1]
    part.y= p1[2]
end

planes.load = function(this)
    paths= {
    { [0]={{24,22},{24,80},{40,96},{62,96},{80,104},{95,104},{95,94}}, -- path 0: Path 0
          {{40,22},{40,80},{48,88},{63,88},{71,80},{73,80}}, -- path 1: Path 1
          {{14,112},{39,112},{56,128},{95,128},{112,111},{112,86}}, -- path 2: Path 2
          {{14,128},{39,128},{56,144},{95,144},{127,112},{127,94}}, -- path 3: Path 3
          {{14,144},{39,144},{55,160},{55,202}}, -- path 4: Path 4
          {{72,210},{72,176},{88,160},{119,160},{135,176},{143,176},{160,159}}, -- path 5: Path 5
          {{88,218},{88,200},{80,192},{80,176},{88,168},{103,168},{128,192},{144,192},{176,160},{176,150}}, -- path 6: Path 6
          {{110,232},{151,232},{175,208},{175,176},{192,159},{192,135}}, -- path 7: Path 7
          {{144,154},{144,86}} -- path 8: Path 8
    },

    { [0]={{40,226},{40,223},{56,207},{56,111}}, -- path 0: Path 0
          {{64,226},{64,144},{72,136},{72,126}}, -- path 1: Path 1
          {{80,226},{80,159},{88,151},{88,142}}, -- path 2: Path 2
          {{95,226},{95,176},{103,168},{103,158}}, -- path 3: Path 3
          {{150,72},{175,72},{199,56},{199,46}}, -- path 4: Path 4
          {{166,88},{192,88},{215,72},{215,46}}, -- path 5: Path 5
          {{150,104},{183,104},{231,72},{231,46}}, -- path 6: Path 6
          {{166,120},{175,120},{247,72},{247,46}}, -- path 7: Path 7
    }
  }

  -- number of particles: 16 per plane
  this.particles = { {}, {} }
  for i=1,16 do
    local p1= {}
    local p2= {}
    particle_init(p1, paths[1][i%#paths[1]])
    particle_init(p2, paths[2][i%#paths[2]])
    add(this.particles[1], p1)
    add(this.particles[2], p2)  
  end


--  texture is 16bit per pixel (required to avoid overflow when blending)
  for i=1,2 do
    this.org_texture[i]= unpod( fetch("texture"..i..".pod") )
    this.texture[i]= userdata("u8", 256,256)
  end

  this.flare= unpod( fetch("flare.pod") )
  this.flare:shr(1, true, 0,0, 32*32, 0,0, 1)
  this.planes[1]= userdata("u8", dst_w, dst_h)
  this.planes[2]= userdata("u8", dst_w, dst_h)
  this.planes[3]= userdata("u8", dst_w, dst_h)
end

planes.init = function(this)
end


function draw_plane(dstbuf, src_texture, dither, pitch, yaw, moveu, movev, posz)

  local texture= userdata("i16", 256,256)
  texture:mutate("u8")
  texture:copy(src_texture, true, 0,0, 1, 1,2, 256*256)
  texture:copy(src_texture, true, 256,1, 1, 1,2, 256*255)
  texture:copy(src_texture, true, 0,255*256*2+1, 1, 1,2, 256)
  texture:mutate("i16")

  -- create temporary buffers
  local index_u=  userdata("i32", dst_w)   -- u-part of final uv coordinate
  local index_v=  userdata("i32", dst_w)   -- v-part of final uv coordinate
  local index_uv= userdata("i32", dst_w) -- uv coordinates
  local temp2x2=  userdata("i16", dst_w*2, 1) -- temp. scanline with 2 neighboring pixels and 2 lines of pixels for filtering 2x2 bilinear
  local temp1=    userdata("i16", dst_w*2, 1) -- temp buffer 1
  local temp2=    userdata("i16", dst_w*2, 1) -- temp buffer 2
  local temp3=    userdata("i16", dst_w, 1) -- temp buffer 3

  local index_v8= userdata("u8", index_v)

  -- camera angle
  local cx, sx= cos(pitch), sin(pitch)
  local cz, sz= cos(yaw), sin(yaw)

  -- interpolate left and right screen border from top to bottom
  local leftx= ((cx-sx)*sz - cz) * 64
  local lefty= ((sx-cx)*cz - sz) * 64 
  local linez= (sx+cx) * 64

  local rightx= leftx + (cz*128)
  local righty= lefty + (sz*128)

  -- delta for left and right side
  local deltax= -sz*cx
  local deltay=  cz*cx
  local deltaz= -sx

  for y=0,134 do

    -- here some things left to optimize
    leftx+= deltax
    lefty+= deltay

    rightx+= deltax
    righty+= deltay

    linez+= deltaz -- leftz and rightz are identical

    -- scaling of texture u,v happens with this factor
    local z= posz*256*8 / linez

    local lu= leftx * z
    local lv= lefty * z

    local ru= rightx * z
    local rv= righty * z

    -- we step through the scanline from left to right [0..dstw-1]
    -- left starts at texture coordinate u0,v0
    -- deltau,deltav step forward one pixel
    -- texture has a fixed size of 256x256

    -- linear interpolate between left and right u,v
    -- use additional integer precision for bilinear filtering
--    index_u:mutate("i32")
    index_u[0]= lu + moveu
    index_u[dst_w-1]= ru + moveu
    index_u:lerp(0,dst_w-1)

    -- same for v
    index_v:mutate("i32")
    index_v[0]= lv + movev
    index_v[dst_w-1]= rv + movev
    index_v:lerp(0,dst_w-1)

    index_u:shr(8, index_uv, 0,0, dst_w) -- copy u
    index_uv:band(255, true, 0,0, dst_w) -- copy u
    -- extract subpixel coefficients for u-filtering
    index_u:band(255, true,  0,0, dst_w, 0,0,1)    -- fetch subpixel coefficients

    -- fetch subpixel coefficients for v-filtering
    index_v:mutate("u8")
    temp3:mutate("u8")
    temp3:copy(index_v,  true, 0,0, 1, 4,2, dst_w)    
    temp3:mutate("i16")

--    index_uv:mutate("u8") -- "mutate" is for free
    index_v:mutate("i32")
    index_uv:mutate("i32")
    index_v:band(0xff00, true, 0,0, dst_w)
    index_uv:bor(index_v, true, 0,0, dst_w) -- copy v
--    index_u:mutate("i32")

    -- fetch texel at positions given in "index_uv"
    -- always pick two horizontally neightbouring pixels
    -- [0..dst_w-1] are the upper texel-pairs of a 2x2 pixel block
    -- [dst_w..dst_w*2-1] are the lower texel-pairs of 2x2 pixel block
    texture:take(index_uv, temp2x2, 0,0, 2, 1,2, dst_w) -- fetch 2x2 pixels
--    texture:take(index_uv, temp2x2, 0,0, 2, 1,2, dst_w) -- fetch 2x2 pixels
    --  0  1    2  3   ...
    -- (c1 c3) (c2 c4)

    -- copy pixels to 16bit buffer so we can multiply without overflow
    temp1:mutate("u8")
    temp2:mutate("u8")
    temp2x2:mutate("u8")

    -- convert 8bit to 16bit
    temp1:copy(temp2x2, true, 0,0,    1, 4,4, dst_w)   -- fetch c1
    temp1:copy(temp2x2, true, 1,2,    1, 4,4, dst_w)   -- fetch c3

    temp2:copy(temp2x2, true, 2,0,    1, 4,4, dst_w)   -- fetch c2
    temp2:copy(temp2x2, true, 3,2,    1, 4,4, dst_w)   -- fetch c4
    temp2x2:mutate("i16")

    -- filtering in u:
    -- temp2[x] = temp1[x] + ((temp2[x]-temp1[x]) * temp3[x] >> 8)
    temp1:mutate("i16")
    temp2:mutate("i16")
    temp2:sub(temp1, true, 0,0, dst_w*2)
    
    temp2:mutate("i32")
    temp2:mul(index_u, true, 0,0, dst_w)
    temp2:mutate("i16")
    temp2:shr(8,     true, 0,0, dst_w*2)
    temp2:add(temp1, true, 0,0, dst_w*2)
    
    temp1:copy(temp2, true, 0,0, 1, 2,1, dst_w)
    temp2:copy(temp2, true, 1,0, 1, 2,1, dst_w)

    -- filtering in y:
    -- temp2[x] = temp1[x] + ((temp2[x]-temp1[x]) * temp3[x] >> 8)

    temp2:sub(temp1, true, 0,0, dst_w)
    temp2:mul(temp3, true, 0,0, dst_w)
    temp2:shr(8,     true, 0,0, dst_w)
    temp2:add(temp1, true, 0,0, dst_w)

    temp2:mutate("i32")
    temp2:add(dither, true, 0,0, dst_w>>1)

    temp2:mutate("u8")
    dstbuf:copy(temp2, true, 0,dst_w*y, 1, 2,1, dst_w)
    temp2:mutate("i16")

    dither= (dither>>16&0xffff)|(dither<<16&0xffff0000) -- swap dither from line to line
  end
end


planes.setparams= function(this, params)
  -- camera angles
  this.pitch= params[2]
  this.yaw=   params[3]

  this.posx1= params[4]*256.0
  this.posy1= params[5]*256.0
  this.posz1= params[6]

  this.posx2= params[7]*256.0
  this.posy2= params[8]*256.0
  this.posz2= params[9]

  this.flash1= params[10]
  this.flash2= params[11]
end

planes.draw= function(this)

  -- camera angles
  local pitch= this.pitch
  local yaw=   this.yaw

  local posx1= this.posx1
  local posy1= this.posy1
  local posz1= this.posz1

  local posx2= this.posx2
  local posy2= this.posy2
  local posz2= this.posz2


  for l=1,2 do
    local dst= this.texture[l]
    dst:copy(this.org_texture[l], true, 0,0, 256, 256,256, 256)

    local parts= this.particles[l]
    for i=1,#parts do
      local p= parts[i]
      local x,y= particle_move(p)
      dst:add(this.flare, true, 0,flr(y-16)*256+flr(x-16), 32, 32,256, 32)
    end
    dst:min(127, true, 0,0, 256*256, 0,0,1)
  end

  draw_plane(this.planes[1], this.texture[1], (2<<16), pitch, yaw, posx1, posy1, posz1)
  this.planes[1]:add(this.flash1, true, 0,0, dst_w*dst_h, 0,0,1)
  this.planes[1]:min(127, true, 0,0, dst_w*dst_h, 0,0,1)

  draw_plane(this.planes[2], this.texture[2], 2,       pitch, yaw*1.2, posx2, posy2, posz2)
  this.planes[2]:add(this.flash2, true, 0,0, dst_w*dst_h, 0,0,1)
  this.planes[2]:min(127, true, 0,0, dst_w*dst_h, 0,0,1)
--  draw_plane(planes[2], texture[2], 2,       pitch, yaw*1.2, posx2, posy2, posz+2)

  -- screen resolution


  this.planes[1]:min(this.planes[2], this.planes[3], 0,0, dst_w*dst_h, 0,0,1)
  this.planes[3]:shr(2, true, 0,0, dst_w*dst_h, 0,0,1)
--  this.planes[3]:min(63, true, 0,0, dst_w*dst_h, 0,0,1)

  this.planes[1]:max(this.planes[2], true, 0,0, dst_w*dst_h, 0,0,1)
  this.planes[2]:sub(this.planes[1], true, 0,0, dst_w*dst_h, 0,0,1)
  this.planes[1]:add(this.planes[3], true, 0,0, dst_w*dst_h, 0,0,1)
  this.planes[1]:min(63, true, 0,0, dst_w*dst_h, 0,0,1)
--  this.planes[2]:min(0, true, 0,0, dst_w*dst_h, 0,0,1)
--  this.planes[1]:sub(this.planes[2], true, 0,0, dst_w*dst_h, 0,0,1)
--  this.planes[1]:min(63, true, 0,0, dst_w*dst_h, 0,0,1)
  this.planes[2]:band(0x40, true, 0,0, dst_w*dst_h, 0,0,1)
  this.planes[1]:bor(this.planes[2], true, 0,0, dst_w*dst_h, 0,0,1)
  this.planes[1]:shr(1, true, 0,0, dst_w*dst_h, 0,0,1)

  -- copy result to screen
  -- convert from 16bit to 8bit output by copying every 2nd byte
  get_display():copy(this.planes[1], true, 0,0, dst_h*dst_w, 0,0, 1)

--  get_display():copy(planes[2], true, 0,0, dst_h*dst_w, 0,0, 1)


--[[
  for y=0,255 do
    get_display():copy(texture[2], true, y*256,y*480, 256, 0,0, 1)
    get_display():shr(1, true, 0,y*480, 480, 0,0, 1)
    get_display():min(31, true, 0,y*480, 480, 0,0, 1)
  end
]]
--  get_display():copy(texture[1], true, 0,0, 1, 2,1, 256)

  --[[
  pal(63,0xff0000, 2)
  pal(62,0xffff00, 2)
  local paths= paths2
  for i=1,#paths do
    local path= paths[i]
 
    color(63)
    for j=1,#path-1 do
      local p0= path[j]
      local p1= path[j+1]
      line(p0[1],p0[2], p1[1],p1[2])
    end
    for j=1,#path do
      local p0= path[j]
      pset(p0[1],p0[2], 62)
    end
    print(i, path[1][1]+1, path[1][2]+1)
 end
 ]]

end

planes.update= function(this)
  poke(0x5508,0x3f,0x3f,0x3f,0x3f)
  local f1= flr(this.flash1*2.56)
  for i=0,31 do
    pal(   i, this.palette[   i*2], 2)
    pal(32+i, this.palette[64+i*2], 2)
  end
end 

