Brownian tree
This page uses content from Wikipedia. The original article was at Brownian_tree. The list of authors can be seen in the page history. As with Rosetta Code, the text of Wikipedia is available under the GNU FDL. (See links for details on variance) |
You are encouraged to solve this task according to the task description, using any language you may know.
Generate and draw a Brownian Tree.
A Brownian Tree is generated as a result of an initial seed, followed by the interaction of two processes.
- The initial "seed" is placed somewhere within the field. Where is not particularly important; it could be randomized, or it could be a fixed point.
- Particles are injected into the field, and are individually given a (typically random) motion pattern.
- When a particle collides with the seed or tree, its position is fixed, and it's considered to be part of the tree.
Because of the lax rules governing the random nature of the particle's placement and motion, no two resulting trees are really expected to be the same, or even necessarily have the same general shape.
C
<lang c>#include <string.h>
- include <stdlib.h>
- include <time.h>
- include <math.h>
- include <FreeImage.h>
- define NUM_PARTICLES 1000
- define SIZE 800
void draw_brownian_tree(int world[SIZE][SIZE]){
int px, py; // particle values int dx, dy; // offsets int i; // set the seed world[rand() % SIZE][rand() % SIZE] = 1;
for (i = 0; i < NUM_PARTICLES; i++){ // set particle's initial position px = rand() % SIZE; py = rand() % SIZE;
while (1){ // randomly choose a direction dx = rand() % 3 - 1; dy = rand() % 3 - 1;
if (dx + px < 0 || dx + px >= SIZE || dy + py < 0 || dy + py >= SIZE){ // plop the particle into some other random location px = rand() % SIZE; py = rand() % SIZE; }else if (world[py + dy][px + dx] != 0){ // bumped into something world[py][px] = 1; break; }else{ py += dy; px += dx; } } }
}
int main(){
int world[SIZE][SIZE]; FIBITMAP * img; RGBQUAD rgb; int x, y; memset(world, 0, sizeof world); srand((unsigned)time(NULL));
draw_brownian_tree(world);
img = FreeImage_Allocate(SIZE, SIZE, 32, 0, 0, 0);
for (y = 0; y < SIZE; y++){ for (x = 0; x < SIZE; x++){ rgb.rgbRed = rgb.rgbGreen = rgb.rgbBlue = (world[y][x] ? 255 : 0); FreeImage_SetPixelColor(img, x, y, &rgb); } } FreeImage_Save(FIF_BMP, img, "brownian_tree.bmp", 0); FreeImage_Unload(img);
}</lang>
C#
<lang csharp>using System; using System.Drawing;
namespace BrownianTree {
class Program { static Bitmap BrownianTree(int size, int numparticles) { Bitmap bmp = new Bitmap(size, size); Rectangle bounds = new Rectangle { X = 0, Y = 0, Size = bmp.Size }; using (Graphics g = Graphics.FromImage(bmp)) { g.Clear(Color.Black); } Random rnd = new Random(); bmp.SetPixel(rnd.Next(size), rnd.Next(size), Color.White); Point pt = new Point(), newpt = new Point(); for (int i = 0; i < numparticles; i++) { pt.X = rnd.Next(size); pt.Y = rnd.Next(size); do { newpt.X = pt.X + rnd.Next(-1, 2); newpt.Y = pt.Y + rnd.Next(-1, 2); if (!bounds.Contains(newpt)) { pt.X = rnd.Next(size); pt.Y = rnd.Next(size); } else if (bmp.GetPixel(newpt.X, newpt.Y).R > 0) { bmp.SetPixel(pt.X, pt.Y, Color.White); break; } else { pt = newpt; } } while (true); } return bmp; }
static void Main(string[] args) { BrownianTree(300, 3000).Save("browniantree.png"); } }
}</lang>
D
Writes a Portable Bit Map image to stdout. A more efficient version generates particles in a disk not too much larger than the current tree. <lang d>import core.stdc.stdlib: rand, srand; import core.stdc.time: time; import core.stdc.stdio: printf, putchar;
enum int WORLD_WIDTH = 800; enum int WORLD_HEIGHT = WORLD_WIDTH; enum int NUM_PARTICLES = 10_000;
static assert(NUM_PARTICLES > 0); static assert((WORLD_WIDTH * WORLD_HEIGHT * 0.7) > NUM_PARTICLES);
alias ubyte[WORLD_WIDTH][WORLD_HEIGHT] TWorld;
nothrow void dla(ref TWorld world) {
world[WORLD_HEIGHT / 2][WORLD_WIDTH / 2] = 1; // put tree seed
foreach (_; 0 .. NUM_PARTICLES) { // particle initial position int px = rand() % WORLD_WIDTH; int py = rand() % WORLD_HEIGHT;
while (true) { // move particle // randomly choose a direction const int dxy = rand() % 9; const int dx = (dxy % 3) - 1; // offsets const int dy = (dxy / 3) - 1;
if (dx + px < 0 || dx + px >= WORLD_WIDTH || dy + py < 0 || dy + py >= WORLD_HEIGHT) { // move the particle to some other random location px = rand() % WORLD_WIDTH; py = rand() % WORLD_HEIGHT; } else if (world[py + dy][px + dx]) { world[py][px] = 1; // particle has touched, set it break; } else { // move particle py += dy; px += dx; } } }
}
nothrow void toPBM(ref const TWorld world) {
printf("P1\n"); // Type=Portable bitmap, Encoding=ASCII printf("%d %d\n", WORLD_WIDTH, WORLD_HEIGHT); foreach (ref line; world) { foreach (pixel; line) printf(pixel ? "1 " : "0 "); putchar('\n'); }
}
void main() {
srand(cast(uint)time(null)); TWorld world; dla(world); toPBM(world);
}</lang> One output, WORLD_WIDTH=34, WORLD_HEIGHT=14, NUM_PARTICLES=30:
P1 34 14 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 1 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Delphi
<lang delphi>const
SIZE = 256; NUM_PARTICLES = 1000;
procedure TForm1.Button1Click(Sender: TObject); type
TByteArray = array[0..0] of Byte; PByteArray = ^TByteArray;
var
B: TBitmap; I: Integer; P, D: TPoint;
begin
Randomize; B := TBitmap.Create; try B.Width := SIZE; B.Height := SIZE; B.PixelFormat := pf8bit;
B.Canvas.Brush.Color := clBlack; B.Canvas.FillRect(B.Canvas.ClipRect); B.Canvas.Pixels[Random(SIZE), Random(SIZE)] := clWhite;
For I := 0 to NUM_PARTICLES - 1 do Begin P.X := Random(SIZE); P.Y := Random(SIZE);
While true do Begin D.X := Random(3) - 1; D.Y := Random(3) - 1; Inc(P.X, D.X); Inc(P.Y, D.Y);
If ((P.X or P.Y) < 0) or (P.X >= SIZE) or (P.Y >= SIZE) Then Begin P.X := Random(SIZE); P.Y := Random(SIZE); end else if PByteArray(B.ScanLine[P.Y])^[P.X] <> 0 then begin PByteArray(B.ScanLine[P.Y-D.Y])^[P.X-D.X] := $FF; Break; end; end; end;
Canvas.Draw(0, 0, B); finally FreeAndNil(B); end;
end;</lang>
Fantom
<lang fantom> using fwt using gfx
class Main {
public static Void main () { particles := Particles (300, 200) 1000.times { particles.addParticle } // add 1000 particles Window // open up a display for the final tree { title = "Brownian Tree" EdgePane { center = ScrollPane { content = ParticleCanvas(particles) } }, }.open }
}
class Particles {
Bool[][] image Int height Int width
new make (Int height, Int width) { this.height = height this.width = width // set up initial image as an array of booleans with one set cell image = [,] width.times |w| { row := [,] height.times { row.add (false) } image.add (row) } image[Int.random(0..<width)][Int.random(0..<height)] = true }
Bool get (Int w, Int h) { return image[w][h] }
Void addParticle () { x := Int.random(0..<width) y := Int.random(0..<height)
Int dx := 0 Int dy := 0 while (!image[x][y]) // loop until hit existing part of the tree { dx = [-1,0,1].random dy = [-1,0,1].random
if ((0..<width).contains(x + dx)) x += dx else // did not change x, so set dx = 0 dx = 0 if ((0..<height).contains(y + dy)) y += dy else dy = 0 }
// put x,y back to just before move onto existing part of tree x -= dx y -= dy
image[x][y] = true }
}
class ParticleCanvas : Canvas {
Particles particles new make (Particles particles) { this.particles = particles }
// provides canvas size for parent scrollpane override Size prefSize(Hints hints := Hints.defVal) { Size(particles.width, particles.height) }
// repaint the display override Void onPaint (Graphics g) { g.brush = Color.black g.fillRect(0, 0, size.w, size.h) g.brush = Color.green particles.width.times |w| { particles.height.times |h| { if (particles.get(w, h)) // draw a 1x1 square for each set particle g.fillRect (w, h, 1, 1) } } }
} </lang>
Fortran
For RCImageBasic and RCImageIO, see Basic bitmap storage/Fortran and Write ppm file#Fortran
<lang fortran>program BrownianTree
use RCImageBasic use RCImageIO
implicit none
integer, parameter :: num_particles = 1000 integer, parameter :: wsize = 800
integer, dimension(wsize, wsize) :: world type(rgbimage) :: gworld integer :: x, y
! init seed call init_random_seed world = 0 call draw_brownian_tree(world)
call alloc_img(gworld, wsize, wsize) call fill_img(gworld, rgb(0,0,0)) do y = 1, wsize do x = 1, wsize if ( world(x, y) /= 0 ) then call put_pixel(gworld, x, y, rgb(255, 255, 255)) end if end do end do
open(unit=10, file='browniantree.ppm', action='write') call output_ppm(10, gworld) close(10)
call free_img(gworld)
contains
! this code is taken from the GNU gfortran online doc subroutine init_random_seed integer :: i, n, clock integer, dimension(:), allocatable :: seed
call random_seed(size = n) allocate(seed(n)) call system_clock(count = clock) seed = clock + 37 * (/ ( i - 1, i = 1, n) /) call random_seed(put = seed) deallocate(seed) end subroutine init_random_seed
function randbetween(a, b) result(res) ! suppose a < b integer, intent(in) :: a, b integer :: res
real :: r
call random_number(r)
res = a + int((b-a)*r + 0.5)
end function randbetween
function bounded(v, ll, ul) result(res) integer, intent(in) :: v, ll, ul logical res
res = ( v >= ll ) .and. ( v <= ul ) end function bounded
subroutine draw_brownian_tree(w) integer, dimension(:,:), intent(inout) :: w
integer :: px, py, dx, dy, i integer :: xsize, ysize
xsize = size(w, 1) ysize = size(w, 2)
w(randbetween(1, xsize), randbetween(1, ysize)) = 1 do i = 1, num_particles px = randbetween(1, xsize) py = randbetween(1, ysize) do dx = randbetween(-1, 1) dy = randbetween(-1, 1) if ( .not. bounded(dx+px, 1, xsize) .or. .not. bounded(dy+py, 1, ysize) ) then px = randbetween(1, xsize) py = randbetween(1, ysize) else if ( w(px+dx, py+dy) /= 0 ) then w(px, py) = 1 exit else py = py + dy px = px + dx end if end do end do end subroutine draw_brownian_tree
end program</lang>
Go
The interpretation here of "collide" in the case of a new particle generated on top of a pixel of the existing tree is not to ignore the particle, but to find a place for it nearby. This properly increases the brightness of the area, reflecting that a particle was generated in the area. Visually, it appears to strengthen existing spines of the tree. <lang go>package main
// Files required to build supporting package raster are found in: // * Bitmap // * Grayscale image // * Write a PPM file
import (
"fmt" "rand" "raster"
)
const w = 400 // image width const h = 300 // image height const n = 15000 // number of particles to add const frost = 65535 // white
var g *raster.Grmap
func main() {
g = raster.NewGrmap(w, h) // off center seed position makes pleasingly asymetrical tree g.SetPx(w/3, h/3, frost) var x, y int
generate:
for a := 0; a < n; { // generate random position for new particle x, y = rand.Intn(w), rand.Intn(h) switch p, ok := g.GetPx(x, y); p { case frost: // position is already set. find a nearby free position. for p == frost { x += rand.Intn(3) - 1 y += rand.Intn(3) - 1 p, ok = g.GetPx(x, y)
// execpt if we run out of bounds, consider the particle lost. if !ok { continue generate } } default: // else particle is in free space. let it wander // untill it touches tree for !hasNeighbor(x, y) { x += rand.Intn(3) - 1 y += rand.Intn(3) - 1 // but again, if it wanders out of bounds consider it lost. _, ok = g.GetPx(x, y) if !ok { continue generate } } } // x, y now specify a free position toucing the tree. g.SetPx(x, y, frost) a++ // progress indicator if a%100 == 0 { fmt.Println(a, "of", n) } } g.Bitmap().WritePpmFile("tree.ppm")
}
var n8 = [][]int{
{-1, -1}, {-1, 0}, {-1, 1}, { 0, -1}, { 0, 1}, { 1, -1}, { 1, 0}, { 1, 1}}
func hasNeighbor(x, y int) bool {
for _, n := range n8 { if p, ok := g.GetPx(x+n[0], y+n[1]); ok && p == frost { return true } } return false
}</lang>
Haskell
The modules Bitmap
, Bitmap.Netpbm
, and Bitmap.BW
are on Rosetta Code. The commented-out type signatures require scoped type variables in order to function.
<lang haskell>import Control.Monad import Control.Monad.ST import Data.STRef import Data.Array.ST import System.Random import Bitmap import Bitmap.BW import Bitmap.Netpbm
main = do
g <- getStdGen (t, _) <- stToIO $ drawTree (50, 50) (25, 25) 300 g writeNetpbm "/tmp/tree.pbm" t
drawTree :: (Int, Int) -> (Int, Int) -> Int -> StdGen -> ST s (Image s BW, StdGen) drawTree (width, height) start steps stdgen = do
img <- image width height off setPix img (Pixel start) on gen <- newSTRef stdgen let -- randomElem :: [a] -> ST s a randomElem l = do stdgen <- readSTRef gen let (i, stdgen') = randomR (0, length l - 1) stdgen writeSTRef gen stdgen' return $ l !! i -- newPoint :: ST s (Int, Int) newPoint = do p <- randomElem border c <- getPix img $ Pixel p if c == off then return p else newPoint -- wander :: (Int, Int) -> ST s () wander p = do next <- randomElem $ filter (inRange pointRange) $ adjacent p c <- getPix img $ Pixel next if c == on then setPix img (Pixel p) on else wander next replicateM_ steps $ newPoint >>= wander stdgen <- readSTRef gen return (img, stdgen) where pointRange = ((0, 0), (width - 1, height - 1)) adjacent (x, y) = [(x - 1, y - 1), (x, y - 1), (x + 1, y - 1), (x - 1, y), (x + 1, y), (x - 1, y + 1), (x, y + 1), (x + 1, y + 1)] border = liftM2 (,) [0, width - 1] [0 .. height - 1] ++ liftM2 (,) [1 .. width - 2] [0, height - 1] off = black on = white</lang>
Icon and Unicon
In this version the seed is randomly set within an inner area and particles are injected in an outer ring.
<lang Icon>link graphics,printf
procedure main() # brownian tree
Density := .08 # % particles to area SeedArea := .5 # central area to confine seed ParticleArea := .7 # central area to exclude particles appearing Height := Width := 400 # canvas
Particles := Height * Width * Density Field := list(Height) every !Field := list(Width)
Size := sprintf("size=%d,%d",Width,Height) Fg := sprintf("fg=%s",?["green","red","blue"]) Label := sprintf("label=Brownian Tree %dx%d PA=%d%% SA=%d%% D=%d%%",
Width,Height,ParticleArea*100,SeedArea*100,Density*100)
WOpen(Label,Size,Fg,"bg=black") | stop("Unable to open Window")
sx := Height * SetInside(SeedArea) sy := Width * SetInside(SeedArea) Field[sx,sy] := 1 DrawPoint(sx,sy) # Seed the field
Lost := 0
every 1 to Particles do {
repeat { px := Height * SetOutside(ParticleArea) py := Width * SetOutside(ParticleArea) if /Field[px,py] then break # don't materialize in the tree } repeat { dx := delta() dy := delta() if not ( xy := Field[px+dx,py+dy] ) then { Lost +:= 1 next # lost try again } if \xy then break # collision px +:= dx # move to clear spot py +:= dy } Field[px,py] := 1 DrawPoint(px,py) # Stick the particle }
printf("Brownian Tree Complete: Particles=%d Lost=%d.\n",Particles,Lost) WDone() end
procedure delta() #: return a random 1 pixel perturbation
return integer(?0 * 3) - 1
end
procedure SetInside(core) #: set coord inside area
return core * ?0 + (1-core)/2
end
procedure SetOutside(core) #: set coord outside area
pt := ?0 * (1 - core) pt +:= ( pt > (1-core)/2, core) return pt
end</lang>
graphics.icn provides graphics printf.icn provides printf
J
<lang j>brtr=:4 :0
seed=. ?x clip=. 0 >. (<:x) <."1 ] near=. [: clip +"1/&(,"0/~i:1) p=.i.0 2 mask=. 1 (<"1 near seed)} x$0 field=.1 (<seed)} x$0 for.i.y do. p=. clip (p +"1 <:?3$~$p),?x b=.(<"1 p) { mask fix=. b#p if.#fix do. NB. if. works around j602 bug: 0(0#a:)}i.0 0 p=. (-.b)# p mask=. 1 (<"1 near fix)} mask field=. 1 (<"1 fix)} field end. end. field
)</lang>
Example use:
<lang j> require'viewmat'
viewmat 480 640 brtr 30000</lang>
Note that building a brownian tree like this takes a while and would be more interesting if this were animated.
Java
<lang java>import java.awt.Graphics; import java.awt.image.BufferedImage; import java.util.*; import javax.swing.JFrame;
public class BrownianTree extends JFrame implements Runnable {
BufferedImage I; private List<Particle> particles; static Random rand = new Random();
public BrownianTree() { super("Brownian Tree"); setBounds(100, 100, 400, 300); setDefaultCloseOperation(EXIT_ON_CLOSE); I = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); I.setRGB(I.getWidth() / 2, I.getHeight() / 2, 0xff00); particles = new LinkedList<Particle>(); }
@Override public void paint(Graphics g) { g.drawImage(I, 0, 0, this); }
public void run() { for (int i = 0; i < 20000; i++) { particles.add(new Particle()); } while (!particles.isEmpty()) { for (Iterator<Particle> it = particles.iterator(); it.hasNext();) { if (it.next().move()) { it.remove(); } } repaint(); } }
public static void main(String[] args) { BrownianTree b = new BrownianTree(); b.setVisible(true); new Thread(b).start(); }
private class Particle {
private int x, y;
private Particle() { x = rand.nextInt(I.getWidth()); y = rand.nextInt(I.getHeight()); }
/* returns true if either out of bounds or collided with tree */ private boolean move() { int dx = rand.nextInt(3) - 1; int dy = rand.nextInt(3) - 1; if ((x + dx < 0) || (y + dy < 0) || (y + dy >= I.getHeight()) || (x + dx >= I.getWidth())) { return true; } x += dx; y += dy; if ((I.getRGB(x, y) & 0xff00) == 0xff00) { I.setRGB(x - dx, y - dy, 0xff00); return true; } return false; } }
}</lang>
JavaScript + <canvas>
Live version <lang javascript>function brownian(canvasId, messageId) {
var canvas = document.getElementById(canvasId); var ctx = canvas.getContext("2d");
// Options var drawPos = true; var seedResolution = 50; var clearShade = 0; // 0..255 // Static state var width = canvas.width; var height = canvas.height; var cx = width/2; var cy = height/2; var clearStyle = "rgba("+clearShade+", "+clearShade+", "+clearShade+", 1)";
// Utilities function radius(x,y) { return Math.sqrt((x-cx)*(x-cy)+(y-cx)*(y-cy)); } function test(x, y) { if (x < 0 || y < 0 || x >= width || y >= height) return false; var data = ctx.getImageData(x, y, 1, 1).data; return data[0] != clearShade || data[1] != clearShade || data[2] != clearShade; } var shade = 120; function setc(x, y, c) { //var imgd = ctx.createImageData(1, 1); //var pix = imgd.data; //pix[0] = pix[1] = pix[2] = c == 255 ? 255 : shade; //pix[3] = 255; //shade = (shade + 1) % 254; //ctx.putImageData(imgd, x, y); //ctx.fillStyle = "rgba("+c+", "+c+", "+c+", 1)"; shade = (shade + 0.02) % 360; if (c) { ctx.fillStyle = "hsl("+shade+", 100%, 50%)"; } else { ctx.fillStyle = clearStyle; } ctx.fillRect (x, y, 1, 1); } function set(x,y) { setc(x,y,true); } function clear(x,y) { setc(x,y,false); }
// Initialize canvas to blank opaque ctx.fillStyle = clearStyle; ctx.fillRect (0, 0, width, height);
// Current position var x; var y;
// Farthest distance from center a particle has yet been placed. var closeRadius = 1;
// Place seed set(cx, cy);
// Choose a new random position for a particle (not necessarily unoccupied) function newpos() { // Wherever particles are injected, the tree will tend to grow faster // toward it. Ideally, particles wander in from infinity; the best we // could do is to have them wander in from the edge of the field. // But in order to have the rendering occur in a reasonable time when // the seed is small, without too much visible bias, we instead place // the particles in a coarse grid. The final tree will cover every // point on the grid. // // There's probably a better strategy than this. x = Math.floor(Math.random()*(width/seedResolution))*seedResolution; y = Math.floor(Math.random()*(height/seedResolution))*seedResolution; } newpos();
var animation; animation = window.setInterval(function () { if (drawPos) clear(x,y); for (var i = 0; i < 10000; i++) { var ox = x; var oy = y; // Changing this to use only the first four directions will result // in a denser tree. switch (Math.floor(Math.random()*8)) { case 0: x++; break; case 1: x--; break; case 2: y++; break; case 3: y--; break; case 4: x++; y++; break; case 5: x--; y++; break; case 6: x++; y--; break; case 7: x--; y--; break; } if (x < 0 || y < 0 || x >= width || y >= height || radius(x,y) > closeRadius+seedResolution+2) { // wandered out of bounds or out of interesting range of the // tree, so pick a new spot var progress = 1000; do { newpos(); progress--; } while ((test(x-1,y-1) || test(x,y-1) || test(x+1,y-1) || test(x-1,y ) || test(x,y ) || test(x+1,y ) || test(x-1,y+1) || test(x,y+1) || test(x+1,y+1)) && progress > 0); if (progress <= 0) { document.getElementById(messageId).appendChild(document.createTextNode("Stopped for lack of room.")); clearInterval(animation); break; } } if (test(x, y)) { // hit something, mark where we came from and pick a new spot set(ox,oy); closeRadius = Math.max(closeRadius, radius(ox,oy)); newpos(); } } if (drawPos) set(x,y); }, 1);
}</lang>
<lang html><html>
<head> <script src="brownian.js"></script> </head> <body onload="brownian('canvas', 'message')"> <canvas id="canvas" width="402" height="402" style="border: 2px inset;"></canvas>
</body>
</html></lang>
Liberty BASIC
<lang lb>'[RC]Brownian motion tree
nomainwin dim screen(600,600) WindowWidth = 600 WindowHeight = 600 open "Brownian" for graphics_nsb_nf as #1 #1 "trapclose [quit]" #1 "down ; fill blue" rad=57.29577951 particles=500
'draw starting circle and mid point for n= 1 to 360 x=300-(200*sin(n/rad)) y=300-(200*cos(n/rad)) #1, "color white ; set ";x;" ";y screen(x,y)=1 next n #1, "color white ; set 300 300" screen(300,300)=1
'set up initial particles dim particle(particles,9)'x y deltax deltay rotx roty for n = 1 to particles gosub [randomparticle] next
'start timed drawing loop timer 17, [draw] wait
[draw] #1 "discard" scan for n = 1 to particles oldx=particle(n,1) oldy=particle(n,2)
'erase particle if not(screen(oldx,oldy)) then #1 "color blue ; set ";oldx;" ";oldy end if
'move particle x particle(n,1)=particle(n,1)+int((sin(particle(n,6)/rad)*10)+particle(n,3)) particle(n,5)=particle(n,5)+6 mod 360 if particle(n,1)>599 or particle(n,1)<1 then gosub [randomparticle]
'move particle y particle(n,2)=particle(n,2)+int((sin(particle(n,5)/rad)*10)+particle(n,4)) particle(n,6)=particle(n,6)+6 mod 360 if particle(n,2)>599 or particle(n,2)<1 then gosub [randomparticle]
'checkhit x=particle(n,1) y=particle(n,2) if screen(x-1,y-1) or screen(x-1,y) or screen(x-1,y+1)_ or screen(x,y-1) or screen(x,y) or screen(x,y+1)_ or screen(x+1,y-1) or screen(x+1,y) or screen(x+1,y+1) then #1 "color white ; set ";particle(n,1);" ";particle(n,2) screen(particle(n,1),particle(n,2))=1 else #1 "color red ; set ";particle(n,1);" ";particle(n,2) end if next wait
[randomparticle] particle(n,1)=int(rnd(0)*599)+1 particle(n,2)=int(rnd(0)*599)+1 particle(n,3)=int(2-rnd(0)*4) particle(n,4)=int(2-rnd(0)*4) particle(n,5)=int(rnd(0)*360) particle(n,6)=int(rnd(0)*360) return
[quit] timer 0 close #1 end</lang>
Lua
The output is stored in as a ppm-image. The source code of these output-functions is located at http://rosettacode.org/wiki/Bitmap/Write_a_PPM_file#Lua, http://rosettacode.org/wiki/Grayscale_image#Lua, http://rosettacode.org/wiki/Basic_bitmap_storage#Lua. <lang lua>function SetSeed( f )
for i = 1, #f[1] do -- the whole boundary of the scene is used as the seed f[1][i] = 1 f[#f][i] = 1 end for i = 1, #f do f[i][1] = 1 f[i][#f[1]] = 1 end
end
function SetParticle( f )
local pos_x, pos_y repeat pos_x = math.random( #f ) pos_y = math.random( #f[1] ) until f[pos_x][pos_y] == 0 return pos_x, pos_y
end
function Iterate( f, num_particles )
for i = 1, num_particles do local pos_x, pos_y = SetParticle( f ) while true do local dx = math.random(5) - 3 local dy = math.random(5) - 3
if ( pos_x+dx >= 1 and pos_x+dx <= #f and pos_y+dy >= 1 and pos_y+dy <= #f[1] ) then if f[pos_x+dx][pos_y+dy] ~= 0 then f[pos_x][pos_y] = 1 break else pos_x = pos_x + dx pos_y = pos_y + dy end end end end
end
size_x, size_y = 400, 400 -- size of the scene
num_particles = 16000
math.randomseed( os.time() )
f = {} for i = 1, size_x do
f[i] = {} for j = 1, size_y do f[i][j] = 0 end
end
SetSeed( f ) Iterate( f, num_particles )
-- prepare the data for writing into a ppm-image file for i = 1, size_x do
for j = 1, size_y do if f[i][j] == 1 then f[i][j] = 255 end end
end Write_PPM( "brownian_tree.ppm", ConvertToColorImage(f) )</lang>
Mathematica
There is a prettier version at the Mathematica demo site. Its source code is also available there but it is not mine.
Loose
<lang Mathematica>canvasdim = 1000; n = 0.35*canvasdim^2; canvas = ConstantArray[0, {canvasdim, canvasdim}]; init = Floor@(0.5*{canvasdim, canvasdim}); (*RandomInteger[canvasdim,2]*) canvas[[init1, init2]] = 1; (*1st particle initialized to midpoint*)
Monitor[ (*Provides real-time intermediate result monitoring*)
Do[ particle = RandomInteger[canvasdim, 2]; While[True, ds = RandomInteger[{-1, 1}, 2]; While[ (*New Particle Domain Limit Section*) !And @@ (0 < (particle + ds)# <= canvasdim & /@ {1, 2}), particle = RandomInteger[canvasdim, 2]; ]; (* Particle Aggregation Section *) If[canvas[[(particle + ds)1, (particle + ds)2]] > 0, canvas[[particle1, particle2]] = i; Break[], particle += ds ]; ], {i, n}], {i, (particle + ds), MatrixPlot@canvas} ]
MatrixPlot[canvas,FrameTicks->None,ColorFunction->"DarkRainbow",ColorRules->{0 -> None}]</lang>
Result:
OCaml
<lang ocaml>let world_width = 800 let world_height = 800 let num_particles = 10_000
let () =
assert(num_particles > 0); assert(world_width * world_height > num_particles);
let dla ~world =
(* particle values *) let px = ref 0 and py = ref 0 in (* put the tree seed *) world.(world_height / 2).(world_width / 2) <- 1; for i = 1 to num_particles do (* set particle's initial position *) px := Random.int world_width; py := Random.int world_height; try while (true) do (* move particle *) (* randomly choose a direction *) let dx = (Random.int 3) - 1 (* offsets *) and dy = (Random.int 3) - 1 in if (dx + !px < 0 || dx + !px >= world_width || dy + !py < 0 || dy + !py >= world_height) then begin (* plop the particle into some other random location *) px := Random.int world_width; py := Random.int world_height; end else if (world.(!py + dy).(!px + dx) <> 0) then begin (* bumped into something, particle set *) world.(!py).(!px) <- 1; raise Exit; end else begin py := !py + dy; px := !px + dx; end done with Exit -> () done
let to_pbm ~world =
print_endline "P1"; (* Type=Portable bitmap, Encoding=ASCII *) Printf.printf "%d %d\n" world_width world_height; Array.iter (fun line -> Array.iter (fun pixel -> print_int pixel) line; print_newline() ) world
let () =
Random.self_init(); let world = Array.make_matrix world_width world_height 0 in dla ~world; to_pbm ~world;
- </lang>
Octave
<lang octave>function r = browniantree(xsize, ysize = xsize, numparticle = 1000)
r = zeros(xsize, ysize, "uint8"); r(unidrnd(xsize), unidrnd(ysize)) = 1; for i = 1:numparticle px = unidrnd(xsize-1)+1; py = unidrnd(ysize-1)+1; while(1) dx = unidrnd(2) - 1; dy = unidrnd(2) - 1; if ( (dx+px < 1) || (dx+px > xsize) || (dy+py < 1) || (dy+py > ysize) )
px = unidrnd(xsize-1)+1; py = unidrnd(ysize-1)+1;
elseif ( r(px+dx, py+dy) != 0 )
r(px, py) = 1; break;
else
px += dx; py += dy;
endif endwhile endfor
endfunction
r = browniantree(200); r( r > 0 ) = 255; jpgwrite("browniantree.jpg", r, 100); % image package</lang>
Perl
Simulation code. Tremendously slow, partly because it doesn't use a grid-based collision checking. Showing three sample images with different STEP and ATTRACT parameters, to demonstrate how sensitive the result is to them.
Code runs until the tree reached specified radius. Output is written to "test.eps" of wherever the current directory is. The 0-0 sample took maybe 3 hours (I don't really know, I went for dinner.) <lang perl>sub PI() { atan2(1,1) * 4 } # The, er, pi sub STEP() { .5 } # How far does the particle move each step. Affects
# both speed and accuracy greatly
sub STOP_RADIUS() { 100 } # When the tree reaches this far from center, end
- At each step, move this much towards center. Bigger numbers help the speed because
- particles are less likely to wander off, but greatly affects tree shape.
- Should be between 0 and 1 ish. Set to 0 for pain.
sub ATTRACT() { .2 }
my @particles = map([ map([], 0 .. 2 * STOP_RADIUS) ], 0 .. 2 * STOP_RADIUS); push @{ $particles[STOP_RADIUS][STOP_RADIUS] }, [0, 0];
my $r_start = 3; my $max_dist = 0;
sub dist2 {
my ($dx, $dy) = ($_[0][0] - $_[1][0], $_[0][1] - $_[1][1]); $dx * $dx + $dy * $dy
}
sub move {
my $p = shift; # moved too far, kill particle # return if dist2($p, [0, 0]) > 2 * $r_start * $r_start; $p->[0] += 2 * $r_start while $p->[0] < -$r_start; $p->[0] -= 2 * $r_start while $p->[0] > $r_start; $p->[1] += 2 * $r_start while $p->[1] < -$r_start; $p->[1] -= 2 * $r_start while $p->[1] > $r_start;
my ($ix, $iy) = (int($p->[0]), int($p->[1])); my $dist = 2 * $r_start * $r_start; my $nearest;
# see if the particle is close enough to stick to an exist one for ($ix - 1 .. $ix + 1) { my $idx = STOP_RADIUS + $_; next if $idx > 2 * STOP_RADIUS || $idx < 0; my $xs = $particles[ $idx ]; for ($iy - 1 .. $iy + 1) { my $idx = STOP_RADIUS + $_; next if $idx > 2 * STOP_RADIUS || $idx < 0; for (@{ $xs->[ $idx ] }) { my $d = dist2($p, $_); next if $d > 2; next if $d > $dist;
$dist = $d; $nearest = $_; } } }
# yes, found one if ($nearest) { my $displace = [ $p->[0] - $nearest->[0], $p->[1] - $nearest->[1] ]; my $angle = atan2($displace->[1], $displace->[0]); $p->[0] = $nearest->[0] + cos($angle); $p->[1] = $nearest->[1] + sin($angle);
push @{$particles[$ix + STOP_RADIUS][$iy + STOP_RADIUS]}, [ @$p ]; $dist = sqrt dist2($p);
if ($dist + 10 > $r_start && $r_start < STOP_RADIUS + 10) { $r_start = $dist + 10 } if (int($dist + 1) > $max_dist) { $max_dist = int($dist + 1); # write_eps(); # system('pstopnm -portrait -xborder 0 -yborder 0 test.eps 2> /dev/null'); # system('pnmtopng test.eps001.ppm 2>/dev/null > test.png'); return 3 if $max_dist >= STOP_RADIUS; } return 2; }
# random walk my $angle = rand(2 * PI); $p->[0] += STEP * cos($angle); $p->[1] += STEP * sin($angle);
# drag particle towards center by some distance my $nudge; if (sqrt(dist2($p, [0, 0])) > STOP_RADIUS + 1) { $nudge = 1; } else { $nudge = STEP * ATTRACT; }
if ($nudge) { $angle = atan2($p->[1], $p->[0]); $p->[0] -= $nudge * cos($angle); $p->[1] -= $nudge * sin($angle); }
return 1;
}
my $count; PARTICLE: while (1) {
my $a = rand(2 * PI); my $p = [ $r_start * cos($a), $r_start * sin($a) ]; while ($_ = move($p)) { given ($_) { when (1) { next } when (2) { $count++; last; } when (3) { last PARTICLE } default { last } } } print STDERR "$count $max_dist/@{[int($r_start)]}/@{[STOP_RADIUS]}\r" unless $count% 7;
}
sub write_eps {
my $size = 128; my $p = $size / (STOP_RADIUS * 1.05); my $b = STOP_RADIUS * $p; if ($p < 1) { $size = STOP_RADIUS * 1.05; $b = STOP_RADIUS; $p = 1; }
my $hp = $p / 2;
open OUT, ">", "test.eps";
# print EPS to standard out print OUT <<"HEAD";
%!PS-Adobe-3.0 EPSF-3.0 %%BoundingBox: 0 0 @{[$size*2, $size*2]} $size $size translate /l{ rlineto }def /c{ $hp 0 360 arc fill }def -$size -$size moveto $size 2 mul 0 l 0 $size 2 mul l -$size 2 mul 0 l closepath 0 setgray fill 0 setlinewidth .1 setgray 0 0 $b 0 360 arc stroke .8 setgray /TimesRoman findfont 16 scalefont setfont -$size 10 add $size -16 add moveto (Step = @{[STEP]} Attract = @{[ATTRACT]}) show 0 1 0 setrgbcolor newpath HEAD
for (@particles) { for (@$_) { printf OUT "%.3g %.3g c ", map { $_ * $p } @$_ for @$_; } } print OUT "\n%%EOF"; close OUT;
}
write_eps;</lang>
PicoLisp
<lang PicoLisp>(load "@lib/simul.l")
(de brownianTree (File Size Cnt)
(let Img (grid Size Size) (put Img (/ Size 2) (/ Size 2) 'pix T) (use (P Q) (do Cnt (setq P (get Img (rand 1 Size) (rand 1 Size))) (loop (setq Q ((if2 (rand T) (rand T) north east south west) P)) (T (; Q pix) (put P 'pix T)) (setq P (or Q (get Img (rand 1 Size) (rand 1 Size)))) ) ) ) (out "img.pbm" (prinl "P1") (prinl Size " " Size) (for L Img (for This L (prin (if (: pix) 1 0)) ) (prinl) ) ) ) )</lang>
Use:
(brownianTree "img.pbm" 300 9000) (call 'display "img.pbm")
PureBasic
<lang PureBasic>#Window1 = 0
- Image1 = 0
- ImgGadget = 0
- NUM_PARTICLES = 3000
- width = 200
- height = 200
- xmax = #width -3
- ymax = #height -3
Define.i Event ,i ,x,y
If OpenWindow(#Window1, 0, 0, #width, #height, "Brownian Tree PureBasic Example", #PB_Window_SystemMenu )
If CreateImage(#Image1, #width, #height) ImageGadget(#ImgGadget, 0, 0, #width, #height, ImageID(#Image1)) StartDrawing(ImageOutput(#Image1)) FrontColor($FFFFFF) Plot( Random(#xmax) , Random(#ymax )) StopDrawing() SetGadgetState(#ImgGadget, ImageID(#Image1)) For i = 1 To #NUM_PARTICLES x = Random(#xmax)+1 : y = Random (#ymax)+1 StartDrawing(ImageOutput(#Image1)) While Point(x+1, y+1) + Point(x, y+1)+Point(x+1, y)+Point(x-1, y-1)+Point(x-1, y)+Point(x, y-1) = 0 x = x + (Random(2)-1) : y = y + (Random(2)-1) If x < 1 Or x > #xmax Or y < 1 Or y > #ymax x = Random(#xmax)+1 : y = Random (#ymax)+1 EndIf Wend Plot(x,y) StopDrawing() SetGadgetState(#ImgGadget, ImageID(#Image1)) Next EndIf
Repeat Event = WaitWindowEvent() Until Event = #PB_Event_CloseWindow
Python
<lang python>import pygame, sys, os from pygame.locals import * from random import randint pygame.init()
MAXSPEED = 15 SIZE = 3 COLOR = (45, 90, 45) WINDOWSIZE = 400 TIMETICK = 1 MAXPART = 50
freeParticles = pygame.sprite.Group() tree = pygame.sprite.Group()
window = pygame.display.set_mode((WINDOWSIZE, WINDOWSIZE)) pygame.display.set_caption("Brownian Tree")
screen = pygame.display.get_surface()
class Particle(pygame.sprite.Sprite):
def __init__(self, vector, location, surface): pygame.sprite.Sprite.__init__(self) self.vector = vector self.surface = surface self.accelerate(vector) self.add(freeParticles) self.rect = pygame.Rect(location[0], location[1], SIZE, SIZE) self.surface.fill(COLOR, self.rect)
def onEdge(self): if self.rect.left <= 0: self.vector = (abs(self.vector[0]), self.vector[1]) elif self.rect.top <= 0: self.vector = (self.vector[0], abs(self.vector[1])) elif self.rect.right >= WINDOWSIZE: self.vector = (-abs(self.vector[0]), self.vector[1]) elif self.rect.bottom >= WINDOWSIZE: self.vector = (self.vector[0], -abs(self.vector[1]))
def update(self): if freeParticles in self.groups(): self.surface.fill((0,0,0), self.rect) self.remove(freeParticles) if pygame.sprite.spritecollideany(self, freeParticles): self.accelerate((randint(-MAXSPEED, MAXSPEED), randint(-MAXSPEED, MAXSPEED))) self.add(freeParticles) elif pygame.sprite.spritecollideany(self, tree): self.stop() else: self.add(freeParticles) self.onEdge()
if (self.vector == (0,0)) and tree not in self.groups(): self.accelerate((randint(-MAXSPEED, MAXSPEED), randint(-MAXSPEED, MAXSPEED))) self.rect.move_ip(self.vector[0], self.vector[1]) self.surface.fill(COLOR, self.rect)
def stop(self): self.vector = (0,0) self.remove(freeParticles) self.add(tree)
def accelerate(self, vector): self.vector = vector
NEW = USEREVENT + 1 TICK = USEREVENT + 2
pygame.time.set_timer(NEW, 50) pygame.time.set_timer(TICK, TIMETICK)
def input(events):
for event in events: if event.type == QUIT: sys.exit(0) elif event.type == NEW and (len(freeParticles) < MAXPART): Particle((randint(-MAXSPEED,MAXSPEED), randint(-MAXSPEED,MAXSPEED)), (randint(0, WINDOWSIZE), randint(0, WINDOWSIZE)), screen) elif event.type == TICK: freeParticles.update()
half = WINDOWSIZE/2
tenth = WINDOWSIZE/10
root = Particle((0,0),
(randint(half-tenth, half+tenth), randint(half-tenth, half+tenth)), screen)
root.stop()
while True:
input(pygame.event.get()) pygame.display.flip()</lang>
Ruby
<lang ruby>require 'rubygems' require 'RMagick'
NUM_PARTICLES = 1000 SIZE = 800
def draw_brownian_tree world
# set the seed world[rand SIZE][rand SIZE] = 1
NUM_PARTICLES.times do # set particle's position px = rand SIZE py = rand SIZE
loop do # randomly choose a direction dx = rand(3) - 1 dy = rand(3) - 1
if dx + px < 0 or dx + px >= SIZE or dy + py < 0 or dy + py >= SIZE # plop the particle into some other random location px = rand SIZE py = rand SIZE elsif world[py + dy][px + dx] != 0 # bumped into something world[py][px] = 1 break else py += dy px += dx end end end
end
world = Array.new(SIZE) { Array.new(SIZE, 0) } srand Time.now.to_i
draw_brownian_tree world
img = Magick::Image.new(SIZE, SIZE) do
self.background_color = "black"
end
draw = Magick::Draw.new draw.fill "white"
world.each_with_index do |row, y|
row.each_with_index do |colour, x| draw.point x, y if colour != 0 end
end
draw.draw img img.write "brownian_tree.bmp"</lang>
Scheme
<lang scheme>; Save bitmap to external file (define (save-pbm bitmap filename) (define f (open-output-file filename)) (simple-format f "P1\n~A ~A\n" (list-ref (array-dimensions bitmap) 0) (list-ref (array-dimensions bitmap) 1)) (do ((c 0 (+ c 1))) ((eqv? c (list-ref (array-dimensions bitmap) 1))) (do ((r 0 (+ r 1))) ((eqv? r (list-ref (array-dimensions bitmap) 0))) (display (array-ref bitmap r c) f)) (newline f)) (close-output-port f) )
- Return a random coordinate in the bitmap that isn't filled yet along with a direction
(define (new-particle bitmap) (define x (random (list-ref (array-dimensions bitmap) 0))) (define y (random (list-ref (array-dimensions bitmap) 1))) (define dx (- (random 3) 1)) (define dy (- (random 3) 1)) ;Repeat until we find an unused location (if (> (array-ref bitmap x y) 0) (new-particle bitmap) (list (list x y) (list dx dy))))
- Check neighboring coordinates to see if a collision occured
(define (collision-check bitmap p) (define c #f) (define oob #f) (define x (list-ref (car p) 0)) (define y (list-ref (car p) 1)) (define dx (list-ref (cadr p) 0)) (define dy (list-ref (cadr p) 1)) (define w (list-ref (array-dimensions bitmap) 0)) (define h (list-ref (array-dimensions bitmap) 1))
; If the particle hasn't gone out of bounds keep checking for a collision (if (or (> 0 x) (> 0 y) (<= w x) (<= h y)) (set! oob #t) (do ((x (- (list-ref (car p) 0) 1) (+ x 1))) ((eqv? x (+ (list-ref (car p) 0) 2))) (do ((y (- (list-ref (car p) 1) 1) (+ y 1))) ((eqv? y (+ (list-ref (car p) 1) 2))) ; Check existing neighbors for collisions (if (and (<= 0 x) (<= 0 y) (> w x) (> h y)) (if (not (zero? (array-ref bitmap x y))) (set! c #t)))))) (if oob #f ; Return false if out of bounds (if c p ; Return the point of collision if a collision occured (if (and (zero? dx) (zero? dy)) #f ; Return false if particle is motionless with no collision (collision-check bitmap (particle-move p))))))
- Plot a particle on the bitmap
(define (particle-plot! bitmap p) (array-set! bitmap 1 (list-ref (car p) 0) (list-ref (car p) 1)))
- Move a particle along its slope
(define (particle-move p) (list (list (+ (list-ref (car p) 0) (list-ref (cadr p) 0)) (+ (list-ref (car p) 1) (list-ref (cadr p) 1))) (cadr p)))
- Grow a brownian tree
(define (grow-brownian-tree! bitmap collisions) (define w (list-ref (array-dimensions bitmap) 0)) (define h (list-ref (array-dimensions bitmap) 1))
; Generate a new particle at a random location (define p (new-particle bitmap))
; Find a collision or lack of one and plot it on the bitmap (set! p (collision-check bitmap p)) (if p (begin ; Display collision number and the place it happened (display collisions)(display ": ")(display (car p))(newline) (set! collisions (- collisions 1)) ; Plot the point (particle-plot! bitmap p)))
; If we're done say so (if (zero? collisions) (display "Done\n"))
; Keep going until we have enough collisions ; or have filled the bitmap (if (and (< 0 collisions) (memq 0 (array->list (array-contents bitmap)))) (grow-brownian-tree! bitmap collisions)))
- Plot a random point to seed the brownian tree
(define (seed-brownian-tree! bitmap) (define p (new-particle bitmap)) (particle-plot! bitmap p))
- Example usage ;;;
- Seed the random number generator
(let ((time (gettimeofday))) (set! *random-state* (seed->random-state (+ (car time) (cdr time)))))
- Generate a tree with 320*240 collisions on a bitmap of the size 640x480
- The bitmap is zeroed to start and written with a one where a collision occurs
(define bitmap (make-array 0 640 480)) (seed-brownian-tree! bitmap) (grow-brownian-tree! bitmap (* 320 240))
- Save to a portable bitmap file
(save-pbm bitmap "brownian-tree.pbm")</lang>
Tcl
<lang tcl>package require Tcl 8.5 package require Tk
set SIZE 300
image create photo brownianTree -width $SIZE -height $SIZE interp alias {} plot {} brownianTree put white -to brownianTree put black -to 0 0 [expr {$SIZE-1}] [expr {$SIZE-1}] proc rnd {range} {expr {int(rand() * $range)}}
proc makeBrownianTree count {
global SIZE # Set the seed plot [rnd $SIZE] [rnd $SIZE] for {set i 0} {$i<$count} {incr i} {
# Set a random particle's initial position set px [rnd $SIZE] set py [rnd $SIZE]
while 1 { # Randomly choose a direction set dx [expr {[rnd 3] - 1}] set dy [expr {[rnd 3] - 1}]
# If we are going out of bounds... if {$px+$dx < 0 || $px+$dx >= $SIZE || $py+$dy < 0 || $py+$dy>=$SIZE} { # Out of bounds, so move back in set dx [expr {[rnd 3] - 1}] set dy [expr {[rnd 3] - 1}] continue }
set ox $px set oy $py # Move/see if we would hit anything incr px $dx incr py $dy if {[lindex [brownianTree get $px $py] 0]} { # Hit something, so plot where we were plot $ox $oy break } } ## For display while things are processing, uncomment next line #update;puts -nonewline .;flush stdout
}
}
pack [label .l -image brownianTree] update makeBrownianTree 1000 brownianTree write tree.ppm</lang>
Visual Basic .NET
Windows Forms Application.
<lang vbnet> Imports System.Drawing.Imaging
Public Class Form1
ReadOnly iCanvasColor As Integer = Color.Black.ToArgb ReadOnly iSeedColor As Integer = Color.White.ToArgb
Dim iCanvasWidth As Integer = 0 Dim iCanvasHeight As Integer = 0
Dim iPixels() As Integer = Nothing
Private Sub BrownianTree()
Dim oCanvas As Bitmap = Nothing Dim oRandom As New Random(Now.Millisecond) Dim oXY As Point = Nothing Dim iParticleCount As Integer = 0
iCanvasWidth = ClientSize.Width iCanvasHeight = ClientSize.Height
oCanvas = New Bitmap(iCanvasWidth, iCanvasHeight, Imaging.PixelFormat.Format24bppRgb)
Graphics.FromImage(oCanvas).Clear(Color.FromArgb(iCanvasColor))
iPixels = GetData(oCanvas)
' We'll use about 10% of the total number of pixels in the canvas for the particle count. iParticleCount = CInt(iPixels.Length * 0.1)
' Set the seed to a random location on the canvas. iPixels(oRandom.Next(iPixels.Length)) = iSeedColor
' Run through the particles. For i As Integer = 0 To iParticleCount Do ' Find an open pixel. oXY = New Point(oRandom.Next(oCanvas.Width), oRandom.Next(oCanvas.Height)) Loop While iPixels(oXY.Y * oCanvas.Width + oXY.X) = iSeedColor
' Jitter until the pixel bumps another. While Not CheckAdjacency(oXY) oXY.X += oRandom.Next(-1, 2) oXY.Y += oRandom.Next(-1, 2)
' Make sure we don't jitter ourselves out of bounds. If oXY.X < 0 Then oXY.X = 0 Else If oXY.X >= oCanvas.Width Then oXY.X = oCanvas.Width - 1 If oXY.Y < 0 Then oXY.Y = 0 Else If oXY.Y >= oCanvas.Height Then oXY.Y = oCanvas.Height - 1 End While
iPixels(oXY.Y * oCanvas.Width + oXY.X) = iSeedColor
' If you'd like to see updates as each particle collides and becomes ' part of the tree, uncomment the next 4 lines (it does slow it down slightly). ' SetData(oCanvas, iPixels) ' BackgroundImage = oCanvas ' Invalidate() ' Application.DoEvents() Next
oCanvas.Save("BrownianTree.bmp") BackgroundImage = oCanvas
End Sub
' Check adjacent pixels for an illuminated pixel. Private Function CheckAdjacency(ByVal XY As Point) As Boolean
Dim n As Integer = 0
For y As Integer = -1 To 1 ' Make sure not to drop off the top or bottom of the image. If (XY.Y + y < 0) OrElse (XY.Y + y >= iCanvasHeight) Then Continue For
For x As Integer = -1 To 1 ' Make sure not to drop off the left or right of the image. If (XY.X + x < 0) OrElse (XY.X + x >= iCanvasWidth) Then Continue For
' Don't run the test on the calling pixel. If y <> 0 AndAlso x <> 0 Then n = (XY.Y + y) * iCanvasWidth + (XY.X + x) If iPixels(n) = iSeedColor Then Return True End If Next Next
Return False
End Function
Private Function GetData(ByVal Map As Bitmap) As Integer()
Dim oBMPData As BitmapData = Nothing Dim oData() As Integer = Nothing
oBMPData = Map.LockBits(New Rectangle(0, 0, Map.Width, Map.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb)
Array.Resize(oData, Map.Width * Map.Height)
Runtime.InteropServices.Marshal.Copy(oBMPData.Scan0, oData, 0, oData.Length)
Map.UnlockBits(oBMPData)
Return oData
End Function
Private Sub SetData(ByVal Map As Bitmap, ByVal Data As Integer())
Dim oBMPData As BitmapData = Nothing
oBMPData = Map.LockBits(New Rectangle(0, 0, Map.Width, Map.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb)
Runtime.InteropServices.Marshal.Copy(Data, 0, oBMPData.Scan0, Data.Length)
Map.UnlockBits(oBMPData)
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load DoubleBuffered = True BackgroundImageLayout = ImageLayout.Center Show() Activate() Application.DoEvents() BrownianTree() End Sub
End Class </lang>
Final output: