/* ビジュアル情報論 課題2
   ベジエ曲面を描くプログラム
   Written by TOYAMA Sumio <sumio@is.s.u-tokyo.ac.jp> */
import java.awt.*;

// doubleでx座標とy座標を表現するクラス。
class DoublePoint{
  double x;
  double y;
    
  DoublePoint(double x, double y){
    this.x = x;
    this.y = y;
  }
}

// ベジエ曲面を実際に表示するためのクラス。
public class ViewBezierSurface extends java.applet.Applet{
  private int R = 2000; // 視点のz座標
  private final int U_KIZAMI = 5; //等パラメータ線の刻み幅
  private final int V_KIZAMI = 5; //同上
  // 回転軸の法線ベクトル。unitVecという名前なのは気のせいです...
  // nullの時はマウスはクリックされていない。
  private DoublePoint unitVec = null;
  private final double phi = 10.0 * Math.PI / 180.0; // 1回で10度回転
  private BezierSurface s = new BezierSurface(U_KIZAMI, V_KIZAMI);

  public void init(){
    setBackground(Color.white);
    setForeground(Color.black);
  }
  
  public boolean mouseDown(Event evt, int x, int y){
    // 回転軸は、マウスが押された点と原点を通る直線
    // に垂直で、xy平面上にある直線。
    int x1 = x - 250, y1 = y - 250; //クリックされた点を現在の座標系に移動。
    if(evt.shiftDown()){
      //SHIFT + クリックで、逆回転。
      //すなわち、原点に点対称の点が押されたと考える。
      unitVec = new DoublePoint((double)(-x1), (double)(-y1));
    }else{
      // 普通のクリックは正回転。
      unitVec = new DoublePoint((double)x1, (double)y1);
    }
    rotate();
    repaint();
    return true;
  }
  
  // Ctrl-Nキーで視点が遠ざかる。
  // Ctrl-Pキーで視点が近づく。
  // 1回の操作で2割5分変化する。
  // 初期値2000。
  public boolean keyDown(Event evt, int key){
    switch(key){
    case 14: // Ctrl+N
      R += (double)R * 0.25;
      break;
    case 16: // Ctrl+P
      if(R > s.MAG) R -= (double)R * 0.25; //Rを球の内部には置かない。
      break;
    }
    repaint();
    return true;
  }

  // 回転後の座標の計算。
  // 回転後の座標で曲面の点が格納されている配列をupdateする。
  // 回転軸は、必ず原点を通る直線で、xy平面上にある。
  // 法線ベクトルがx軸となす角度をθとする。
  // 実際に回転したい角度をφとする。(φは固定で10度)
  private void rotate(){
    double u = unitVec.x; // 軸の法線ベクトルのx座標
    double v = unitVec.y; // 同じくy座標
    double unitVecDist = Math.sqrt(u * u + v * v); // 法線ベクトルの大きさ
    double cosphi = Math.cos(phi), sinphi = Math.sin(phi);// sinφとcosφ
    double sinth = v / unitVecDist; // sin(θ)
    double costh = u / unitVecDist; // cos(θ)
    double x, y, z; //回転前の座標
    for(int patch = 0; patch < s.patchNum; patch++){
      for(int i = 0; i <= U_KIZAMI; i++){
	for(int j = 0; j<= V_KIZAMI; j++){
	  x = s.x[patch][i][j];
	  y = s.y[patch][i][j];
	  z = s.z[patch][i][j];
	  // 以下、変換行列との乗算。
	  // (cos^2θcosφ+sin^2θ, cosθsinθ(cosφ-1), cosθsinφ)
	  s.x[patch][i][j] = (costh * costh * cosphi + sinth * sinth) * x +
	    sinth * costh * (cosphi - 1.0) * y +
	    costh * sinphi * z;
	  // (sinθcosθ(cosφ-1), sin^2θcosφ+cos^2θ, sinθsinφ)
	  s.y[patch][i][j] = sinth * costh * (cosphi - 1.0) * x +
	    (sinth * sinth * cosphi + costh * costh) * y +
	    sinth * sinphi * z;
	  // (-cosθsinφ, -sinθsinφ, cosφ)
	  s.z[patch][i][j] = - sinphi * costh * x - 
	    sinphi * sinth * y + 
	    cosphi * z;
	}
      }
    }
  }
  
  // 透視投影後の座標計算。
  private Point perspectiveProjection(int patch, int i, int j){
    int projectedX, projectedY;
    projectedX = 
      (int)(((double)R * s.x[patch][i][j]) / ((double)R - s.z[patch][i][j]));
    projectedY = 
      (int)(((double)R * s.y[patch][i][j]) / ((double)R - s.z[patch][i][j]));
    return(new Point(projectedX, projectedY));
  }
  
  // 描画。
  public void paint(Graphics g){
    Point prevPoint, nextPoint;
    // 原点を(250, 250)に設定。(アプレットの広さは500x500を仮定している)
    g.translate(250, 250);
    g.setColor(Color.red);

    // 等パラメータ線を描く。
    for(int p = 0; p < s.patchNum; p++){
      // i is fixed.
      for(int i = 0; i <= U_KIZAMI; i++){
        prevPoint = perspectiveProjection(p, i, 0);
        for(int j = 0; (j + 1) <= V_KIZAMI; j++){
          nextPoint = perspectiveProjection(p, i, j + 1);
          g.drawLine(prevPoint.x, prevPoint.y, nextPoint.x, nextPoint.y);
          prevPoint = nextPoint;
        }
      }
      // j is fixed.
      for(int j = 0; j <= V_KIZAMI; j++){
        prevPoint = perspectiveProjection(p, 0, j);
        for(int i = 0; (i + 1) <= U_KIZAMI; i++){
          nextPoint = perspectiveProjection(p, i + 1, j);
          g.drawLine(prevPoint.x, prevPoint.y, nextPoint.x, nextPoint.y);
          prevPoint = nextPoint;
        }
      }
    }
  }
}

// ベジエ曲面上の点の座標を表すクラス。
class BezierSurface{
  public double x[][][]; // x(patch, u, v)
  public double y[][][]; // y(patch, u, v)
  public double z[][][]; // z(patch, u, v)

  // 球面の制御点のデータ。
  private final double PATCH[][][] = {
    {{0.000,   0.000,   1.000}, //up_side-1
     {0.552,   0.000,   1.000},
     {1.000,   0.000,   0.552},
     {1.000,   0.000,   0.000},
     {0.000,   0.000,   1.000},
     {0.552,   0.305,   1.000},
     {1.000,   0.552,   0.552},
     {1.000,   0.552,   0.000},
     {0.000,   0.000,   1.000},
     {0.305,   0.552,   1.000},
     {0.552,   1.000,   0.552},
     {0.552,   1.000,   0.000},
     {0.000,   0.000,   1.000},
     {0.000,   0.552,   1.000},
     {0.000,   1.000,   0.552},
     {0.000,   1.000,   0.000}}, // end of up_side-1
    
    {{-0.000,   0.000,   1.000}, //up_side-2
     {-0.000,   0.552,   1.000},
     {-0.000,   1.000,   0.552},
     {-0.000,   1.000,   0.000},
     {-0.000,   0.000,   1.000},
     {-0.305,   0.552,   1.000},
     {-0.552,   1.000,   0.552},
     {-0.552,   1.000,   0.000},
     {-0.000,   0.000,   1.000},
     {-0.552,   0.305,   1.000},
     {-1.000,   0.552,   0.552},
     {-1.000,   0.552,   0.000},
     {-0.000,   0.000,   1.000},
     {-0.552,   0.000,   1.000},
     {-1.000,   0.000,   0.552},
     {-1.000,   0.000,   0.000}}, //end of up_side-2
    
    {{-0.000,  -0.000,   1.000}, //up_side-3
     {-0.552,  -0.000,   1.000},
     {-1.000,  -0.000,   0.552},
     {-1.000,  -0.000,   0.000},
     {-0.000,  -0.000,   1.000},
     {-0.552,  -0.305,   1.000},
     {-1.000,  -0.552,   0.552},
     {-1.000,  -0.552,   0.000},
     {-0.000,  -0.000,   1.000},
     {-0.305,  -0.552,   1.000},
     {-0.552,  -1.000,   0.552},
     {-0.552,  -1.000,   0.000},
     {-0.000,  -0.000,   1.000},
     {-0.000,  -0.552,   1.000},
     {-0.000,  -1.000,   0.552},
     {-0.000,  -1.000,   0.000}}, //end of up_side-3
    
    {{0.000,  -0.000,   1.000}, //up_side-4
     {0.000,  -0.552,   1.000},
     {0.000,  -1.000,   0.552},
     {0.000,  -1.000,   0.000},
     {0.000,  -0.000,   1.000},
     {0.305,  -0.552,   1.000},
     {0.552,  -1.000,   0.552},
     {0.552,  -1.000,   0.000},
     {0.000,  -0.000,   1.000},
     {0.552,  -0.305,   1.000},
     {1.000,  -0.552,   0.552},
     {1.000,  -0.552,   0.000},
     {0.000,  -0.000,   1.000},
     {0.552,  -0.000,   1.000},
     {1.000,  -0.000,   0.552},
     {1.000,  -0.000,   0.000}}, //end of up_side-4
    
    {{0.000,   0.000,  -1.000},//down_side-1
     {0.000,   0.552,  -1.000},
     {0.000,   1.000,  -0.552},
     {0.000,   1.000,  -0.000},
     {0.000,   0.000,  -1.000},
     {0.305,   0.552,  -1.000},
     {0.552,   1.000,  -0.552},
     {0.552,   1.000,  -0.000},
     {0.000,   0.000,  -1.000},
     {0.552,   0.305,  -1.000},
     {1.000,   0.552,  -0.552},
     {1.000,   0.552,  -0.000},
     {0.000,   0.000,  -1.000},
     {0.552,   0.000,  -1.000},
     {1.000,   0.000,  -0.552},
     {1.000,   0.000,  -0.000}}, //end of down_side-1
    
    {{-0.000,   0.000,  -1.000}, //down_side-2
     {-0.552,   0.000,  -1.000},
     {-1.000,   0.000,  -0.552},
     {-1.000,   0.000,  -0.000},
     {-0.000,   0.000,  -1.000},
     {-0.552,   0.305,  -1.000},
     {-1.000,   0.552,  -0.552},
     {-1.000,   0.552,  -0.000},
     {-0.000,   0.000,  -1.000},
     {-0.305,   0.552,  -1.000},
     {-0.552,   1.000,  -0.552},
     {-0.552,   1.000,  -0.000},
     {-0.000,   0.000,  -1.000},
     {-0.000,   0.552,  -1.000},
     {-0.000,   1.000,  -0.552},
     {-0.000,   1.000,  -0.000}}, // end of down_side-2
    
    {{-0.000,  -0.000,  -1.000}, // down_side-3
     {-0.000,  -0.552,  -1.000},
     {-0.000,  -1.000,  -0.552},
     {-0.000,  -1.000,  -0.000},
     {-0.000,  -0.000,  -1.000},
     {-0.305,  -0.552,  -1.000},
     {-0.552,  -1.000,  -0.552},
     {-0.552,  -1.000,  -0.000},
     {-0.000,  -0.000,  -1.000},
     {-0.552,  -0.305,  -1.000},
     {-1.000,  -0.552,  -0.552},
     {-1.000,  -0.552,  -0.000},
     {-0.000,  -0.000,  -1.000},
     {-0.552,  -0.000,  -1.000},
     {-1.000,  -0.000,  -0.552},
     {-1.000,  -0.000,  -0.000}}, //end of down_side-3
    
    {{0.000,  -0.000,  -1.000}, //down_side-4
     {0.552,  -0.000,  -1.000},
     {1.000,  -0.000,  -0.552},
     {1.000,  -0.000,  -0.000},
     {0.000,  -0.000,  -1.000},
     {0.552,  -0.305,  -1.000},
     {1.000,  -0.552,  -0.552},
     {1.000,  -0.552,  -0.000},
     {0.000,  -0.000,  -1.000},
     {0.305,  -0.552,  -1.000},
     {0.552,  -1.000,  -0.552},
     {0.552,  -1.000,  -0.000},
     {0.000,  -0.000,  -1.000},
     {0.000,  -0.552,  -1.000},
     {0.000,  -1.000,  -0.552},
     {0.000,  -1.000,  -0.000}}}; // end of down_side-4

  // 次数
  public final int mOrder = 3;
  public final int nOrder = 3;
  // パッチの数
  public final int patchNum = PATCH.length;
  
  // 拡大率。
  final int MAG = 150;
  
  
  // 二項係数 n C r を求める
  //   n C r = n * (n-1)... / r!
  private double binomial(int n, int r){
    int j = n;
    double ans = 1.0;
    if(n == 0 || r == 0) return ans;
    for(int i = 1; i <= r; j--, i++){
      ans *= (double)j / (double)i;
    }
    return ans;
  }
  
  // Bernsteinの基底関数。
  // B^n_i(u) = n C i ・ u^i (1 - u)^{n - i}
  private double bernstein(int n, int i, double u){
    return (binomial(n, i) * Math.pow(u, i) * Math.pow(1.0 - u, n - i));
  }

  BezierSurface(int uKizami, int vKizami){
    // array has to been allocated Kizami-haba + 1.
    // because entry need both the start point (u or v = 0.0) and
    // the end point(u or v = 1.0).
    x = new double[patchNum][uKizami + 1][vKizami + 1];
    y = new double[patchNum][uKizami + 1][vKizami + 1];
    z = new double[patchNum][uKizami + 1][vKizami + 1];
    
    // 刻み幅。
    double uHaba = 1.0 / (double)uKizami;
    double vHaba = 1.0 / (double)vKizami;
    // 制御点の座標 (temporary use)
    double ctlX = 0.0, ctlY = 0.0, ctlZ = 0.0;
    double u = 0.0, v = 0.0; //曲線のパラメータ
    double coefficient = 0.0; // temporary use
    for(int p = 0; p < patchNum; p++){ // loop for patches.
      // loop for increasing indices of array.
      for(int uIdx = 0; uIdx <= uKizami; uIdx++){
        u = uHaba * uIdx;
        for(int vIdx = 0; vIdx <= vKizami; vIdx++){
          v = vHaba * vIdx;
          // Σi=0-->n Σj=0-->m .... (for summation)
          for(int i = 0, idx = 0; i <= nOrder; i++){
            for(int j = 0; j <= mOrder; j++, idx++){
              ctlX = PATCH[p][idx][0]; //制御点
              ctlY = PATCH[p][idx][1];
              ctlZ = PATCH[p][idx][2];
              coefficient = bernstein(nOrder, i, u) * bernstein(mOrder, j, v);
              x[p][uIdx][vIdx] += ctlX * coefficient * (double)MAG;
              y[p][uIdx][vIdx] += ctlY * coefficient * (double)MAG;
              z[p][uIdx][vIdx] += ctlZ * coefficient * (double)MAG;
            }
          }
        }
      }
    }
  }
}
