// very popular Catmull-Rom method to draw smooth curve
//
// see http://www.t-pot.com/program/2_3rdcurve/index.html for detail
// (written in japanese)
//
// smooth curve from x0 to x_(n+1) through x1 ... xn
// general formula: x(t) = a * t^3 + b * t^2 +c * t + d
//
//                                (  0  2  0  0 ) ( 0   )
// x_0(t) = 0.5 * ( 1 t t^2 t^3 ) |  0 -3  4 -1 | | P_0 |
//                                |  0  1 -2  1 | | P_1 |
//                                (  0  0  0  0 ) ( P_2 )
//
//                                (  0  2  0  0 ) ( P_(i-1) )
// x_i(t) = 0.5 * ( 1 t t^2 t^3 ) | -1  0  1  0 | | P_i     |
//                                |  2 -5  4 -1 | | P_(i+1) |
//                                ( -1  3 -3  1 ) ( P_(i+2) )
//
//                                (  0  2  0  0 ) ( P_(n-1) )
// x_n(t) = 0.5 * ( 1 t t^2 t^3 ) | -1  0  1  0 | | P_n     |
//                                |  1 -2  1  0 | | P_(n+1) |
//                                (  0  0  0  0 ) ( 0       )

class CubicCurve {
  // coefficient of a cubic curve
  // x(t) = cf0 + cf1*t + cf2*t^2 + cf3 * t^3
  public var cf0( __getCoeff0, __setCoeff0 ):Point3D;
  public var cf1( __getCoeff1, __setCoeff1 ):Point3D;
  public var cf2( __getCoeff2, __setCoeff2 ):Point3D;
  public var cf3( __getCoeff3, __setCoeff3 ):Point3D;

  public function new( ?p0:Point3D,
                       ?p1:Point3D,
                       ?p2:Point3D,
                       ?p3:Point3D ) {
    generate( p0, p1, p2, p3 );
  }

  public function clear() {
    cf0 = new Point3D();
    cf1 = new Point3D();
    cf2 = new Point3D();
    cf3 = new Point3D();
  }

  public function generate( ?p0:Point3D,
                            ?p1:Point3D,
                            ?p2:Point3D,
                            ?p3:Point3D ):CubicCurve {
    clear();
    // do nothing if it is impossible to construct curve
    if ( ( p1 == null || p2 == null ) || ( p0 == null && p3 == null ) ) {
      return( this );
    }
    if ( p0 == null ) {        // x_0(t)
      // 4th argument is dummy value
      cf0 = __calculate(  2.0,  0.0,  0.0,  0.0, p1, p2, p3 );
      cf1 = __calculate( -3.0,  4.0, -1.0,  0.0, p1, p2, p3 );
      cf2 = __calculate(  1.0, -2.0,  1.0,  0.0, p1, p2, p3 );
    } else if ( p3 == null ) { // x_n(t)
      cf0 = __calculate(  0.0,  2.0,  0.0,  0.0, p0, p1, p2 );
      cf1 = __calculate( -1.0,  0.0,  1.0,  0.0, p0, p1, p2 );
      cf2 = __calculate(  1.0, -2.0,  1.0,  0.0, p0, p1, p2 );
    } else {                   // x_i(t)
      cf0 = __calculate(  0.0,  2.0,  0.0,  0.0, p0, p1, p2, p3 );
      cf1 = __calculate( -1.0,  0.0,  1.0,  0.0, p0, p1, p2, p3 );
      cf2 = __calculate(  2.0, -5.0,  4.0, -1.0, p0, p1, p2, p3 );
      cf3 = __calculate( -1.0,  3.0, -3.0,  1.0, p0, p1, p2, p3 );
    }
    cf0.multiply( 0.5 );
    cf1.multiply( 0.5 );
    cf2.multiply( 0.5 );
    cf3.multiply( 0.5 );
    return( this );
  }

  private function __calculate( v0:Float,
                                v1:Float,
                                v2:Float,
                                v3:Float,
                                p0:Point3D,
                                p1:Point3D,
                                p2:Point3D,
                                ?p3:Point3D = null ):Point3D {
    var ret:Point3D = new Point3D( 0.0, 0.0, 0.0 );
    ret.add( Point3D.getMultiply( p0, v0 ) );
    ret.add( Point3D.getMultiply( p1, v1 ) );
    ret.add( Point3D.getMultiply( p2, v2 ) );
    if ( p3 != null ) ret.add( Point3D.getMultiply( p3, v3 ) );
    return( ret );
  }

  public function getVal( ?num:Float ):Point3D {
    var ret:Point3D = cf0.clone();
    if ( num == null ) return( ret );
    var num2:Float = num * num;
    var num3:Float = num2 * num;
    ret.add( Point3D.getMultiply( cf1, num ) );
    ret.add( Point3D.getMultiply( cf2, num2 ) );
    ret.add( Point3D.getMultiply( cf3, num3 ) );
    return( ret );
  }

  public function toString():String {
    return( cf0.toString() + " " + cf1.toString() + " " +
            cf2.toString() + " " + cf3.toString() );
  }

  public function __getCoeff0():Point3D { return( cf0 ); }
  public function __getCoeff1():Point3D { return( cf1 ); }
  public function __getCoeff2():Point3D { return( cf2 ); }
  public function __getCoeff3():Point3D { return( cf3 ); }
  public function __setCoeff0( c:Point3D ):Point3D {
    cf0 = c.clone();
    return( cf0 );
  }
  public function __setCoeff1( c:Point3D ):Point3D {
    cf1 = c.clone();
    return( cf1 );
  }
  public function __setCoeff2( c:Point3D ):Point3D {
    cf2 = c.clone();
    return( cf2 );
  }
  public function __setCoeff3( c:Point3D ):Point3D {
    cf3 = c.clone();
    return( cf3 );
  }
}
