
voxel = {
  fov =  0.5,
  camx = 200,
  camy = 180,
  camh = 120,
  ang  = 0.0,
  pitch= 0.85,   -- initial pitch in radians (positive = look down)
  roll = 0.0,    -- initial roll in radians (positive = clockwise)
  horizon=56,
  height_scale=220.0,
  colors=0,
  flash=1.0,

-- runtime data
  heightmap= nil,
  colormap= nil,
  fogtable= nil,

  palette= { 
    [0]= { [0]= 0x070101, 0x120301, 0x1d0601, 0x260901, 0x2e0b04, 0x350d08, 0x3c0f0d, 0x441110, 
                0x4e140f, 0x58150a, 0x5f1706, 0x661802, 0x6f1901, 0x791b02, 0x841b06, 0x8f190a, 
                0x98150d, 0xa01710, 0xa51d12, 0xa92213, 0xab2614, 0xaf2c15, 0xb33417, 0xb73c19, 
                0xba421a, 0xbc461b, 0xbf4c1c, 0xc3541e, 0xc75c20, 0xcc6423, 0xcf6a24, 0xd16e25, 
                0xd57427, 0xd97b29, 0xdc8029, 0xde842a, 0xe1882b, 0xe38c2b, 0xe7922c, 0xeb9a2d, 
                0xefa02e, 0xf2a42f, 0xf4a831, 0xf6ac32, 0xf8b034, 0xfab436, 0xfcb93a, 0xfebe3f, 
                0xffc144, 0xffc447, 0xffc74d, 0xffc952, 0xffcb56, 0xffce5c, 0xffd163, 0xffd368, 
                0xffd66e, 0xffda75, 0xffde7b, 0xffe283, 0xffe689, 0xffec93, 0xfff39d, 0xfff9a7 },
				
		 { [0]= 0x000000, 0x08001a, 0x0a0020, 0x0c0025, 0x0d0127, 0x0d0229, 0x0e032b, 0x0e052f, 
		        0x0f0733, 0x0e0937, 0x0e0b3a, 0x100d3e, 0x131043, 0x1a1247, 0x22134b, 0x2a134e, 
				0x301351, 0x371354, 0x3e1257, 0x45115b, 0x4c0f5e, 0x520d61, 0x580c64, 0x5e0b66, 
				0x640e65, 0x6a1162, 0x6e1460, 0x71165e, 0x75185b, 0x791959, 0x7e1a57, 0x821854, 
				0x871750, 0x8c174d, 0x91184b, 0x961b49, 0x9a1f48, 0x9d2448, 0xa02848, 0xa32d48, 
				0xa63248, 0xaa3849, 0xad3d49, 0xb1444a, 0xb44a4a, 0xb74f4b, 0xba544b, 0xbd594c, 
				0xc15f4c, 0xc5654d, 0xc96d4d, 0xce744c, 0xd2794b, 0xd6804a, 0xdc8848, 0xe39145, 
				0xe99b41, 0xf1a63d, 0xf8b13a, 0xfdbd3e, 0xffc548, 0xffcc56, 0xffd366, 0xffe080 },
				
		 { [0]= 0x000000, 0x08001c, 0x0b0022, 0x0c0126, 0x0d0228, 0x0d0329, 0x0e032b, 0x0e042c, 
				0x0e042e, 0x0f0631, 0x100736, 0x11093a, 0x120b3e, 0x130f42, 0x131345, 0x121747, 
				0x111b48, 0x101f49, 0x0f234a, 0x0d274b, 0x0c2a4b, 0x092d4c, 0x06314c, 0x03344d, 
				0x01364e, 0x003851, 0x003a54, 0x003b58, 0x013b5c, 0x033c60, 0x053c64, 0x073b69, 
				0x0a3b6d, 0x0c3b72, 0x0e3b75, 0x103b79, 0x123a7e, 0x143982, 0x163887, 0x18378b, 
				0x1a3790, 0x1b3894, 0x1d3a98, 0x1e3d9c, 0x2041a0, 0x2146a4, 0x224caa, 0x2352af, 
				0x2459b4, 0x2560b9, 0x2667bf, 0x286ec4, 0x2974c9, 0x2b7bcf, 0x2e82d5, 0x3089dc, 
				0x3391e4, 0x3599eb, 0x38a2f3, 0x3babfa, 0x3cb4fe, 0x3cbcfe, 0x3ac3fb, 0x37cbf4 },
				
		 { [0]= 0x000000, 0x00020a, 0x000411, 0x000616, 0x00081a, 0x000a1e, 0x000c21, 0x000f24, 
				0x001327, 0x011829, 0x011e2b, 0x02232d, 0x03282e, 0x052e31, 0x093434, 0x0c3a36, 
				0x0f4038, 0x114537, 0x114834, 0x114b30, 0x114d2b, 0x114e27, 0x114f23, 0x11501f, 
				0x11521b, 0x125517, 0x125815, 0x135c14, 0x146114, 0x156514, 0x156914, 0x166e13, 
				0x177213, 0x177612, 0x177a11, 0x157f0f, 0x12840b, 0x118908, 0x128f04, 0x179401, 
				0x209900, 0x289c00, 0x30a100, 0x38a500, 0x3fa900, 0x46ad00, 0x4fb200, 0x59b702, 
				0x63bc04, 0x6bc106, 0x71c507, 0x78c909, 0x7fce0a, 0x86d20a, 0x8cd70a, 0x94dd0a, 
				0x9ce309, 0xa5e909, 0xaef00a, 0xb9f80d, 0xc2fd12, 0xc9ff1b, 0xcfff24, 0xd6ff30 },
				
  },  

  packed_colormap= nil,
  packed_heightmap= nil,

  turn_speed=0.020,
  move_speed=1.00,
  height_speed=2.0,
  pitch_speed=0.0015,    -- radians per frame (~0.86 deg/frame)
  roll_speed=0.0015     -- radians per frame

}


voxel.setparams= function(this, params)
  this.fov =  params[2]*3.141/180.0
  this.camx = params[3]
  this.camy = params[4]
  this.camh = params[5]
  this.ang  = params[6]*3.141/180.0
  this.pitch= params[7]*3.141/180.0
  this.roll = params[8]*3.141/180.0
  this.horizon=params[9]
  this.height_scale=params[10]
  this.colors=flr(params[11])&3
  this.flash=params[12]
end


voxel.load= function(this)
  this.fogtable= userdata("u8", 256,256)
  for c=0,63 do
    for shade=0,127 do
      local t= 127-shade*shade/128 -- 255 - flr(shade*2)

      local best= c*t/128
      this.fogtable:set(c, shade, best)
    end
  end

  this.heightmap= unpod( fetch("voxelheight.pod") )
  this.colormap=  unpod( fetch("voxelcol.pod") )
end


voxel.init= function(this)
end


voxel.update= function(this)
  local colors= this.palette[this.colors]
  local flash = this.flash
  for i = 0, 63 do
    local c = colors[i]
    local r = ((c >> 16 & 0xff)*flash)//1
    local g = ((c >> 8 & 0xff)*flash)//1
    local b = ((c & 0xff)*flash)//1
    if (r>255) r=255
    if (g>255) g=255
    if (b>255) b=255
    pal(i, (r << 16) | (g << 8) | b, 2)
  end
end


voxel.draw= function(this)

  assert(this.heightmap ~= nil, "heightmap nil!")
  assert(this.colormap  ~= nil, "colormap nil!")
  assert(this.fogtable  ~= nil, "fogtable nil!")

  local fov =  this.fov
  local camx = this.camx
  local camy = this.camy
  local ch =   this.camh
  local ang  = this.ang
  local pitch= this.pitch   -- initial pitch in radians (positive = look down)
  local roll = this.roll    -- initial roll in radians (positive = clockwise)

  local cols= 240

  -- roll: per-column horizon offset = tan(roll) * (col_x - screen_center)
  local tan_r = math.sin(roll) / math.cos(roll)

  -- Inline roll offset DDA (eliminates roll_offsets table)
  local tan_p = math.sin(pitch) * 55 / math.cos(pitch)
  local hz = this.horizon - tan_p - cols/4 * tan_r
  local hs= -this.height_scale * 256.0

  local index_u= userdata("i32", 200)
  local index_v= userdata("i32", 200)
  local index_uv= userdata("i32", 200)
  local heights= userdata("i32", 200)
  local colors= userdata("u8", 200)
  local lookup= userdata("i32", 200)
  local divd= userdata("i32", 200)

  local cx= flr(camx*256.0)
  local cy= flr(camy*256.0)
  
  local steps= 190

  for i=0,steps do
    local shade= flr(i*255/steps)-128
    --flr(i*255/steps)-128
    if (shade<0) shade=0
    shade= shade*shade//128
    if (shade>127) shade=127
    lookup[i]= shade<<8
  end

  -- precalc depth scaling table
  local z1= 40*256 -- far
  local prevz= z1
  local dz= 256
  local prevy= flr(128 * 200 * 256 / z1)
  local step= 0
  while (step<steps) do
    local y = flr(128 * 200 * 256 / z1)
    if (y~=prevy) then
      prevy=y
      divd[step]= z1
      step+=1
--      dz= (z1-prevz)/4
      prevz= z1
    end
    if (step<30) then 
      z1+=256*2
    else
      z1+=256
    end
  end 

--  divd:lerp(0,199)  

  local a1,a2= ang-fov, ang+fov
  local dirx = math.cos(a1) * 1024.0
  local diry = math.sin(a1) * 1024.0
  local ddirx= math.cos(a2) * 1024.0
  local ddiry= math.sin(a2) * 1024.0
  ddirx= (ddirx-dirx)*2/cols
  ddiry= (ddiry-diry)*2/cols

  local prevstart=0
  for x=0, cols-1, 2 do
    -- linear interpolate between angles because we have a planar screen
    dirx+=ddirx
    diry+=ddiry

--    index_u:copy(dirx, true, 0,0, steps, 0,0,1)
    divd:mul(dirx, index_u, 0,0, steps, 0,0, 1)
    index_u:shr(11, true, 0,0, steps, 0,0, 1)
    index_u:add(cx, true, 0,0, steps, 0,0, 1)

    divd:mul(diry, index_v, 0,0, steps, 0,0, 1)
    index_v:shr(11, true, 0,0, steps, 0,0, 1)
    index_v:add(cy, true, 0,0, steps, 0,0, 1)

    index_u:mutate("u8")
    index_v:mutate("u8")
    index_uv:mutate("u8")
    index_uv:copy(index_u, true, 1,0, 1, 4,4, steps)
    index_uv:copy(index_v, true, 1,1, 1, 4,4, steps)
    index_uv:mutate("i32")
    index_u:mutate("i32")
    index_v:mutate("i32")

    -- fetch bytes from heightmap into i32
    heights:clear()
    heights:mutate("u8")
    this.heightmap:take(index_uv, heights, 0,0, 1, 1,4, steps)
    heights:mutate("i32")

    -- fetch colors into 2d lookup table for fogging
    lookup:mutate("u8")
    this.colormap:take(index_uv, lookup, 0,0, 1, 1,4, steps)
    lookup:mutate("i32")
    this.fogtable:take(lookup, colors, 0,0, 1, 1,1, steps)

    heights:sub(ch,   true, 0,0, 1, 0,1, steps)
    heights:mul(hs,   true, 0,0, 1, 0,1, steps)
    heights:div(divd, true, 0,0, steps, 0,0, 1)
    heights:add(hz,   true, 0,0, steps, 0,0, 1)

    local top = 135

    -- start from bottom until we hit the screen
    local start= prevstart
    while (heights[start] < top and start>0) do
      start-=1
    end
    
    while (heights[start] > top and start<steps) do
      start+=1
    end
    prevstart= start

    for i = start, steps-1 do
      local y= heights[i]
--      local y = hz + (height - ch) * -hs / d
      if y < top then
        line(x, y, x, top, colors[i])
        top = y
        if top <= 0 then break end
      end
    end

    hz = hz + tan_r  -- interpolate horizon
  end  -- column loop

  for x=0,239,2 do
    get_display():copy(get_display(), true, x,x+1, 1, 240,240, 135)
  end

--  spr(this.heightmap, 0, 0)
end

