Click here to Skip to main content
15,881,757 members
Articles / Desktop Programming / Win32

Real Tree 2

Rate me:
Please Sign up or sign in to vote.
4.95/5 (52 votes)
2 Apr 2009CPOL4 min read 66.8K   2.1K   75   15
This application shows a simple algorithm for drawing random flowers and trees. The logic is based on fractal sets.
Sample Image Image 2

Contents

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.

Image 3

And, the directory structure:

Image 4

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.

Image 5

What About Recursion?

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:

Image 6

And the result is shown with 3 different selections:

Image 7

Image 8

Image 9

Division per Step = 2Division per Step = 4Division 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:

Image 10

Using the Code

The algorithm is simple, something like the following flowchart:

Image 11

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

Loading Images

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()
{
    loadPictures();
    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)];

    picTree.Image = Properties.Resources.about;

    // 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
private void loadPictures()
{
    // add some pictures from internal resources
    picsBase.Add(Properties.Resources.ground1);
    picsBackground.Add(Properties.Resources.Edinburgh_Castle__Edinburgh__Scotland );
    picsFlowers.Add(Properties.Resources.Flower__30_);
    picsFlowers.Add(Properties.Resources.Flower__32_);
    picsFruits.Add(Properties.Resources.icons6362);
    picsFruits.Add(Properties.Resources.icons6367);
    picsLeaves.Add(Properties.Resources.leaf_31);

    //load some pictures from Hard disk (different sub directories)
    loadFromHard("Ground", picsBase);
    loadFromHard("Back", picsBackground);
    loadFromHard("Fruits", picsFruits);
    loadFromHard("Flowers", picsFlowers);
    loadFromHard("Leaves", picsLeaves);
}

// 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))
            {
                // add image
                try
                {
                    Image image = Image.FromFile(myFile.FullName);
                    myPicList.Add(image);
                }
                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
        {
            //adding branch info to collection.
            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;
                myAllBranchCollection.Add(myBranchInfo);
            }

            //adding leaves to collection
            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;

                        myAllObjectsCollection.Add(myObjectInfo);
                    }
                }
            }

            //adding flowers to collection
            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;

                        myAllObjectsCollection.Add(myObjectInfo);
                    }
                }
            }

            //adding fruits to collection
            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;

                        myAllObjectsCollection.Add(myObjectInfo);
                    }
                }
            }
        }
        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.

Image 12

Points of Interest

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

Image 13

You can add your own samples in the menu; add values in Click event of the item as follows:

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.

Image 14

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
CEO
Iran (Islamic Republic of) Iran (Islamic Republic of)

Comments and Discussions

 
QuestionThanks Pin
Mohammad Reza Khosravi5-Jan-13 18:56
Mohammad Reza Khosravi5-Jan-13 18:56 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey14-Mar-12 22:04
professionalManoj Kumar Choubey14-Mar-12 22:04 
GeneralMy vote of 5 Pin
Hendra Yudha P3-Jan-12 20:54
Hendra Yudha P3-Jan-12 20:54 
Generalcheck maple in code project Pin
Arash Javadi18-May-09 11:56
Arash Javadi18-May-09 11:56 
GeneralNice Job Pin
Siavash Mortazavi14-Apr-09 19:42
Siavash Mortazavi14-Apr-09 19:42 
GeneralSelamın Aleykhüm Pin
Haluk_YILMAZ26-Feb-09 22:52
Haluk_YILMAZ26-Feb-09 22:52 
GeneralL-Systems Pin
darrellp17-Feb-09 7:08
darrellp17-Feb-09 7:08 
General[Message Deleted] Pin
Mohammad Reza Khosravi17-Feb-09 7:45
Mohammad Reza Khosravi17-Feb-09 7:45 
GeneralRe: L-Systems Pin
darrellp17-Feb-09 14:36
darrellp17-Feb-09 14:36 
GeneralRe: L-Systems Pin
martinkeefe3-Mar-09 11:26
martinkeefe3-Mar-09 11:26 
GeneralThe most beautiful fractal tree Pin
Maxim_Barsuk9-Feb-09 21:15
Maxim_Barsuk9-Feb-09 21:15 
GeneralNice! Pin
WKremlor9-Feb-09 7:53
WKremlor9-Feb-09 7:53 
GeneralVery Good Pin
bilo819-Feb-09 6:10
bilo819-Feb-09 6:10 
General[Message Deleted] Pin
Mohammad Reza Khosravi9-Feb-09 9:42
Mohammad Reza Khosravi9-Feb-09 9:42 
GeneralRe: Very Good Pin
datacore11-Feb-09 1:43
datacore11-Feb-09 1:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.