// Tube3D: 3-dimensional tube corresponding to Tube in VMD

class Tube3D extends Polygon {
  static public var trial_vec:Point3D = new Point3D( 1.15102, 0.18347, 0.4823 );

  public function new( pa:Array< Point3D >,
                       ?radius:Float = 1.0,
                       ?quality:Int = 0 ) {
    super();
    if ( pa.length <= 2 ) return;
    var p:Point3D = pa[1].clone().sub( pa[0] );
    var tr:Point3D = Tube3D.trial_vec.clone();
    tr.removeParallel( p );
    tr.normalize();
    var prevvec:Point3D = Point3D.getCross( tr, p );
    prevvec.normalize();
    __generateVertexes( pa[0], tr, prevvec, radius, quality );
    for ( i in 1 ... pa.length ) {
      // note: if i = 1, the same plane direction will be generated.
      p = pa[i].clone().sub( pa[i-1] );
      var ntr:Point3D = tr.clone();
      ntr.removeParallel( p );
      ntr.normalize();
      var unitvec:Point3D = Point3D.getCross( ntr, p );
      unitvec.normalize();
      if ( unitvec.getAngle( prevvec ) > Math.PI ) unitvec.multiply( -1.0 );
      __generateVertexes( pa[i], ntr, unitvec, radius, quality );
      prevvec = unitvec;
      tr = ntr;
    }
    var q3:Int = quality + 3;
    var offset:Int = verts.length;
    // Vertexes for Bottom and Top Faces
    for ( i in 0 ... q3 ) addVertexPos( verts[i].pos );
    for ( i in offset - q3 ... offset ) addVertexPos( verts[i].pos );

    // UV
    var _ul:Float = 0.1;
    var _ur:Float = 0.9;
    var uvb:Array< UVCoord > = new Array< UVCoord >();
    var uvb2:Array< UVCoord > = new Array< UVCoord >();
    var uvt:Array< UVCoord > = new Array< UVCoord >();
    var uvt2:Array< UVCoord > = new Array< UVCoord >();
    var uh:Float = 1.0 / ( quality + 1 );
    for ( i in 0 ... quality + 2 ) {
      if ( i != quality ) {
        uvb.push( new UVCoord( 0.0, 1.0 - ( i + 0.5 ) * uh ) );
        uvt.push( new UVCoord( 1.0, 1.0 - ( i + 0.5 ) * uh ) );
      }
      uvb2.push( new UVCoord( _ul, 1.0 - i * uh ) );
      uvt2.push( new UVCoord( _ur, 1.0 - i * uh ) );
    }

    var l:Float = 0.8 / ( pa.length - 1 );
    var h:Float = 1.0 / q3;
    var uvs:Array< UVCoord > = new Array< UVCoord >();
    var uvp:Array< UVCoord > = new Array< UVCoord >();
    for ( i in 0 ... q3 + 1 ) uvp.push( new UVCoord( 0.1, h * i ) );
    for ( i in 1 ... pa.length ) {
      var px = 0.1 + l * i;
      uvs = [];
      for ( j in 0 ... q3 + 1 ) uvs.push( new UVCoord( px, h * j ) );
      for ( j in 0 ... q3 ) {
        var basem:Int = q3 * ( i - 1 ) + j;
        var base:Int = q3 * i + j;
        var basem1 = q3 * ( i - 1 );
        var base1 = q3 * i;
        basem1 += ( j == q3 - 1 ) ? 0 : j + 1;
        base1 += ( j == q3 - 1 ) ? 0 : j + 1;
        addFace( new Face( basem, basem1, base, uvp[j], uvp[j+1], uvp[j] ) );
        addFace( new Face( base, basem1, base1, uvs[j], uvp[j+1], uvs[j+1] ) );
      }
      uvp = [];
      for ( uv in uvs ) uvp.push( uv );
    }

    // Faces
    //// bottom and top faces
    p = pa[1].clone().sub( pa[0] );
    var p0:Point3D = Point3D.getSub( verts[1].pos, verts[0].pos );
    var p1:Point3D = Point3D.getSub( verts[2].pos, verts[0].pos );
    var flag:Bool = Point3D.getCross( p0, p1 ).dot( p ) < 0;
    for ( i in 0 ... quality + 1 ) {
      if ( !flag ) {
        addFace( new Face( offset, offset+i+2, offset+i+1,
                           uvb[i], uvb2[i+1], uvb2[i] ) );
      } else {
        addFace( new Face( offset, offset+i+1, offset+i+2,
                           uvb[i], uvb2[i], uvb2[i+1] ) );
      }
    }
    var i0 = pa.length - 2;
    var v0 = verts.length - q3;
    p = pa[i0].clone().sub( pa[i0+1] );
    var p0:Point3D = Point3D.getSub( verts[v0+1].pos, verts[v0].pos );
    var p1:Point3D = Point3D.getSub( verts[v0+2].pos, verts[v0].pos );
    var flag:Bool = Point3D.getCross( p0, p1 ).dot( p ) < 0;
    offset += q3;
    for ( i in 0 ... quality + 1 ) {
      if ( !flag ) {
        addFace( new Face( offset, offset+i+1, offset+i+2,
                           uvt[i], uvt2[i], uvt2[i+1] ) );
      } else {
        addFace( new Face( offset, offset+i+2, offset+i+1,
                           uvt[i], uvt2[i+1], uvt2[i] ) );
      }
    }
  }

  private function __generateVertexes( o:Point3D,
                                       oux:Point3D,
                                       ouy:Point3D,
                                       r:Float,
                                       q:Int ):Void {
    var pi2:Float = Math.PI * 2.0;
    var ux:Point3D = Point3D.getMultiply( oux, r );
    var uy:Point3D = Point3D.getMultiply( ouy, r );
    for ( i in 0 ... q + 3 ) {
      var x:Float = pi2 * i / ( q + 3 );
      var np:Point3D = o.clone();
      np.add( Point3D.getMultiply( ux, Math.cos( x ) ) );
      np.add( Point3D.getMultiply( uy, Math.sin( x ) ) );
      addVertexPos( np );
    }
  }
}
