// --------------------------------------------------------------------
// wm3d - A Flash Molecular Viewer
//
// Copyright (c) 2011-2013, tamanegi (tamanegi@users.sourceforge.jp)
// All rights reserved.
//
// 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 wmutil;

import tinylib.Point3D;

/**
 Catmull-Rom method to draw smooth curve

 See http://www.t-pot.com/program/2_3rdcurve/index.html for detail.
 (written in japanese)

 General formula of smooth curve from x0 to x_(n+1) through x1 ... xn
 is 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 {
  /**
    constant cf0 of a cubic curve
    x(t) = cf0 + cf1*t + cf2*t^2 + cf3 * t^3
  **/
  @:isVar public var cf0( get, set ):Point3D;
    /**
      getter of `cf0`
    **/
    public function get_cf0():Point3D { return( cf0 ); }
    /**
      setter of `cf0`
    **/
    public function set_cf0( c:Point3D ):Point3D {
      cf0 = c.clone();
      return( cf0 );
    }
  /**
    coefficient cf1 of a cubic curve
    x(t) = cf0 + cf1*t + cf2*t^2 + cf3 * t^3
  **/
  @:isVar public var cf1( get, set ):Point3D;
    /**
      getter of `cf1`
    **/
    public function get_cf1():Point3D { return( cf1 ); }
    /**
      setter of `cf1`
    **/
    public function set_cf1( c:Point3D ):Point3D {
      cf1 = c.clone();
      return( cf1 );
    }
  /**
    coefficient cf2 of a cubic curve
    x(t) = cf0 + cf1*t + cf2*t^2 + cf3 * t^3
  **/
  @:isVar public var cf2( get, set ):Point3D;
    /**
      getter of `cf2`
    **/
    public function get_cf2():Point3D { return( cf2 ); }
    /**
      setter of `cf2`
    **/
    public function set_cf2( c:Point3D ):Point3D {
      cf2 = c.clone();
      return( cf2 );
    }
  /**
    coefficient cf3 of a cubic curve
    x(t) = cf0 + cf1*t + cf2*t^2 + cf3 * t^3
  **/
  @:isVar public var cf3( get, set ):Point3D;
    /**
      getter of `cf3`
    **/
    public function get_cf3():Point3D { return( cf3 ); }
    /**
      setter of `cf3`
    **/
    public function set_cf3( c:Point3D ):Point3D {
      cf3 = c.clone();
      return( cf3 );
    }

  // #####################################################################
  /**
    Constructor.
    If positions are given, Catmull-Rom curve associated with those points
    will be generated. See also generate() function.
  **/
  public function new( ?p0:Point3D,
                       ?p1:Point3D,
                       ?p2:Point3D,
                       ?p3:Point3D ) {
    generate( p0, p1, p2, p3 );
  }

  /**
    reset all data
  **/
  public function clear() {
    cf0 = new Point3D();
    cf1 = new Point3D();
    cf2 = new Point3D();
    cf3 = new Point3D();
  }

  /**
    generate Catmull-Rom curve. First OR last argument can be null, it is
    necessary in drawing curves on terminals. The return value is the
    `this` instance itself.
  **/
  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 used in calculating coefficients
  **/
  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 );
  }

  /**
    returns the position x(num) in the curve, where
    x(num) = cf0 + cf1*(num) + cf2*(num)^2 + cf3 * (num)^3.
  **/
  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 );
  }

  /**
    returns coefficients as a string
  **/
  public function toString():String {
    return( cf0.toString() + " " + cf1.toString() + " " +
            cf2.toString() + " " + cf3.toString() );
  }
}
