Recently I've been looking at Povray, pyprocessing, and cfdg (version 3.0) as tools for creating digital images. I have branched two separate blogs where I mainly explore jruby + processing and processing.py

Thursday, 28 January 2010

3D Hilbert Using LSystems and Processing

See the P3D applet running at the open processing gallery.
Revised version 30 January 2010, I'm now using the excellent PeasyCam library, it makes setting up the camera so easy... Use mouse wheel to zoom, mouse drag to rotate etc.
   1 import processing.opengl.*;
   2 import java.text.*;
   3 import lsystem.*;  // My custom LSystem library available at Kenai version 0.5.0
   4                    // http://kenai.com/projects/l-system-utilities/downloads
   5 import peasy.*;    // PeasyCam custom library v0.8.1 available at 
   6                    // http://mrfeinberg.com/peasycam/#download
   7 PeasyCam cam;
   8 Grammar grammar;
   9 
  10 float distance = 72;
  11 int depth = 3;
  12 float theta = radians(90);
  13 float phi = radians(90);
  14 String production = "";
  15 
  16 void setup() {
  17   size(500, 500, OPENGL);
  18   configureOpenGL();  
  19   cam = new PeasyCam(this, 100);  
  20   cam.setMinimumDistance(100);
  21   cam.setMaximumDistance(500);
  22   setupGrammar();
  23   noStroke();  
  24 }
  25 
  26 void configureOpenGL(){
  27   hint(ENABLE_OPENGL_4X_SMOOTH);
  28   hint(DISABLE_OPENGL_ERROR_REPORT);
  29 }
  30 
  31 void setupGrammar(){
  32   grammar = new SimpleGrammar(this, "X");   // this only required to allow applet to call dispose()
  33   grammar.addRule('X', "^<XF^<XFX-F^>>XFX&F+>>XFX-F>X->");
  34   production = grammar.createGrammar(depth);
  35   distance *= pow(0.5, depth);
  36 }
  37 
  38 void draw() {
  39   background(0);
  40   lights();
  41   render();
  42 }
  43 
  44 void render() {
  45   translate(-40, 40, -40);  // center the hilbert empirically set looks good to me
  46   fill(0, 75, 152); 
  47   lightSpecular(204, 204, 204); 
  48   specular(255, 255, 255); 
  49   shininess(1.0); 
  50   CharacterIterator it = new StringCharacterIterator(production);
  51   for (char ch = it.first(); ch != CharacterIterator.DONE; ch = it.next()) {
  52     switch (ch) {
  53     case 'F': 
  54       translate(0, distance/-2, 0);
  55       box(distance/9, distance, distance/9);
  56       translate(0, distance/-2, 0);
  57       break;
  58     case '-':
  59       rotateX(theta);
  60       break;
  61     case '+':
  62       rotateX(-theta);
  63       break;
  64     case '>':
  65       rotateY(theta);
  66       break;
  67     case '<':
  68       rotateY(-theta);
  69       break;
  70     case '&':
  71       rotateZ(-phi);
  72       break;
  73     case '^':
  74       rotateZ(phi);
  75       break;
  76     case 'X':
  77       break;  
  78     default:
  79       System.err.println("character " + ch + " not in grammar");
  80     }
  81   }
  82 }


Tuesday, 26 January 2010

PureDyne Carrot and Coriander and PyProcessing

Well if you haven't had enough fun yet, there's plenty more things to experiment with and it won't cost a lot either:-
I've recently been playing with one of the most recent Linux distro releases, namely PureDyne Carrot and coriander which is aimed particularly at visual and audio artists (processing/arduino/inkscape and a whole heap of audio stuff, pre-installed). Does a nice job of configuring my graphics card (opengl works out of the box).
Then there is PyProcessing which works very nicely from jEdit (download the community python commando file to run it directly from the editor).
It will give me a chance to brush up my python, I'm very rusty, I only really did very much with it when I took up the python challenge. I was also very impressed by Pardus the Turkish linux distro that has based its distro on python scripts (seems a lot more elegant than assorted bash/perl/python scripts you get with most distros).




Turns out I don't think I'll doing too much python scripting a hour or so spent working trying to get the included samples working has convinced me to stick with ruby-processing, just because it mainly works with the exception of some issues of opengl on linux.

Friday, 22 January 2010

A Simple Fern Fractal with LSystems

I was too busy watching a BBC four program about Brian Eno to write this up properly initially, a very interesting program I would recommend to all aspiring digital artists....
I found this fern sketch at Gareth Spor artists website. Here I have modified it somewhat to include color, and to use my LSystems library for processing (I'm now thinking about expanding that library to have a Pen class [extending Turtle] and PenStack [extending TurtleStack] this could make the code clearer, and make it much easier to code such sketches).
   1 // FernTest.pde
   2 import java.util.Deque;
   3 import java.util.ArrayDeque;
   4 import java.text.CharacterIterator;
   5 import java.text.StringCharacterIterator;
   6 import lsystem.*;
   7 
   8 
   9 float DELTA = PI/180 * 34.9;  // boring = 36 too symmetrical
  10 color initColor = color(0, 255, 0); // light green stem?
  11 Fern fern;
  12 Grammar grammar;
  13 String production;
  14 PenStack ps;
  15 
  16 void setup() {
  17   size(800, 800);
  18   background(0);
  19   noLoop();
  20   ps = new PenStack();
  21   grammar = new SimpleGrammar(this, "FD");
  22   grammar.addRule('D', "C+@FD");
  23   grammar.addRule('C', "B");
  24   grammar.addRule('B', "[7+#FD][7-#FD]"); // abbreviated grammar
  25   production = grammar.createGrammar(18);
  26   fern = new Fern(production, DELTA);
  27 }
  28 
  29 void draw() {
  30   background(0);
  31   fern.draw(100.0, 600.0, 0.0, 100.0, initColor);
  32 }
  33 
  34 class Fern {
  35 
  36   String production;
  37   float delta;
  38 
  39   Fern(String prod, float angle) {
  40     production = prod;
  41     delta = angle;
  42   }
  43 
  44   void draw(float x, float y, float theta, float len, color col) {
  45     Pen drawPen;
  46     int repeats = 1;
  47     drawPen = new Pen(x, y, theta, len, delta, col);
  48     CharacterIterator it = new StringCharacterIterator(production);
  49     for (char ch = it.first(); ch != CharacterIterator.DONE; ch = it.next()) {
  50       switch (ch) {
  51       case 'F':  // move forward
  52         drawPen.drawForward();
  53         break;
  54       case '+':  //turn right
  55         drawPen.turnRight(repeats);
  56         repeats = 1;
  57         break;
  58       case '-':  //turn left
  59         drawPen.turnLeft(repeats);
  60         repeats = 1;
  61         break;
  62       case '#':  //resize line length
  63         drawPen.resizeLength(0.33);
  64         drawPen.changeColor(-20);
  65         break;
  66       case '@':  //resize line length
  67         drawPen.resizeLength(0.9);
  68         drawPen.changeColor(-10);
  69         break;
  70       case '[':  //push state
  71         ps.push(drawPen.clone());
  72         break;
  73       case ']':  //pop state
  74         drawPen = ps.pop();
  75         break;
  76       case '7':
  77         repeats += 7;
  78         break;
  79       case 'B': // do nothing except confirm character in grammar
  80         break;
  81       case 'C': // do nothing except confirm character in grammar
  82         break;
  83       case 'D': // do nothing except confirm character in grammar
  84         break;
  85       default:
  86         System.err.println("character " + ch + " not in grammar");
  87         break;
  88       }
  89     }
  90   }
  91 }
  92 
  93 /**
  94 * All attributes have primitive types therefore cloning is OK!
  95 */
  96 class Pen implements Cloneable{
  97 
  98   float x, y, theta, len, delta;
  99   int col;
 100 
 101   Pen(float xx, float yy, float direction, float len, float angle, int col) {
 102     x = xx;
 103     y = yy;
 104     theta = direction;
 105     this.len = len;
 106     delta = angle;
 107     this.col = col;
 108   }
 109 
 110   void drawForward() {
 111     float x0 = x;
 112     float y0 = y;
 113     x += len * cos(theta);
 114     y += len * sin(theta);
 115     strokeWeight(2);
 116     stroke(col);
 117     line(x0, y0, x, y);
 118   }
 119 
 120   void turnLeft(int repeats) {
 121     theta += TWO_PI % (repeats * delta);
 122   }
 123 
 124   void turnRight(int repeats) {
 125     theta -= TWO_PI % (repeats * delta);
 126   }
 127 
 128   void resizeLength(float factor) {
 129     len *= factor;
 130   }
 131 
 132   /**
 133    * A somewhat arbitary implementation of a changeColor function
 134    * here only the green channel is altered (HSB mode could be used
 135    * instead of RGB for easier color manipulation).
 136    */
 137 
 138   void changeColor(float increment){
 139     float gree = green(col);
 140     col = color(0, gree + increment, 0);
 141   }
 142   
 143   /**
 144    * Shallow copy Pen to save state, only storing primitive types so OK
 145    * @return a new copy of Pen instance having same state as original
 146    */
 147   Object clone(){
 148     try{ 
 149       return super.clone();
 150     }
 151     catch(CloneNotSupportedException ex){
 152       ex.printStackTrace();
 153     }
 154     return null;
 155   }
 156 }
 157 
 158 class PenStack {
 159   Deque penStack;
 160   
 161   PenStack(){
 162     penStack = new ArrayDeque();
 163   }
 164   
 165   void push(Object pen){
 166     penStack.push(pen);
 167   }
 168   
 169   Pen pop(){
 170     return (Pen)penStack.pop();
 171   }
 172 }

My the use of color change is somewhat restrained, try increasing the values for more effect, or adjust the red and/or blue channels for a much more funky version.

Wednesday, 20 January 2010

Exploring using enum for LSystem grammar with processing

Having just experimented with using symbols in ruby processing, it was a natural (well I thought so anyway) to explore enum. Now you can't do this in the ide as yet but here is where I started in NetBeans:-
   1 package lsystem;
   2 
   3 /**
   4  * Grammar.java
   5  * @author Martin Prout
   6  */
   7 public enum Grammar {
   8     FORWARD, PLUS, THREE, FOUR, FIVE, MINUS, SAVE, RESTORE;
   9 }

I had the view that I wasn't out to create a DSL so I would need to create an interface from regular String input to my enum grammar which yielded the following:-

   1 /*
   2  * CharacterInterface.java
   3  */
   4 package lsystem;
   5 
   6 import java.util.Map;
   7 import java.util.HashMap;
   8 import java.util.Collections;
   9 /**
  10  *
  11  * @author Martin Prout
  12  */
  13 public interface CharacterInterface  {
  14     Grammar[] tokens = Grammar.values();
  15     Character[] initial = {'F', '+', '3', '4', '5', '-', '[', ']'};
  16     Map<Character, Grammar> map = X.newMap();
  17 
  18     static class X {
  19 
  20         private X() {
  21         }
  22 
  23         private static Map<Character, Grammar> newMap() {
  24             Map<Character, Grammar> map = new HashMap<Character, Grammar>();
  25             for (int i = 0; i< tokens.length; i++)
  26             map.put(initial[i], tokens[i]);
  27             return Collections.unmodifiableMap(map);
  28         }
  29     }
  30 }

Now I've broken out of the processing ide I'm making full use of the java collections library (no support for generics using the ide, though interestingly it doesn't complain about generics if you stuff them in a library as per my lsystem library, the problem is with antlr).
Here is the implementing class that produces the Grammar from the input axiom and rules:-

   1 /*
   2  * SimpleGrammar.java
   3  */
   4 package lsystem;
   5 
   6 import java.util.*;
   7 import java.text.CharacterIterator;
   8 import java.text.StringCharacterIterator;
   9 
  10 /**
  11  * Implements Grammar interface
  12  * SimpleGrammar class that provides convenience method for working with l-systems
  13  * NB 'map' comes from Character Interface
  14  * @author Martin Prout
  15  */
  16 public class SimpleGrammar implements CharacterInterface {
  17 
  18     private Grammar[] axiom;
  19     private HashMap<Grammar, Grammar[]> rules;
  20 
  21     /**
  22      * @param axiom String
  23      */
  24     public SimpleGrammar(String axiom) {
  25         this.axiom = stringToArray(axiom);
  26         rules = new HashMap<Grammar, Grammar[]>();
  27     }
  28 
  29     private Grammar[] stringToArray(String aString) {
  30         List<Grammar> ruleArray = new ArrayList<Grammar>();
  31 
  32         CharacterIterator it = new StringCharacterIterator(aString);
  33         for (char ch = it.first(); ch != CharacterIterator.DONE; ch = it.next()) {
  34             ruleArray.add(map.get(ch));
  35         }
  36         return ruleArray.toArray(new Grammar[ruleArray.size()]);
  37     }
  38 
  39     public void addRule(char premise, String rule) {
  40         Grammar[] ruleArray = stringToArray(rule);
  41         rules.put(map.get(premise), ruleArray);
  42     }
  43 
  44     public Grammar[] getRule(Grammar premise) {
  45         return rules.get(premise);
  46     }
  47 
  48     public boolean hasKey(Grammar premise) {
  49         return rules.containsKey(premise);
  50     }
  51 
  52     /**
  53      * Private parseRules helper function
  54      * @param prod Grammar[]
  55      * @return production Grammar[]
  56      */
  57     private Grammar[] parseRules(Grammar[] production) {
  58         List<Grammar> prod = new ArrayList<Grammar>();
  59         for (int i = 0; i < production.length; i++) {
  60             if (hasKey(production[i])) {
  61                 prod.addAll(Arrays.asList(getRule(production[i])));
  62             } else {
  63                 prod.add(production[i]);
  64             }
  65         }
  66         return prod.toArray(new Grammar[prod.size()]);
  67     }
  68 
  69     public Grammar[] createGrammar(int repeats) {
  70         Grammar[] production = axiom;
  71         for (int i = 0; i < repeats; i++) {
  72             production = parseRules(production);
  73         }
  74         return production;
  75     }
  76 
  77     public Grammar[] createGrammar() {
  78         return createGrammar(0);
  79     }
  80 
  81     public static <Grammar> Grammar[] concat(Grammar[] first, Grammar[] second) {
  82         Grammar[] result = Arrays.copyOf(first, first.length + second.length);
  83         System.arraycopy(second, 0, result, first.length, second.length);
  84         return result;
  85     }
  86 }

Here is my penrose snowflake:-

   1 // PenroseSnowflake.java
   2 package lsystem;
   3 
   4 import java.util.*;
   5 import processing.core.*;
   6 
   7 public class PenroseSnowflake extends PApplet {
   8 
   9     float drawLength;
  10     float xpos;
  11     float ypos;
  12     float DELTA = PI / 10; // 18 degrees
  13     float theta = 0;
  14     SimpleGrammar grammar;
  15     Grammar[] production;
  16 
  17     @Override
  18     public void setup() {
  19         size(1000, 1000);
  20         createLSystem();
  21         background(0);
  22         stroke(255, 0, 0);
  23         noFill();
  24         smooth();
  25         translateRules();
  26         noLoop();
  27     }
  28 
  29     public void createLSystem() {
  30         int generations = 3;                 // set no of recursions
  31         String axiom = "F3-F3-F3-F3-F3-";  // NB  '-' without prefix repeat = 1 (so 3 means 4 repeats or 72 degrees)
  32         grammar = new SimpleGrammar(axiom);  // initialize custom library
  33         grammar.addRule('F', "F3-F3-F45-F++F3-F"); // add rule
  34         float startLength = 480;
  35         production = grammar.createGrammar(generations);
  36         drawLength = startLength * pow(0.4f, generations);
  37     }
  38 
  39     public void translateRules() {
  40         xpos = width / 4;
  41         ypos = height * 0.9f;
  42         int repeats = 1;
  43         for (int i = 0; i < production.length; i++) {
  44             switch (production[i]) {
  45                 case FORWARD:
  46                     line(xpos, ypos, xpos = getNextX(xpos, drawLength, theta, repeats), 
  47                       ypos = getNextY(ypos, drawLength, theta, repeats));
  48                     repeats = 1;
  49                     break;
  50                 case PLUS:
  51                     theta += (DELTA * repeats);
  52                     repeats = 1;
  53                     break;
  54                 case MINUS:
  55                     theta -= (DELTA * repeats);
  56                     repeats = 1;
  57                     break;
  58 
  59                 case THREE:
  60                     repeats += 3;
  61                     break;
  62                 case FOUR:
  63                     repeats += 4;
  64                     break;
  65                 case FIVE:
  66                     repeats += 5;
  67                     break;
  68                 default:
  69                     System.err.println("character " + production[i] + " not in grammar");
  70             }
  71         }
  72     }
  73 
  74     private float getNextX(float xpos, float drawLength, float angle, float repeats){
  75         xpos += drawLength * repeats * cos(angle);
  76         return xpos;
  77     }
  78 
  79     private float getNextY(float ypos, float drawLength, float angle, float repeats){
  80         ypos += drawLength * repeats * sin(angle);
  81         return ypos;
  82     }
  83 
  84     static public void main(String args[]) {
  85         PApplet.main(new String[]{"--present", "--bgcolor=#666666", "--stop-color=#cccccc", "lsystem.PenroseSnowflake"});
  86     }
  87 }

Followers

Blog Archive

About Me

My photo
Pembrokeshire, United Kingdom
I have developed JRubyArt and propane new versions of ruby-processing for JRuby-9.1.5.0 and processing-3.2.2