14,974,182 members
Articles / Desktop Programming / Win32
Article
Posted 9 Feb 2009

59.5K views
75 bookmarked

# Real Tree 2

Rate me:
This application shows a simple algorithm for drawing random flowers and trees. The logic is based on fractal sets.

## Introduction

When you watch the shapes made by this software, you may imagine that there are huge and complex mathematical formulas behind it, but it’s not true. The program uses only some simple formulas (like SIN and COS) with a recursive method, and that’s all. All other parts of the program are to make the shapes look better and natural.

In the previous version (DotNet Real Tree), I explained some mathematical logic, and now I wish to add some new features that make it more funny (like real leaves and flowers), but I removed some parts of the program that I thought may make some issues (such as Swastika!). I also removed some other controls and scales to make the algorithm easier and more useful.

## What is a Fractal?

A fractal is a shape made by repeating a mathematical formula. Every time, it does a similar action and when you start it from anywhere, it will make the same shape. There are some famous formulas for this job, such as Mandelbrot set or Julia set.

## How It Works?

The technique that is used in this application is very similar to the file system in your computer. When you are working on your hard disk drive (e.g. Drive D:\ as in the following figure), you see some files and directories. When you go to a directory, you will see some other files and directories, and when you continue to go into new directories, you will find more; eventually there is a final point that there are no more directories, so you have to go back and search another place.

And, the directory structure:

In this application, the directories are similar to the branches, and files are equal to leaves, flowers or fruits on the tree. As your directories may contain different files, your tree’s branches may contain different objects.

Our main function in this application gets information of the current branch and calculates and draws new branches. There is a control for choosing your ideal divisions:

And the result is shown with 3 different selections:

 Division per Step = 2 Division per Step = 4 Division per Step = 10

But the procedure makes only one level (step) of the tree, what about the others? This is what we want to speak about.

This is our main procedure in brief:

C#
```private void nextBranch(float startX, float startY, float startAngle)
{
if (myTreeInfo.myStep >=  myTreeInfo.totalSteps) return;
myTreeInfo.myStep++;

endX = startX + (float)Math.Cos(startAngle * Math.PI / 180) * branchSize;
endY = startY - (float)Math.Sin(startAngle * Math.PI / 180) * branchSize;

DrawImage();

for (int j = 1; j <= myTreeInfo.divisionPerStep; j++)
{
newAngle = startAngle - maxAngle/2+ maxAngle*(j-1);
nextBranch(endX, endY, newAngle);
}

myTreeInfo.myStep--;
}```

And I simulate it practically with 4 steps of generation for branches, you can download the Flash version here:

## Using the Code

The algorithm is simple, something like the following flowchart:

Some parts of the code are as follows:

### Variables

Initially I declared some Lists and Classes. `objectCollection` class is for collecting all information about flowers, leaves and fruits (in calculation time). And `BranchCollection` class is for collecting information of branches (the items for drawing a branch are different from a flower or a fruit). There is also a `treeInfo` class for holding general information of the tree, such as maximum Tree size, angle between branches and more.

C#
```private List<string> imageTypes = new List<string>(new string[]
{ "*.gif", "*.png", "*.jpg", "*.bmp" });
private List<Image> picsBackground=new List<Image> ();
private List<Image> picsBase=new List<Image> ();
private List<Image> picsLeaves=new List<Image> ();
private List<Image> picsFlowers=new List<Image> ();
private List<Image> picsFruits=new List<Image>();

private class objectCollection    	// for collecting information of flowers,
// leaves and fruits.
{
internal Image myImage = null;
internal float myX = 0;
internal float myY = 0;
internal float myWidth = 0;
internal float myHeight = 0;
internal int myStep = 0;
}
private objectCollection myObjectInfo;
private List<objectCollection> myAllObjectsCollection;

private class BranchCollection
{
internal float startX = 0;
internal float startY = 0;
internal float endX = 0;
internal float endY = 0;
internal int myStep = 0;
internal float myWidth = 0;
internal Color myColor;
}
private BranchCollection myBranchInfo;
private List<BranchCollection> myAllBranchCollection;

private class treeInfo
{
internal int myStep, totalSteps;
internal bool fixedSize, fixedAngle, brokenBranches;
internal float divisionPerStep, startingBranch, maxSize, maxAngle, maxBrokenBranches;
internal float leafLevel, trunkHeight, widthSize;
internal float myProgress, flowerPercent, fruitPercent, leafPercent;
}
private treeInfo myTreeInfo=new treeInfo() ;

private Bitmap myBitmapTree;
private Pen myPen = new Pen(Color.Black);
private Random myRandom = new Random();
private static bool plzStopCalculation; // for manually stopping the calculation
private static bool plzStopDrawing; 	// for manually stopping the drawing```

Then I load some images from hard disk and internal resources and select random images for different parts of the application (Background, Fruits...) .

C#
```private void firstStart()
{
picGround.BackgroundImage = picsBase[myRandom.Next(picsBase.Count)];
picBack.BackgroundImage = picsBackground[myRandom.Next(picsBackground.Count)];
picFlower.BackgroundImage = picsFlowers[myRandom.Next(picsFlowers.Count)];
picFruit.BackgroundImage = picsFruits[myRandom.Next(picsFruits.Count)];
picLeaf.BackgroundImage = picsLeaves[myRandom.Next(picsLeaves.Count)];

// making "SAVE" directory for saving output images
try
{
DirectoryInfo myDir = new DirectoryInfo(Application.StartupPath + "\\Save\\");
if (!myDir.Exists) myDir.Create();
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}

// at the start of the program, load some pictures and shapes
{
// add some pictures from internal resources

//load some pictures from Hard disk (different sub directories)
}

// load pictures from the hard disk
private void loadFromHard(string myDir, List<Image> myPicList)
{
DirectoryInfo myFileDir = new DirectoryInfo(Application.StartupPath + "\\" + myDir);
if (myFileDir.Exists)
{
// For each image extension (.jpg, .png, etc.)
foreach (string imageType in imageTypes)
{
// all graphic files in the directory
foreach (FileInfo myFile in myFileDir.GetFiles(imageType))
{
try
{
Image image = Image.FromFile(myFile.FullName);
}
catch (OutOfMemoryException)
{
continue;
}
}
}
}
}```

### RUN Button

This button has 3 different actions:

1. Starting a new process.
2. Stopping the calculation.
3. Stopping the drawing.

The following procedures control this button:

C#
```private void btnOK_Click(object sender, System.EventArgs e)
{
if (btnOK.Text == "Run")
{
goRun();
}
else if (btnOK.Text == "Stop Calculation") buttonsStatus(1);
else if (btnOK.Text == "Stop Drawing") buttonsStatus(2);
}

private void goRun()
{
setPictures();
Application.DoEvents();
getTreeInfo();
// go for calculations
buttonsStatus(0);
calculateTree();
// go for drawing
buttonsStatus(1);
if (!plzStopDrawing) startPaint();
if (mnuAutoSave.Checked) ImageSave(); //Auto save image on hard disk

// get ready for another user order
buttonsStatus(2);
}

private void buttonsStatus(byte myStatus)
{
if (myStatus == 0)
{
// start of calculation
plzStopCalculation = false;
plzStopDrawing = false;
progressBar.Visible = true;
btnOK.Text = "Stop Calculation";
}
else if (myStatus == 1)
{
// end of calculation and start of drawing
plzStopCalculation = true;
btnOK.Text = "Stop Drawing";
}
else
{
// at the end of drawing
plzStopCalculation = true;
plzStopDrawing = true;
progressBar.Visible = false;
btnOK.Text = "Run";
}
} ```

At the beginning of a new shape, the following procedures change random images and set control values to variables.

C#
```// choose random picture for different parts
private void setPictures()
{
if (rdoBackgroundTexture.Checked)
{
if (chkRandomGround.Checked) picGround.BackgroundImage =
picsBase[myRandom.Next(picsBase.Count)];
if (chkRandomBack.Checked) picBack.BackgroundImage =
picsBackground[myRandom.Next(picsBackground.Count)];
}
else
{
if (chkRandomColor.Checked)lblBackgroundColor.BackColor =
Color.FromArgb(myRandom.Next(255), myRandom.Next(255), myRandom.Next(255));
}
if (chkRandomFlower.Checked && chkFlowerObjects.Checked)
picFlower.BackgroundImage = picsFlowers[myRandom.Next(picsFlowers.Count)];
if (chkRandomFruit.Checked && chkFruitObjects.Checked)
picFruit.BackgroundImage = picsFruits[myRandom.Next(picsFruits.Count)];
if (chkRandomLeaf.Checked && chkLeafObjects.Checked)
picLeaf.BackgroundImage = picsLeaves[myRandom.Next(picsLeaves.Count)];
}

//getting data from Controls
private void getTreeInfo()
{
myTreeInfo.totalSteps = (int)updTotalSteps.Value;

myTreeInfo.divisionPerStep = (int)updDivisionPerStep.Value;

myTreeInfo.startingBranch = (int)updStartingBranch.Value;
myTreeInfo.maxSize = (float)updMaxSize.Value;
myTreeInfo.maxAngle = (float)updMaxAngle.Value;
myTreeInfo.maxBrokenBranches = (float)updBrokenBranches.Value;

myTreeInfo.fixedSize = chkFixedSize.Checked;
myTreeInfo.fixedAngle = chkFixedAngle.Checked;
myTreeInfo.brokenBranches = chkBrokenBranches.Checked;

myTreeInfo.leafLevel = (float)trbBranchLevel.Value;
myTreeInfo.trunkHeight = (float)trbTrunkHeight.Value;
myTreeInfo.widthSize = (float)trbWidthSize.Value;

myTreeInfo.flowerPercent = (float)updFlowerObjects.Value;
myTreeInfo.fruitPercent = (float)updFruitObjects.Value;
myTreeInfo.leafPercent = (float)updLeafObjects.Value;

//some corrections in data
if (chkFixedAngle.Checked) myTreeInfo.maxAngle *= 2 / myTreeInfo.divisionPerStep;
myTreeInfo.maxSize -= (myTreeInfo.trunkHeight / 5 - 8) * 1.6F;
if (myTreeInfo.maxSize < 1) myTreeInfo.maxSize = 1;
}```

### Calculations

This is the main part; the following procedure starts the recursion method.

C#
```private void calculateTree()
{
myTreeInfo.myStep = 0; // starting Step.
myTreeInfo.myProgress = 0;
myAllBranchCollection = new List<BranchCollection>();
myAllObjectsCollection = new List<objectCollection>();
nextBranch(picTree.Width / 2, picTree.Height *4 / 5, 90);
}```

And this is the recursion part:

C#
```private void nextBranch(float startX, float startY, float startAngle)
{
float endX, endY, newAngle, angleGrow, branchSize;
if (!plzStopCalculation && myTreeInfo.myStep < myTreeInfo.totalSteps)
{
//following 6 lines are only for showing progress bar.
if (myTreeInfo.myStep == 3)
{
myTreeInfo.myProgress +=	(float)(100 /
Math.Pow(myTreeInfo.divisionPerStep, myTreeInfo.myStep));
if (myTreeInfo.myProgress > 100) myTreeInfo.myProgress = 100;
progressBar.Value = (int)myTreeInfo.myProgress;
}

// for making broken branches, also when you reach the maximum step.
if (myTreeInfo.brokenBranches && myTreeInfo.myStep > 2 &&
myRandom.NextDouble ()*100 < myTreeInfo.maxBrokenBranches +
(myTreeInfo.maxBrokenBranches * ((myTreeInfo.myStep * 2 -
myTreeInfo.totalSteps) / myTreeInfo.totalSteps)) * 0.7) return;

myTreeInfo.myStep++;

//different colors from root to leaves
myPen.Color = Color.FromArgb(100, (int)(255 *
myTreeInfo.myStep / myTreeInfo.totalSteps), 35);

//different width for branches from root to leaves.
//you can replace following 2 lines with "myPen.Width=3;".
myPen.Width = 10 * myTreeInfo.widthSize *
(float)Math.Pow((myTreeInfo.totalSteps - myTreeInfo.myStep), 3) /
(float)Math.Pow(myTreeInfo.totalSteps, 4);
if (myPen.Width < 1) myPen.Width = 1;

// size of current branch. you can replace following 9 lines
// with only "branchSize=15;".
branchSize = (myTreeInfo.totalSteps - myTreeInfo.myStep *
myTreeInfo.leafLevel / 50);
if (myTreeInfo.leafLevel >= 50) branchSize *= myTreeInfo.leafLevel / 50;
else branchSize *= (myTreeInfo.leafLevel + 50) / 100;
if (branchSize <= 0) branchSize = 1;
branchSize *= (float)(picTree.Height /
Math.Pow(myTreeInfo.totalSteps, 1.9)) * myTreeInfo.maxSize / 80;
if (!myTreeInfo.fixedSize) branchSize *=
(float)(myRandom.NextDouble() * 2 + 0.1); // only when Size is not fixed.

// more control for height of trunk.
if (myTreeInfo.myStep < 3) branchSize +=
picTree.Height / (30 * myTreeInfo.myStep) + branchSize *
(myTreeInfo.trunkHeight / 10 - 4) / (myTreeInfo.myStep + 1.5F) *
myTreeInfo.totalSteps / 15;

// calculating end points. [* Math.PI / 180] is for changing degrees to radians.
endX = startX + (float)Math.Cos(startAngle * Math.PI / 180) * branchSize;
endY = startY - (float)Math.Sin(startAngle * Math.PI / 180) * branchSize;

try
{
if (myTreeInfo.myStep >= myTreeInfo.startingBranch) // this "if" condition
//is for "Starting Branch" control.
{
myBranchInfo = new BranchCollection();
myBranchInfo.startX = startX;
myBranchInfo.startY = startY;
myBranchInfo.endX = endX;
myBranchInfo.endY = endY;
myBranchInfo.myStep = myTreeInfo.myStep;
myBranchInfo.myColor = myPen.Color;
myBranchInfo.myWidth = myPen.Width;
}

if (chkLeafObjects.Checked)
{
if (myTreeInfo.myStep > myTreeInfo.totalSteps / 4) //leaves must be
//only on higher branches
{
if (myRandom.Next(100000) * myTreeInfo.myStep <
Math.Pow(myTreeInfo.leafPercent, 4))// how many leaves ?
{
myObjectInfo = new objectCollection();
myObjectInfo.myImage = picLeaf.BackgroundImage;
myObjectInfo.myStep = myBranchInfo.myStep;
float myScale = (float)myRandom.Next(13) /
myObjectInfo.myImage.Width;
myObjectInfo.myWidth = myObjectInfo.myImage.Width * myScale;
myObjectInfo.myHeight = myObjectInfo.myImage.Height * myScale;
myObjectInfo.myX = startX - myObjectInfo.myWidth / 2;
myObjectInfo.myY = startY - myObjectInfo.myHeight / 2;

}
}
}

if (chkFlowerObjects.Checked)
{
if (myTreeInfo.myStep > myTreeInfo.totalSteps / 2) //flowers must be
//only on higher branches
{

if (myRandom.Next(100000) * myTreeInfo.myStep <
Math.Pow(myTreeInfo.flowerPercent, 3.5))// how many flowers ?
{
myObjectInfo = new objectCollection();
myObjectInfo.myImage = picFlower.BackgroundImage;
myObjectInfo.myStep = myBranchInfo.myStep;
float myScale = (float)myRandom.Next(12) /
myObjectInfo.myImage.Width;
myObjectInfo.myWidth = myObjectInfo.myImage.Width * myScale;
myObjectInfo.myHeight = myObjectInfo.myImage.Height * myScale;
myObjectInfo.myX = startX - myObjectInfo.myWidth / 2;
myObjectInfo.myY = startY - myObjectInfo.myHeight / 2;

}
}
}

if (chkFruitObjects.Checked)
{
if (myTreeInfo.myStep > myTreeInfo.totalSteps * 4 / 5) //fruits must
// be only on higher branches
{
if (myRandom.Next(100000) * myTreeInfo.myStep <
Math.Pow(myTreeInfo.fruitPercent, 3))// how many fruits ?
{
myObjectInfo = new objectCollection();
myObjectInfo.myImage = picFruit.BackgroundImage;
myObjectInfo.myStep = myBranchInfo.myStep;
float myScale = (float)myRandom.Next(15) /
myObjectInfo.myImage.Width;
myObjectInfo.myWidth = myObjectInfo.myImage.Width * myScale;
myObjectInfo.myHeight = myObjectInfo.myImage.Height * myScale;
myObjectInfo.myX = startX - myObjectInfo.myWidth / 2;
myObjectInfo.myY = startY - myObjectInfo.myHeight / 2;

}
}
}
}
catch (Exception)
{
//MessageBox.Show("Error");
}

//recursion part.
for (int j = 1; j <= myTreeInfo.divisionPerStep; j++)
{
// calculating angle for next branches.
angleGrow = myTreeInfo.maxAngle; // range of differences
if (myTreeInfo.fixedAngle) angleGrow =
angleGrow / 2 - angleGrow * (j - myTreeInfo.divisionPerStep / 2);
else angleGrow *= (float)(myRandom.NextDouble() * 2 - 1);
newAngle = (startAngle + angleGrow) % 360;

nextBranch(endX, endY, newAngle);	// runs itself again.
}

myTreeInfo.myStep--; // go back
Application.DoEvents();
}
}```

### Drawing

In the previous version, drawing and calculation parts were in the same procedure, but here, I divided them into 2 parts. Drawing of objects is in order by their levels (steps).

C#
``` private void startPaint()
{
try
{
// setting graphics
myBitmapTree = new Bitmap(picTree.Width , picTree.Height );

Graphics gTree=Graphics.FromImage(myBitmapTree );
gTree.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

// drawing landscape
if (rdoBackgroundTexture.Checked)
{
gTree.DrawImage(picBack.BackgroundImage,
new Rectangle(0, 0, picTree.Width, picTree.Height),
new Rectangle(0, 0, picBack.BackgroundImage.Width,
picBack.BackgroundImage.Height), GraphicsUnit.Pixel);
gTree.DrawImage(picGround.BackgroundImage,
new Rectangle(0, picTree.Height * 2 / 3, picTree.Width,
picTree.Height / 3), new Rectangle(0, 0,
picGround.BackgroundImage.Width,
picGround.BackgroundImage.Height), GraphicsUnit.Pixel);
}
else
{
gTree.FillRectangle(new SolidBrush(lblBackgroundColor.BackColor),
0, 0, picTree.Width, picTree.Height);

}
// drawing all branches and objects of the collections in order by their steps
progressBar.Value = 0;
for (int i = 0; i <= myTreeInfo.totalSteps ; i++)
{
foreach (BranchCollection myB in myAllBranchCollection)
{
if (i == myB.myStep-1 ) DrawTree(gTree,  myB);
}

picTree.Image = myBitmapTree;
foreach (objectCollection myP in myAllObjectsCollection)
{
if (i == myP.myStep ) DrawPicture(gTree,  myP);
}

picTree.Image = myBitmapTree;
progressBar.Value = i * 100 / myTreeInfo.totalSteps;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}

// Draw Tree Branches on the scene
private void DrawTree(Graphics gTree,  BranchCollection myB)
{
Application.DoEvents();
if(!plzStopDrawing) gTree.DrawLine(new Pen(myB.myColor, myB.myWidth),
myB.startX, myB.startY, myB.endX, myB.endY);
}

// Draw flowers, leaves and fruits on the scene
private void DrawPicture(Graphics gTree,  objectCollection myP)
{
if (!plzStopDrawing) gTree.DrawImage
(myP.myImage, myP.myX, myP.myY, myP.myWidth, myP.myHeight);
}```

That's it.

## GUI

The GUI is very simple.

## Points of Interest

There are some predefined samples that you can access from the main menu:

C#
```private void mnuFlower6_Click(object sender, EventArgs e)
{
putInfo(150, 49, 8, 0, 55, 0, 38, 1, 9800, 40, 40, 20, 50, 1,
0, 30, 1, 1, 0, 50, 1, 0, 0, 0, 0);
} ```

The values are in order by placing Controls on the form. And following the procedure sets the values on the controls.

C#
```private void putInfo(params int[] infoArray)
{
updTotalSteps.Value = infoArray[0];
updDivisionPerStep.Value = infoArray[1];
updStartingBranch.Value = infoArray[2];
chkFixedSize.Checked = Convert.ToBoolean(infoArray[3]);
updMaxSize.Value = infoArray[4];
chkFixedAngle.Checked = Convert.ToBoolean(infoArray[5]);
updMaxAngle.Value = infoArray[6];
chkBrokenBranches.Checked = Convert.ToBoolean(infoArray[7]);
updBrokenBranches.Value = (decimal)infoArray[8] / 100;
trbBranchLevel.Value = infoArray[9];
trbTrunkHeight.Value = infoArray[10];
trbWidthSize.Value = infoArray[11];

chkLeafObjects.Checked = Convert.ToBoolean(infoArray[13]);
chkFlowerObjects.Checked = Convert.ToBoolean(infoArray[17]);
chkFruitObjects.Checked = Convert.ToBoolean(infoArray[21]);
if (chkLeafObjects.Checked)
{
if (infoArray[14] > 0) picLeaf.BackgroundImage = picsLeaves[infoArray[14] - 1];
if (infoArray[15] > 0) updLeafObjects.Value = infoArray[15];
chkRandomLeaf.Checked = Convert.ToBoolean(infoArray[16]);
}
if (chkFlowerObjects.Checked)
{
if (infoArray[18] > 0) picFlower.BackgroundImage = picsFlowers[infoArray[18] - 1];
if (infoArray[19] > 0) updFlowerObjects.Value = infoArray[19];
chkRandomFlower.Checked = Convert.ToBoolean(infoArray[20]);
}
if (chkFruitObjects.Checked)
{
if (infoArray[22] > 0) picFruit.BackgroundImage = picsFruits[infoArray[22] - 1];
if (infoArray[23] > 0) updFruitObjects.Value = infoArray[23];
chkRandomFruit.Checked = Convert.ToBoolean(infoArray[24]);
}```

Also you can change textures and pictures of objects by yourself. There are several directories in the executable file's place that contain these images and you can change or add more.

## History

• First release (Feb 9, 2009)
• Update 1 (Feb 16, 2009): Added Timer
• Update 2 (Feb 25, 2009): Added Save options
• Update 3 (Mar 23, 2009): Some small changes

## Share

 CEO Iran (Islamic Republic of)

 First Prev Next
 My vote of 5 Manoj Kumar Choubey14-Mar-12 22:04 Manoj Kumar Choubey 14-Mar-12 22:04
 My vote of 5 Hendra Yudha P3-Jan-12 20:54 Hendra Yudha P 3-Jan-12 20:54
 check maple in code project Arash Javadi18-May-09 11:56 Arash Javadi 18-May-09 11:56
 Nice Job Siavash Mortazavi14-Apr-09 19:42 Siavash Mortazavi 14-Apr-09 19:42
 Selamın Aleykhüm Haluk_YILMAZ26-Feb-09 22:52 Haluk_YILMAZ 26-Feb-09 22:52
 L-Systems darrellp17-Feb-09 7:08 darrellp 17-Feb-09 7:08