// --------------------------------------------------------------------
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// --------------------------------------------------------------------

package tinylib.primitives;

// RounderCylinder
//
// UV mapping(quality=0):
// 0   0.3     0.7   1
// +----+------+----+
// |   /|      |\   |
// |   \|______|/   |
// |   /|      |\   |
// |   \|      |/   |
// |   /+------+\   |
// |   \|      |/   |
// +----+------+--+-+
//
// q=0: triangle prism (3)
// q=1: hexagonal prism (6)
// q=2: dodecagonal prism (12)
// q=3: tetracosagonal prism (24)
//       ...
// q=n: 3*(2^n) prism

/**
  Licorice in VMD
**/

class RoundedCylinder extends Cylinder {
  @:isVar public var quality( get, set ):Int;
    public function get_quality():Int { return( quality ); }
    public function set_quality( q:Int ) { quality = q; return( quality ); }

  @:isVar public var height( get, set ):Float;
    public function get_height():Float { return( height ); }
    public function set_height( h:Float ):Float { height = h; return( height ); }
  @:isVar public var radius( get, set ):Float;
    public function get_radius():Float { return( radius ); }
    public function set_radius( 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 q2:Int = Std.int(Math.pow( 2, quality ));
    var qr3:Int = 3 * q2;
    var pi2:Float = Math.PI * 2.0;

    var v:Array< Point3D > = new Array< Point3D >();
    // vertexes at cylinder moiety
    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 ) ) );
    }
    // end point 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 >();
    // cylinder part
    for ( i in 0 ... qr3 ) idx.push( i );           // totnum: qr3
    for ( i in 0 ... qr3 ) idx.push( qr3 + i );     // 2*qr3
    // sphere part 0
    for ( i in 0 ... 4 ) idx.push( __getRolled( i*q2, qr3 ) );
                                                    // 2*qr3+4
    // top
    idx.push( qr3 * 2 );                            // 2*qr3+5
    // sphere part 1
    for ( i in 0 ... 4 ) idx.push( qr3 + __getRolled( i*q2, qr3 ) );
                                                    // 2*qr3+6
    // top
    idx.push( qr3 * 2 + 1 );                        // 2*qr3+10
    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 ... 4 ) {
      uvb.push( new UVCoord( 0.0, 1.0 - i / 4 ) );
    }
    var uvt:Array< UVCoord > = new Array< UVCoord >();
    for ( i in 0 ... 4 ) {
      uvt.push( new UVCoord( 1.0, 1.0 - i / 4 ) );
    }

    // 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 = 2 * qr3 + 4; // top vertex
    var bp = 2 * qr3;
    var ob:Point3D = new Point3D( 0, -h, 0 );
    for ( i in 0 ... 3 ) {
      var q2i = q2 * i;
      var q2ip1 = q2 * ( i + 1 );
      addFaceRecursively0( quality + 1,
                           new Face( tp, bp + i + 1, bp + i,
                                     uvb[i], uvml[q2ip1], uvml[q2i] ), r, ob );
    }
    var tp = 2 * qr3 + 9; // top vertex
    var bp = 2 * qr3 + 5;
    var ot:Point3D = new Point3D( 0, h, 0 );
    for ( i in 0 ... 3 ) {
      var q2i = q2 * i;
      var q2ip1 = q2 * ( i + 1 );
      addFaceRecursively0( quality + 1,
                           new Face( tp, bp + i, bp + i + 1,
                                     uvt[i], uvmr[q2i], uvmr[q2ip1] ), r, ot );
    }
  }

  public override function addVertexNormals():Void {
    super.addVertexNormals();
    //var qr3:Int = quality + 3;
    var qr3:Int = 3 * Std.int(Math.pow( 2, quality ));
    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;
      }
    }
  }
}
