• fullscreen
  • Control.pde
  • Diffusion.pde
  • Interaction.pde
  • Params.pde
  • Reaction.pde
  • Symmetry.pde
  • Turing_McCabe.pde
  • /////////////////////////////////////////////////////////////////////////////
    //                                                                         //
    //                            ControlP5 based GUI                          //
    //                                                                         //
    /////////////////////////////////////////////////////////////////////////////
    
    import controlP5.*;
    
    ControlP5 controlP5;
    CheckBox checkbox;
    RadioButton radio;
    Textlabel label;
    Slider slider;
    
    final static int CONTROLLER = 0, TAB = 1, GROUP = 2;
    final static int RESOLUTION = 1, SYMMETRY = 2, OPTIONS = 3, BRUSH = 4;
    final static int STEP_OFFSET = 5, STEP_SCALE = 6, COLOR_OFFSET = 7, BLUR_LEVEL = 8;
    final static int OPT_COLOR = 0, OPT_INVERT = 1;
    
    boolean isInit, isUpdate;
    
    int imgWidth, imgHeight;
    int id;
    int cx, cy;
    int ygap = 36;
    int controlWidth = 98;
    
    
    /////////////////////////////// draw ////////////////////////////////////////
    
    void drawControls() {
      controlP5.draw();
    }
    
    
    void drawImage(PImage img) {
      pushMatrix();
        translate(spacing, spacing);
        noFill();
        stroke(0);
        rect(-1,-1, imgWidth+1, imgHeight+1);
        image(img, 0, 0, imgWidth, imgHeight);
      popMatrix();
    }
    
    
    ////////////////////////////// setup ////////////////////////////////////////
    
    void setupControls() {
      
      isInit = false;
      controlP5 = new ControlP5(this);
      
      cx = imgWidth + 2 * spacing;
      cy = spacing;
       
      // custom color scheme
      controlP5.setColorActive(140);
      controlP5.setColorBackground(220);
      controlP5.setColorLabel(0);
      controlP5.setColorForeground(190);
        
      setupResolutionControl();
      setupSymmetryControl();
      setupOptionsControl();
      setupParamsControl();
      setupBrushControl();
      
      isInit = true;
      
    }
    
    void setupResolutionControl() {
      heading("RESOLUTION");
      radio = controlP5.addRadioButton("resolution", cx, cy);
      radio.setNoneSelectedAllowed(false);
      radio.setId(RESOLUTION);
      radio.setItemsPerRow(3);
      radio.setSpacingColumn(40);
      radio.addItem("resolution2" ,2).setLabel("lores");
      radio.addItem("resolution1", 1).setLabel("hires");
      cy += ygap;
    }
    
    void setupSymmetryControl() {
      heading("SYMMETRY");
      radio = controlP5.addRadioButton("symmetry", cx, cy);
      radio.setNoneSelectedAllowed(false);
      radio.setId(SYMMETRY);
      radio.setItemsPerRow(3);
      radio.setSpacingColumn(12);
      radio.setSpacingRow(10);
      radio.setItemHeight(20);
      radio.setItemWidth(20);
      for(int i=0; i<=6; i++) {
        if(i==1) continue;
        radio.addItem("symmetry" + i, i).setDisplay(new PieDisplay(20, i));
      }
      cy += 40 + ygap;
    }
    
    void setupOptionsControl() {
      heading("OPTIONS");
      checkbox = controlP5.addCheckBox("options",cx, cy);
      checkbox.setId(OPTIONS);
      checkbox.setItemsPerRow(2);
      checkbox.setSpacingColumn(40);
      checkbox.setSpacingRow(10);
      checkbox.addItem("color", OPT_COLOR);
      checkbox.addItem("invert", OPT_INVERT);
      cy += ygap;  
    }
    
    void setupParamsControl() {
      heading("PARAMS");
      cy += 5;
      makeSlider("step offset").setId(STEP_OFFSET);
      makeSlider("step scale").setId(STEP_SCALE);
      makeSlider("diffusion").setId(BLUR_LEVEL);
      slider = makeSlider("color offset");
      slider.setId(COLOR_OFFSET);
      slider.setLabel("color");
      cy += ygap/2;
    }
    
    void setupBrushControl() {
      heading("BRUSH"); 
      radio = controlP5.addRadioButton("brush",cx, cy);
      radio.activateEvent(false);
      radio.setNoneSelectedAllowed(false);
      radio.setId(BRUSH);
      radio.setItemsPerRow(4);
      radio.setSpacingColumn(10);
      radio.setItemHeight(20);
      radio.setItemWidth(20);
      for(int i=3; i>=1; i--) {
        radio.addItem("brush" + i, i).setDisplay(new PieDisplay((1+i)*5)); 
      } 
      cy += ygap;   
    }
    
    
    ////////////////////////////// update ///////////////////////////////////////
    
    // mapping: params ==> controls
    
    void updateControls() {
      if(isInit) {
        isUpdate = false;
        updateResolutionControl();
        updateSymmetryControl();
        updateOptionsControl();
        updateParamsControl();
        updateBrushControl();
        isUpdate = true;
      }
    } 
    
    void updateResolutionControl() {
      getRadioButton("resolution").activate("resolution" + resolution); 
    }
    
    void updateSymmetryControl() {
      getRadioButton("symmetry").activate("symmetry" + symmetry);
    }
    
    void updateParamsControl() {
      updateSlider("step offset", stepOffsetMin, stepOffsetMax, stepOffset);
      updateSlider("step scale", stepScaleMin, stepScaleMax, stepScale);
      updateSlider("color offset", 0, 255, colorOffset);
      updateSlider("diffusion", 0, 1, blurFactor);
    }
    
    void updateOptionsControl() {
      checkbox = getCheckBox("options");
      getToggle("color").setValue(colMode);
      getToggle("invert").setValue(invertMode);
    }
    
    void updateBrushControl() {
      getRadioButton("brush").activate("brush" + brush);
    }
    
    
    /////////////////////////////// events ///////////////////////////////////////
    
    //  mapping: controls ==> params
    
    void controlEvent(ControlEvent theEvent) {
      
    
       // ignore events triggered during setup
      if(!isInit || !isUpdate) return;
      
      // all other events are handled via their type and ID
      int val, id, type = theEvent.type();
      
      switch ( type ) {
        
        case GROUP:
        
          ControlGroup theGroup = theEvent.group();
           // println("group "+theGroup.name());
          val = (int) theGroup.value();
          id = theGroup.id();
          
          switch ( id ) {
            
            case SYMMETRY:
              symmetry = val;
              resetParams();
              break;
              
            case BRUSH:
             brush = val;
             break;
             
            case OPTIONS: 
              boolean[] opts = toBoolean(theGroup.arrayValue());
              colMode = opts[OPT_COLOR];
              invertMode = opts[OPT_INVERT];
              break;
              
            case RESOLUTION:
              resolution = val;
              resetParams();
              break;
          }
          break;
          
        case CONTROLLER:
        
          Controller theController = theEvent.controller();
          // println("controller "+ theController.name());
          val = (int) theController.value();
          id = theController.id();
          
          switch ( id ) {
            
            case COLOR_OFFSET:
              colorOffset = (int) map(val, 0, 100, 0, 255);
              break;
              
            case STEP_SCALE:
              stepScale = map(val, 0, 100, stepScaleMin, stepScaleMax);
              updateParams();
              break;
              
            case STEP_OFFSET:
              stepOffset = map(val, 0, 100, stepOffsetMin, stepOffsetMax);
              updateParams();
              break;
              
            case BLUR_LEVEL:
              blurFactor = map(val, 0, 100, 0, 1);
              updateParams();
              break;
          }
    
          break;
      }   
    }
    
    
    ////////////////////////// helper functions //////////////////////////////////
    
    RadioButton getRadioButton(String name) {
      return (RadioButton) controlP5.group(name);
    }
    
    CheckBox getCheckBox(String name) {
      return (CheckBox) controlP5.group(name);
    }
    
    Slider getSlider(String name) {
      return (Slider) controlP5.controller(name);
    }
    
    Toggle getToggle(String name) {
      return (Toggle) controlP5.controller(name); 
    }
    
    void heading(String str) {
      label = controlP5.addTextlabel("heading" + id++, str, cx, cy);
      label.setFont(controlP5.grixel);
      label.setColorValue(0);
      label.setLetterSpacing(2);
      label.setWidth(controlWidth);
      cy += ygap / 2;
    }
    
    Slider makeSlider(String name) {
      cy += 11;
      slider = controlP5.addSlider(name, 0, 100,50, cx, cy, 100, 12);
      slider.setDecimalPrecision(0);
      slider.captionLabel().style().moveMargin(-15,0,0,-103);
      cy += 22;
      return slider;
    }
    
    void updateSlider(String name, float vmin, float vmax, float val) {
      getSlider(name).setValue((int) map(val, vmin, vmax, 0, 100));
    }
    
    boolean[] toBoolean(float[] a) {
      boolean[] a2 = new boolean[a.length];
      for(int i = 0; i < a.length; i++) a2[i] = (a[i] == 1);
      return a2;
    }
    
    /////////////////////////////////////////////////////////////////////////////
    //                                                                         //
    //           Fast Diffusion Algorithm for Turing Patterns                  //
    //                                                                         //
    /////////////////////////////////////////////////////////////////////////////
    
    
    void blur(float[] from, float[] buffer, int w, int h) {
      for (int y = 0; y < h; y++) for (int x = 0; x < w; x++) {
        int i = y * w + x;
        int E = x > 0 ? - 1 : 0;
        int N = y > 0 ? - w : 0;
        buffer[i] = buffer[i+N] + buffer[i+E] - buffer[i+N+E] + from[i];
      }
    }
    
    void collect(float to[], float[] buffer, int w, int h, int radius) {
      for (int y = 0; y < h; y++) for (int x = 0; x < w; x++) {
        int minx = max(0, x - radius);
        int maxx = min(x + radius, w - 1);
        int miny = max(0, y - radius);
        int maxy = min(y + radius, h - 1);
        int area = (maxx - minx) * (maxy - miny);
        to[y * w + x] = (buffer[maxy * w + maxx] - buffer[maxy * w + minx] - buffer[ miny * w + maxx] + buffer[miny * w + minx]) / area;
      }
    }
    
    /////////////////////////////////////////////////////////////////////////////
    //                                                                         //
    //                     Mouse and Keyboard Interaction                      //
    //                                                                         //
    /////////////////////////////////////////////////////////////////////////////
    
    
    ////////////////////////// mouse interaction ////////////////////////////////
    
    int imgMouseX, imgMouseY;
    boolean drawing;
    
    void interaction() {
      // interaction inside the image
      imgMouseX = mouseX - spacing;
      imgMouseY = mouseY - spacing;
      cursor (drawing ? CROSS : ARROW);
      if(mousePressed) mouseDown();
    }
    
    boolean insideImage() {
      return imgMouseX>0 && imgMouseX < imgWidth && imgMouseY > 0 && imgMouseY < imgHeight; 
    }
    
    void mousePressed() {
      if(mouseButton!=CENTER) {
        drawing = insideImage();
      }
    }
    
    void mouseReleased() {
      drawing = false; 
    }
    
    void mouseDragged() {
      if (mouseButton==CENTER) {
        colorOffset += 256 * (dist(pmouseX, pmouseY, mouseX, mouseY) / width);
      }
    }
    
    void mouseDown() {
      
      // add a circular drop of chemical
      
      if(drawing && mouseButton != CENTER) {
      int x0 = imgMouseX / resolution;
      int y0 = imgMouseY / resolution;
      int brushIndex = levels - 4 + constrain(brush, 1, 3);
      int r = radii[brushIndex] / resolution;
      int xmin = max(0, x0-r), xmax = min(w, x0+r);
      int ymin = max(0, y0-r), ymax = min(h, y0+r);
      for(int y = ymin; y < ymax; y++) for(int x = xmin; x < xmax; x++) {
          if(dist(x, y, x0, y0) < r)
            grid[x + w*y] = mouseButton == LEFT ? gmax : gmin;
        }
      }
    }
    
    
    ////////////////////////// keyboard interaction /////////////////////////////
    
    void keyPressed() {
      
      switch( key ) {
        
        case ' ': resetParams(true); break;
        
        case 'c': colMode = !colMode; break;
        case 'i': invertMode = !invertMode; break;
        
        case 'l': resolution = 2; resetParams(); break;
        case 'h': resolution = 1; resetParams(); break;
        
        case '+': brush = min(3, brush+1); break;
        case '-': brush = max(1, brush-1); break;
    
        
        case 'q': stepOffset += .001; break;
        case 'w': stepScale += .001; break;
        case 'e': blurFactor += .01; break; 
        case 'r': colorOffset =  (colorOffset + 1) % 256; break;
        
        case 'a': stepOffset -= .001; break;
        case 's': stepScale -= .001; break;
        case 'd': blurFactor -= .01; break;
        case 'f': colorOffset = (colorOffset + 256 - 1) % 256; break;
        
        default:
          // select symmetry via number keys
          if(key>='0' && key <= '6') {
            symmetry = key - '0';
            if(symmetry == 1) symmetry = 0;
            resetParams();
          } else return;
      }
      
      stepScale = constrain(stepScale, stepScaleMin, stepScaleMax);
      stepOffset = constrain(stepOffset, stepOffsetMin, stepOffsetMax);
      blurFactor = constrain(blurFactor, 0, 1);
      
      updateParams(); 
      updateControls();
    }
    
    
    /////////////////////////////////////////////////////////////////////////////
    //                                                                         //
    //      Parameters for the Multi-Color Multi-Level Turing Pattern          //
    //                                                                         //
    /////////////////////////////////////////////////////////////////////////////
    
    int w, h, n, levels, blurlevels;
    float gridmin, gridmax, colormax, colormin;
    
    float[] grid, colorgrid, bestVariation;
    float[] diffusionLeft, diffusionRight, blurBuffer;
    float[] stepSizes, colorShift;
    boolean[] direction;
    int[] bestLevel, radii;
    PImage buffer;
    
    // independent params
    
    float base = 2.0;
    float stepScale = .01;
    float stepOffset = .01;
    float blurFactor = 1.0;
    int symmetry = 0;
    int colorOffset = 0;
    
    
    
    
    void resetParams() {
      resetParams(false);
    }
    
    void resetParams(boolean randomize) {
        
      // set resolution; 
      imgWidth = (width - controlWidth - 3 * spacing) ;
      imgHeight = (height - 2 * spacing);  
      w = imgWidth / resolution;
      h = imgHeight / resolution;
      
      
      // allocate space
      n = w * h;
      grid = new float[n];
      diffusionLeft = new float[n];
      diffusionRight = new float[n];
      blurBuffer = new float[n];
      bestVariation = new float[n];
      bestLevel = new int[n];
      direction = new boolean[n];
      colorgrid = new float[n];
      buffer = createImage(w, h, RGB);
      
      // initialize the grid with noise
      for (int i = 0; i < n; i++) {
        grid[i] = random(-1, +1);
      }
      
      // initialize pattern params
      if(randomize) randomizeParams();
      updateParams(); 
    }
    
    void randomizeParams() {
      
       // relates to the complexity (how many blur levels)
      base = random(1.5, 2.4); 
       
      // these values affect how fast the image changes
      stepScale = random(stepScaleMin, stepScaleMax / 5);
      stepOffset = random(stepOffsetMin, stepOffsetMax / 5);
      
      // color scheme of the image
      colorOffset = (int) random(255);
      
      // rotational symmetry groups
      symmetry = int(random(.5, 6.5));
      if(symmetry==1) symmetry = 0;
      
      // random shape blurring
      blurFactor = random(0.5, 1.0);
    }
    
    
    void updateParams() {
      
      // updating the dependent variables
      
      levels = (int) (log(max(w,h)) / log(base)) - 1;
      blurlevels = (int) max(0, (levels+1) * blurFactor - .5);
            
      radii = new int[levels];
      stepSizes = new float[levels];
      colorShift = new float[levels];
      
      for (int i = 0; i < levels; i++) {
        int radius = (int) pow(base, i);
        radii[i] = radius;
        stepSizes[i] = log(radius) * stepScale + stepOffset;
        colorShift[i] = (i % 2 == 0 ? -1.0 : 1.0) * (levels-i);
      }
      
    }
    
    /////////////////////////////////////////////////////////////////////////////
    //                                                                         //
    //     Algorithm for the Multi-Color Multi-Level Turing Pattern            //
    //                                                                         //
    /////////////////////////////////////////////////////////////////////////////
    
    int gmin, gmax;
    
    void calculatePattern() {
    
      float[] source = grid;
      float[] target = diffusionRight;
      
      for (int level = 0; level < levels; level++) {
        
        // diffuse chemical to the target layer
        int radius = radii[level]; 
        if(level <= blurlevels) blur(source, blurBuffer, w, h);
        collect(target, blurBuffer, w, h, radius);
        
        // check/save bestLevel and bestVariation
        for (int i = 0; i < n; i++) {
          float variation = abs(source[i] - target[i]);
          if (level == 0 || variation < bestVariation[i]) {
            bestVariation[i] = variation;
            bestLevel[i] = level;
            direction[i] = source[i] > target[i];
          }
        }
        
        // update diffusion matrices
        if(level==0) {
          source = target;
          target = diffusionLeft;
        } else {
          float[] swap = source;
          source = target;
          target = swap;
        }
        
      }
     
      // update grid from bestLevel
      gridmin = colormin = MAX_FLOAT;
      gridmax = colormax = MIN_FLOAT;
      for (int i = 0; i < n; i++) {
        float curStep = stepSizes[bestLevel[i]];
        if (direction[i]) {
          grid[i] += curStep;
          colorgrid[i] += curStep * colorShift[bestLevel[i]];
        }
        else {
          grid[i] -= curStep;
          colorgrid[i] -= curStep * colorShift[bestLevel[i]];
        }
        gridmin = min(gridmin, grid[i]);
        gridmax = max(gridmax, grid[i]);
        colormin = min(colormin, colorgrid[i]);
        colormax = max(colormax, colorgrid[i]);   
      }
     
      // normalize to [-1, +1]
      float range = (gridmax - gridmin) / 2;
      float colorrange = (colormax - colormin) / 2;
      for (int i = 0; i < n; i++) {
        grid[i] = ((grid[i] - gridmin) / range) - 1;
        colorgrid[i] = ((colorgrid[i] - colormin) / colorrange) - 1;
      }
      
    }
    
    
    void renderPattern(PImage img) {
      img.loadPixels();
      color[] pixels = buffer.pixels;
      gmin = invertMode ? -1 : +1;
      gmax = invertMode ? +1 : -1;
      for (int i = 0; i < n; i++) {
        float h = int(map(colorgrid[i], gmin, gmax, 0, 127) + colorOffset)  % 256;
        float b = map(grid[i], gmin, gmax, 0, 255);
        float s = (255-b) / 2;
        pixels[i] = colMode ? color(h, s, b) : color(b);
      }
      img.updatePixels();
    }
    
    
    /////////////////////////////////////////////////////////////////////////////
    //                                                                         //
    //    Quick Symmetry mapping - for incremental rotational symmetery        //
    //                                                                         //
    /////////////////////////////////////////////////////////////////////////////
    
    float[] sinus =   { 0, sin(TWO_PI/1), sin(TWO_PI/2),  sin(TWO_PI/3),  sin(TWO_PI/4),  sin(TWO_PI/5),  sin(TWO_PI/6) };
    float[] cosinus = { 0, cos(TWO_PI/1), cos(TWO_PI/2),  cos(TWO_PI/3),  cos(TWO_PI/4),  cos(TWO_PI/5),  cos(TWO_PI/6) };
    
    int getSymmetry(int i, int w, int h) {
      if(symmetry <= 1) return i;
      if(symmetry == 2) return n - 1 - i;
      int x1 = i % w;
      int y1 = i / w;
      float dx = x1 - w/2;
      float dy = y1 - h/2;
      int x2 = w/2 + (int)(dx * cosinus[symmetry] + dy * sinus[symmetry]);
      int y2 = h/2 + (int)(dx * -sinus[symmetry] + dy * cosinus[symmetry]);
      int j = x2 + y2 * w;
      return j<0 ? j+n : j>=n ? j-n : j;
    }
    
    
    void symmetry() {
      if(symmetry <=1) return;
      for(int i = 0; i < n; i++) {
        grid[i] = grid[i] * .9 + grid[getSymmetry(i, w, h)] * .1;
      }
    }
    
    /////////////////////////////////////////////////////////////////////////////
    //                                                                         //
    //         Multi-Level Multi-Color Turing-McCabe Pattern Explorer          //
    //                                                                         //
    /////////////////////////////////////////////////////////////////////////////
    
    // (c) bit.craft 2011
    
    // Turing-McCabe patterns are based on a  multi-layer single-chemical reaction-diffusion system,
    // as first described by Jonathan Mc Cabe [1]
    
    // This code extends upon the very efficient implementation posted by Kyle McDonald [2]
    // The basic algorithm for Turing-McCabe Patterns was detailled by Jason Rampe [3]
    // and first implemented in Processing by Frederik Vanhoutte [4]
    
    // [1] http://www.jonathanmccabe.com/Cyclic_Symmetric_Multi-Scale_Turing_Patterns.pdf
    // [2] http://www.openprocessing.org/visuals/?visualID=31195
    // [3] http://softologyblog.wordpress.com/2011/07/05/multi-scale-turing-patterns/
    // [4] http://www.wblut.com/2011/07/13/mccabeism-turning-noise-into-a-thing-of-beauty/
    
    
    boolean colMode = true;
    boolean invertMode = false;
    int resolution = 2;
    int spacing = 20;
    int brush = 2;
    float stepOffsetMin = .001;
    float stepOffsetMax = .2;
    float stepScaleMin = .001;
    float stepScaleMax = .1;
    
    void setup() {
      size(800, 500, P2D);
      //size(720, 600, P2D);
      resetParams();
      setupControls();
      updateControls();
      colorMode(HSB);
    }
    
     
    void draw() {
      symmetry();
      calculatePattern();
      background(255);
      renderPattern(buffer);
      drawImage(buffer);
      drawControls();
      interaction();  
      // if(frameCount % 20 == 0) println(frameRate);
    }
    
    

    code

    tweaks (0)

    license

    advertisement

    bitcraft

    Turing-McCabe Pattern Explorer

    Add to Faves Me Likey@! 107
    You must login/register to add this sketch to your favorites.

    Turing-McCabe patterns are based on a multi-layer single-chemical reaction-diffusion system, as first described by Jonathan Mc Cabe.
    This code extends upon the very efficient implementation posted by Kyle McDonald.
    ( http://www.openprocessing.org/visuals/?visualID=31195 )

    Press space to create a new pattern.

    Simo Endre
    10 Aug 2011
    Beautiful!
    Kles4enko Andrey
    12 Aug 2011
    Нихуясе!
    Xiaohan Zhang
    22 Aug 2011
    I've got a screenshot of Kyle's implementation as my background right now. The beauty of it is just unbelievable.
    Karl Hiner
    22 Aug 2011
    This is astoundingly gorgeous!
    Ale
    24 Aug 2011
    Hats off!
    jacques maire
    2 Jan 2012
    extraordinaire exploitation des ressources de Processing
    kl wa
    14 Jan 2012
    great work!!
    what is that: P5

    please
    Fabien Rioli
    21 Mar 2012
    Soooo organic !
    Karl Hiner
    21 Mar 2012
    @Gottfried - ControlP5 is a UI library for Processing - http://www.sojamo.de/libraries/controlP5/
    gil
    4 Apr 2012
    super!
    Luiz Zanotello
    20 Apr 2012
    This is absolutely incredible!
    hallo Karl

    yes ok thanks for that P5
    g
    hallo Karl

    yes ok thanks for that P5
    g
    qdiiibp
    24 Dec 2012
    Added a mod of this sketch my "best of OpenProcessing" Android Live Wallpaper:

    https://play.google.com/store/apps/details?id=glesii.prozessing.android&feature=search_result#
    Michal Huller
    14 Jun 2013
    Beautiful! Plasma full of life.
    lawrence ervin
    26 Nov 2013
    You are an amazing creator. Thank you for the inspiration!
    You need to login/register to comment.