"A human being should be able to change a diaper, plan an invasion, butcher a hog, conn a ship, design a building, write a sonnet, balance accounts, build a wall, set a bone, comfort the dying, take orders, give orders, cooperate, act alone, solve equations, analyze a new problem, pitch manure, program a computer, cook a tasty meal, fight efficiently, die gallantly. Specialization is for insects." (Robert A. Heinlein)

Thursday, 18 December 2008

Merry (fractal) Christmas

This is my first Christmas as a blogger and, an old coder like me couldn't help not to post some Christmas related piece of code. I spent some of my spare time, mainly while travelling by train, writing a small java program to draw a fractal tree and I finally tuned it to produce something resembling a Christmas tree.
The algorithm used is fairly simple:  just draw a branch (a segment) and then attach some others branches to it calling recursively the same branch method. Recursive calls are stopped when branch length falls below a giver value. to make the final result a little more realistic a little randomness is added and branch color is decided on the basis of branch length.
The result is far from being satisfying (and lightyears far from artistic results as in here) but I don't think I'll get something better before Christmas.
  
Here is the code of the tree drawing class while the user interface was generated by the Netbeans plugin with only minimal manual coding needed.
package ph.mm.demo.fractals;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Random;

/**
 *
 @author user
 */
public class TreePainter {

    Graphics2D graphics = null;
    private double scale = 0.42;
    private double scale2 = 0.49;
    private double angle =115;
    private double minLength = 3;
    private double rangle = 0;
    private double split = 0.6;
    private double noise = 0.05;
    private long seed = 3214;
    private Random rnd=null;
    private double x0=200;
    private double y0=350;
    private double x1=200;
    private double y1=200;
    float colScale = 0;
            
        
    public TreePainter(Graphics g) {
        graphics = (Graphics2D)g;
        graphics.setBackground(Color.BLACK);
        graphics.clearRect(00500500);
        rangle=Math.toRadians(angle);
        rnd = new Random(seed);
        colScale = (float)(1/(y0-y1));
    }
   
    public void paint(){
        Line2D l = new Line2D.Double(x0, y0, x1, y1);
        branch(l);
    }
    
    public void branch(Line2D l){
        Rectangle2D r = l.getBounds2D();
        double size = Math.sqrt(r.getWidth()*r.getWidth() + r.getHeight()*r.getHeight());
        graphics.setColor(getCol(size));
        graphics.draw(l);
        
        if(size>minLength){
            double cAngle=0;
            cAngle = Math.acos((l.getX2()-l.getX1())/size);
            System.out.println(size+ ", " + Math.toDegrees(cAngle));
            if(l.getY1()>l.getY2()) cAngle=-cAngle;
            
            Point2D pSplit = new Point2D.Double(l.getX1() (l.getX2()-l.getX1())*split,
                                                l.getY1() (l.getY2()-l.getY1())*split);
            Point2D pEnd1 =  new Point2D.Double(l.getX2() + size*scale*getNoise()*Math.cos(cAngle)
                                                l.getY2() + size*scale*getNoise()*Math.sin(cAngle));
            Point2D pEnd2 =  new Point2D.Double(l.getX2() + size*scale*getNoise()* Math.cos(cAngle*getNoise()+rangle)
                                                l.getY2() + size*scale*getNoise()*Math.sin(cAngle*getNoise()+rangle));
            Point2D pEnd3 =  new Point2D.Double(l.getX2() + size*scale*getNoise()* Math.cos(cAngle*getNoise()-rangle)
                                                l.getY2() + size*scale*getNoise()*Math.sin(cAngle*getNoise()-rangle));
            Point2D pEnd4 =  new Point2D.Double(pSplit.getX() + size*scale2*getNoise()* Math.cos(cAngle*getNoise()+rangle)
                                                pSplit.getY() + size*scale2*getNoise()*Math.sin(cAngle*getNoise()+rangle));
            Point2D pEnd5 =  new Point2D.Double(pSplit.getX() + size*scale2*getNoise()* Math.cos(cAngle*getNoise()-rangle)
                                                pSplit.getY() + size*scale2*getNoise()*Math.sin(cAngle*getNoise()-rangle));
            branch(new Line2D.Double(l.getP2(), pEnd1));
            branch(new Line2D.Double(l.getP2(), pEnd2));
            branch(new Line2D.Double(l.getP2(), pEnd3));
            branch(new Line2D.Double(pSplit, pEnd4));
            branch(new Line2D.Double(pSplit, pEnd5));
        }
        
    }
    
    private double getNoise(){
        return (rnd.nextDouble()-0.5)*2*noise+1;
    }
    
    private Color getCol(double size){
        if(size<minLength)
            return new Color(0.05F0.5F0.2F);
        else if(size<minLength*2)
            return new Color(0.05F,0.4F0.1F);
        else if(size<minLength*10)
            return new Color(0.05F,0.3F0.3F);
        else
            return new Color(0.3F,0.3F0.1F);
    }
            
}