Drawing Recursive Tree Turtle Java

This is a simple Java program that draws a fractal tree by using either lines or filled polygons. It contains two JFrames, the first one for drawing the shape, and the second one for changing some control parameters.

  • Download control - 29.5 KB

Introduction

This is a simple Java program that draws a fractal tree by using either lines (classic fractal tree) or filled polygons ("upgraded" fractal tree). I got the idea from a Wikipedia page about fractals.

The tree is drawn in a way that for each iteration, each shape from the previous iteration is split into two new shapes, always at the same angle, and the length and thickness are multiplied by a factor in each iteration.

The program consists of two JFrames.

The first one is where the drawing happens; it is controlled from the main method - the redrawing is done in an infinite loop which is conditioned by some parameters, and it is calling the paint method to draw the tree.

Here are some screenshots of the tree:

Image 1 Image 2

There is also the second JFrame - this one contains components like JRadioButton, JTextBox and JCheckBox. All of these components accept inputs for the parameters that control the way the tree is drawn.

Image 3

Usage

The Control Frame Parameters

There are altogether 11 parameters that control the drawing of the tree. All the parameters that get changed will be applied as soon as the current drawing is complete; if the tree is already fully drawn, then a new drawing loop will be started, using the changed parameters immediately upon the change.

  • Repeat - determines whether the tree should be redrawn in a loop indefinitely, or drawn just once (and redrawn only upon eventual change of the parameters)
  • Shape - determines the basic shape used for drawing the tree (line or polygon)
  • Color - opens a JColorChooser control - it affects the color of the tree (only for polygons)
  • Iterations - determines the depth level of the algorithm (how many levels of branches is drawn)
  • Thickness (base) - thickness of the first (base) branch of the tree
  • Thickness factor - factor by which the thickness of each next level of branches is multiplied (will accept > 1, but it is advisable to use < 1)
  • Length (base) - length of the first (base) branch of the tree
  • Length factor - factor by which the length of each next level of branches is multiplied (will accept > 1, but it should be < 1)
  • Split angle ° - angle (in degrees) by which each branch is split into the next two branches
  • Sleep after each shape - means that the algorithm will pause for the entered number of ms before each next shape
  • Sleep after each iteration - means that the algorithm will pause for the entered number of ms before each next iteration

Setting the Parameters

In the default setup (after opening the app), the tree will be drawn with lines, in a loop. The splitting angle will be 60°, and the tree will be drawn in 10 iterations (levels of branches), with each next level's lines shortened by the factor of 0.8. The default speed of the drawing will be set to 500ms pause between iterations.

If you choose the "No" option on Repeat, the tree will finish drawing, and will not redraw any further. While in this state, the tree will be redrawn only if any of the parameters are changed.

If you wish to play around with the parameters, note that the program will immediately pick up the change after the whole tree is drawn. There is no "confirmation" of change (i.e., "lostfocus" event or "apply" button).

There is no limitation to the input into textboxes; if an invalid value is entered (i.e., NaN value), then the change will not be taken into consideration, until valid text is entered. Be careful of what numbers you are putting into the textboxes because the tree can become distorted if some implied logical limitations are not considered:

  • Very high number of iterations could slow down the app and will result in too short branches once it reaches higher iterations.
  • Take into consideration the size of the frame (600x600) when choosing base thickness and length.
  • Thickness and length factor should be kept under 1.
  • Don't turn off both sleeps as it will result in tree being drawn so fast it would become invisible.

The Code

Both JFrames are drawn from the main method, but the control frame is built from a custom class MyControlFrame which extends the JFrame class. All the components of this frame, including the action listeners, are instantiated inside its constructor method.

The MyControlFrame class contains private variables that hold the control parameters used in the drawing algorithm. These variables are both populated and pulled from the main method by calling the frame's public getter methods:

          int          iter =          10;          int          thick =          50;          double          thickF =          0.6;          int          len =          100;          double          lenF =          0.8;          double          angle =          60;          int          sleepEachShapeMillis =          20;          int          sleepEachIterMillis =          500;

i.e., method for getting the angle:

          public          double          getAngleChoice() {          try          {         angle = Double.parseDouble(txtAngle.getText());     }          catch(NumberFormatException ex)     {     }          return          angle; }

The Main Loop

After creating both frames, the program runs an infinite loop, in which all of the variables are constantly read from the control frame, and checked against the previous value after each iteration. If there was any change in the parameters, the boolean isChanged shall be set, telling the program that in the next iteration, the tree should be redrawn with the new parameters. If the parameter repeat is set to "Yes", then the tree will be redrawn in every iteration of the main loop.

          boolean          isChanged = true; repeat = control.getRepeatChoice(); shape = control.getShapeChoice(); color = control.getColorChoice(); iter = control.getIterChoice(); thick = control.getThickChoice(); thickFactor = control.getThickFChoice(); len = control.getLenChoice(); lenFactor = control.getLenFChoice(); angle = control.getAngleChoice(); sleepEachShapeMillis = control.getSleepEachShapeChoice(); sleepEachIterMillis = control.getSleepEachIterChoice();           while(true) {          if((repeat == RepeatEnum.YES) || isChanged)     {          try          {             frame.repaint();             paint(panel, shape, color, iter, thick, thickFactor,                    len, lenFactor, angle, sleepEachShapeMillis, sleepEachIterMillis);             Thread.sleep(10);         }          catch          (Exception e) {                       break;                      }         isChanged = false;     }     repeat = control.getRepeatChoice();          shapeNEW = control.getShapeChoice();     colorNEW = control.getColorChoice();     iterNEW = control.getIterChoice();     thickNEW = control.getThickChoice();     thickFactorNEW = control.getThickFChoice();     lenNEW = control.getLenChoice();     lenFactorNEW = control.getLenFChoice();     angleNEW = control.getAngleChoice();     sleepEachShapeMillisNEW = control.getSleepEachShapeChoice();     sleepEachIterMillisNEW = control.getSleepEachIterChoice();          if(shapeNEW != shape || colorNEW != color || iterNEW != iter ||         thick != thickNEW || thickFactor != thickFactorNEW || len != lenNEW ||         lenFactor != lenFactorNEW || angle != angleNEW ||         sleepEachShapeMillis != sleepEachShapeMillisNEW ||         sleepEachIterMillis != sleepEachIterMillisNEW)         isChanged = true;     shape = shapeNEW;     color = colorNEW;     iter = iterNEW;     thick = thickNEW;     thickFactor = thickFactorNEW;     len = lenNEW;     lenFactor = lenFactorNEW;     angle = angleNEW;     sleepEachShapeMillis = sleepEachShapeMillisNEW;     sleepEachIterMillis = sleepEachIterMillisNEW; }

paint Method

This method redraws the tree in the main frame. It takes as argument the JPanel component from the main frame, in which the tree is drawn, and it also takes all the parameters from the control frame (10 in total).

          public          static          void          paint(JPanel panel, ShapeEnum shape, Color color,          int          maxIter,          int          thickness,          double          thickFactor,          int          length,          double          lenFactor,          double          angle,          int          sleepEachShapeMillis,          int          sleepEachIterMillis);

The method first retrieves the graphics context from the panel, and applies some rendering hints to make the drawing nicer:

Graphics2D g2d = (Graphics2D) panel.getGraphics(); RenderingHints rh =          new          RenderingHints(             RenderingHints.KEY_ANTIALIASING,             RenderingHints.VALUE_ANTIALIAS_ON         ); g2d.addRenderingHints(rh); g2d.setColor(color);

Two arraylists are used to hold a collection of last points (of the current branch level - iteration) and also a collection of last angles. The angles in this context mark the angle between each line of the last iteration and the ordinate (vertical) line.

Image 4

Before the loop, the initial line (or polygon) is drawn, with the initial parameters. (For the polygon tree, the initial polygon is slightly different than the other polygons (it is a rectangle instead of trapesoid).)

Then, a for loop is entered which will run through the set number of iterations.

First, length of the line/polygon is adjusted.

length *= lenFactor;

Next, all the last points are read in a while loop, and for each of these points, two additional lines/polygons will be drawn - one in positive, and one in negative direction. To achieve this, an angle from the angle ArrayList (theta) corresponding to the current point will be increased (or decreased) by delta, which is half of the splitting angle. (If you take a look at the picture above, you can see that for an angle of 60°, each consequent branch is added (or deducted) 30° delta.)

theta = (int)(tmpThetas.get(tmpPoints.indexOf(pt)) + Math.pow(direction, i) * delta);

At this point, a line, or a polygon is drawn.

The x and y parameters for the upper point of the line are calculated using the Pythagoras' theorem; since we know the coordinates of the point vertically up, and the angle by which we need to rotate it:

Image 5

  • Δx = length * cos(90°-theta) = length * sin(theta)
  • Δy = length * sin(90°-theta) = length * cos(theta)
  • x' = x + Δx = x + length * sin(theta)
  • y' = y - Δy = y - length * cos(theta)

In case of polygon, the points that are saved in each iteration are the points in the middle of the upper side of the polygon:

Image 6

All the four points of the polygon are calculated by applying rotation to upper middle point (like with the line), and then translating upper and lower middle point to the "left" or to the "right" for thickness / 2. The thickness of each polygon is additionally reduced in the final transform of each upper point to get a trapezoid shape.

             rect.xpoints[0] = (int)(pt.x + length *                    Math.sin(Math.toRadians(theta)));          rect.xpoints[0] -= (int)((thickness /          2) *                     Math.sin(Math.toRadians(90          - theta)));    rect.xpoints[0] += (int)((thickness * (1          - thickFactor) /          2) *                     Math.sin(Math.toRadians(90          - theta)));    rect.ypoints[0] = (int)(pt.y - length *                    Math.cos(Math.toRadians(theta)));          rect.ypoints[0] -= (int)((thickness /          2) *                     Math.cos(Math.toRadians(90          - theta)));    rect.ypoints[0] += (int)((thickness * (1          - thickFactor) /          2) *                     Math.cos(Math.toRadians(90          - theta)));     rect.xpoints[1] = pt.x - (int)((thickness /          2) *                    Math.sin(Math.toRadians(90          - theta)));     rect.ypoints[1] = pt.y - (int)((thickness /          2) *                    Math.cos(Math.toRadians(90          - theta)));      rect.xpoints[2] = pt.x + (int)((thickness /          2) *                    Math.sin(Math.toRadians(90          - theta)));     rect.ypoints[2] = pt.y + (int)((thickness /          2) *                    Math.cos(Math.toRadians(90          - theta)));      rect.xpoints[3] = (int)(pt.x + length *                    Math.sin(Math.toRadians(theta)));          rect.xpoints[3] += (int)((thickness /          2) *                     Math.sin(Math.toRadians(90          - theta)));    rect.xpoints[3] -= (int)((thickness * (1          - thickFactor) /          2) *                     Math.sin(Math.toRadians(90          - theta)));    rect.ypoints[3] = (int)(pt.y - length *                    Math.cos(Math.toRadians(theta)));          rect.ypoints[3] += (int)((thickness /          2) *                     Math.cos(Math.toRadians(90          - theta)));    rect.ypoints[3] -= (int)((thickness * (1          - thickFactor) /          2) *                     Math.cos(Math.toRadians(90          - theta)));    rect.invalidate();

In the last step, thickness is multiplied by the thickness reduction factor.

thickness *= thickFactor;

Here is the complete code of this method:

          public          static          void          paint(JPanel panel, ShapeEnum shape, Color color,          int          maxIter,          int          thickness,          double          thickFactor,          int          length,          double          lenFactor,          double          angle,          int          sleepEachShapeMillis,          int          sleepEachIterMillis) {                Graphics2D g2d = (Graphics2D) panel.getGraphics();     RenderingHints rh =          new          RenderingHints(                 RenderingHints.KEY_ANTIALIASING,                 RenderingHints.VALUE_ANTIALIAS_ON             );     g2d.addRenderingHints(rh);     g2d.setColor(color);          int          w = panel.getSize().width;          int          h = panel.getSize().height;          boolean          sleepEachShape = sleepEachShapeMillis ==          0          ? false : true;          boolean          sleepEachIter = sleepEachIterMillis ==          0          ? false : true;          double          delta, theta;          Line2D line =          new          Line2D.Double();     Polygon rect =          new          Polygon();     Point pt =          new          Point();     Point pt2;     ArrayList<Point> startPoints, tmpPoints;     ArrayList<Double> thetas, tmpThetas;          Iterator<Point> listIterator;          int          direction = -1;          delta = angle /          2;     theta =          0;          startPoints =          new          ArrayList<Point>();     tmpPoints =          new          ArrayList<Point>();     thetas =          new          ArrayList<Double>();     tmpThetas =          new          ArrayList<Double>();                    if(sleepEachIter)          try          {             Thread.sleep(sleepEachIterMillis);         }          catch          (InterruptedException e) {                          e.printStackTrace();         }     rect.npoints =          4;     rect.xpoints =          new          int[4];     rect.ypoints =          new          int[4];     rect.xpoints[0] = w /          2          - thickness /          2;     rect.xpoints[1] = w /          2          + thickness /          2;     rect.xpoints[2] = w /          2          + thickness /          2;     rect.xpoints[3] = w /          2          - thickness /          2;     rect.ypoints[0] = h;     rect.ypoints[1] = h;     rect.ypoints[2] = (int)(h - length);     rect.ypoints[3] = (int)(h - length);          if(shape == ShapeEnum.LINE)     {         line.setLine(w /          2, h, w /          2, h - length);         g2d.drawLine((int)line.getX1(),          (int)line.getY1(), (int)line.getX2(), (int)line.getY2());     }          else          {         g2d.fillPolygon(rect);     }     startPoints.add(new          Point(w /          2, (int)(h - length)));     thetas.add(theta);          direction = -1;          for(int          iteration =          0; iteration < maxIter; iteration++)     {          if(sleepEachIter)          try          {                 Thread.sleep(sleepEachIterMillis);             }          catch          (InterruptedException e) {                                  e.printStackTrace();             }         length *= lenFactor;         tmpPoints = startPoints;         tmpThetas = thetas;         startPoints =          new          ArrayList<Point>();         thetas =          new          ArrayList<Double>();         listIterator = tmpPoints.iterator();          while(listIterator.hasNext())              {             pt = listIterator.next();             rect =          new          Polygon();             line =          new          Line2D.Double();             rect.npoints =          4;          for(int          i =          0; i <          2; i++)                 {                                  theta = (int)(tmpThetas.get(tmpPoints.indexOf(pt)) +                          Math.pow(direction, i) * delta);          if(shape == ShapeEnum.LINE)                 {                     pt2 =          new          Point();                     pt2.x = (int)(pt.x + length * Math.sin(Math.toRadians(theta)));                     pt2.y = (int)(pt.y - length * Math.cos(Math.toRadians(theta)));                     line.setLine(pt.x, pt.y, pt2.x, pt2.y);                 }          else          {                     rect.xpoints =          new          int[4];                     rect.ypoints =          new          int[4];                                                                                    rect.xpoints[0] = (int)(pt.x + length *                                        Math.sin(Math.toRadians(theta)));                            rect.xpoints[0] -= (int)((thickness /          2) *                                        Math.sin(Math.toRadians(90          - theta)));                       rect.xpoints[0] += (int)((thickness * (1          - thickFactor) /          2) *                                         Math.sin(Math.toRadians(90          - theta)));                                                                                                     rect.ypoints[0] = (int)(pt.y - length *                                         Math.cos(Math.toRadians(theta)));                           rect.ypoints[0] -= (int)((thickness /          2) *                                         Math.cos(Math.toRadians(90          - theta)));                      rect.ypoints[0] += (int)((thickness * (1          - thickFactor) /          2) *                                         Math.cos(Math.toRadians(90          - theta)));                                                                                                                          rect.xpoints[1] = pt.x - (int)((thickness /          2) *                                        Math.sin(Math.toRadians(90          - theta)));                       rect.ypoints[1] = pt.y - (int)((thickness /          2) *                                        Math.cos(Math.toRadians(90          - theta)));                                            rect.xpoints[2] = pt.x + (int)((thickness /          2) *                                        Math.sin(Math.toRadians(90          - theta)));                       rect.ypoints[2] = pt.y + (int)((thickness /          2) *                                        Math.cos(Math.toRadians(90          - theta)));                                            rect.xpoints[3] = (int)(pt.x + length *                                         Math.sin(Math.toRadians(theta)));                           rect.xpoints[3] += (int)((thickness /          2) *                                         Math.sin(Math.toRadians(90          - theta)));                      rect.xpoints[3] -= (int)((thickness * (1          - thickFactor) /          2) *                                         Math.sin(Math.toRadians(90          - theta)));                                                                                                     rect.ypoints[3] = (int)(pt.y - length *                                        Math.cos(Math.toRadians(theta)));                            rect.ypoints[3] += (int)((thickness /          2) *                                         Math.cos(Math.toRadians(90          - theta)));                      rect.ypoints[3] -= (int)((thickness * (1          - thickFactor) /          2) *                                         Math.cos(Math.toRadians(90          - theta)));                                                                                                     rect.invalidate();                 }                                                   startPoints.add(new          Point(                                     (int)(pt.x + length * Math.sin(Math.toRadians(theta))),                                     (int)(pt.y - length * Math.cos(Math.toRadians(theta)))                                 ));                 thetas.add(theta);          if(sleepEachShape)          try          {                         Thread.sleep(sleepEachShapeMillis);                     }          catch          (InterruptedException e) {                                                  e.printStackTrace();                     }          if(shape == ShapeEnum.LINE)                 {                     g2d.drawLine((int)line.getX1(), (int)line.getY1(),                                   (int)line.getX2(), (int)line.getY2());                 }          else          {                                          g2d.fillPolygon(rect);                     g2d.draw(rect);                 }             }         }         thickness *= thickFactor;      } }

History

  • 3rd June, 2020: Initial version

desimonegookishe.blogspot.com

Source: https://www.codeproject.com/Articles/5269971/Fractal-tree-with-lines-polygons-written-in-Java

0 Response to "Drawing Recursive Tree Turtle Java"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel