/*
 * $B%3%s%T%e!<%?%0%i%U%#%C%/%9FCO@(BI
 *    1 Bezier$B6JLL$r1#LL>C5n$r$7$FI=<($9$k!#(B
 *  Programmed by Yoshizumi TANAKA. 2000/2
 *
 * *Button*
 *     Initialize $B!D(B $B=i4|>uBV!J(B4x4 $B$N%a%C%7%e!K$KLa$7$^$9!#(B
 *           Draw $B!D(B $B@)8f%M%C%H$H(BBezier$B6JLL$NJQ49$r$7$^$9!#(B
 *
 * *Mouse*
 *  (1) $B6uGr$r$D$+$s$G%I%i%C%0$9$k$H2sE>$7$^$9!#(B
 *  (2) $B3J;RE@$r$D$+$s$G%I%i%C%0$9$k$H@)8fE@$N9b$5$rJQ99$G$-$^$9!#(B
 *
 */


import java.applet.*;
import java.awt.*;

public class BezierCurvedSurface extends Applet {
    Dimension appsize;
    int mouseX, mouseY;
    int mouseJob, tmp_i, tmp_j;
    boolean autoR, state;
    Panel p1 = new Panel();
    Button b3, b4;
    Mesh m;
    
    public void init() {
	setBackground(new Color(0, 0, 64));
	setForeground(Color.white);
	appsize = size();
    
	p1.setBackground(new Color(192, 192, 192));
	p1.setForeground(Color.black);
	p1.add(b3 = new Button("Initialize"));
	p1.add(b4 = new Button("Draw"));
    
	m = new Mesh(4, 4, 4, 4);
	m.Scale(200);
	m.setScreen(appsize.width, appsize.height);
	m.setViewPoint(500, 20, 30);
	m.setViewRefPoint(0, 0, 50);

	setLayout(new BorderLayout());
	add("North" , p1);
	add("South", m);
	resize(appsize);    
    } // end of BezierCurvedSurface.init
    
    public void paint(Graphics g) {
	m.calcRadian();
	if (state)
	    m.DrawBezierCurve(g, mouseJob);
	else
	    m.DrawMesh(g);
	showStatus("theta="+m.theta+", phi="+m.phi+", autoR="+autoR);
	// System.err.println("["+i+"] x =  "+m.pos[0][i].y);
    } //end of paint

    public boolean action(Event e, Object o) {
	if (e.target == b3) { // initialize
	    m.Init();
	    m.Scale();
	    repaint();
	    return true;
	} else if (e.target == b4) { // draw bezier curve
	    state = !state;
	    repaint();
	    return true;
	} else { return false; }
    }
    public boolean mouseDown(Event e, int x, int y) {
	double x_eps, y_eps;
    
	mouseJob = 0;
	mouseX = x;
	mouseY = y;
    
	x_eps = y_eps = 5.0;
	for(int i=0; i<m.x_size; i++)
	    for(int j=0; j<m.y_size; j++)
		if (Math.abs(m.prj[i][j].x - mouseX) <= x_eps &&
		    Math.abs(m.prj[i][j].y - mouseY) <= y_eps) {
		    mouseJob = 1;
		    tmp_i = i; tmp_j = j;
		    x_eps = Math.abs(m.prj[i][j].x - mouseX);
		    y_eps = Math.abs(m.prj[i][j].y - mouseY);
		}
	return true;
    }
    public boolean mouseDrag(Event e, int x, int y) {
	if (mouseJob == 0) {
	    m.theta -= x - mouseX;
	    m.phi   += y - mouseY;
	    if (m.theta > 180) m.theta -= 360;
	    if (m.theta < -180) m.theta += 360;
	} else if (mouseJob == 1) {
	    m.pos[tmp_i][tmp_j].z -= y - mouseY;
	}
	mouseX = x;
	mouseY = y;
	repaint();
	return true;
    }
    public boolean mouseUp(Event e, int x, int y) {
	return true;
    }
} // end of class BezierCurvedSurface


class Mesh extends Canvas {
    Point3 pos [][];
    Point3 prj [][];
    int width, height;
    int x_sc_mid, y_sc_mid;
    int x_size, y_size;
    int x_init, y_init;
    int x_max, y_max;
    int scale;
    double rand_rate = 0.15;
    double R, theta, phi;
    double ct, cp, st, sp;
    Point3 ViewRef = new Point3();
    
    public Mesh() { x_size = y_size = 0; }
    public Mesh(int ix, int iy, int mx, int my) {
	x_init = ix;
	y_init = iy;
	x_max = mx;
	y_max = my;
	pos = new Point3[x_max][y_max];
	prj = new Point3[x_max][y_max];
	for(int j=0; j<y_max; j++)
	    for(int i=0; i<x_max; i++) {
		pos[i][j] = new Point3();
		prj[i][j] = new Point3();
	    }
	Init();
    }
    public void Init() {
	double tmp;
	
	x_size = x_init;
	y_size = y_init;
	
	for(int i=0; i<x_size; i++) {
	    tmp = (double) (2*i - x_size + 1) / (x_size - 1);
	    for(int j=0; j<y_size; j++) {
		pos[i][j].x = tmp;
		pos[i][j].z = 0.0;
	    }
	}
	for(int j=0; j<y_size; j++) {
	    tmp = (double) (2*j - y_size + 1) / (y_size - 1);
	    for(int i=0; i<x_size; i++)
		pos[i][j].y = tmp;
	}
    }
    public void Scale(int n) {
	scale = n;
	Scale();
    }
    public void Scale() {
	for(int j=0; j<y_size; j++)
	    for(int i=0; i<x_size; i++) {
		pos[i][j].x *= scale;
		pos[i][j].y *= scale;
		pos[i][j].z *= scale;
	    }
    }
    public void setViewRefPoint(double vrx, double vry, double vrz) {
	ViewRef.x = vrx;
	ViewRef.y = vry;
	ViewRef.z = vrz;
    }
    public void setViewPoint(double r, double th, double ph) {
	final double RAD = Math.PI / 180.0;
	R = r;
	theta = th;
	phi = ph;
    }
    public void calcRadian() {
	final double RAD = Math.PI / 180.0;
	double th = theta * RAD;
	double ph =   phi * RAD;
	st = Math.sin(th);
	ct = Math.cos(th);
	sp = Math.sin(ph);
	cp = Math.cos(ph);
    }
    public void setScreen(int x, int y) {
	width = x;
	height = y;
	x_sc_mid = x/2;
	y_sc_mid = y/2;
    }
    Point3 oneThird(Point3 a, Point3 b) {
	Point3 p;
	double r;
	p = new Point3((2 * a.x + b.x) / 3,
		       (2 * a.y + b.y) / 3,
		       (2 * a.z + b.z) / 3);
	r = Math.sqrt(Math.pow(a.x - b.x, 2) +
		      Math.pow(a.y - b.y, 2));
	p.z += (Math.random()*2 - 1) * r * rand_rate;
	return p;
    }
    public void makeDetail() {
	if ((3 * x_size - 2 > x_max) ||
	    (3 * y_size - 2 > y_max)) return;
    
	int i, j;
	/* X $BJ}8~(B */
	for(i=0; i<y_size; i++)
	    for(j=x_size-1; j>=0; j--)
		pos[3*j][i] = pos[j][i];
	x_size = 3 * x_size - 2;
	for(i=0; i<y_size; i++)
	    for(j=0; j<x_size-1; j+=3) {
		pos[j+1][i] = oneThird(pos[j][i], pos[j+3][i]);
		pos[j+2][i] = oneThird(pos[j+3][i], pos[j][i]);
	    }    
	/* Y $BJ}8~(B */
	for(i=0; i<x_size; i++)
	    for(j=y_size-1; j>=0; j--)
		pos[i][j*3] = pos[i][j];
	y_size = 3 * y_size - 2;
	for(i=0; i<x_size; i++)
	    for(j=0; j<y_size-1; j+=3) {
		pos[i][j+1] = oneThird(pos[i][j], pos[i][j+3]);
		pos[i][j+2] = oneThird(pos[i][j+3], pos[i][j]);
	    }
    }
    Point3 trans(Point3 p) {
	Point3 q;
	double tmp, d_rate;
	
	q = new Point3(p.x - ViewRef.x,
		       p.y - ViewRef.y,
		       p.z - ViewRef.z);
	
	tmp =   q.x * ct + q.y * st;
	q.x = - q.x * st + q.y * ct;
	q.y = - tmp * sp + q.z * cp;
	q.z =   tmp * cp + q.z * sp;
	
	d_rate = R / (R - q.z);
	q.x =   q.x * d_rate + x_sc_mid;
	q.y = - q.y * d_rate + y_sc_mid;
	
	return q;
    }
    public void DrawMesh(Graphics g) {
	g.setColor(Color.white);
	
	for(int j=0; j<y_size; j++)
	    for(int i=0; i<x_size; i++)
		prj[i][j] = trans(pos[i][j]);
	
	for(int j=0; j<y_size; j++) {
	    for(int i=0; i<x_size-1; i++)
		Line(g, i, j, i+1, j);
	    if (j < y_size - 1)
		for(int i=0; i<x_size; i++)
		    Line(g, i, j, i, j+1);
	}
    }
    Point3 innerDividedPoint(Point3 a, Point3 b, double t) {
	Point3 q = new Point3();
	
	q.x = a.x + t * (b.x - a.x);
	q.y = a.y + t * (b.y - a.y);
	q.z = a.z + t * (b.z - a.z);

	return q;
    }
    public void DrawBezierCurve(Graphics g, int mouseJob) {
	Point3 tmp_y [][] = new Point3[4][3];
	Point3 tmp_x [] = new Point3[3];
	int px, py;
	int ymin [] = new int[width];
	int ymax [] = new int[width];
	double dx, dy;
	g.setColor(Color.red);

	for(int i=0; i<3; i++) {
	    for(int j=0; j<4; j++)
		tmp_y[j][i] = new Point3();
	    tmp_x[i] = new Point3();
	}
	for(int i=0; i<width; i++) {
	    ymin[i] = height;
	    ymax[i] = 0;
	}
	if (mouseJob == 1) {
	    dx = 0.02;
	    dy = 0.1;
	} else {
	    dx = 0.0020;
	    dy = 0.025;
	}

	if (theta > 0) 
	for(double t=1.0; t>0.0; t-=dy) {
	    for(int j=0; j<4; j++) {
		for(int i=0; i<3; i++)
		    tmp_y[j][i] = innerDividedPoint(pos[j][i], pos[j][i+1], t);
		for(int i=0; i<2; i++)
		    tmp_y[j][i] = innerDividedPoint(tmp_y[j][i], tmp_y[j][1+1], t);
		tmp_y[j][0] = innerDividedPoint(tmp_y[j][0], tmp_y[j][1], t);
	    }
	    for(double s=0.0; s<1.0; s+=dx) {
		for(int i=0; i<3; i++)
		    tmp_x[i] = innerDividedPoint(tmp_y[i][0], tmp_y[i+1][0], s);
		for(int i=0; i<2; i++)
		    tmp_x[i] = innerDividedPoint(tmp_x[i], tmp_x[i+1], s);
		tmp_x[0] = trans(innerDividedPoint(tmp_x[0], tmp_x[1], s));
		px = (int)tmp_x[0].x;
		py = (int)tmp_x[0].y;
		if (px>0 && px<width) {
		    if (py<ymin[px]) {
			ymin[px] = py;
			g.drawLine(px, py, px, py);
		    }
		    if (py>ymax[px]) {
			ymax[px] = py;
			g.drawLine(px, py, px, py);
		    }
		}
	    }
	}
	else 
	for(double t=0.0; t<1.0; t+=dy) {
	    for(int j=0; j<4; j++) {
		for(int i=0; i<3; i++)
		    tmp_y[j][i] = innerDividedPoint(pos[j][i], pos[j][i+1], t);
		for(int i=0; i<2; i++)
		    tmp_y[j][i] = innerDividedPoint(tmp_y[j][i], tmp_y[j][1+1], t);
		tmp_y[j][0] = innerDividedPoint(tmp_y[j][0], tmp_y[j][1], t);
	    }
	    for(double s=0.0; s<1.0; s+=dx) {
		for(int i=0; i<3; i++)
		    tmp_x[i] = innerDividedPoint(tmp_y[i][0], tmp_y[i+1][0], s);
		for(int i=0; i<2; i++)
		    tmp_x[i] = innerDividedPoint(tmp_x[i], tmp_x[i+1], s);
		tmp_x[0] = trans(innerDividedPoint(tmp_x[0], tmp_x[1], s));
		px = (int)tmp_x[0].x;
		py = (int)tmp_x[0].y;
		if (px>0 && px<width) {
		    if (py<ymin[px]) {
			ymin[px] = py;
			g.drawLine(px, py, px, py);
		    }
		    if (py>ymax[px]) {
			ymax[px] = py;
			g.drawLine(px, py, px, py);
		    }
		}
	    }
	}
    }
    void Line(Graphics g, int i, int j, int k, int h) {
	// g.setColor(new Color(127+i*4, 127+j*4, 255));
	g.drawLine((int) prj[i][j].x, (int) prj[i][j].y,
		   (int) prj[k][h].x, (int) prj[k][h].y);
    }
} // end of class Mesh


class Point3 {
    public double x, y, z;
    Point3() { }
    Point3(Point3 p) {
	x = p.x;
	y = p.y;
	z = p.z;
    }
    Point3(double init_x, double init_y, double init_z) {
	x = init_x;
	y = init_y;
	z = init_z;
    }
}

