// 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.display.BitmapData;
import flash.display.MovieClip;

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 >();
    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 ) );
    // 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 o = ( quality + 1 ) * 3;
    var n = 3 + quality;
    for ( i in 0 ... qr3 ) {
      var toi = o + i;
      var toi1 = (i==qr3-1) ? o : o + 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 __drawDoubleColoredMovieClip( color0:Int,
                                                 color1:Int,
                                                 ?size:Int = 64 ):MovieClip {
    var mc:MovieClip = new MovieClip();
    mc.graphics.beginFill( color0 );
    mc.graphics.drawRect( 0, 0, size / 2, size );
    mc.graphics.endFill();
    mc.graphics.beginFill( color1 );
    mc.graphics.drawRect( size / 2, 0, size / 2, size );
    mc.graphics.endFill();
    return( mc );
  }

  public function createDashedDoubleColoredTexture( c:Context3D,
                                                    d:Int,
                                                    color0:Int,
                                                    color1:Int,
                                                    size:Int = 256 ):Void {
    var mc:MovieClip = __drawDoubleColoredMovieClip( color0, color1, size );
    var bd:BitmapData = new BitmapData( size, size, true, 0x00FFFFFF );
    bd.draw( mc );
    // strange magic number appears ...
    var ebw:Int = Std.int( 0.8 * bd.width / ( d * 2 + 1 ) );
    var offset:Int = Std.int( 0.1 * bd.width );
    for ( i in 0 ... d ) {
      var myx:Int = ebw * ( 2 * i + 1 );
      var mynextx:Int = ebw * 2 * ( i + 1 );
      for ( j in myx ... mynextx ) {
        for ( k in 0 ... bd.height ) {
          bd.setPixel32( j + offset, k, 0x00FFFFFF );
        }
      }
    }
    texture = c.createTexture( size, size, flash.display3D.Context3DTextureFormat.BGRA, false );
    texture.uploadFromBitmapData( bd );
  }

  public function createDoubleColoredTexture( c:Context3D,
                                              color0:Int,
                                              color1:Int,
                                              ?size:Int = 64 ):Void {
    var mc:MovieClip = __drawDoubleColoredMovieClip( color0, color1, size );
    var bd:BitmapData = new BitmapData( size, size );
    bd.draw( mc );
    texture = c.createTexture( size, size, flash.display3D.Context3DTextureFormat.BGRA, false );
    texture.uploadFromBitmapData( bd );
  }

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