tikz.within( '*' )

local cos = math.cos
local sin = math.sin
local pi  = math.pi

-- granularity of calculation
local fine = 10

-- factor the projection length is reduced
local fproj = 0.35

-- strength of the ellipse mediums
local s1 = 1.00
local s2 = 1.50
local s3 = 1.75

local e1 = ellipse{ at=p{ 0.00,  0.00 }, xradius=3.00, yradius=3.00 }
local e2 = ellipse{ at=p{ 0.40,  1.20 }, xradius=0.85, yradius=0.50 }
local e3 = ellipse{ at=p{ 0.00, -0.89 }, xradius=1.00, yradius=0.60 }

draw{ fill='black!08!white', line_width=1, e1 }
draw{ fill='black!16!white', line_width=1, e2 }
draw{ fill='black!24!white', line_width=1, e3 }

-- list of projection angles
local listphi =
{
       -1/4 * pi,
        2/4 * pi,
        5/4 * pi,
}

-- length of projection lines
local lenmaintop = 5.0
local lenmainbot = 6.5

for proji, phimain in ipairs( listphi )
do
       local lmain =
               line{
                       p{ cos( phimain ), sin( phimain ) } * -lenmaintop,
                       p{ cos( phimain ), sin( phimain ) } *  lenmainbot,
               }
       local phinorm = phimain - pi/2

       -- gets a point on the projection.
       -- dp: distance from projection center
       -- dv: projection value
       function getpproj( dp, dv )
               return(
                       lmain.p2
                       + p{ cos( phinorm ) * dp,         sin( phinorm ) * dp         }
                       + p{ cos( phimain ) * dv * fproj, sin( phimain ) * dv * fproj }
               )
       end

       -- distance of projection line
       local ddis = 1 / fine

       -- list of all projection points
       local listp = { }

       for i = -3.5 * fine, 3.5 * fine
       do
               local p1i =
                       lmain.p1
                       + p{ cos( phinorm ) * ddis * i, sin( phinorm ) * ddis * i }
               local li = line{ p1i, p1i + ( lmain.p2 - lmain.p1 ) }
               local lv = 0
               local is1 = e1.intersectLine( li )

               if is1 and #is1 > 1
               then
                       local lis = line{ is1[ 1 ], is1[ 2 ] }
                       lv = lv + lis.length * s1
               end

               local is2 = e2.intersectLine( li )
               if is2 and #is2 > 1
               then
                       local lis = line{ is2[ 1 ], is2[ 2 ] }
                       lv = lv + lis.length * s2
               end

               local is3 = e3.intersectLine( li )
               if is3 and #is3 > 1
               then
                       local lis = line{ is3[ 1 ], is3[ 2 ] }
                       lv = lv + lis.length * s3
               end

               local pproj = getpproj( ddis * i, lv    )
               if i % ( 0.7 * fine ) == 0
               then
                       draw{
                               dotted, arrow, line_width=0.5,
                               line{ li.p1, getpproj( ddis * i, -0.20 ) },
                       }
                       draw{
                               dotted, line_width=0.5,
                               line{ li.p1, pproj },
                       }
               end
               table.insert( listp, pproj )
       end

       -- draws the projection screen
       local lenlproj = 8
       draw{
               line{
                       getpproj(  lenlproj / 2, 0 ),
                       getpproj( -lenlproj / 2, 0 ),
               }
       }

       -- draws the projection curve
       draw{ line_width=1, polyline{ table.unpack( listp ) } }
end