TextureEvolver.java

Java source for TextureEvolver, 684 lines. Raw file

import java.awt.image.*;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Event;
import java.awt.Image;
import java.awt.Font;



//---- Node ------------------------------------------------------------------//



class Node
{
  //- Constants representing each type of operation

  static final int NUM    =   0;
  static final int ADD    =   1;
  static final int SUB    =   2;
  static final int MUL    =   3;
  static final int DIV    =   4;
  static final int LOG    =   5;
  static final int SIN    =   6;
  static final int COS    =   7;
  static final int MOD    =   8;
  static final int ROUND  =   9;
  static final int MIN    =  10;
  static final int MAX    =  11;
  static final int LUT0   =  12;
  static final int LUT1   =  13;
  static final int LUT2   =  14;
  static final int VARX   =  15;
  static final int VARY   =  16;
  static final int XSQYSQ =  17;
  static final int NSQNSQ =  18;
  static final int XELSEY =  19;  //  = if X > Y then X else Y
  static final int SINCOS =  20;  //  = sin(n0(x,x)) + cos(n1(y,y))
  static final int ROTXY  =  21;  //  = n0(X',Y') where (X',Y') is (X,Y) rotated by value radians
  static final int ABS    =  22;  //  = Abs(n0)
  static final int EVEN   =  23;  //  = n1(y,y) if n0(x,y) is even and n2(x,x) if it is odd
  static final int BLUR   =  24;  //  average of pixel and four around it at a distance of 'value'
  static final int EMBOSS =  25;  //  emboss pixel with four around it at a distance of 'value'

  static final int NUMTYPES =  26;

  //  hmmm... what are these for
  //  tp looks like the number of parameters, but what is tq??
  
  static int[] tp = {0,  2,  2,  2, 2, -1,  1,  1, -2,  1,  2,  2,  3,  3,  3, 0, 0,  1,  2,  2,  2,  1, 1,  3,  1,  1};
  static int[] tq = {8, 12, 12, 10, 7,  0, 12, 12,  0, 15, 12, 12, 10, 10, 10, 8, 8, 10, 10, 12, 10, 15, 4,  2,  0,  0};

//  static int[] tq = {1,  1,  1,  1, 0,  0,  1,  1,  0,  1,  1,  1,  1,  1,  1, 1, 1,  0,  0,  1,  0,  1, 1,  1,  0,  0};

//  static int[] tq = {1,  1,  1,  1, 1,  0,  1,  1,  0, 1,  1,  1,  1,  1,  1, 1, 1, 1, 1,  1,  1, 1, 1,  1,  1,  1};

  private int type = 0;
  private float value = 0;

  static int lasttype = -1;

  private Node[] n = null;

  //---- evaluate

  public float evaluate(float x, float y)
  {
    switch(type)
    {
      case NUM   : return value;
      case ADD   : return value * (n[0].evaluate(x,y) + n[1].evaluate(x,y));
      case SUB   : return value * (n[0].evaluate(x,y) - n[1].evaluate(x,y));
      case MUL   : return value * (n[0].evaluate(x,y) * n[1].evaluate(x,y));
      case DIV   : return value * (div(n[0].evaluate(x,y), n[1].evaluate(x,y)));
      case LOG   : return log(n[0].evaluate(x,y));
      case SIN   : return value * (float)Math.sin(n[0].evaluate(x,y));
      case COS   : return value * (float)Math.cos(n[0].evaluate(x,y));
      case MOD   : return mod(n[0].evaluate(x,y), n[1].evaluate(x,y));
      case ROUND : return value * (float)(int)(n[0].evaluate(x,y)+0.5);
      case MIN   : return value * min(n[0].evaluate(x,y), n[1].evaluate(x,y));
      case MAX   : return value * max(n[0].evaluate(x,y), n[1].evaluate(x,y));
      case LUT0  : return value * n[0].evaluate(n[1].evaluate(x,y), n[2].evaluate(x,y));
      case LUT1  : return value * n[1].evaluate(n[2].evaluate(x,y), n[0].evaluate(x,y));
      case LUT2  : return value * n[2].evaluate(n[0].evaluate(x,y), n[1].evaluate(x,y));
      case VARX  : return value + x;
      case VARY  : return value + y;
      case XSQYSQ: return (x*x+y*y)*n[0].evaluate(x,y);
      case NSQNSQ: { float n0 = n[0].evaluate(x,y), n1 = n[1].evaluate(x,y); return n0*n0+n1*n1; }
      case XELSEY: return n[0].evaluate(x,y) > n[1].evaluate(x,y) ? value - x : value - y;
      case SINCOS: return value * (float)(Math.sin(n[0].evaluate(x,x)) + Math.cos(n[1].evaluate(y,y)));
      case ROTXY : { float n1 = value, s = (float)Math.sin(n1), c = (float)Math.cos(n1); return n[0].evaluate(x*c-y*s,x*s+y*c); }
      case ABS   : { float n0 = n[0].evaluate(x,y); return n0 > 0 ? n0 : -n0; }
      case EVEN  : return (((int)n[0].evaluate(x,y)) & 2) == 0 ? n[1].evaluate(y,y) : n[2].evaluate(x,x);
      case BLUR  : return value + 0.2f*(n[0].evaluate(x,y) + n[0].evaluate(x-value*0.2f,y) + n[0].evaluate(x+value*0.2f,y) + n[0].evaluate(x,y-value*0.2f) + n[0].evaluate(x,y+value*0.2f));
      case EMBOSS: return value + 5.0f*n[0].evaluate(x,y) - n[0].evaluate(x-value*0.2f,y) - n[0].evaluate(x+value*0.2f,y) - n[0].evaluate(x,y-value*0.2f) - n[0].evaluate(x,y+value*0.2f);
    }

    return (float)value;
  }

  //---- toString

  public String toString()
  {
    switch(type)
    {
      case NUM   : return ""+value;
      case ADD   : return "("+n[0]+" + "+n[1]+")";
      case SUB   : return "("+n[0]+" - "+n[1]+")";
      case MUL   : return "("+n[0]+" * "+n[1]+")";
      case DIV   : return "("+n[0]+" / "+n[1]+")";
      case LOG   : return "log("+n[0]+")";
      case SIN   : return "sin("+n[0]+")";
      case COS   : return "cos("+n[0]+")";
      case MOD   : return "("+n[0]+" % "+n[1]+")";
      case ROUND : return "round("+n[0]+")";
      case MIN   : return "min("+n[0]+", "+n[1]+")";
      case MAX   : return "max("+n[0]+", "+n[1]+")";
      case LUT0  : return "lut0("+n[0]+", "+n[1]+", "+n[2]+")";
      case LUT1  : return "lut1("+n[0]+", "+n[1]+", "+n[2]+")";
      case LUT2  : return "lut2("+n[0]+", "+n[1]+", "+n[2]+")";
      case VARX  : return "X";
      case VARY  : return "Y";
      case XSQYSQ: return "((x*x+y*y) * "+n[0]+")";
      case XELSEY: return "("+n[0]+" > "+n[1]+" ? X : Y)";
      case SINCOS: return "(sin(Y=X, "+n[0]+") + cos(X=Y, "+n[1]+"))";
      case ROTXY : return "ROTXY("+n[0]+")";
      case ABS   : return "abs("+n[0]+")";
      case EVEN  : return "EVEN("+n[0]+","+n[1]+","+n[2]+")";
      case BLUR  : return "BLUR("+n[0]+")";
      case EMBOSS: return "EMBOSS("+n[0]+")";
    }

    return "()";
  }

  //---- Node - creates a blank node

  private Node()
  {
  }

  //---- Node - creates a tree with the given depth

  public Node(int depth)
  {
    //  Stop the same node appearing twice in a row..

    int tempp = -1;
    if(lasttype > -1) { tempp = tp[lasttype]; tp[lasttype] = -1; }

    //  Pick a random node type - if we are at the bottom depth only allow types with no parameters

    if(depth > 2)
    {
      int q=0;
      for(type=0; type<NUMTYPES; type++) if(tp[type] > 0) { q += tq[type]; }
      q = (int)(Math.random()*q);
      for(type=0; q>tq[type] || tp[type] == 0; type++) if(tp[type] > 0) { q -= tq[type]; }
    }
    else if(depth > 0)
    {
      int q=0;
      for(int type=0; type<NUMTYPES; type++) if(tp[type] >= 0) { q += tq[type]; }
      q = (int)(Math.random()*q);
      for(type=0; q>tq[type] || tp[type] < 0; type++) if(tp[type] >= 0) { q -= tq[type]; }
    }
    else
    {
      int q=0;
      for(type=0; type<NUMTYPES; type++) if(tp[type] == 0) { q += tq[type]; }
      q = (int)(Math.random()*q);
      for(type=0; q>tq[type] || tp[type] != 0; type++) if(tp[type] == 0) { q -= tq[type]; }
    }

    if(tempp > -1) tp[lasttype] = tempp;
    lasttype = type;

    //  Pick a random value

    value = (float)(Math.random()*2.0-1.0);

    //  Make the correct number of subnodes

    if(tp[type] > 0)
    {
      n = new Node[tp[type]];
      for(int i=0; i<tp[type]; i++) n[i] = new Node(depth-1);
    }
    else
    {
      n = null;
    }

  }

  //---- numNodes - counts the nodes in this node's tree

  public int numNodes()
  {
    int t = 1;
    for(int i=0; i<tp[type]; i++) t += n[i].numNodes();
    return t;
  }

  //---- getNode - gets a node from the tree

  int getNode_index;

  private Node getNode(Node nn)
  {
    if(getNode_index <= 0) return nn;
    getNode_index--;

    for(int i=0; i<nn.tp[nn.type]; i++)
    {
      Node getn = getNode(nn.n[i]);
      if(getn != null) return getn;
    }

    return null;
  }

  public Node getNode(int index)
  {
    getNode_index = index;
    Node nn = getNode(this);
    return nn == null ? this : nn;
  }

  //---- setNode - modifies a node in the tree

  public int setNode(int index, Node nn)
  {
    if(index <= 0) return -1;

    int t = 1;

    for(int i=0; i<tp[type]; i++)
    {
      if(index == t) { n[i] = nn; return -1; }
      int k = n[i].setNode(index-t, nn);
      if(k == -1) return -1;
      t += k;
    }

    return t;
  }

  //---- makeCopy - returns a copy of this node tree

  public Node makeCopy()
  {
    Node nn = new Node();

    nn.type = type;
    nn.value = value;

    if(tp[type] <= 0)
    {
      nn.n = null;
    }
    else
    {
      nn.n = new Node[tp[type]];
      for(int i=0; i<tp[type]; i++) nn.n[i] = n[i].makeCopy();
    }

    return nn;
  }

  //---- randomValues

  public void randomValues()
  {
    value = (float)(Math.random()*2.0-1.0);
    for(int i=0; i<tp[type]; i++) n[i].randomValues();
  }

  //---- min, max, div, log, mod

  private float max(float x, float y) { return x > y ? x : y; }
  private float min(float x, float y) { return x < y ? x : y; }
  private float div(float x, float y) { return y > -0.000001 && y < 0.000001 ? 0 : x/y; }
  private float log(float x) { return x == 0 ? 0 : x < 0 ? (float)Math.log(-x) : (float)Math.log(x); }
  private float mod(float x, float y) { return y > -0.000001 && y < 0.000001 ? 0 : x-((float)((int)(x/y)))*y; };

}



//---- Entity ----------------------------------------------------------------//



class Entity
{
  private Node n;

  //- Entity - creates a new simple random entity

  public Entity()
  {
    n = new Node(3);
  }

  //- Entity - creates an entity from the given node tree

  public Entity(Node n)
  {
    if(n == null) { int i = 0, j= 5; j=5/i; }
    this.n = n.makeCopy();
  }

  //- getPixel

  public float getValue(float x, float y)
  {
    return n.evaluate(x, y);
  }

  //- mutate - mutates this entity

  public void mutate()
  {
    float action = (float)Math.random();

    if(action < 0.25)
    {
      //  25% chance of swapping two nodes in the tree around

      int r1 = (int)(Math.random()*(n.numNodes()-1)+1);
      int r2 = (int)(Math.random()*(n.numNodes()-1)+1);

      Node n1 = n.getNode(r1).makeCopy();
      Node n2 = n.getNode(r2).makeCopy();

      n.setNode(r1, n2);
      n.setNode(r2, n1);
    }
    else if(action < 0.45)
    {
      //  20% chance of overwriting part of the tree with a new tree

      int r = (int)(Math.random()*(n.numNodes()-1)+1);
      Node nn = new Node((int)(Math.random()*4+1));

      n.setNode(r, nn);
    }
    else if(action < 0.70)
    {
      //  25% chance that a bit of tree will be inserted somewhere

      int r = (int)(Math.random()*(n.numNodes()-1)+1);
      Node nn = new Node((int)(Math.random()*2+1));
      int rr = (int)(Math.random()*(nn.numNodes()-1)+1);

      nn.setNode(rr, n.getNode(r).makeCopy());
      n.setNode(r, nn);
    }
    else if(action < 0.85)
    {
      //  15% chance that the insertion will happen at the top of the tree so root node is moved inside a new tree

      Node nn = new Node((int)(Math.random()*2+1));
      int rr = (int)(Math.random()*(nn.numNodes()-1)+1);

      nn.setNode(rr, n.makeCopy());
      n = nn;
    }
    else if(action < 1.00)
    {
      // 15% chance that a subtree will randomise it's value variables

      int r = (int)(Math.random()*n.numNodes());
      if(r== 0) { n.randomValues(); } else { n.getNode(r).randomValues(); }
    }
    else
    {
      //  0% chance that the tree will be replaced by a totally new tree

      Node nn = new Node((int)(Math.random()*4+2));
      n = nn;
    }
  }

  //- getMutation - returns a mutation of this entity

  public Entity getMutation()
  {
    Entity e = new Entity(n);
    int r = (int)(Math.random()*4)+1;
    for(int i=0; i<r; i++) e.mutate();
    return e;
  }

  //- toString

  public String toString()
  {
    return ""+n;
  }

}



//---- TextureEvolver --------------------------------------------------------//

public class TextureEvolver extends java.applet.Applet implements Runnable
{
  //- Data

  //  Mouse variables

  int mouse_oldx = 0, mouse_oldy = 0;
  int mouse_x = 0, mouse_y = 0;
  boolean mouse_drag = false;

  //  Thread variables

  Thread runThread;
  int drawx, drawy;
  int drawx2, drawy2;
  int doing = 0;

  //  Graphics variables

  int im_w = 600, im_h = 320;
  Graphics img;
  Image im;

  //  Palette

  Color[] drawcol;

  //  Entities

  Entity[] ent;
  Entity entParent, entParent2;



  //- Init

  public void init()
  {
    System.out.println("LKS Texture Evolver v0.1");

    drawcol = new Color[256];

    int s = 1;
    double rr=0, gg=0, bb=0;
    for(int i=0; i<256; i++)
    {
      s--;
      if(s <= 0) {
        rr = 0.5 + 2.0*(((i*i*i-5)&15)/15.0);
        gg = 0.5 + 2.0*(((3*i*i+9)&15)/15.0);
        bb = 0.5 + 2.0*(((i*i-i+1)&15)/15.0);
        s = 4 + ((3*i*i-8*i*i*i+17*i)&31);
      }
      float r = (float)(0.5 + 0.49*Math.sin(rr*i*3.14159/32.0));
      float g = (float)(0.5 + 0.49*Math.sin(gg*i*3.14159/32.0));
      float b = (float)(0.5 + 0.49*Math.sin(bb*i*3.14159/32.0));
      if(r < 0f) r = 0f; if(g < 0f) g = 0f; if(b < 0f) b = 0f;
      if(r > 1f) r = 1f; if(g > 1f) g = 1f; if(b > 1f) b = 1f;

//      drawcol[i] = new Color(r, g, b);

      drawcol[i] = new Color((float)i/256f, (float)i/256f, (float)i/256f);

    }

    im = createImage(im_w, im_h);
    img = im.getGraphics();

    img.setColor(Color.blue);
    img.fillRect(0, 0, im_w, im_h);

    for(int i=0; i<256; i++)
    {
      img.setColor(drawcol[i]);
      img.fillRect(i*2, 320-8, 2, 8);
    }

    //- Create entities

    entParent = new Entity();
    entParent2 = new Entity();

    ent = new Entity[12];
    for(int i=0; i<12; i++) ent[i] = new Entity();

    doing = 3;
  }



  public void start()
  {
    if(runThread == null)
    {
      runThread = new Thread(this);
      runThread.start();
    }
  }



  public void stop()
  {
    if(runThread != null)
    {
      runThread.stop();
      runThread = null;
    }
  }


  private void startDrawChildren()
  {
    doing &= ~1;

    img.setColor(Color.black);
    img.fillRect(256+16, 0, 80*4, 80*3);
    repaint();

    for(int i=0; i<12; i++) ent[i] = entParent.getMutation();
    drawx = 0; drawy = 0;
    doing |= 1;
  }

  private void startDrawParent(int ix, int iy)
  {
    doing &= ~2;

    int i = ix+iy*4;
    System.out.print("\n\nEntity "+i+":\n\n"+ent[i]+"\n\n");

    img.setColor(Color.black);
    img.fillRect(0, 0, 256, 256);
    repaint();

    entParent = ent[i];
    drawx2 = 0; drawy2 = 0;
    doing |= 2;
  }

  private void drawChildren()
  {
    if(drawy < 64)
    {
      int cc = -1, c;

      for(int i=0; i<12; i++)
      {
        int ox = (i&3)*80+256+80-64;
        int oy = (i>>2)*80;

        for(drawx = 0; drawx < 64; drawx++)
        {
          c = ((int)(256f*ent[i].getValue(drawx*(1f/64f), drawy*(1f/64f))))&255;

          if(cc != c)
          {
            img.setColor(drawcol[c]);
            cc = c;
          }

          img.fillRect(drawx + ox, drawy + oy, 1, 1);
        }
      }

      drawy ++;

    } else { doing &= ~1; }

    repaint();
  }



  private void drawParent()
  {
    if(drawy2 < 256)
    {
      int cc = -1, c;

      for(drawx2 = 0; drawx2 < 256; drawx2++)
      {
        c = ((int)(256f*entParent.getValue(drawx2*(1f/256f), drawy2*(1f/256f))))&255;

        if(cc != c)
        {
          img.setColor(drawcol[c]);
          cc = c;
        }

        img.fillRect(drawx2, drawy2, 1, 1);
      }

      drawy2 ++;

    } else { doing &= ~2; }

    repaint();
  }



  public void run()
  {
    while(true)
    {
      while(doing == 0) try { runThread.sleep(200); } catch (InterruptedException e) { return; }
      if((doing & 1) != 0) drawChildren();
      if((doing & 2) != 0) drawParent();
    }
  }



  public boolean mouseDown(Event evt, int x, int y)
  {
    mouse_oldx = x;
    mouse_oldy = y;
    mouse_x = x;
    mouse_y = y;
    mouse_drag = true;
    return true;
  }


  
  public boolean mouseUp(Event evt, int x, int y)
  {
    int ix = (x-256-16)/80;
    int iy = y/80;

    if(x > 0 && x< 256 && y > 0 && y < 256)
    {
      startDrawChildren();
    }
    else if(ix>= 0 && ix < 4 && iy >= 0 && iy < 3)
    {
      startDrawParent(ix, iy);
    }

    mouse_drag = false;
    return true;
  }
  


  public boolean mouseDrag(Event e, int x, int y)
  {
    mouse_x = x;
    mouse_y = y;
    repaint();
    return true;
  }



  public void update(Graphics g)
  {
    paint(g);
  }



  public void paint(Graphics g)
  {
    g.drawImage(im, 0, 0, this);
    if(mouse_drag) {
      g.setColor(Color.white);
      g.fillRect(mouse_oldx, mouse_oldy, mouse_x-mouse_oldx, mouse_y-mouse_oldy);
    }
  }

}