Monday, December 26, 2011

JavaFX 2.0 Path Alternatives

In the post JavaFX 2.0 Christmas Tree (JavaFX 2.0 Shapes), I demonstrated using JavaFX 2.0's Path class in conjunction with MoveTo and LineTo to draw a simple Christmas tree. In this post, I look at using three alternatives to Path for drawing the Christmas tree.

The single class implementation (ChristmasTreePath.java) drawing the JavaFX 2.0 Christmas tree was nearly 350 lines in length (including comments and white space). In this post, I tried to keep the source structure roughly the same, but the three alternatives to Path (Polyline, Polygon, and SVGPath) lead to smaller class sizes (each about 210+ lines including comments and white space).

The easiest method to compare across the implementations is the method for drawing the tree stump. The next four code listings show the original Path-based implementation followed by the implementations using Polyline, Polygon, and SVGPath.

Stump Drawing with Path
   /**
    * Draw stump of tree.
    * 
    * @return Path representing Christmas tree stump.
    */
   private Path buildStumpPath()
   {
      final Path path = new Path();

      int coordX = LEFT_STUMP_X;
      int coordY = LEFT_STUMP_Y;
      final MoveTo startingPoint = new MoveTo(coordX, coordY);
      path.getElements().add(startingPoint);

      coordY += STUMP_HEIGHT;
      final LineTo leftStumpSide = new LineTo(coordX, coordY);
      path.getElements().add(leftStumpSide);

      coordX += STUMP_WIDTH;
      final LineTo stumpBottom = new LineTo(coordX, coordY);
      path.getElements().add(stumpBottom);

      coordY -= STUMP_HEIGHT;
      final LineTo rightStumpSide = new LineTo(coordX, coordY);
      path.getElements().add(rightStumpSide);

      coordX -= STUMP_WIDTH;
      final LineTo topStump = new LineTo(coordX, coordY);
      path.getElements().add(topStump);

      path.setFill(Color.BROWN);

      return path;
   }
Stump Drawing with Polyline
   /**
    * Draw stump of tree.
    * 
    * @return Polyline representing Christmas tree stump.
    */
   private Polyline buildStumpPolyline()
   {
      int coordX = LEFT_STUMP_X;
      int coordY = LEFT_STUMP_Y;

      final double[] stumpPoints =
         new double[]
         {
            coordX, coordY,
            coordX, coordY += STUMP_HEIGHT,
            coordX += STUMP_WIDTH, coordY,
            coordX, coordY -= STUMP_HEIGHT,
            coordX -= STUMP_WIDTH, coordY
         };
      final Polyline polyline = new Polyline(stumpPoints);

      polyline.setFill(Color.BROWN);

      return polyline;
   }
Stump Drawing with Polygon
   /**
    * Draw stump of tree.
    * 
    * @return Polygon representing Christmas tree stump.
    */
   private Polygon buildStumpPolygon()
   {
      int coordX = LEFT_STUMP_X;
      int coordY = LEFT_STUMP_Y;

      final double[] stumpPoints =
         new double[]
         {
            coordX, coordY,
            coordX, coordY += STUMP_HEIGHT,
            coordX += STUMP_WIDTH, coordY,
            coordX, coordY -= STUMP_HEIGHT
         };
      final Polygon polygon = new Polygon(stumpPoints);

      polygon.setFill(Color.BROWN);

      return polygon;
   }
Stump Drawing with SVGPath
   /**
    * Draw stump of tree.
    * 
    * @return SVG Path representing Christmas tree stump.
    */
   private SVGPath buildStumpSvgPath()
   {
      int coordX = LEFT_STUMP_X;
      int coordY = LEFT_STUMP_Y;

      final StringBuilder stumpPoints = new StringBuilder();
      stumpPoints.append("M").append(coordX).append(",").append(coordY);
      stumpPoints.append(" L").append(coordX).append(",").append(coordY += STUMP_HEIGHT);
      stumpPoints.append(" L").append(coordX += STUMP_WIDTH).append(",").append(coordY);
      stumpPoints.append(" L").append(coordX).append(",").append(coordY -= STUMP_HEIGHT);
      stumpPoints.append(" L").append(coordX -= STUMP_WIDTH).append(",").append(coordY);

      final SVGPath svgPath = new SVGPath();
      svgPath.setContent(stumpPoints.toString());

      svgPath.setFill(Color.BROWN);

      return svgPath;
   }

The portion for drawing the green part of the Christmas tree demonstrates even more significant differences between the Path implementation and the other three implementations. The four code listings comparing these implementations of drawing the green portion of the tree are shown next.

Drawing Tree with Path
   /**
    * Draw left side of the Christmas tree from top to bottom.
    * 
    * @param path Path for left side of Christmas tree to be added to.
    * @param startingX X portion of the starting coordinate.
    * @param startingY Y portion of the starting coordinate.
    * @return Coordinate with x and y values.
    */
   private Coordinate drawLeftSide(
      final Path path, final int startingX, final int startingY)
   {
      int coordX = startingX - DELTA_X;
      int coordY = startingY + DELTA_Y;
      final LineTo topLeft = new LineTo(coordX, coordY);
      path.getElements().add(topLeft);

      coordX += BRANCH_LENGTH;
      final LineTo topLeftHorizontal = new LineTo(coordX, coordY);
      path.getElements().add(topLeftHorizontal);

      coordX -= DELTA_X;
      coordY += DELTA_Y;
      final LineTo secondLeft = new LineTo(coordX, coordY);
      path.getElements().add(secondLeft);

      coordX += BRANCH_LENGTH;
      final LineTo secondLeftHorizontal = new LineTo(coordX, coordY);
      path.getElements().add(secondLeftHorizontal);

      coordX -= DELTA_X;
      coordY += DELTA_Y;
      final LineTo thirdLeft = new LineTo(coordX, coordY);
      path.getElements().add(thirdLeft);

      coordX += BRANCH_LENGTH;
      final LineTo thirdLeftHorizontal = new LineTo(coordX, coordY);
      path.getElements().add(thirdLeftHorizontal);

      coordX -= DELTA_X;
      coordY += DELTA_Y;
      final LineTo fourthLeft = new LineTo(coordX, coordY);
      path.getElements().add(fourthLeft);

      coordX += BRANCH_LENGTH;
      final LineTo fourthLeftHorizontal = new LineTo(coordX, coordY);
      path.getElements().add(fourthLeftHorizontal);

      return new Coordinate(coordX, coordY);
   }

   /**
    * Draw right side of the Christmas tree from bottom to top.
    * 
    * @param path Path for right side of Christmas tree to be added to.
    * @param startingX X portion of the starting coordinate.
    * @param startingY Y portion of the starting coordinate.
    * @return Coordinate with x and y values.
    */
   private Coordinate drawRightSide(
      final Path path, final int startingX, final int startingY)
   {
      int coordX = startingX + BRANCH_LENGTH;
      int coordY = startingY;
      final LineTo bottomHorizontal = new LineTo(coordX, coordY);
      path.getElements().add(bottomHorizontal);

      coordX -= DELTA_X;
      coordY -= DELTA_Y;
      final LineTo bottomBranch = new LineTo(coordX, coordY);
      path.getElements().add(bottomBranch);

      coordX += BRANCH_LENGTH;
      final LineTo secondHorizontal = new LineTo(coordX, coordY);
      path.getElements().add(secondHorizontal);

      coordX -= DELTA_X;
      coordY -= DELTA_Y;
      final LineTo secondBottomBranch = new LineTo(coordX, coordY);
      path.getElements().add(secondBottomBranch);

      coordX += BRANCH_LENGTH;
      final LineTo thirdHorizontal = new LineTo(coordX, coordY);
      path.getElements().add(thirdHorizontal);

      coordX -= DELTA_X;
      coordY -= DELTA_Y;
      final LineTo thirdBottomBranch = new LineTo(coordX, coordY);
      path.getElements().add(thirdBottomBranch);

      coordX += BRANCH_LENGTH;
      final LineTo fourthHorizontal = new LineTo(coordX, coordY);
      path.getElements().add(fourthHorizontal);

      coordX -= DELTA_X;
      coordY -= DELTA_Y;
      final LineTo fourthBottomBranch = new LineTo(coordX, coordY);
      path.getElements().add(fourthBottomBranch);

      return new Coordinate(coordX, coordY);
   }

   /**
    * Build the exterior path of a Christmas Tree.
    * 
    * @return Path representing the exterior of a simple Christmas tree drawing.
    */
   private Path buildChristmasTreePath()
   {
      int coordX = TOP_CENTER_X;
      int coordY = TOP_CENTER_Y;
      final Path path = new Path();
      final MoveTo startingPoint = new MoveTo(coordX, coordY);
      path.getElements().add(startingPoint);

      final Coordinate bottomLeft = drawLeftSide(path, coordX, coordY);
      coordX = bottomLeft.x + TREE_BOTTOM_WIDTH;
      coordY = bottomLeft.y;

      final LineTo treeBottom = new LineTo(coordX, coordY);
      path.getElements().add(treeBottom);

      drawRightSide(path, coordX, coordY);

      path.setFill(Color.GREEN);

      return path;
   }
Drawing Tree with Polyline
   /**
    * Build the exterior polyline of a Christmas Tree.
    * 
    * @return Polyline representing the exterior of a simple Christmas tree drawing.
    */
   private Polyline buildChristmasTreePolyline()
   {
      int coordX = TOP_CENTER_X;
      int coordY = TOP_CENTER_Y;

      final double[] treePoints =
         new double[]
         {
            coordX, coordY,
            coordX -= DELTA_X, coordY += DELTA_Y,
            coordX += BRANCH_LENGTH, coordY,
            coordX -= DELTA_X, coordY += DELTA_Y,
            coordX += BRANCH_LENGTH, coordY,
            coordX -= DELTA_X, coordY += DELTA_Y,
            coordX += BRANCH_LENGTH, coordY,
            coordX -= DELTA_X, coordY += DELTA_Y,
            coordX += BRANCH_LENGTH, coordY,
            coordX += TREE_BOTTOM_WIDTH, coordY,
            coordX += BRANCH_LENGTH, coordY,
            coordX -= DELTA_X, coordY -= DELTA_Y,
            coordX += BRANCH_LENGTH, coordY,
            coordX -= DELTA_X, coordY -= DELTA_Y,
            coordX += BRANCH_LENGTH, coordY,
            coordX -= DELTA_X, coordY -= DELTA_Y,
            coordX += BRANCH_LENGTH, coordY,
            coordX -= DELTA_X, coordY -= DELTA_Y
         };

      final Polyline polyline = new Polyline(treePoints);
      polyline.setFill(Color.GREEN);

      return polyline;
   }
Drawing Tree with Polygon
   /**
    * Build the exterior polygon of a Christmas Tree.
    * 
    * @return Polygon representing the exterior of a simple Christmas tree drawing.
    */
   private Polygon buildChristmasTreePolygon()
   {
      int coordX = TOP_CENTER_X;
      int coordY = TOP_CENTER_Y;

      final double[] treePoints =
         new double[]
         {
            coordX, coordY,
            coordX -= DELTA_X, coordY += DELTA_Y,
            coordX += BRANCH_LENGTH, coordY,
            coordX -= DELTA_X, coordY += DELTA_Y,
            coordX += BRANCH_LENGTH, coordY,
            coordX -= DELTA_X, coordY += DELTA_Y,
            coordX += BRANCH_LENGTH, coordY,
            coordX -= DELTA_X, coordY += DELTA_Y,
            coordX += BRANCH_LENGTH, coordY,
            coordX += TREE_BOTTOM_WIDTH, coordY,
            coordX += BRANCH_LENGTH, coordY,
            coordX -= DELTA_X, coordY -= DELTA_Y,
            coordX += BRANCH_LENGTH, coordY,
            coordX -= DELTA_X, coordY -= DELTA_Y,
            coordX += BRANCH_LENGTH, coordY,
            coordX -= DELTA_X, coordY -= DELTA_Y,
            coordX += BRANCH_LENGTH, coordY,
            coordX -= DELTA_X, coordY -= DELTA_Y
         };

      final Polygon polygon = new Polygon(treePoints);
      polygon.setFill(Color.GREEN);

      return polygon;
   }
Drawing Tree with SVGPath
   /**
    * Build the exterior SVG Path of a Christmas Tree.
    * 
    * @return SVG Path representing the exterior of a simple Christmas tree drawing.
    */
   private SVGPath buildChristmasTreeSvgPath()
   {
      int coordX = TOP_CENTER_X;
      int coordY = TOP_CENTER_Y;

      final StringBuilder treePoints = new StringBuilder();
      treePoints.append("M").append(coordX).append(",").append(coordY);
      treePoints.append(" L").append(coordX -= DELTA_X).append(",")
                             .append(coordY += DELTA_Y);
      treePoints.append(" L").append(coordX += BRANCH_LENGTH).append(",")
                             .append(coordY);
      treePoints.append(" L").append(coordX -= DELTA_X).append(",")
                             .append(coordY += DELTA_Y);
      treePoints.append(" L").append(coordX += BRANCH_LENGTH).append(",")
                             .append(coordY);
      treePoints.append(" L").append(coordX -= DELTA_X).append(",")
                             .append(coordY += DELTA_Y);
      treePoints.append(" L").append(coordX += BRANCH_LENGTH).append(",")
                             .append(coordY);
      treePoints.append(" L").append(coordX -= DELTA_X).append(",")
                             .append(coordY += DELTA_Y);
      treePoints.append(" L").append(coordX += BRANCH_LENGTH).append(",")
                             .append(coordY);
      treePoints.append(" L").append(coordX += TREE_BOTTOM_WIDTH).append(",")
                             .append(coordY);
      treePoints.append(" L").append(coordX += BRANCH_LENGTH).append(",")
                             .append(coordY);
      treePoints.append(" L").append(coordX -= DELTA_X).append(",")
                             .append(coordY -= DELTA_Y);
      treePoints.append(" L").append(coordX += BRANCH_LENGTH).append(",")
                             .append(coordY);
      treePoints.append(" L").append(coordX -= DELTA_X).append(",")
                             .append(coordY -= DELTA_Y);
      treePoints.append(" L").append(coordX += BRANCH_LENGTH).append(",")
                             .append(coordY);
      treePoints.append(" L").append(coordX -= DELTA_X).append(",")
                             .append(coordY -= DELTA_Y);
      treePoints.append(" L").append(coordX += BRANCH_LENGTH).append(",")
                             .append(coordY);
      treePoints.append(" L").append(coordX -= DELTA_X).append(",")
                             .append(coordY -= DELTA_Y);

      final SVGPath svgPath = new SVGPath();
      svgPath.setContent(treePoints.toString());
      svgPath.setFill(Color.GREEN);

      return svgPath;
   }

The result of running all four of these implementations is the same. There are advantages to the alternatives to Path, but in many ways the differences are a matter of taste. If hard-coded numbers are used, the alternatives to Path become very succinct.

No comments: