// Cylinder class
//
// # of Vertexes(quality=0): 6
// # of Faces(triangle): 8
//
// UV mapping(quality=0):
//  0 0.1         0.9 1
//  +-+------------+-+
//  | |            | |
//  | +------------+ |
//  | |            | |
//  |/+------------+\|
//  |\|            |/|
//  +-+------------+-+
//
//  Faces and UV positions:
//     0- 1- 2 - left          (0-1-2)
//     3- 4- 5 - middle-bottom (1-3-2)
//     6- 7- 8 - middle-bottom (3-4-2)
//     9-10-11 - center        (2-4-0)
//    12-13-14 - center        (4-5-0)
//    15-16-17 - middle-top    (0-5-1)
//    18-19-20 - middle-top    (5-3-1)
//    21-22-23 - right         (5-4-3)

import flash.display3D.Context3D;

class Cylinder extends Polygon {
  public function new( ?r:Float = 1.0,
                       ?h:Float = 1.0,
                       ?quality:Int = 0,
                       ?exclude_bottom:Bool = false,
                       ?exclude_top:Bool = false ) {
    super();
    if ( r == 0.0 || h == 0.0 ) return;
    var pl:Float = 0.1;
    var pr:Float = 0.9;

    h *= 0.5;
    var qr3:Int = quality + 3;
    var v:Array< Point3D > = new Array< Point3D >();
    var pi2:Float = Math.PI * 2.0;
    for ( i in 0 ... qr3 ) {
      var x:Float = pi2 * cast( i, Float ) / cast( qr3, Float );
      v.push( new Point3D( r * Math.sin( x ), -h, r * Math.cos( x ) ) );
    }
    for ( i in 0 ... qr3 ) {
      var x:Float = pi2 * cast( i, Float ) / cast( qr3, Float );
      v.push( new Point3D( r * Math.sin( x ), h, r * Math.cos( x ) ) );
    }
    var idx:Array< Int > = new Array< Int >();
    if ( !exclude_bottom ) {
      idx.push( 0 );
      idx.push( 1 );
      idx.push( 2 );
      // bottom face; flat shading
      for ( i in 0 ... quality ) {
        idx.push( 2 );
        idx.push( __getRolled( i + 3, qr3 ) );
        idx.push( __getRolled( i + 4, qr3 ) );
      }
    }
    // middle faces; use vertex normal
    for ( i in 0 ... qr3 ) idx.push( __getRolled( i + 1, qr3 ) );
    for ( i in 0 ... qr3 ) idx.push( qr3 + __getRolled( i + 1, qr3 ) );
    if ( !exclude_top ) {
      // top face; flat shading
      idx.push( qr3 );
      idx.push( qr3 + 2 );
      idx.push( qr3 + 1 );
      for ( i in 0 ... quality ) {
        idx.push( qr3 + 2 );
        idx.push( qr3 + __getRolled( i + 4, qr3 ) );
        idx.push( qr3 + __getRolled( i + 3, qr3 ) );
      }
    }
    for ( i in idx ) addVertexPos( v[i] );

    // uv-coordinate
    var uh:Float = 1.0 / qr3;
    var uvl:Array< UVCoord > = new Array< UVCoord >();
    var uvr:Array< UVCoord > = new Array< UVCoord >();
    uvl.push( new UVCoord( 0.0, 1.0 - 0.5 * uh ) );
    uvr.push( new UVCoord( 1.0, 1.0 - 0.5 * uh ) );
    for ( i in 0 ... quality ) {
      uvl.push( new UVCoord( 0.0, 1.0 - ( 2.5 + cast( i, Float ) ) * uh ) );
      uvr.push( new UVCoord( 1.0, 1.0 - ( 2.5 + cast( i, Float ) ) * uh ) );
    }
    var uvml:Array< UVCoord > = new Array< UVCoord >();
    var uvmr:Array< UVCoord > = new Array< UVCoord >();
    for ( i in 0 ... qr3 + 1 ) {
      uvml.push( new UVCoord( pl, 1.0 - uh * cast( i, Float ) ) );
      uvmr.push( new UVCoord( pr, 1.0 - uh * cast( i, Float ) ) );
    }

    var offset = 0;
    if ( !exclude_bottom ) {
      for ( i in 0 ... quality + 1 ) {
        addFace( new Face( offset, offset+2, offset+1,
                           uvl[i==0?i:2], uvml[i+1], uvml[i] ) );
        offset += 3;
      }
    }
    var n = 3 + quality;
    for ( i in 0 ... qr3 ) {
      var toi = offset + i;
      var toi1 = (i==qr3-1) ? offset : offset + i + 1;
      addFace( new Face( toi, toi1, toi+n, uvml[i], uvml[i+1], uvmr[i] ) );
      addFace( new Face( toi+n, toi1, toi1+n, uvmr[i], uvml[i+1], uvmr[i+1] ) );
    }
    if ( !exclude_top ) {
      offset += 2 * qr3;
      for ( i in 0 ... quality + 1 ) {
        addFace( new Face( offset, offset+2, offset+1,
                           uvr[i==0?i:2], uvmr[i+1], uvmr[i] ) );
        offset += 3;
      }
    }
  }

  private function __getRolled( i:Int,
                                mx:Int):Int {
    if ( i >= mx ) return( i - mx );
    return( i );
  }
}
