Skip to content

Instantly share code, notes, and snippets.

@freshtonic
Created May 27, 2012 14:56
Show Gist options
  • Save freshtonic/2814588 to your computer and use it in GitHub Desktop.
Save freshtonic/2814588 to your computer and use it in GitHub Desktop.
Fractal mountains vista
<!DOCTYPE html>
<html>
<head>
<script type='text/javascript' src='/javascript/jquery-1.7.2-min.js'></script>
<script type='text/javascript'>
(function(){
var mountains = null;
var canvas = null;
var context = null;
var mountainGap = null;
var noiseImage = null;
/* Wraps context-modifying operations in a save() & restore() call */
var preserveContext = function(op) {
try {
context.save();
op();
} finally {
context.restore();
}
};
var jitter = 0.1;
var jitterDirection = function() {
return Math.random() < 0.5 ? -1 : 1;
};
/*
Creates a fractal mountain of *segmentCount* line segments.
The algorithm works by adding a new point to a line where the new
height is chosen as the average of the adjacent heights, plus some
(+ve or -ve) jitter, the amount of which is a function of the distance
between the adjacent points.
The x-coordinate of the new point is always half way between the points.
*/
var generateFractalMountain = function(segmentCount) {
var segmentWidth = canvas.width / segmentCount;
var mountain = [{x:0,y:100 + jitterDirection() * 50}, {x:segmentWidth * segmentCount, y:100 + jitterDirection() * 50}];
var distance = function(p1,p2) {
var xdistance = p2.x - p1.x;
var ydistance = Math.abs(p2.y - p1.y);
return Math.sqrt(xdistance*xdistance + ydistance*ydistance);
};
var split = function(p1Index, p2Index) {
var p1 = mountain[p1Index];
var p2 = mountain[p2Index];
if (Math.abs(p2.x - p1.x) >= (2 * segmentWidth)) {
var newX = (p1.x + p2.x) / 2;
var newY = ((p1.y + p2.y) / 2) + distance(p1,p2) * jitterDirection() * jitter;
var newPoint = {x:newX, y:newY};
var newIndex = p2Index;
p2Index+=1;
mountain.splice(newIndex, 0, newPoint);
// FIXME: data structure is wrong, should be using a tree.
// While the following operation seems to complete in a reasonable time
// frame on my machine, its probably O(N^2).
split(mountain.indexOf(p1), mountain.indexOf(newPoint));
split(mountain.indexOf(newPoint), mountain.indexOf(p2));
}
};
split(0, 1);
return mountain;
};
/*
Creates a new mountain that is the result of interpolation between
two other mountains.
The new mountain is the average of the first two, with some added jitter
so reduce the smoothing that occurs due to averaging.
*/
var interpolate = function(mountain1, mountain2) {
var newMountain = new Array(mountain1.length);
for (var i = 0; i < newMountain.length; i++) {
newMountain[i] = {
x: mountain1[i].x,
y: ((mountain1[i].y + mountain2[i].y) / 2) + jitterDirection() * 0.3
};
}
return newMountain;
};
var generateMountains = function(mountainCount) {
var mountains = new Array(mountainCount);
mountains[0] = generateFractalMountain(1024);
for (var i = 1; i < mountains.length; i++) {
mountains[i] = interpolate(generateFractalMountain(1024), mountains[i-1]);
}
return mountains;
};
var drawSky = function() {
preserveContext(function(){
var gradient = context.createRadialGradient(75,75,600,75,75,1500);
//gradient.addColorStop(0, "#8ED6FF"); // light blue
gradient.addColorStop(0, "#9EE6FF"); // light blue
gradient.addColorStop(1, "#004CB3"); // dark blue
context.fillStyle = gradient;
context.fillRect(0, 0, canvas.width, canvas.height);
});
};
var drawMountain = function(mountain, offset) {
preserveContext(function(){
context.fillStyle = "rgba(0, 33, 0, 0.2)"
context.beginPath();
context.moveTo(mountain[0].x, offset + mountain[0].y);
for (var i = 1; i < mountain.length; i++) {
context.lineTo(mountain[i].x, offset + mountain[i].y);
}
context.lineTo(mountain[mountain.length - 1].x,canvas.height);
context.lineTo(0,canvas.height);
context.lineTo(0,mountain[0].y);
context.closePath();
context.fill();
});
};
var drawMountains = function() {
var numMountains = mountains.length;
var offset = 100;
for (var i = 0; i < mountains.length; i++) {
drawMountain(mountains[i], offset);
offset += mountainGap;
}
};
var drawSun = function() {
preserveContext(function() {
var gradient = context.createRadialGradient(75,75,30,75,75,50);
gradient.addColorStop(0, 'rgba(255,255,255,0.7)');
gradient.addColorStop(0.2, 'rgba(244,242,1,0.8)');
gradient.addColorStop(1, 'rgba(255,255,255,0)');
context.fillStyle = gradient;
context.beginPath();
context.arc(75, 75, 70, 0, Math.PI*2, true);
context.closePath();
context.fill();
});
};
var drawNoise = function() {
preserveContext(function()
var pattern = context.createPattern(noiseImage, "repeat");
context.fillStyle = pattern;
context.globalAlpha = 0.05;
context.fillRect(0, 0, canvas.width, canvas.height);
});
};
var draw = function() {
drawSky();
drawMountains();
drawSun();
drawNoise();
};
var setCanvasDimensions = function() {
console.log("Setting canvas dimensions to: " + $(window).width() + ", " + $(window).height());
$('#background').attr('width', $(window).width());
$('#background').attr('height', $(window).height());
};
var init = function() {
noiseImage = new Image();
noiseImage.src = 'images/noise.png';
noiseImage.onload = function(){
draw();
}
canvas = canvas || document.getElementById('background');
setCanvasDimensions();
mountains = generateMountains(5);
context = context || canvas.getContext("2d");
mountainGap = canvas.height / (mountains.length + 1);
};
$(document).ready(init);
})();
</script>
<style tyoe='text/css'>
body {
margin:0;
padding:0;
height:100%;
}
</style>
<title>Vista of Fractal Mountains</title>
</head>
<body>
<canvas id='background'></canvas>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment