/*
 *
 * J4.1 version
 * A RAJOUTER : bouton de choix entre haute et basse qualité
 *              bouton de choix divers pavages
 *
 */

import java.awt.*;
import java.util.*;
import java.applet.Applet;
import java.awt.event.*;
import java.awt.image.*;
import java.awt.geom.*;

public class Hyp
    extends Applet
    implements MouseListener,
	       MouseMotionListener,
	       Runnable,
	       ActionListener,
	       FocusListener,
	       LayoutManager
{

    public static final int WORD_LENGTH_MAX=10;

    public final static int
	XOFF=20,
	YOFF=20,
	SIZE=300,
	XOFF2=40,
	YOFF2=250,
	BSIZE=50,
	YOFF3=110,
	XOFF4=25,
	YOFF4=300,
	XOFF5=10,
	YOFF5=10,
	XOFF6=10,
	YOFF6=40,
	BBSIZE=28,
	CSIZE=3;

    public int TIME;
    public static final int
	DEFAULT_TIME=0;

    public boolean possible; // Is the covering possible ?
    public int
	pValue, // nb of sides of the regular polygon
	sValue; // nb of polygons per vertex
    public static final int
	DEFAULT_P=5,
	DEFAULT_S=4;

    public double side; // Length of the sides of the polygons
    public double radius; // outer radius of the polygons
    public double diam; // not the diameter, but twice the radius
    public Complex[] director, director2;

    public final static int
	NOTHING=0,
	FORWARD=1,
	TURNLEFT=2,
	TURNRIGHT=3,
	CLICK=4;

    public final static double
	FACTOR=10.0;

    public final static double
	VS=0.4, // size of vector
	STEP=0.02, // mathematical step when moving forward
	STEP2=0.03, // mathematical step factor when click-moving
	ASTEP=0.04, // angular step
	STP=0.1, // line draw step
	DEFAULT_FOG=0.7,
	FOG_MAX=2.64;


    public double
	x,y,   // position of character in the map
	cs,sn, // direction of the tangent in the map
	fog,   // euclidean radius at which the fog hides everything
	clickx,	clicky; // where did the user last click ?

    public static final double
	tcs=Math.cos(ASTEP),
	tsn=Math.sin(ASTEP),
	/*
	t2cs=Math.cos(2.0*Math.PI/7.0),
	t2sn=Math.sin(2.0*Math.PI/7.0),
	ALPHA=t2cs/t2sn,
	lambda=(1-t2cs)/t2sn,
	*/
	t2cs=Math.cos(2.0*Math.PI/3.0),
	t2sn=Math.sin(2.0*Math.PI/3.0),
	seuil=0.151,
	t3cs=Math.cos(Math.PI),
	t3sn=Math.sin(Math.PI);

    //    public double ALPHA;

    public Thread thread;

    public int
	type; // used in thread

    //    public Image image;
    public BufferedImage bufferedImage,mask;
    public Graphics2D g;
	//,maskGraphics;

    public Random rand;

    public TextField fogText,delayText,pText,sText;

    public boolean bascule; // needed to avoid loops in TextField updates

    public Font font1=new Font("Times New Roman",Font.PLAIN,14);
    public Font font2=new Font("Times New Roman",Font.PLAIN,24);


    public static final double r0=0.2757977793391885;
	//r0=0.49697,//seuil=0.35;

    //    0.49697042539518089617
    //    0.26607724526008789112
    //    0.27579777933918858713
    //    0.30074261910000000000

    public Complex J1=new Complex(0.5,0.8660254037844386);
    public Complex J2=new Complex(0.5,-0.8660254037844386);
    public Complex J3=new Complex(-0.5,0.8660254037844386);
    public Complex J4=new Complex(-0.5,-0.8660254037844386);

    //    public static final double L0=0.2757977793391885;
    //    public static final double P0=0.3007426191000000;

    public long lastMovedTime;

//    public static final int NB_SEGMENTS = 469;
//    public static final int NB_SEGMENTS = table.length;

    // Interface Runnable

    public void run() {
	Thread curThread = Thread.currentThread();
	while(thread==curThread) {
	    doStep();
	    repaint();
	    try {
		long newMovedTime=new Date().getTime();
		thread.sleep(Math.max(0,Math.min(TIME,
			 TIME-(newMovedTime-lastMovedTime))));
		lastMovedTime=newMovedTime;
	    }
	    catch(InterruptedException e) {
	    }
	}
    }

    // Interface MouseListener    

    public void mouseClicked(MouseEvent e) {
    }
    public void mousePressed(MouseEvent e) {
	int clX,clY;
	clX=e.getX();
	clY=e.getY();
	type=NOTHING;
	clickx=((double) (e.getX()-XOFF-SIZE/2))/(double)(SIZE/2);
	clicky=-((double) (e.getY()-YOFF-SIZE/2))/(double)(SIZE/2);
	if(clickx*clickx+clicky*clicky<1.0) {
	    type=CLICK;
	}
	if(XOFF+SIZE+XOFF2+BSIZE/2<clX &&
	   clX<XOFF+SIZE+XOFF2+BSIZE+BSIZE/2 &&
	   YOFF2-BSIZE>clY && clY>YOFF2-2*BSIZE) {
	    type=FORWARD;
	}
	if(XOFF+SIZE+XOFF2<clX &&
	   clX<XOFF+SIZE+XOFF2+BSIZE &&
	   YOFF2>clY && clY>YOFF2-BSIZE) {
	    type=TURNLEFT;
	}
	if(XOFF+SIZE+XOFF2+BSIZE<clX &&
	   clX<XOFF+SIZE+XOFF2+2*BSIZE &&
	   YOFF2>clY && clY>YOFF2-BSIZE) {
	    type=TURNRIGHT;
	}
	if(type!=NOTHING) {
	    if((e.getModifiers() & InputEvent.SHIFT_MASK) ==
	       InputEvent.SHIFT_MASK) {
		doStep();
		repaint();
	    }
	    else {
		thread=new Thread(this, "HypThread");
		    thread.start();
	    }
	}
    }
    public void mouseReleased(MouseEvent e) {
	thread=null;
    }
    public void mouseEntered(MouseEvent e) {
    }
    public void mouseExited(MouseEvent e) {
    }

    // Interfac MouseMotionListener

    public void mouseMoved(MouseEvent e) {
    }
    public void mouseDragged(MouseEvent e) {
	clickx=((double) (e.getX()-XOFF-SIZE/2))/(double)(SIZE/2);
	clicky=-((double) (e.getY()-YOFF-SIZE/2))/(double)(SIZE/2);
    }

    // Interface ActionListener

    public void actionPerformed(ActionEvent e) {
	if(!bascule) {
	    Object sc=e.getSource();
	    if(sc.equals(fogText)) {
		updateFog();
	    }
	    else if(sc.equals(delayText)) {
		updateDelay();
	    }
	    else if(sc.equals(pText)) {
		updateP();
	    }
	    else if(sc.equals(sText)) {
		updateS();
	    }
	}
	else {
	    bascule=false;
	}
    }

    // Interface FocusListener

    public void focusGained(FocusEvent e) {
    }
    public void focusLost(FocusEvent e) {
	Component sc=e.getComponent();
	if(sc.equals(fogText)) {
	    updateFog();
	}
	else if(sc.equals(delayText)) {
	    updateDelay();
	}
	else if(sc.equals(pText)) {
	    updateP();
	}
	else if(sc.equals(sText)) {
	    updateS();
	}
    }

    // Interface LayoutManager

    public void addLayoutComponent(String name, Component comp) {
	// Do nothing
    }
    public void removeLayoutComponent(Component comp) {
	// Do nothing
    }
    public Dimension minimumLayoutSize(Container parent) {
	return getSize();
    }
    public Dimension preferredLayoutSize(Container parent) {
	return getSize();
    }
    public void layoutContainer(Container parent) {
	position();
    }

    // Custom methods

    /* Absolute positioning */
    public void position() {
	fogText.setBounds(XOFF+SIZE+XOFF2+20,YOFF3,80,20);
	delayText.setBounds(XOFF+SIZE+XOFF4+20,YOFF4,80,20);
	pText.setBounds(XOFF+SIZE+XOFF5,YOFF5,40,20);
	sText.setBounds(XOFF+SIZE+XOFF6,YOFF6,40,20);
    }

    public void doStep() {
	double numx,numy,vx,vy,d,denx,deny,nx,ny,rox,roy,ncs,nsn,wx,wy;
	switch(type) {
	case FORWARD :
	    vx=STEP*cs;
	    vy=STEP*sn;
	    numx=1+x*vx+y*vy;
	    numy=x*vy-y*vx;
	    d=numx*numx+numy*numy;
	    denx=x+vx;
	    deny=y+vy;
	    nx=(denx*numx+deny*numy)/d;
	    ny=(deny*numx-denx*numy)/d;
	    x=nx;
	    y=ny;
	    
	    rox=(numx*numx-numy*numy)/d;
	    roy=-2*numx*numy/d;
	    ncs=cs*rox-sn*roy;
	    nsn=sn*rox+cs*roy;
	    cs=ncs;
	    sn=nsn;
	    replace();
	    replace2();
	    break;
	case TURNLEFT :
	    ncs=cs*tcs-sn*tsn;
	    nsn=sn*tcs+cs*tsn;
	    cs=ncs;
	    sn=nsn;
	    replace2();
	    break;
	case TURNRIGHT :
	    ncs=cs*tcs+sn*tsn;
	    nsn=sn*tcs-cs*tsn;
	    cs=ncs;
	    sn=nsn;
	    replace2();
	    break;
	case CLICK :
	    vx=STEP2*(cs*clicky+sn*clickx);
	    vy=STEP2*(sn*clicky-cs*clickx);
	    numx=1+x*vx+y*vy;
	    numy=x*vy-y*vx;
	    d=numx*numx+numy*numy;
	    denx=x+vx;
	    deny=y+vy;
	    nx=(denx*numx+deny*numy)/d;
	    ny=(deny*numx-denx*numy)/d;
	    x=nx;
	    y=ny;
	    
	    rox=(numx*numx-numy*numy)/d;
	    roy=-2*numx*numy/d;
	    ncs=cs*rox-sn*roy;
	    nsn=sn*rox+cs*roy;
	    cs=ncs;
	    sn=nsn;
	    replace();   
	    replace2();
	    break;
	}
    }

    public void replace() {
	boolean ok,begun;
	Complex z=new Complex(x,y);
	Complex dir=new Complex(cs,sn);
	begun=false;
	do {
	    double ax,ay,aux,ncs,nsn,nx,ny;
	    Complex nz,a,centre,b;
	    ok=true;
	    double r2=z.norm();
	    if(r2>diam*diam) {
		begun=true;
		ok=false;
		// D'abord, tournons
		double alpha=Math.atan2(z.im,z.re);
		double k=Math.floor(sValue*alpha/(2.0*Math.PI));
		aux=-k*2.0*Math.PI/sValue;
		a=new Complex(Math.cos(aux),Math.sin(aux));
		z=z.mul(a);
		dir=dir.mul(a);
		// Puis, faisons un "phi"
		centre=new Complex(radius*Math.cos(Math.PI/sValue)
				   ,radius*Math.sin(Math.PI/sValue));
		b=centre.mul(z.conj());
		b.re=1.0-b.re;
		b.im=-b.im;
		b=b.mul(b);
		dir=dir.mul(b);
		z=phi(centre.opposite(),z);
		// Tournons à nouveau
		alpha=Math.atan2(z.im,z.re);
		k=Math.floor(pValue*(alpha/(2.0*Math.PI)-0.5-0.5/sValue+0.5/pValue));
		aux=-k*2.0*Math.PI/pValue;
		a=new Complex(Math.cos(aux),Math.sin(aux));
		z=z.mul(a);
		dir=dir.mul(a);
		// puis, "phi" encore
		centre.re=-centre.re;
		centre.im=-centre.im;
		b=centre.mul(z.conj());
		b.re=1.0-b.re;
		b.im=-b.im;
		b=b.mul(b);
		dir=dir.mul(b);
		z=phi(centre.opposite(),z);
	    }
	    /*
	    if(x*ALPHA<Math.abs(y)) {
		ok=false;
		ax=x*t2cs-y*t2sn;
		ay=y*t2cs+x*t2sn;
		x=ax;
		y=ay;
		ncs=cs*t2cs-sn*t2sn;
		nsn=sn*t2cs+cs*t2sn;
		cs=ncs;
		sn=nsn;
	    }
	    else {
		if (x>seuil) {
		    Complex aux=new Complex(x,y);
		    Complex dir=new Complex(cs,sn);
		    Complex a,diff;
		    double r;
		    a=new Complex(1-r0*aux.re,-r0*aux.im);
		    a=a.mul(a);
		    r=(1-r0*r0)/(a.re*a.re+a.im*a.im);
		    diff=new Complex(a.re*r,-a.im*r);
		    aux=phi(new Complex(-r0,0),aux);
		    dir=dir.mul(diff);
		    aux=aux.mul(new Complex(t3cs,-t3sn));
		    dir=dir.mul(new Complex(t3cs,-t3sn));
		    x=aux.re;
		    y=aux.im;
		    cs=dir.re;
		    sn=dir.im;
		    ok=false;
		}
	    }
	    */
	} 
	while(!ok);
		// enfin,
	if(begun) {
	    x=z.re;
	    y=z.im;
	    double aux=Math.sqrt(dir.norm());
	    cs=dir.re/aux;
	    sn=dir.im/aux;
	}
    }

    public void replace2() {
	double nrm=cs*cs+sn*sn;
	if(nrm>1.01 || nrm<0.99) {
	    double sqt=Math.sqrt(nrm);
	    cs=cs/sqt;
	    sn=sn/sqt;
	}
    }

    public void drawCross(Graphics gg, int x, int y, int s) {
	gg.drawLine(x-s,y-s,x+s,y+s);
	gg.drawLine(x-s,y+s,x+s,y-s);
    }

    public int xToX(double x) {
	return((int)(FACTOR*(XOFF+((1+x)/2)*(double)SIZE)));
    }

    public int yToY(double y) {
	return((int)(FACTOR*(YOFF+((1-y)/2)*(double)SIZE)));
    }

    public class Complex {
	double re,im;
	public Complex(double x, double y) {
	    re=x;
	    im=y;
	}
	public Complex() {
	    re=0;
	    im=0;
	}
	public Complex(Complex z) {
	    re=z.re;
	    im=z.im;
	}
	public Complex conj() {
	    return new Complex(re,-im);
	}
	public Complex mul(Complex a) {
	    return new Complex(a.re*re-a.im*im,a.re*im+a.im*re);
	}
	public double norm() {
	    return re*re+im*im;
	}
	public Complex opposite() {
	    return new Complex(-re,-im);
	}
    }

    /*
     * phi(a,z) = (a+z)/(1+conj(a).z)
     */
    public Complex phi(Complex z0, Complex z) {
	double ax,ay,bx,by,d;
	ax=z0.re+z.re;
	ay=z0.im+z.im;
	bx=1+z0.re*z.re+z0.im*z.im;
	by=z0.re*z.im-z0.im*z.re;
	d=bx*bx+by*by;
	return(new Complex((ax*bx+ay*by)/d,
			   (ay*bx-ax*by)/d));
    }


    public void segment(Graphics gg,
                double x1, double y1, double x2, double y2) {
	Complex mz1=new Complex(-x1,-y1);
	Complex z1=new Complex(x1,y1);
	Complex z2=new Complex(x2,y2);
	Complex z3=phi(mz1,z2);
	Complex z4,nz4;
	z4=new Complex(z1);
	double t=0;
	double d=z3.re*z3.re+z3.im*z3.im;
	if(d>STP*STP) {
	    d=Math.sqrt(d);
	    Complex z5=new Complex(z3.re/d,z3.im/d);
	    while(t<d) {
		t=(t+STP)/(1.0+STP*t);
		if(t>d) {t=d;}
		nz4=phi(z1,new Complex(t*z5.re,t*z5.im));
		gg.drawLine(xToX(z4.re),yToY(z4.im),
			   xToX(nz4.re),yToY(nz4.im));
		z4=new Complex(nz4);
	    }
	}
	else {
	    gg.drawLine(xToX(x1),yToY(y1),
		       xToX(x2),yToY(y2));
	}
    }

    public void updateFogText() {
	    bascule=true;
	fogText.setText(String.valueOf(fog));
    }

    public void updateDelayText() {
	    bascule=true;
	delayText.setText(String.valueOf(TIME));
    }

    public void updatePText() {
	    bascule=true;
	pText.setText(String.valueOf(pValue));
    }

    public void updateSText() {
	    bascule=true;
	sText.setText(String.valueOf(sValue));
    }

    public void updateFog() {
	try {
	    fog=Double.valueOf(fogText.getText()).doubleValue();
	    if(fog<0.01) { fog=0.01; updateFogText(); }
	    if(fog>FOG_MAX) { fog=FOG_MAX; updateFogText(); }
	}
	catch(NumberFormatException e) {
	    fog=DEFAULT_FOG; updateFogText();
	}
	setMaskAccordingToFog();
	repaint();
    }

    public void updateDelay() {
	try {
	    TIME=Integer.valueOf(delayText.getText()).intValue();
	    if(TIME<0) { TIME=0; updateDelayText(); }
	    if(TIME>10000) { TIME=10000; updateDelayText(); }
	}
	catch(NumberFormatException e) {
	    TIME=DEFAULT_TIME; updateDelayText();
	}
	repaint();
    }

    public void updateP() {
	try {
	    pValue=Integer.valueOf(pText.getText()).intValue();
	    if(pValue<3) { pValue=3; updatePText(); }
	    if(pValue>99) { pValue=99; updatePText(); }
	}
	catch(NumberFormatException e) {
	    pValue=DEFAULT_P; updatePText();
	}
	psComputations();
	repaint();
    }

    public void updateS() {
	try {
	    sValue=Integer.valueOf(sText.getText()).intValue();
	    if(sValue<3) { sValue=3; updateSText(); }
	    if(sValue>99) { sValue=99; updateSText(); }
	}
	catch(NumberFormatException e) {
	    sValue=DEFAULT_S; updateSText();
	}
	psComputations();
	repaint();
    }

    public void psComputations() {
	possible=sValue*pValue-2*(pValue+sValue)>0;
	if(possible) {
	    double r;
	    r=Math.sqrt(
	        Math.cos(Math.PI*(1.0/pValue+1.0/sValue))
               /Math.cos(Math.PI*(1.0/pValue-1.0/sValue))
			    );
	    radius=r;
	    diam=2.0*r/(1.0+r*r);
	    side=2.0*Math.sin(Math.PI/pValue)
		/Math.sqrt(r*r+1.0/(r*r)-2.0*Math.cos(2*Math.PI/pValue));
	    director=new Complex[sValue-1];
	    director2=new Complex[sValue];
	    double alpha;
	    for(int i=0; i<sValue-1; i++) {
		alpha=2.0*Math.PI*(0.5+(i+1.0)/sValue);
		director[i]=new Complex(Math.cos(alpha),Math.sin(alpha));
	    }
	    for(int i=0; i<sValue; i++) {
		alpha=2.0*Math.PI*(i+1.0)/sValue;
		director2[i]=new Complex(Math.cos(alpha),Math.sin(alpha));
	    }
	    //	    ALPHA=Math.tan(Math.PI/sValue.0*1.05),
	    /*
	    int m=(pValue-1)/2;
	    char[] c=new char[m];
	    for(int i=0;i<m;i++) {
		c[i]='d';
	    }
	    stringRight=new String(c);
	    int n=pValue-2-m;
	    if(n>0) {
		c=new char[];
		for(int i=0;i<m;i++) {
		    c[i]='g';
		}
		stringLeft=new String(c);
	    }
	    else {
		stringLeft="";
	    }
	    */
	}
    }

    /*
     * Avance z dans la direction dir d'un pas de longueur side
     * puis branche en fonction de la combinatoire donnée par word
     */
    public void recurse(Complex z, Complex dir, String word) {
	if(word.length()<WORD_LENGTH_MAX) {
	    Complex nz,ndir,aux;
	    double dd;
	    // new z
	    nz=phi(z,dir);
	    // new dir
	    aux=dir.mul(z.conj());
	    aux.re+=1.0;
	    aux=aux.mul(aux);
	    ndir=dir.mul(aux.conj());
	    // normalization of ndir
	    dd=side/Math.sqrt(ndir.norm());
	    ndir.re*=dd;
	    ndir.im*=dd;
	    // draw the segment
	    float a=(float)(Math.min(z.norm(),nz.norm())/fog);
	    if(a<1.0f) {
		//		g.setColor(new Color(1.0f,a,a));
		segment(g,z.re,z.im,nz.re,nz.im);
		// decide whether to recurse or not
		boolean leftMost, rightMost;
		int m=(pValue-1)/2;
		int n=pValue-2-m;
		int l=word.length();
		if(l>=m) {
		    rightMost=true;
		    for(int i=0; i<m; i++) {
			rightMost=rightMost && word.charAt(l-1-i)=='d';
		    }
		}
		else {
		    rightMost=false;
		}
		if(!rightMost) {
		    if(l>=n) {
			leftMost=true;
			for(int i=0; i<n; i++) {
			    leftMost=leftMost && word.charAt(l-1-i)=='g';
			}
		    }
		    else {
			leftMost=false;
		    }
		    recurse(nz,ndir.mul(director[0]),word+"d");
		    for(int i=1; i<sValue-2;i++) {
			recurse(nz,ndir.mul(director[i]),word+"o");
		    }
		    if(!leftMost) {
			recurse(nz,ndir.mul(director[sValue-2]),word+"g");
		    }
		}
	    }
	}	
    }    

    //

    public void setMaskAccordingToFog() {
	double tr;
	int c=SIZE/2;
	WritableRaster wr=mask.getRaster();
	int[] a=new int[4];
	a[0]=255;
	a[1]=255;
	a[2]=255;
	for(int i=0; i<SIZE; i++) {
	    for(int j=0; j<SIZE; j++) {
		
		tr=(i-c)*(i-c)+(j-c)*(j-c);
		tr=4.0*tr/(SIZE*SIZE);
		if(tr>0.99) {
		    if(tr>1) {
			tr=1;
		    }
		    else {
			tr=0;
		    }
		}
		else {
		    tr=Math.log((1+tr)/(1-tr))/2;		    
		    tr=1-tr/fog;
		    tr=Math.max(0,tr);
    //		    tr=Math.sqrt(tr);
		}
		a[3]=(int)(255.0*(1-tr));
		wr.setPixel(i,j,a);		
		/*
		// DrawRect is SO SLOW !
		maskGraphics.setColor(new Color(1.0f,1.0f,1.0f,(float)tr));
		maskGraphics.drawRect(i,j,0,0);
		*/
	    }
	}
    }

    // Overrid methods

    public void update(Graphics gg) {

	// Absolute Positioning

	// Painting

	Dimension dim=getSize();

	// fills with a custom background color
	g.setColor(new Color(0.7f,0.7f,0.7f));
	g.fillRect(0,0,dim.width-1,dim.height-1);
	// fills the buttons rectangle in bluish gray
	g.setColor(new Color(0.7f,0.7f,0.8f));
	g.fillRect(XOFF+SIZE+XOFF2,YOFF2-BSIZE,BSIZE,BSIZE);
	g.fillRect(XOFF+SIZE+XOFF2+BSIZE,YOFF2-BSIZE,BSIZE,BSIZE);
	g.fillRect(XOFF+SIZE+XOFF2+BSIZE/2,YOFF2-2*BSIZE,BSIZE,BSIZE);
	// draw various black lines
	g.setColor(Color.black);
	// a box surrounding the applet
	g.drawRect(0,0,dim.width-1,dim.height-1);
	// some text
	g.setFont(font1);
	g.drawString("fog (between 0 and 1)",XOFF+SIZE+XOFF2-15,YOFF3-15);
	g.drawString("animation delay (milliseconds)",XOFF+SIZE+XOFF4-30,YOFF4-15);
	g.drawString("polygon sides",XOFF+SIZE+XOFF5+50,YOFF5+14);
	g.drawString("polygons per vertex",XOFF+SIZE+XOFF6+50,YOFF6+14);
	/*
	int m=(pValue-1)/2;
	int n=pValue-2-m;
	g.drawString(String.valueOf(m),0,20);
	g.drawString(String.valueOf(n),0,40);
	*/
	// the buttons boundaries
	g.drawRect(XOFF+SIZE+XOFF2,YOFF2-BSIZE,BSIZE,BSIZE);
	g.drawRect(XOFF+SIZE+XOFF2+BSIZE,YOFF2-BSIZE,BSIZE,BSIZE);
	g.drawRect(XOFF+SIZE+XOFF2+BSIZE/2,YOFF2-2*BSIZE,BSIZE,BSIZE);
	// draw the arrows in the buttons
	int N=7;
	double xa[]={0.35,0.65,0.65,0.8,0.5,0.2,0.35};
	double ya[]={0.15,0.15,0.55,0.55,0.85,0.55,0.55};
	int[] xa2=new int[N];
	int[] ya2=new int[N];
	for(int i=0; i<N; i++) {
	    xa2[i]=XOFF+SIZE+XOFF2+BSIZE/2+(int)(xa[i]*(double)BSIZE);
	    ya2[i]=YOFF2-BSIZE-(int)(ya[i]*(double)BSIZE);
	}
	g.setColor(Color.blue);
	g.fillPolygon(xa2,ya2,7);
	for(int i=0; i<N; i++) {
	    xa2[i]=XOFF+SIZE+XOFF2+(int)((1-ya[i])*(double)BSIZE);
	    ya2[i]=YOFF2-(int)(xa[i]*(double)BSIZE);
	}
	g.fillPolygon(xa2,ya2,7);
	for(int i=0; i<N; i++) {
	    xa2[i]=XOFF+SIZE+XOFF2+BSIZE+(int)(ya[i]*(double)BSIZE);
	    ya2[i]=YOFF2-(int)((1-xa[i])*(double)BSIZE);
	}
	g.fillPolygon(xa2,ya2,7);

	// fill the circle with white
	g.setColor(Color.white);
	g.fillOval(XOFF,YOFF,1+SIZE,1+SIZE);

	if(possible) {
	// Let's set the pixel positioning precision to higher (FACTOR)
	AffineTransform savedTransform=g.getTransform();
	Stroke savedStroke=g.getStroke();

	AffineTransform af=new AffineTransform();
	af.scale(1/FACTOR,1/FACTOR);
	g.transform(af);

	g.setStroke(new BasicStroke((float) FACTOR));
	g.setColor(Color.red);

	Complex mz0=new Complex(-x,-y);
	Complex rho1=new Complex(sn,cs);
	Complex dir=new Complex(sn*side,cs*side);
	mz0=mz0.mul(rho1);
	//	recurse(mz0,dir.mul(director2[0]),"");
	for(int i=0; i<sValue; i++) {
	    recurse(mz0,dir.mul(director2[i]),"");
	}

	g.setTransform(savedTransform);
	g.setStroke(savedStroke);

	g.drawImage(mask,XOFF,YOFF,this);

	// draw a cross at the character's position
	g.setColor(Color.red);
	drawCross(g,XOFF+SIZE/2,YOFF+SIZE/2,CSIZE);
	}
	else {
	    g.setColor(Color.black);
	    g.setFont(font2);
	    g.drawString("Impossible",XOFF + SIZE/4 + 20,YOFF+SIZE/2-12);
	}

	// draw the circle
	g.setColor(Color.black);
	g.drawOval(XOFF,YOFF,1+SIZE,1+SIZE);

	gg.drawImage(bufferedImage,0,0,this);
    }

    public void paint(Graphics gg) {
	update(gg);
    }

    public void init() {
	setLayout(this);

	//	setBackground(Color.lightGray);
        x=0;
	y=0;
	double angle=Math.PI/2;
	cs=Math.cos(angle);
	sn=Math.sin(angle);
	clickx=0;
	clicky=0;
	type=NOTHING;
	thread=null;
	addMouseListener(this);
	addMouseMotionListener(this);
	Dimension dim=getSize();
	//	image=createImage(dim.width,dim.height);
	bufferedImage=new BufferedImage(dim.width,dim.height,BufferedImage.TYPE_INT_RGB);
	g=bufferedImage.createGraphics();
	g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
	g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,RenderingHints.VALUE_STROKE_PURE);
	rand= new Random();
	/*	NB_SEGMENTS=0;
	try {
	    URL myURL=new URL(getCodeBase(), "hepta.data");
	    InputStream is=myURL.openStream();
	    for(int i=0; i<NB_SEGMENTS; i++) {
		is.read();
	    }
	    is.close();
	} catch(IOException e) {
	}*/
	fog=DEFAULT_FOG;
	TIME=DEFAULT_TIME;
	pValue=DEFAULT_P;
	sValue=DEFAULT_S;
	psComputations();
	fogText=new TextField("fog",5);
	delayText=new TextField("delay",5);
	pText=new TextField("p",2);
	sText=new TextField("s",2);
	add(fogText);
	add(delayText);
	add(pText);
	add(sText);
	updateFogText();
	updateDelayText();
	updatePText();
	updateSText();
	bascule=false;
	fogText.addActionListener(this);
	fogText.addFocusListener(this);
	delayText.addActionListener(this);
	delayText.addFocusListener(this);
	pText.addActionListener(this);
	pText.addFocusListener(this);
	sText.addActionListener(this);
	sText.addFocusListener(this);
	/* MARCHE PAS...
	fogText.setFont(font1);
	delayText.setFont(font1);
	pText.setFont(font1);
	sText.setFont(font1);
	*/

	lastMovedTime=new Date().getTime();

	mask=new BufferedImage(SIZE,SIZE,BufferedImage.TYPE_INT_ARGB);
	//	maskGraphics=mask.createGraphics();
	setMaskAccordingToFog();
    }

    public void stop() {
        thread=null;
    }

}
