I've been doing some JavaScript for fun again. This time I tried my hand at drawing the Dragon curve - a neat fractal that consists of one line traveling across the plane, never crossing itself, and creating an interesting shape in the process.
There's several ways to go about generating the Dragon curve; for instance, one could apparently figure out the current turning direction as a function of the number of the turn. What I found easiest was one of the recursive approaches: For a dragon curve of "order" n, create two dragon curves of order n-1, then rotate one around the end point of the other. (A dragon curve of order 0 is just a line segment.)
I'm using three classes here (can you call them that in JS?): One for the Dragon itself, one for points, and one for "polylines", which are basically wrappers for an array of points.
Aside from the always-welcome general feedback, what I'd like to know is this:
- Am I doing JavaScript OOP right? For my Markov text generator, I've made the main function act as a kind of constructor and then added methods by modifying the
prototype
object. This time my functions just directly return an object with the necessary attributes. I'm not sure how these two approaches differ, if at all. - Although the code seems reasonably fast for orders less than about 16, can you recommend any best practices on how to improve the performance?
function Point(x, y) {
return {
x: x,
y: y,
rotateAround: function(p) {
var dx = p.x - this.x;
var dy = p.y - this.y;
this.x = p.x + dy;
this.y = p.y - dx;
}
};
}
function Polyline(points) {
return {
points: points,
getLastPoint: function() {
return this.points[this.points.length - 1];
},
rotateAround: function(p) {
for (var i = 0; i < this.points.length; i++) {
this.points[i].rotateAround(p);
}
},
removeLastPoint: function() {
this.points.splice(-1, 1);
},
reverse: function() {
this.points.reverse();
},
append: function(polyline) {
this.points = this.points.concat(polyline.points);
},
draw: function(ctx) {
ctx.beginPath();
for (var i = 0; i < this.points.length - 1; i++) {
ctx.lineTo(this.points[i + 1].x, this.points[i + 1].y);
}
ctx.stroke();
}
};
}
function DragonCurve(x, y, lineLength, order) {
if (order === 0) {
var points = [];
points[0] = new Point(x, y);
points[1] = new Point(x + lineLength, y);
polyline = new Polyline(points);
} else {
var predecessor = new DragonCurve(x, y, lineLength, order - 1);
polyline = clone(predecessor.polyline);
var toAppend = clone(polyline);
toAppend.rotateAround(polyline.getLastPoint());
toAppend.removeLastPoint(); // the rotation point occurs in both polylines
toAppend.reverse(); // ensure lines are drawn in the correct order
polyline.append(toAppend);
}
return {
polyline: polyline,
draw: function(ctx) {
ctx.moveTo(this.x, this.y);
polyline.draw(ctx);
}
};
}
function clone(obj) { // from http://stackoverflow.com/a/122190/3972493
if (obj === null || typeof(obj) !== 'object') {
return obj;
}
var temp = obj.constructor();
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
temp[key] = clone(obj[key]);
}
}
return temp;
}
And here's how it might be used:
<canvas style="border: 1px solid gray;" id="my-canvas" width="800" height="600"></canvas>
<script type="text/javascript" src="DragonCurve.js"></script>
<script type="text/javascript">
var ctx = document.getElementById('my-canvas').getContext('2d');
var curve = new DragonCurve(185, 200, 2, 16);
curve.draw(ctx);
</script>