Here's my attempt to draw Koch snowflake. My initial state is a line instead of an equilateral triangle so that I am able to fit maximum detail in a mobile phone window. My thought process was:
- Start with two points A, B separated distance r.
- Draw three points between them, P1, P2, P3.
- P1 at distance r/3 from A on line AB.
- P2 at r/3 from the P1 at 60 degrees from the AB.
- P3 at 2*r/3 from A on line AB.
Code
public class KochView extends View {
List<PointF> points = new LinkedList<>();
private int depth = 6;
public KochView(Context context) {
super(context);
}
public KochView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
}
@Override
protected void onDraw(final Canvas canvas) {
final int H = getHeight();
final int W = getWidth();
final Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setAlpha((int) (255 * 0.99));
paint.setStrokeWidth(1.0f);
// Adjust start/end points for the Koch 'snowflake' depending on whether we are in
// portrait...
if (H > W) {
points.add(new PointF(0.05f * W, 0.01f * H));
points.add(new PointF(0.05f * W, 0.99f * H));
}// or landscape mode...
else {
points.add(new PointF(0.01f * W, 0.95f * H));
points.add(new PointF(0.99f * W, 0.95f * H));
}
// The current algo does not draw start and end points, so we draw them manually...
drawPoint(canvas, paint, points.get(0));
drawPoint(canvas, paint, points.get(1));
doPairWise(points, new Action<PointF>() {
@Override
public List<PointF> exec(PointF first, PointF second) {
// Find the distance between the given two points
double r = dist(first, second);
// And the angle they subtend w.r.t the x-axis
float th = (float) (Math.atan2(second.y - first.y, second.x - first.x));
// Get the first point according the Koch snowflake iteration definition. I.e. at
// 1/3 rd the distance, on the line joining th two points.
PointF p1 = getPoint(first, (float) (r / 3), th);
// Get the second point, at 1/3 distance from the *first* point, at an angle of
// 60 degrees from the line joining the given points,
PointF p2 = getPoint(p1, (float) (r / 3), (float) (th - PI / 3));
// Get the third point, at 2/3 the distance, on the line joining the given points.
PointF p3 = getPoint(first, (float) (2 * r / 3), th);
// Draw all three...
drawPoint(canvas, paint, p1);
drawPoint(canvas, paint, p2);
drawPoint(canvas, paint, p3);
// Add them to a list...
List<PointF> more = new LinkedList<>();
more.add(p1);
more.add(p2);
more.add(p3);
// Return the list of points for further iteration
return more;
}
});
}
/**
* Get a point at radial distance 'r' and a given angle 't' w.r.t a given point.
*
* @param src The source point.
* @param r The radial distance in pixels.
* @param t The angle in radians.
* @return The new point.
*/
private PointF getPoint(PointF src, float r, float t) {
PointF result = new PointF();
result.x = (int) (src.x + r * Math.cos(t));
result.y = (int) (src.y + r * Math.sin(t));
return result;
}
/**
* Recursively do an action on consecutive pair from a list. Here the action is drawing three
* more points in between them to plat a Koch snowflake.
*
* @param list The list of items to make the pairs from.
* @param action The action to do. In this case 'action' should draw the points on the canvas
* and return them for calculation of future iterations.
*/
private void doPairWise(List<PointF> list, Action<PointF> action) {
// New points are added to this list alongwith existing ones and this is used as the list
// of points for the next iteration.
List<PointF> tmp = new LinkedList<>();
// Depth to terminate the recursion at
if (--depth <= 0) {
return;
}
int size = list.size();
for (int i = 0; i < size - 1; i += 1) {
final List<PointF> more = action.exec(list.get(i), list.get(i + 1));
// Add the starting point to the list of all points...
tmp.add(list.get(i));
// ...add the three points in the iteration of a Koch snowflake.
tmp.addAll(more);
}
// finally add the last point to all points.
tmp.add(list.get(size - 1));
// Repeat the above process with the new list and the same "action"
doPairWise(tmp, action);
}
private interface Action<T> {
/**
* Do something with a pair of objects. Optionally, return a list of the same type.
*
* @param first First object to do smth with.
* @param second Second object to smth with.
* @return Optionally return a list of same type as inputs.
*/
List<T> exec(T first, T second);
}
/**
* Convenience method. to draw a point on a canvas with a give paint.
*
* @param canvas The canvas to draw the point on.
* @param paint The paint to draw the point with.
* @param point The point to draw.
*/
private static void drawPoint(Canvas canvas, Paint paint, PointF point) {
canvas.drawPoint(point.x, point.y, paint);
}
/**
* Returns the Euclidean distance between two points.
*
* @param p1 First point.
* @param p2 Second point.
* @return The distance, as the crow flies, between the given points.
*/
private static double dist(PointF p1, PointF p2) {
return dist(p1.x, p2.x, p1.y, p2.y);
}
private static double dist(double x1, double x2, double y1, double y2) {
return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
}
}
Output:
Depth = 6
Depth = 8