// RounderCylinder
//
// UV mapping(quality=0):
// 0   0.3     0.7   1
// +----+------+----+
// |   /|      |\   |
// |   \|______|/   |
// |   /|      |\   |
// |   \|      |/   |
// |   /+------+\   |
// |   \|      |/   |
// +----+------+--+-+

class RoundedCylinder extends Cylinder {
  public var quality( __getQuality, __setQuality ):Int;
    public function __getQuality():Int { return( quality ); }
    public function __setQuality( q:Int ) { quality = q; return( quality ); }

  public var height( __getHeight, __setHeight ):Float;
    public function __getHeight():Float { return( height ); }
    public function __setHeight( h:Float ):Float { height = h; return( height ); }
  public var radius( __getRadius, __setRadius ):Float;
    public function __getRadius():Float { return( radius ); }
    public function __setRadius( r:Float ):Float { radius = r; return( radius ); }

  // ##################################################################

  public function new( ?r:Float,
                       ?h:Float,
                       ?q:Int = 0 ) {
    super( 0.0, 0.0 );
    quality = q;

    var pl:Float = 0.3;
    var pr:Float = 0.7;

    height = h;
    radius = r;
    h *= 0.5;
    var qr3:Int = quality + 3;

    var pi2:Float = Math.PI * 2.0;

    var v:Array< Point3D > = new Array< Point3D >();
    // vertexes of a cylinder
    for ( i in 0 ... qr3 ) {
      var x:Float = pi2 * i / qr3;
      v.push( new Point3D( r * Math.sin( x ), -h, r * Math.cos( x ) ) );
    }
    for ( i in 0 ... qr3 ) {
      var x:Float = pi2 * i / qr3;
      v.push( new Point3D( r * Math.sin( x ), h, r * Math.cos( x ) ) );
    }
    // vertexes of spherical parts
    v.push( new Point3D( 0, -h-r, 0 ) );
    v.push( new Point3D( 0, h+r, 0 ) );

    var idx:Array< Int > = new Array< Int >();
    for ( i in 0 ... qr3 ) idx.push( i );           // qr3
    for ( i in 0 ... qr3 ) idx.push( qr3 + i );     // 2*qr3
    for ( i in 0 ... qr3 + 1 ) idx.push( __getRolled( i, qr3 ) );
                                                    // 3*qr3+1
    idx.push( qr3 * 2 );                            // 3*qr3+2
    for ( i in 0 ... qr3 + 1 ) idx.push( qr3 + __getRolled( i, qr3 ) );
                                                    // 4*qr3+3
    idx.push( qr3 * 2 + 1 );                        // 4*qr3+4
    for ( i in idx ) addVertexPos( v[i] );

    // UV
    var uh:Float = 1.0 / cast( qr3, Float );
    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 * i ) );
      uvmr.push( new UVCoord( pr, 1.0 - uh * i ) );
    }
    var uvb:Array< UVCoord > = new Array< UVCoord >();
    for ( i in 0 ... qr3 ) {
      uvb.push( new UVCoord( 0.0, 1.0 - ( 0.5 + i ) / qr3 ) );
    }
    var uvt:Array< UVCoord > = new Array< UVCoord >();
    for ( i in 0 ... qr3 ) {
      uvt.push( new UVCoord( 1.0, 1.0 - ( 0.5 + i ) / qr3 ) );
    }

    // Faces
    for ( i in 0 ... qr3 ) {
      var toi = i;
      var toi1 = (i==qr3-1) ? 0 : i + 1;
      addFace( new Face( toi, toi1, toi+qr3, uvml[i], uvml[i+1], uvmr[i] ) );
      addFace( new Face( toi+qr3, toi1, toi1+qr3, uvmr[i], uvml[i+1], uvmr[i+1] ) );
    }
    var tp = 3 * qr3 + 1;
    var bp = 2 * qr3;
    var ob:Point3D = new Point3D( 0, -h, 0 );
    for ( i in 0 ... qr3 ) {
      addFaceRecursively0( quality,
                           new Face( tp, bp + i + 1, bp + i,
                                     uvb[i], uvml[i+1], uvml[i] ), r, ob );
    }
    var tp = 4 * qr3 + 3;
    var bp = 3 * qr3 + 2;
    var ot:Point3D = new Point3D( 0, h, 0 );
    for ( i in 0 ... qr3 ) {
      addFaceRecursively0( quality,
                           new Face( tp, bp + i, bp + i + 1,
                                     uvt[i], uvmr[i], uvmr[i+1] ), r, ot );
    }
  }

  public override function addVertexNormals():Void {
    super.addVertexNormals();
    var qr3:Int = quality + 3;
    var h:Float = height * 0.5;
    var ob:Point3D = Point3D.getMultiply( direction, -h );
    var ot:Point3D = Point3D.getMultiply( direction, h );
    var numv:Int = verts.length - 2 * qr3;
    for ( i in 0 ... numv ) {
      var myind:Int = i + 2 * qr3;
      var v:Point3D = Point3D.getSub( verts[myind].pos, origin );
      var v1:Point3D = Point3D.getSub( v, ob );
      var v2:Point3D = Point3D.getSub( v, ot );
      // ad hoc
      if ( v1.sqabs() > v2.sqabs() ) {
        v2.normalize();
        verts[myind].normal = v2;
      } else {
        v1.normalize();
        verts[myind].normal = v1;
      }
    }
  }
}
