/*
<applet code="Bezier.class" width="400", height="460"></applet>
*/
/*
 *ベジェ曲線による描画ツール
 *[機能]
 *太さ変更、閉曲線ON/OFF、塗りつぶしON/OFF
 *曲線追加、制御点移動、位置変更、結合、削除
 *
 *JAVAのデモや講義のサンプルを参考にしました。
 *わかりやすくしようと思い
 *コメントは入れるようにしたんですが
 *逆に見づらいかも・・・
 *
 */
/*
 *Bezier.java 1.0 05/5/31
 */

import java.applet.Applet;//Appletクラスライブラリ
import java.awt.*;        //Graphicsライブラリ
import java.awt.event.*; //イベント系
import java.util.Vector;//リスト？クラス

//Polygonに追加してBPolygon宣言
class BPolygon extends Polygon{
    boolean close;//閉曲線フラグ
    boolean nuri;//塗りつぶしフラグ
    int hutosa=0;//太さ
    public BPolygon(int[] x,int[] y,int n,
		    int h,boolean cl,boolean nu){
	super(x,y,n);
	close=cl;nuri=nu;hutosa=h;
    }
    public BPolygon(){
	super();
    }
}

public class Bezier extends Applet{
    DrawPanel panel;//表示画面、コントロール画面制作
    DrawControls controls;
    //開始時に真っ先に実行
    public void init(){
	setLayout(new BorderLayout()); 
	panel = new DrawPanel();
	controls = new DrawControls(panel);
	add("Center",panel);
	add("South",controls);
	panel.offs();
    };
    //削除（必要かどうかわからないけど一様）
    public void destroy() {
	remove(panel);
	remove(controls);
    }
}
//絵をここに描く
class DrawPanel extends Panel implements MouseListener,
					 MouseMotionListener {
    public static final int ADD = 0;//モード変更用変数
    public static final int EDIT = 1;
    public static final int COMBINE = 2;
    int	mode=ADD;
    
    int datax[][] = {
	{111,72,123,121,33,58,109},
	{110,99,111,138},
	{136,102,99,108},
	{109,126,199,275,295,324},
	{130,149,183},
	{138,159,181},
	{110,178,141,323,},
	{144,154,154,146,},
    };
    int datay[][] = {
	{94,134,167,188,247,260,267},
	{269,291,295,298},
	{301,282,304,324},
	{323,350,348,347,264,198},
	{160,155,175},
	{180,184,175},
	{96,60,137,197,},
	{161,166,175,180,},
    };
    int datao[]={
	2,1,1,2,1,1,5,20
    };
    
    Vector pv=new Vector();//リスト:画像データ
    static int WIDTH=400, HEIGHT=400;//画面サイズ
    int ndiv=100;//分割数
    int cap_point=-1;//制御点番号
    BPolygon p =new BPolygon();//多角形データ
    BPolygon pp;//リスト内参照用多角形
    int objnum=0,number=0;//データ位置、頂点位置
    boolean seigyo=true;
    int bufx,bufy;//座標一時保管用変数
    int cnum=-1,cpoint=-1;//combine用変数
    Image buf;//オフスクリーン
    Graphics bufg;
    TextField strx,stry;
    
    public DrawPanel() {
	addMouseMotionListener(this);//マウスイベントを処理します宣言
	addMouseListener(this);
	strx=new TextField(" ",40);
	stry=new TextField(" ",40);
	//add(strx);
	//add(stry);
	initdata();
    }
    public void update(Graphics g){
	paint(bufg);
	g.drawImage(buf, 0, 0, this);
    }
    public void offs(){
	if(buf==null){
	    buf = createImage(WIDTH,WIDTH);
	    bufg=buf.getGraphics();
	}
    }
    //初期データ入力
    void initdata(){
	boolean c=false,n=false;
	for(int i=0;i<datax.length;i++){
	    objnum++;
	    if(datao[i]/10==1) c=true;
	    else if(datao[i]/10==2) n=true;
	    pv.add(new BPolygon(datax[i],datay[i],
				datax[i].length,datao[i]%10,c,n));
	}
    }
    //描く
    public void paint( Graphics g ){	
	g.setColor(Color.white); 	
	g.fillRect(0 , 0 ,WIDTH-1,HEIGHT-1);//背景塗りつぶし
	g.setColor(Color.black);
	g.drawRect(0 , 0 ,WIDTH-1,HEIGHT-1);//枠を描く
	//多角形を描く
	if(seigyo){
	    for(int i=0;i<objnum;i++){//作成済みすべて
		if(this.mode==EDIT && i==number) continue;
		pp=(BPolygon)pv.elementAt(i);
		BezierCurve(g,Color.black,pp);
	    }
	}
	if(this.mode==ADD || number==objnum)//作成or選択中のもの
	    pp=p;	
	else{
	    pp=(BPolygon)pv.elementAt(number);
	    if(p.npoints>1)
		BezierCurve(g,Color.black,p);//作成中のもの表示
	}
	if(pp.npoints>1)
	    BezierCurve(g,Color.blue,pp);
	g.setColor( Color.red );	 
	if(this.mode!=COMBINE)
	    g.drawPolyline(pp.xpoints,pp.ypoints,pp.npoints);
	drawTen(g,pp);
	if(cnum!=-1){
	    g.setColor( Color.green );
	    pp=(BPolygon)pv.elementAt(cnum);
	    g.fillOval(pp.xpoints[cpoint]-6,pp.ypoints[cpoint]-6,12,12);
	}
	//zahyo();
    }
    //制御点描く
    public void drawTen(Graphics g,BPolygon p){
	int x,y;
	for(int i = 0; i < p.npoints; i++){
  		if(this.mode == COMBINE &&
		   i != 0 && i != p.npoints-1) continue; 
		x=p.xpoints[i];
		y=p.ypoints[i];
			g.fillRect(x-3,y-3, 7, 7);
			g.drawString(Integer.toString(i+1),x-2,y-4);
  	}
    }
    //曲線描く
	public void BezierCurve(Graphics g, Color color, BPolygon p){ 
	    int i, j, n;
	    double x1, x2, y1, y2,t;
	    x1 = p.xpoints[0];y1 = p.ypoints[0];n=p.npoints;
	    BPolygon bp= new BPolygon();//曲線座標の入れ物
	    bp.addPoint((int)x1,(int)y1);
	    
	    g.setColor(color);
	    double bernstein[] = new double[n];
	    for(int k=0;k<=ndiv;k++){
		t=(double)k/(double)ndiv;
		
		for(i=0;i<n;i++){   // bernstein関数を計算
		    bernstein[i]=(double)kj(n-1)/(kj(i)*kj(n-1-i));
		    for(j=1;j<=i;j++) bernstein[i] *= t;
		    for(j=1;j<=n-1-i;j++) bernstein[i] *= (1-t);
		}
		x2 = y2 = 0;       	
		for(i=0;i<n;i++){//座標決定
		    x2 += p.xpoints[i]*bernstein[i];
		    y2 += p.ypoints[i]*bernstein[i];
		}
		bp.addPoint((int)x2,(int)y2);
		
		int h;//太さをだす       
		if(k<50) 
		    h=(int)(t*10*p.hutosa)+1;
		else
		    h=(int)((10-t*10)*p.hutosa)+1;
			g.fillRect((int)x2-h/2,(int)y2-h/2,h,h);
	    }
	    if(p.close && n>2){//閉曲線(それなりには見える)
		BezierCurve(g,color,combinepoint(p,p,0,n-1));
	    }
	    //Bezier曲線描く
		if(p.nuri){//塗りつぶしＯＮ
		    g.fillPolygon((Polygon)bp);
		}
		else//塗りつぶしＯＦＦ
		    g.drawPolyline(bp.xpoints,bp.ypoints,bp.npoints);	
	}
    //モード変更
    public void setDrawMode(int mode) {
	switch (mode) {
	case ADD:
	case EDIT:
	case COMBINE:
	    cnum=-1;
	    cap_point=-1;
	    this.mode = mode;
	    break;
	default: //例外な値を除く
	    throw new IllegalArgumentException();
	}
	repaint();
    }
    //選択中の多角形Noを増減＆返す
    public int setNumber(int num) {
	if(objnum==0) return 0;
	number=number+num;
	if(number>objnum) 
	    number=0;
	else if(number<0)
	    number=objnum;
	if(this.mode!=ADD)	
	    repaint();
	cap_point=-1;
	return number;
    }
    //太さを増
    public void setHutosa() {
	if(this.mode==ADD || number==objnum){
	    p.hutosa++;
	    if(p.hutosa>5) 
		p.hutosa=0;
	}
	else{
	    pp=(BPolygon)pv.elementAt(number);
	    pp.hutosa++;
	    if(pp.hutosa>5) 
		pp.hutosa=0;
	    pv.set(number,pp);
	}
	repaint();
    }
    //データ削除(1=すべて,その他の時=選択・追加データ)
    public void DeleteP(int n){
	if(n==1){
	    pv.removeAllElements();
	    p.reset();
	    number=objnum=0;
	}
	else if(this.mode==ADD || number==objnum){
	    p.reset();
	}
	else{
	    pv.remove(number);
	    pv.trimToSize();
	    objnum--;
	}
	cnum=-1;
	cap_point=-1;
	repaint();
    }
    //塗りつぶしフラグ
    public void setNuri(){
	if(this.mode==ADD || number==objnum){
			p.nuri=!p.nuri;
	}
	else{
	    pp=(BPolygon)pv.elementAt(number);
	    pp.nuri=!pp.nuri;
	    pv.set(number,pp);
	}
		repaint();
    }
    //閉曲線フラグ
    public void setClose(){
	if(this.mode==ADD || number==objnum){
			p.close=!p.close;
	}
	else{
	    pp=(BPolygon)pv.elementAt(number);
	    pp.close=!pp.close;
	    pv.set(number,pp);
	}
	repaint();
	}
    //線を表示するか？
	public void setSen(){
	    seigyo=!seigyo;
		repaint();
	}
	// 階乗を計算
    public int kj(int num){
    int total = 1;
    if(num == 0) return 1;
    for(; num > 1; num--)
    	total *= num;
    return total;
    }
    //座標表示
    public void zahyo(){
	if(number==objnum) return;
	pp=(BPolygon)pv.elementAt(number);
	String c1="{",c2="{";
	for(int i=0;i<pp.npoints;i++){
	    c1+=Integer.toString(pp.xpoints[i])+",";
	    c2+=Integer.toString(pp.ypoints[i])+",";
	}
	c1+="}";c2+="}";
	strx.setText(c1);stry.setText(c2);
    }
    //繋げたふり:二箇所の間に制御点を二つおいて曲線を作る
    public BPolygon combinepoint(BPolygon bp1,BPolygon bp2,int p1,int p2){
	int [] x = new int [4];
	int [] y = new int [4];
	int tuika_p1,tuika_p2;
	
	x[0]=bp1.xpoints[p1];y[0]=bp1.ypoints[p1];//始点
	if(p1==0) tuika_p1=1;
	else tuika_p1=bp1.npoints-2;
	x[1]=bp1.xpoints[tuika_p1];y[1]=bp1.ypoints[tuika_p1];//間点1
	
	x[3]=bp2.xpoints[p2];y[3]=bp2.ypoints[p2];//終点
	if(p2==0) tuika_p2=1;
	else tuika_p2=bp2.npoints-2;
	x[2]=bp2.xpoints[tuika_p2];y[2]=bp2.ypoints[tuika_p2];//間点2
	
	x[1]=2*x[0]-x[1];y[1]=2*y[0]-y[1];//間点を点対称にする
	x[2]=2*x[3]-x[2];y[2]=2*y[3]-y[2];
	return new BPolygon(x,y,4,bp2.hutosa,false,bp2.nuri);	
    }
    //マウス系イベント
    //マウスがドラッグされたら
    //掴んだものを移動
    public void mouseDragged(MouseEvent e) {
   	e.consume();
	if(cap_point == -1 ) return;
	int x=e.getX(),y=e.getY();
	if(number==objnum){
	    if(cap_point==-100){
		pp.translate(x-bufx,y-bufy);
		bufx=x;bufy=y;
	    }
	    else{
		p.xpoints[cap_point]=x;
		p.ypoints[cap_point]=y;
	    }
	}
	else{
	    pp=(BPolygon)pv.elementAt(number);
	    if(cap_point==-100){
		pp.translate(x-bufx,y-bufy);
		bufx=x;bufy=y;
	    }
	    else{
		pp.xpoints[cap_point]=x;
		pp.ypoints[cap_point]=y;
		pv.set(number,pp);
	    }
  	}
	repaint();
    }
    //マウスが動いたら
    public void mouseMoved(MouseEvent e){
    }
    //マウスが押されたら
    //編集モード専)制御点をつかむ
    public void mousePressed(MouseEvent e) {
	e.consume();
	if(this.mode!=EDIT) return;
	int x=e.getX(),y=e.getY(),i;
	if(number==objnum)
		  pp=p;
	else
	    pp=(BPolygon)pv.elementAt(number);					
	
	for(i = 0;i<pp.npoints;i++){
	    if(pp.xpoints[i]-3 <= x && pp.xpoints[i]+3 >= x &&
	       pp.ypoints[i]-3 <= y && pp.ypoints[i]+3 >= y){
		cap_point = i;	
		break;   //点を掴んだ
	    }
	} 	
	if(cap_point<0 && pp.contains((double)x,(double)y)){
	    cap_point=-100;//多角形を掴んだ
	    bufx=x;bufy=y;
	}
    }
    //離されたら
    //編集モード専)制御点を離す
    public void mouseReleased(MouseEvent e) {
	e.consume();
	if(this.mode!=EDIT) return;
	cap_point = -1;	
    }
    //マウスが入ったら
    public void mouseEntered(MouseEvent e) {
    }
    //マウスが出て行ったら
    //編集モード専)制御点を離す
    public void mouseExited(MouseEvent e) {
	e.consume();
	if(this.mode!=EDIT) return; 
	cap_point=-1;
    }
    //クリックされたら:
    public void mouseClicked(MouseEvent e) {
  	e.consume();
	if(this.mode==EDIT) return;
	int x,y,n;
	x=e.getX();y=e.getY();n=p.npoints;
	//繋げるモード)二箇所選択で繋げる
	if(this.mode==COMBINE){
	    if(number==objnum){ 
		cnum=-1;
		repaint();
		return;
	    }
	    pp=(BPolygon)pv.elementAt(number);
	    n=pp.npoints;
	    int oldcpoint=cpoint;
	    cpoint=-1;
	    if(pp.xpoints[0]-3 <= x && pp.xpoints[0]+3 >= x &&
	       pp.ypoints[0]-3 <= y && pp.ypoints[0]+3 >= y)
		cpoint=0;
	    else if(pp.xpoints[n-1]-3 <= x && pp.xpoints[n-1]+3 >= x &&
		    pp.ypoints[n-1]-3 <= y && pp.ypoints[n-1]+3 >= y)
		cpoint=n-1;
	    
	    if(cpoint!=-1){
		if(cnum==-1)
		    cnum=number;
		else{
		    pv.add(combinepoint((BPolygon)pv.elementAt(cnum),
					(BPolygon)pv.elementAt(number),oldcpoint,cpoint));
		    objnum++;
		    cnum=-1;
		}
	    }
	    else
		cnum=-1;
	}
	//追加モード)頂点追加・ダブルクリックで新しい多角形へ
	else{
	    if(n>1 && p.xpoints[n-1]-3<=x && p.ypoints[n-1]-3<=y &&
	       p.xpoints[n-1]+3>=x && p.ypoints[n-1]+3>=y ){
		objnum++;
		pv.add(new BPolygon(p.xpoints,p.ypoints,p.npoints,
				    p.hutosa,p.close,p.nuri));
		p.reset();
		p.close=false;
		p.nuri=false;
		p.hutosa=0;
	    }
	    else
		p.addPoint(x,y);
	}
	repaint();
    }
}
//操作パネル
class DrawControls extends Panel implements ItemListener,ActionListener {
    DrawPanel target;
    Label nlbl,hlbl;
    Dimension size;
    public DrawControls(DrawPanel target) {
	this.target = target;
	setBackground(Color.lightGray);
	setSize(size= new Dimension(400,60));
	setLayout(new FlowLayout());//パネル位置調整
	
	Choice shapes = new Choice();//ボタン等宣言・追加
	shapes.addItemListener(this);
	shapes.addItem("ADD_MODE");
	shapes.addItem("EDIT_MODE");
	shapes.addItem("COMBINE_MODE");
	shapes.setBackground(Color.lightGray);
	add(shapes);
	Button b;
	add(b=new Button("<<"));
	b.addActionListener(this);
	add(nlbl=new Label("0"));
	add(b=new Button(">>"));
	b.addActionListener(this);
	add(b=new Button("CLEAR"));
	b.addActionListener(this);
	add(b=new Button("ALL_CLEAR"));
	b.addActionListener(this);
	add(b=new Button("HUTOSA"));
	b.addActionListener(this);
	add(b=new Button("FILL"));
	b.addActionListener(this);
	add(b=new Button("CLOSE"));
	b.addActionListener(this);
	Checkbox cbox;
	add(cbox=new Checkbox("POINTS",true));
	cbox.addItemListener(this);
    }
    //ボタン等をちゃんと配置するためのもの
    //いいやり方がわからないのでこれでむりやり
    // レイアウトマネージャに報告する最適サイズ
    public Dimension getPreferredSize(){
  	return size;// 最適サイズは「必要サイズ」
    }     	
    // レイアウトマネージャに報告する必要サイズ
    public Dimension getMinimumSize(){           	
  	return size;// 必要サイズを返す
    }
    //イベントの処理1
    
    public void itemStateChanged(ItemEvent e) {
	if (e.getSource() instanceof Choice) {
	    String choice = (String) e.getItem();
	    if(choice.equals("ADD_MODE")) 
		target.setDrawMode(DrawPanel.ADD);
	    else if(choice.equals("EDIT_MODE")) 
		target.setDrawMode(DrawPanel.EDIT);
	    else if(choice.equals("COMBINE_MODE")) 
		target.setDrawMode(DrawPanel.COMBINE);
	}
	else if(e.getSource() instanceof Checkbox)
	    target.setSen();
    }
    //イベントの処理2
    public void actionPerformed(ActionEvent ev) {
	if (ev.getSource() instanceof Button) {
	    String label = ev.getActionCommand();
	    if(label.equals(">>"))
		nlbl.setText(Integer.toString(target.setNumber(1)));
	    else if(label.equals("<<"))
		nlbl.setText(Integer.toString(target.setNumber(-1)));
	    else if(label.equals("ALL_CLEAR")){
		target.DeleteP(1);
		nlbl.setText(Integer.toString(0));
	    }
	    else if(label.equals("CLEAR")){
		target.DeleteP(0);
		nlbl.setText(Integer.toString(target.setNumber(0)));
	    }
	    else if(label.equals("HUTOSA")){
		target.setHutosa();
	    }
	    else if(label.equals("FILL"))
		target.setNuri();
	    else if(label.equals("CLOSE"))
		target.setClose();
	}
    }
}
