Comp212 Project #2: Koch Curve Hints

[back to main project page]

Supplied Support Classes

LRStruct package: code & docs

You are not required to use the following classes, they are provided because we don't want you focusing on the mathematical aspects of the program.

The following is assumed by the classes below.

See the documentation for the following classes: (regular Point version)

If your Koch curve object uses Point2D.Doubles for the two endpoints, download this version:

If your Koch curve object uses Points for the two endpoints, download this version:


The MakeManyAlgo The Koch system uses an abstract factory ability to manufacture a base case curve to create the first instance of the Koch curve and also to reset the Koch curve. It's ability to create the non-base case Koch curve is used in the "growing" process -- the base case uses it to generate and install the inductive case. To generate the non-base case Koch curve, the a factory executes a MakeManyAlgo algorithm on the list (LRStruct) of prototype points to create a LRStruct filled with base-case Koch curves. The MakeManyAlgo recursively uses a mathematical process called an Affine transformation.

MakeManyAlgo assumes the Koch curve class is called "Koch". If your corresponding class has a different name, go into the code of MakeManyAlgo and change "Koch" to the name you are using.

Affine transform

An Affine transformation is a technique that enables one to take points defined in terms of one coordinate system (here, the prototype points defined on the (0,0)-(1,0) interval) and rotate, scale and translate them so that they appear in a different coordinate system (here, the beginning and end points of the Koch curve being drawn at the moment). That is, (0,0) will become the first point of the Koch curve ("a") and (1,0) will become the last point ("b") and all the in-between points will be in-between a and b, and enlarged and angled to correspond to the size and angle determined by the distance between points a and b. The Affine transformation process needs two things: the previous point processed (since a Koch curve is defined over an interval between two points) and an AffineXForm object. The AffineXForm object is capable of executing all the mathematics needed for the transformation. The AffineXForm object needs to know the interval to which it is transforming the prototype data, hence it is handed those two Points at construction time.

Affine data

The AffineData class is what is called a "wrapper" class (another Adapter pattern technically) and "wraps" the two necessary things (the AffineXForm object and the previous Point into a single object that can be passed as the input to the algorithm. The inductive case method makes an AffineData object using the endpoints it is given. The AffineData's constructor uses those endpoints to first make the AffineXForm object. Its "previous point" is initialized to (0,0). The MakeManyAlgo transforms the previous point and the current prototype point it is working on. Using the transformed points, it then instantiates a base case Koch object. The MakeManyAlgo then recurs on to the next prototype point. When the end of the prototype list is encountered, the point (1, 0) is used as the current point and the algorithm is done. The key issue here is that the Affine transform plus the MakeManyAlgo allows us to define a prototype growth pattern independent from sizes, positions and angles of its actual applications during the growing process.

Math help:

Calculating the height of an equilateral triangle

Given a base dimension of base, the height is base*Math.sqrt(3.0)/2.0.

Calculating the 3rd point in a triangle

Given two arbitratry points, A and B, what is the point C that forms an equilateral triangle with A and B?

Remember that given V = (Vx, Vy) then (-Vy, Vx) is perpendicular to it.  Likewise, (Vy, -Vx) is perpendicular to it.

Recalling your Euclidian geometry, Point C is thus halfway from A to B, and sqrt(3)/2 times that distance in the perpendicular to the direction from A to B.

To calculate this, first define the vector from A to B : 
          V = (Bx-Ax, By-Ay). 

Then C is simply: 
           (Cx, Cy) = (Ax, Ay) + (1/2) (Vx, Vy) + (sqrt(3)/2)(-Vy, Vx)

(Using the other form for the perpendicular direction will put C on the other side of the A-B line.)

In Java:

     int vx = b.x - a.x;
     int vy = b.y - a.y;
     Point c = new Point( a.x + vx/2 - (int)((Math.sqrt(3.0)/2.0)*vy), 
                                  a.y + vy/2 + (int)((Math.sqrt(3.0)/2.0)*vx)); 

Note the casting of doubles to ints because Math.sqrt() returns a double.    Note the breaking of the code into two lines to make it easier to read.

Tech note:  sqrt() is a "static" method of the Math class, so that's why we can call the method directly on the class without having a Math object first.

Calculating the other 2 points of a rectangle:

Given two arbitrary points, A and B, what are the remaining 2 points that form a rectangle?

Rectangles are easy.   All you need to do is to go in perpendicular direction from each point the same distance, which is determined by teh ratio of the length and width.

That is, let V be the perpendicular line segment (vector) to A-B, scaled by the length to width ratio:
      V = (-(By-Ay), (Bx-Ax)) * ratio

Then the remaining points, C and D (going clockwise: A->B->C->D->A) are simply the vector V added to the points B and A respectively:

          C =  B + V     and      D =  A + V

In Java:

     double ratio = [whatever you want];
     int vx = -(int)((b.y - a.y) * ratio);
     int vy = (int)((b.x - a.x)*ratio);
     Point c = new Point( b.x +vx, b.y+vy); 
     Point d = new Point( a.x + vx, a.y+vy);      

Likewise, if the opposite signs for vx and vy are used, the rectangle will be on the opposite side of the A-B line.  Note also the casting done because ratio may be less than 1 and thus needs to be a double not an int.

Dynamic class loading:

Since the view cannot directly access any class in the model, it must send a "message" to the model asking for a class to be loaded when the user wants to change what Koch factory is being used. For simplicity's sake, let assume that a drop-list exists in the view that holds the fully qualified class name of the desired factory (i.e. includes the package). When the user selects a factory from the drop-list, the string which holds the classname is sent to the model.

In the model, the appropriate class corresponding to the string must be loaded and the appropriate constructor called. This is accomplished using Java's dynamic class loading capability plus its "reflection" capability. "Reflection" is the ability to look a the structure of a class at run time, in this case, to find out how many input parameters are required for the constructor.

Below is a method that changes the current factory being used, given a String containing the name of the requested file:

		// Assume the existence of "aFactory", the current factory in use.
        public void changeFactoryTo(String s) {
            try {
			  // get the first constructor of class "s":
              java.lang.reflect.Constructor c =  Class.forName(s).getConstructors()[0];
			  // get an array of arrays of input parameters.   Here, arrays for zero and one input parameter: 
              Object [][] args = new Object[][]{new Object[]{}, new Object[]{aFactory}};
			  // create a new instance given the number of input parameters for constructor:
              aFactory = (AFactory) c.newInstance(args[c.getParameterTypes().length]);
            catch(Exception ex) {

Random Suggestions and Thoughts