// 3D percolating cluster,  Evgeny Demidov, 17 Dec 2001
import java.awt.*;
import java.awt.event.*;
import java.util.StringTokenizer;
public class Cluster3D extends java.applet.Applet
  implements MouseListener, MouseMotionListener, KeyListener, ActionListener{
  int w,h, h2,w2, mx0,my0, iCol[], maxCl, hBut = 30,
    xi[],yi[],zi[], x1[],y1[],zs[], Fx[][],Fy[][],
    L = 30, L1, cl[][][];
  double p = .312,  fiX = .2, fiY = .3, dfi = .01, scale, z1[];
  boolean painted = false;
  Image buffImage;  Graphics buffGraphics;
  Label lbP;        TextField tfP;      Button btNew;
  Color[] col;

public void init() {
  w = getSize().width;  h = getSize().height - hBut;  h2=h/2; w2=w/2;
  String s=getParameter("L");
  if (s != null) L = Integer.parseInt(s);
  s=getParameter("p");
  if (s != null) p = Double.valueOf(s).doubleValue();
  int L3 = L*L*L, L2 = L+2;  L1 = L+1;
  scale = w2/(Math.sqrt(3.)*L);
  xi = new int[L3];  yi = new int[L3];  zi = new int[L3];
  x1 = new int[L3];  y1 = new int[L3];  zs = new int[L3];
  z1 = new double[L3];
  for (int i = 0; i < maxCl; i++){
   xi[i] = 2*(int)(10*(Math.random() - .5));
   yi[i] = 2*(int)(10*(Math.random() - .5));
   zi[i] = 2*(int)(10*(Math.random() - .5));
   zs[i] = i;}
  cl = new int[L2][L2][L2];
  for (int i = 0; i < L2; i++)
   for (int j = 0; j < L2; j++)
    for (int k = 0; k < L2; k++) cl[i][j][k] = -1;
  Fx = new int[3][];  Fy = new int[3][];           // 3 cube faces
  for (int i = 0; i < 3; i++){ Fx[i] = new int[4]; Fy[i] = new int[4];}
  col = new Color[256];
  for (int i = 0; i < 256; i++) col[i] = new Color(i, i, i);
  iCol = new int[3];
  buffImage = createImage(w, h);   buffGraphics = buffImage.getGraphics();
  addMouseListener(this);   addMouseMotionListener(this);
  lbP = new Label("p", Label.RIGHT);  add(lbP);
  tfP = new TextField( "" + (float)p, 5);  add(tfP);
  tfP.addKeyListener(this);
  btNew = new Button("New");   btNew.addActionListener(this);
  add(btNew);
  built();
}
public void built(){
  for (int i = 1; i < L1; i++)
   for (int j = 1; j < L1; j++)
    for (int k = 1; k < L1; k++) cl[i][j][k] = 0;
  int i0 = L/2,
  dx[] = {1,-1,0,0,0,0}, dy[] = {0,0,1,-1,0,0}, dz[] = {0,0,0,0,1,-1};
  cl[i0][i0][i0] = 1;
  for (int i = 0; i < 6; i++)
    cl[(xi[i]=i0+dx[i])][(yi[i]=i0+dy[i])][(zi[i]=i0+dz[i])] = 2;
  int nb = 5;
  do{
   int n = nb--, xn = xi[n],  yn = yi[n],  zn = zi[n];
   if (cl[xn][yn][zn] == 2){
    if (Math.random() > p) cl[xn][yn][zn] = -1;
    else{
     cl[xn][yn][zn] = 1;
     for (int i = 0; i < 6; i++){
      int xt = xn+dx[i],  yt = yn+dy[i],  zt = zn+dz[i];
      if (cl[xt][yt][zt] == 0){
       cl[xt][yt][zt] = 2;
       xi[++nb] = xt;  yi[nb] = yt;  zi[nb] = zt;}}}}
  } while (nb >= 0);
  maxCl = 0;
  for (int i = 1; i < L1; i++)
   for (int j = 1; j < L1; j++)
    for (int k = 1; k < L1; k++)
     if (cl[i][j][k] == 1){
       xi[maxCl] = 2*(i-i0); yi[maxCl] = 2*(j-i0); zi[maxCl++] = 2*(k-i0);}
  for (int i = 0; i < maxCl; i++) zs[i] = i;
  rotate();
}
public void destroy(){
  removeMouseListener(this);  removeMouseMotionListener(this);
}
public void mouseClicked(MouseEvent e){}       // event handling
public void mousePressed(MouseEvent e) {
  mx0 = e.getX();  my0 = e.getY();
  e.consume();
}
public void mouseReleased(MouseEvent e){}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e)  {}
public void mouseDragged(MouseEvent e) {
  int x1 = e.getX();  int y1 = e.getY();
  if ( e.isShiftDown() )  scale *= Math.exp(-(y1 - my0)/(double)w);
  else   fiX += dfi*(y1 - my0);
  fiY += dfi*(x1 - mx0);   mx0 = x1;  my0 = y1;
  rotate();
  repaint();
  e.consume();
}
public void mouseMoved(MouseEvent e){}
public void keyTyped(KeyEvent e){}
public void keyPressed(KeyEvent e){}
public void keyReleased(KeyEvent e){
  final int keyEnter = 10;
  if (e.getKeyCode() == keyEnter){
    try{ p = Double.valueOf(tfP.getText()).doubleValue();
    }catch(NumberFormatException ne){}
    built();
    repaint();
  }
  e.consume();
}
public void actionPerformed(ActionEvent e){
  built();
  repaint();
}
public void rotate(){
  double ct = Math.cos(fiX), cf = Math.cos(fiY),
         st = Math.sin(fiX), sf = Math.sin(fiY),
         ix = scale*cf,                   kx =  scale*sf,
         iy = scale*st*sf, jy = scale*ct, ky = -scale*st*cf,
         iz = -255*ct*sf,  jz = 255*st,   kz = 255*ct*cf;
  int  Vx[] = {(int)(ix+kx), (int)(ix+kx), (int)(ix-kx), (int)(ix-kx), // cube
              (int)(-ix+kx),(int)(-ix+kx),(int)(-ix-kx),(int)(-ix-kx)},
  Vy[] = {(int)(iy+jy+ky), (int)(iy-jy+ky), (int)(iy-jy-ky), (int)(iy+jy-ky),
         (int)(-iy+jy+ky),(int)(-iy-jy+ky),(int)(-iy-jy-ky),(int)(-iy+jy-ky)},
  x0123[] = {Vx[0],Vx[1],Vx[2],Vx[3]}, y0123[] = {Vy[0],Vy[1],Vy[2],Vy[3]},
  x4567[] = {Vx[4],Vx[5],Vx[6],Vx[7]}, y4567[] = {Vy[4],Vy[5],Vy[6],Vy[7]},
  x0374[] = {Vx[0],Vx[3],Vx[7],Vx[4]}, y0374[] = {Vy[0],Vy[3],Vy[7],Vy[4]},
  x1265[] = {Vx[1],Vx[2],Vx[6],Vx[5]}, y1265[] = {Vy[1],Vy[2],Vy[6],Vy[5]},
  x0451[] = {Vx[0],Vx[4],Vx[5],Vx[1]}, y0451[] = {Vy[0],Vy[4],Vy[5],Vy[1]},
  x3762[] = {Vx[3],Vx[7],Vx[6],Vx[2]}, y3762[] = {Vy[3],Vy[7],Vy[6],Vy[2]};
  if (iz > 0){ Fx[0] = x0123; Fy[0] = y0123;}
  else{ Fx[0] = x4567; Fy[0] = y4567;}
  if (jz > 0){ Fx[1] = x0374; Fy[1] = y0374;}
  else{ Fx[1] = x1265; Fy[1] = y1265;}
  if (kz > 0){ Fx[2] = x0451; Fy[2] = y0451;}
  else{ Fx[2] = x3762; Fy[2] = y3762;}
  iCol[0] = (int)Math.abs(iz);  iCol[1] = (int)Math.abs(jz);
  iCol[2] = (int)Math.abs(kz);
  for (int i = 0; i < maxCl; i++){          // clusters grid
    x1[i] = (int)(ix*xi[i] + kx*zi[i]);
    y1[i] = (int)(iy*xi[i] + jy*yi[i] + ky*zi[i]);
    z1[i] = iz*xi[i] + jz*yi[i] + kz*zi[i];}
//System.out.println(" Hi"+"  ");
  for (int i = maxCl - 1; --i >= 0;){
    boolean flipped = false;
    for (int j = 0; j <= i; j++) {
      int a = zs[j],  b = zs[j + 1];
      if (z1[a] > z1[b]) {
        zs[j + 1] = a;   zs[j] = b;
        flipped = true;
      }
    }
    if (!flipped) break;
  }
  painted = false;
}

public void paint(Graphics g) {
 if ( !painted ){
  int px[] = new int[4],  py[] = new int[4];
  buffGraphics.setColor(Color.blue);
  buffGraphics.fillRect(0, 0, w, h);
  for (int l = 0; l < maxCl; l++){
    int k = zs[l];
    for (int i = 0; i < 3; i++){
      for (int j = 0; j < 4; j++){
        px[j] = w2 + x1[k] + Fx[i][j];   py[j] = h2 - y1[k] - Fy[i][j];}
      buffGraphics.setColor(col[iCol[i]]);
      buffGraphics.fillPolygon(px,py, 4);} }
  painted = true;}
 g.drawImage(buffImage, 0,hBut, this);
 showStatus( "fiX=" + (int)(360*fiX) + "   fiY=" + (int)(360*fiY));
}
public void update(Graphics g)   {  paint(g);  }

}
