Purpose: We describe the method we use to construct these fractal curves. We will create the Java classes which carry out these constructions.
public final class LineSegment { private final Complex start, end; public LineSegment(Complex starting_point, Complex ending_point) { start = starting_point; end = ending_point; } public Complex startingPoint() { return start; } public Complex endingPoint() { return end; } }The class also includes a method to compute the length of a line segment, and a method to return the midpoint. See the full source code here: LineSegment.java.
Our program will have a few classes and interfaces to work with certain geometric objects. These will be in a our geometry package.
An interface is a collection of prototypes of methods without implementations. The advantage of creating interfaces is that it allows us create multiple implementations which can be used interchangeably.
Planar Maps: We will define an interface called PlanarMap, it will contain the prototype for a single function which describes the map of the plane. The following code defines this interface:
package geometry; import number.Complex; public interface PlanarMap { Complex map(Complex z); }
Above, the map function above is a prototype of a method which takes as input a complex number and outputs a new complex number.
What follows are two implementations and a demonstration program:
package geometry; import number.Complex; public final class Similarity implements PlanarMap { private final Complex w,a; public Similarity(Complex w, Complex a) { this.w=w; this.a=a; } /** Apply the similarity to z returning w*z+a. */ public Complex map(Complex z) { return w.mult(z).add(a); } }The words implements PlanarMap indicates that the Similarity class will include all the methods in the PlanarMap interface. This is just the map function.
public class PlanarMapDemo { /** Apply the map f one hundred times to the point z and return the result. */ public static Complex applyOneHundredTimes(PlanarMap f, Complex z) { for (int i = 0; i < 100; i++) { z = f.map(z); } return z; } public static void main(String[] args) { Complex z=new Complex(0.5, 0.8); /** The following defines the map z maps to 3*i*z/4+1. */ Similarity f=new Similarity(new Complex(0,0.75),new Complex(1)); System.out.println("We have f^100(z)="+applyOneHundredTimes(f,z)+"."); /** The following defines the map z maps to z^2. */ ComplexPolynomial g=new ComplexPolynomial(new Complex(0), new Complex(0), new Complex(1)); System.out.println("We have g^100(z)="+applyOneHundredTimes(g,z)+"."); } }See the full source code here: PlanarMapDemo.java.
We might call the process of moving from Pn to Pn+1 refinement. In general, a refinement replaces each edge with a polygonal path which better approximates the desired curve.
From this point of view, the edges of a path play a more fundamental role than the vertices. We will take this into account when creating our definition of path.
package java.util; public interface Iterator<E> { boolean hasNext(); E next(); void remove(); }Some comments:
package path; import geometry.LineSegment; public interface EdgeIterator { boolean hasNext(); LineSegment next(); }
We can use objects of this type just as we use Java's Iterator type. We will use it to iterate over the edges of a polygonal path.
package path; import number.Complex; public interface PolygonalPath { Complex startingPoint(); Complex endingPoint(); EdgeIterator iterator(); }
import java.util.*; // For working with lists. import number.Complex; public class VertexPath implements PolygonalPath { private final List<Complex> vert; // The list of vertices /** Construct from a list of complex numbers. */ public VertexPath(Complex... vertices) { // Vertices is an array of complex numbers. vert=new LinkedList<Complex>(); for (int i=0; i<vertices.length; i++) { vert.add(vertices[i]); } } }
The list vert stores our vertices. The type of the list is List<Complex>. The <Complex> indicates that this will be a list of Complex numbers. The List interface is a generic interface meaning that the list can store any specified type of object. Java has many built in classes for working with common data structures involving collections of objects, including lists.
This constructor is called with a list of Complex numbers. So we can build a triangle with:
VertexPath p = new VertexPath( new Complex(0.0),new Complex(1.0), new Complex(0.5, -Math.sqrt(3) / 2), new Complex(0.0) );
Inside the constructor, we make vert a new linked list of Complex numbers. Here, the parameter vertices is an array. We iterate from i=0 to i = (vertices.length-1), and add each vertices[i] to our vertex list.
public Complex endingPoint() { return vert.get(vert.size() - 1); } public Complex startingPoint() { return vert.get(0); }The code just returns the first and last compex number in the list vert.
private class MyEdgeIterator implements EdgeIterator { private Iterator<Complex> vertex_iterator; private Complex current_vertex; public MyEdgeIterator() { vertex_iterator = vert.iterator(); // Create an iterator which moves over the vertices. current_vertex = vertex_iterator.next(); // Record the first vertex. // The next vertex returned by "vertex_iterator.next()" will be the 2nd vertex of the list. } /** Return true if there is another segment in the path. */ public boolean hasNext() { return vertex_iterator.hasNext(); // return true if vertex_iterator has another vertex. } /** Return the next segment in the path. */ public LineSegment next() { Complex a = current_vertex; current_vertex = vertex_iterator.next(); return new LineSegment(a, current_vertex); } } /** Return an iterator over the segments making up the path. */ public EdgeIterator iterator() { return new MyEdgeIterator(); }We emphasize that the above code all appears inside the VertexPath class. Further comments:
public interface Path { Complex startingPoint(); Complex endingPoint(); PathIterator iterator(); }
public interface PathIterator { boolean hasNext(); Path next(); }
Observe that a Path is define very much like a PolygonalPath. The only difference is that instead of allowing the program to iterate over line segments, it allows the program to iterate over paths.
public class UnitCircleExample implements Path { private VertexPath path; public UnitCircleExample() { // Begin with a polygonal approximation using the fourth roots of unity. path = new VertexPath(new Complex(1, 0), new Complex(0, 1), new Complex(-1, 0), new Complex(0, -1), new Complex(1, 0)); } /** Takes a segment with endpoints on the unit circle, and returns a path * constructed from three points on the circle. */ private UnitCircleExample(LineSegment s) { Complex m = s.midpoint(); path = new VertexPath(s.startingPoint(), m.div(m.abs()), // midpoint projected onto circle s.endingPoint()); } public Complex startingPoint() { return path.startingPoint(); } public Complex endingPoint() { return path.endingPoint(); } private class CircleIterator implements PathIterator { EdgeIterator it; public CircleIterator() { it = path.iterator(); } @Override public boolean hasNext() { return it.hasNext(); } @Override public UnitCircleExample next() { // it.next() returns the next segment in the path. return new UnitCircleExample(it.next()); } } public PathIterator iterator() { return new CircleIterator(); } }
public class SubstitutionFractal implements Path { final PolygonalPath p; final PolygonalPath gamma; public SubstitutionFractal(PolygonalPath p, PolygonalPath gamma) { this.p=p; this.gamma=gamma; } private class SubstitutionIterator implements PathIterator { private EdgeIterator it; public SubstitutionIterator() { it=p.iterator(); } public boolean hasNext() { return it.hasNext(); } public SubstitutionFractal next() { Similarity sim=new Similarity(it.next()); // sim is the similarity sending 0 and 1 to the endpoints of the segment it.next(). // "new VertexPath(gamma,sim)" gives the image of gamma under the similarity. return new SubstitutionFractal(new VertexPath(gamma, sim),gamma); } } public PathIterator iterator() { return new SubstitutionIterator(); } }