Substrate

July 2, 2008 | drawing

This example is based on "Substrate" by Jared Tarbell (complexification.net). Some modifications were made to the color rendering algorithm. (Warning: the new MacRuby version eats a lot more memory than the older RubyCocoa version, so you may want to lower the number of cracks or frames)

#!/usr/local/bin/macruby

# Ported to HotCocoa::Graphics by James Reynolds 6/1/2008 from:
# Substrate Watercolor
# j.tarbell   June, 2004
# Albuquerque, New Mexico
# complexification.net

include HotCocoa
include Graphics

FRAMES = 800
DIMX = 400
DIMY = 400

# sand painters
COLORSIZE = 2.0
CRACKSIZE = 1.0
MAXCRACKS = 100
SANDGRAINS = 64

# color parameters
MAXPAL = 256
NUMPAL = 0
HUEDRIFT = 0.0

# grab colors
i = Image.new('v2.jpg')
GOODCOLOR = i.colors(MAXPAL)

# grid of CRACKS
CGRID = Array.new(DIMX*DIMY);
CRACKS = Array.new(MAXCRACKS);
$numcracks = 0;
C = Canvas.new :type => :image, :filename => 'substrate.png', :size => [400,400]
C.background(Color.white);

# METHODS --------------------------------------------------

def makeCrack()
  if ($numcracks<MAXCRACKS)
    # make a new crack instance
    CRACKS[$numcracks] = Crack.new
    $numcracks += 1
  end
end

def beginstuff
  # erase crack grid
  for y in 0...DIMY do
    for x in 0...DIMX do
      CGRID[y*DIMX+x] = 10001;
    end
  end
  # make random crack seeds
  for k in 0...16 do
    i = rand(DIMX*DIMY-1)
    CGRID[i]=rand(360)
  end

  # make just three CRACKS
  $numcracks=0;
  for k in 0...3 do
    makeCrack()
  end
  C.background(Color.white);
end

# OBJECTS -------------------------------------------------------

class Crack

  def initialize
    puts "Crack.new"
    # find placement along existing crack
    @sp = SandPainter.new(C)
    @sp.grains = SANDGRAINS
    @sp.grainsize = COLORSIZE
    @sp.huedrift = HUEDRIFT
    @sp.color = choose(GOODCOLOR)
    @verbose = true
    findStart();
  end

  def findStart()

    # pick random point
    px=0;
    py=0;

    # shift until crack is found
    found=false;
    timeout = 0;
    #while ((!found) && (timeout < 1000))
    while ((!found))# || (timeout > 1000))
      px = rand(DIMX)
      py = rand(DIMY)
      if (CGRID[py*DIMX+px]<10000)
        found=true;
        puts "found crack at #{px},#{py} with angle #{CGRID[py*DIMX+px]} (#{found})" if @verbose
      else
        #puts "checked #{px},#{py}" if @verbose
      end
      timeout += 1
    end

    if (found)
      # start crack
      a = CGRID[py*DIMX+px];
      if (random(100)<50)
        a-=90+random(-2,2.1)#.to_i;
      else
        a+=90+random(-2,2.1)#.to_i;
      end
      startCrack(px,py,a);
    else
      println("timeout: "+timeout);
    end
  end

  def startCrack(x, y, t)
    puts "starting crack at #{x},#{y} with angle #{t}" if @verbose
    @x=x;
    @y=y;
    @t=t;#%360;
    @x+=0.61*cos(@t*PI/180);
    @y+=0.61*sin(@t*PI/180);
  end

  def move()
    # continue cracking
    @x+=0.42*cos(@t*PI/180);
    @y+=0.42*sin(@t*PI/180);

    # bound check
    z = 0.33;
    cx = (@x+random(-z,z)).to_i;  # add fuzz
    cy = (@y+random(-z,z)).to_i;

    # draw sand painter
    regionColor();

    # draw black crack
    C.fill(Color.black);
    C.oval(@x+random(-z,z),@y+random(-z,z),CRACKSIZE,CRACKSIZE);

    if ((cx>=0) && (cx<DIMX) && (cy>=0) && (cy<DIMY))
      # safe to check
      if ((CGRID[cy*DIMX+cx]>10000) || (CGRID[cy*DIMX+cx]-@t).abs<5)
        # continue cracking
        CGRID[cy*DIMX+cx]=@t.to_i
      elsif ((CGRID[cy*DIMX+cx]-@t).abs>2)
        # crack encountered (not self), stop cracking
        findStart();
        makeCrack();
      end
    else
      # out of bounds, stop cracking
      findStart();
      makeCrack();
    end
  end

  def regionColor()
    # start checking one step away
    rx=@x;
    ry=@y;
    openspace=true;

    # find extents of open space
    while (openspace)
      # move perpendicular to crack
      rx+=0.81*sin(@t*PI/180);
      ry-=0.81*cos(@t*PI/180);
      cx = rx.to_i
      cy = ry.to_i
      if ((cx>=0) && (cx<DIMX) && (cy>=0) && (cy<DIMY))
        # safe to check
        if (CGRID[cy*DIMX+cx]>10000)
          # space is open
        else
          openspace=false;
        end
      else
        openspace=false;
      end
    end
    # draw sand painter
    @sp.render(@x,@y,rx,ry);
  end
end

beginstuff

for i in 0..FRAMES do
  # crack all CRACKS
  for n in 0...$numcracks do
    puts "frame #{i} moving crack #{n}"
    CRACKS[n].move();
  end
end

C.save