├── frontier ├── sketch.properties ├── frontier.pde ├── Sky.pde ├── Tree.pde ├── Mountains.pde └── Building.pde ├── img ├── tree.png ├── building.png ├── landscape.png ├── buildings-day.png └── buildings-night.png ├── .gitignore ├── README.md └── js ├── src ├── Sky.js ├── helpers.js ├── Tree.js ├── Mountains.js ├── noise.js ├── setup.js └── Building.js ├── build.pl ├── frontier.min.js └── frontier.js /frontier/sketch.properties: -------------------------------------------------------------------------------- 1 | mode.id=processing.mode.java.JavaMode 2 | mode=Java 3 | -------------------------------------------------------------------------------- /img/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davepagurek/Frontier/HEAD/img/tree.png -------------------------------------------------------------------------------- /img/building.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davepagurek/Frontier/HEAD/img/building.png -------------------------------------------------------------------------------- /img/landscape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davepagurek/Frontier/HEAD/img/landscape.png -------------------------------------------------------------------------------- /img/buildings-day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davepagurek/Frontier/HEAD/img/buildings-day.png -------------------------------------------------------------------------------- /img/buildings-night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davepagurek/Frontier/HEAD/img/buildings-night.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | .DS_STORE 3 | 4 | # Mobile Tools for Java (J2ME) 5 | .mtj.tmp/ 6 | 7 | # Package Files # 8 | *.jar 9 | *.war 10 | *.ear 11 | 12 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 13 | hs_err_pid* 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Frontier 2 | Procedural landscape generation in Processing. Read about how it works 3 | 4 | ## Screenshots 5 | Landscape in the day: 6 | 7 | 8 | Landscape at night: 9 | 10 | -------------------------------------------------------------------------------- /frontier/frontier.pde: -------------------------------------------------------------------------------- 1 | void setup() { 2 | size(1280, 720); 3 | drawScene(); 4 | } 5 | 6 | void draw() { 7 | } 8 | 9 | void mousePressed() { 10 | drawScene(); 11 | } 12 | 13 | void drawScene() { 14 | background(255); 15 | 16 | float time = random(1); 17 | 18 | Sky sky = new Sky(time); 19 | sky.draw(); 20 | 21 | Mountains mountains = new Mountains(height*0.2, height*0.7, time); 22 | mountains.draw(); 23 | } 24 | -------------------------------------------------------------------------------- /js/src/Sky.js: -------------------------------------------------------------------------------- 1 | function Sky(time) { 2 | var sky, horizon; 3 | if (time < 0.5) { 4 | sky = lerpColor("#B9F7D5", "#57C8F0", map(time, 0, 0.5, 0, 1)); 5 | horizon = lerpColor("#FCED42", "#E365ED", map(time, 0, 0.5, 0, 1)); 6 | } else { 7 | sky = lerpColor("#C282E5", "#1A285A", map(time, 0.5, 1, 0, 1)); 8 | horizon = lerpColor("#82E3E5", "#BFE1FC", map(time, 0.5, 1, 0, 1)); 9 | } 10 | 11 | this.draw = function() { 12 | setGradient(0, 0, width, height, sky, horizon); 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /js/src/helpers.js: -------------------------------------------------------------------------------- 1 | function throttle(fn, threshhold, scope) { 2 | threshhold || (threshhold = 250); 3 | var last, 4 | deferTimer; 5 | return function () { 6 | var context = scope || this; 7 | 8 | var now = +new Date, 9 | args = arguments; 10 | if (last && now < last + threshhold) { 11 | // hold on to it 12 | clearTimeout(deferTimer); 13 | deferTimer = setTimeout(function () { 14 | last = now; 15 | fn.apply(context, args); 16 | }, threshhold); 17 | } else { 18 | last = now; 19 | fn.apply(context, args); 20 | } 21 | }; 22 | } 23 | 24 | -------------------------------------------------------------------------------- /js/build.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use File::Slurp; 4 | use autodie; 5 | 6 | my @files = qw( 7 | helpers.js 8 | noise.js 9 | Building.js 10 | Tree.js 11 | Mountains.js 12 | Sky.js 13 | setup.js 14 | ); 15 | 16 | my $content = ""; 17 | 18 | for my $file (@files) { 19 | $content .= read_file("src/$file") . "\n\n"; 20 | } 21 | 22 | 23 | open my $condensed, ">", "frontier.js"; 24 | print $condensed $content; 25 | close $condensed; 26 | 27 | #remove comments 28 | $content =~ s|//.*$||mg; 29 | $content =~ s|/\*.*?\*/||mg; 30 | 31 | #condense whitespace 32 | $content =~ s/\s+/ /mg; 33 | 34 | open my $minified, ">", "frontier.min.js"; 35 | print $minified $content; 36 | close $minified; 37 | 38 | 39 | -------------------------------------------------------------------------------- /frontier/Sky.pde: -------------------------------------------------------------------------------- 1 | class Sky { 2 | color sky, horizon; 3 | final int Y_AXIS = 1, 4 | X_AXIS = 2; 5 | 6 | Sky(float time) { 7 | if (time < 0.5) { 8 | sky = lerpColor(#B9F7D5, #57C8F0, map(time, 0, 0.5, 0, 1)); 9 | horizon = lerpColor(#FCED42, #E365ED, map(time, 0, 0.5, 0, 1)); 10 | } else { 11 | sky = lerpColor(#C282E5, #1A285A, map(time, 0.5, 1, 0, 1)); 12 | horizon = lerpColor(#82E3E5, #BFE1FC, map(time, 0.5, 1, 0, 1)); 13 | } 14 | } 15 | 16 | void draw() { 17 | setGradient(0, 0, width, height, sky, horizon, Y_AXIS); 18 | } 19 | 20 | private void setGradient(int x, int y, float w, float h, color c1, color c2, int axis) { 21 | noFill(); 22 | 23 | if (axis == Y_AXIS) { // Top to bottom gradient 24 | for (int i = y; i <= y+h; i++) { 25 | float inter = map(i, y, y+h, 0, 1); 26 | color c = lerpColor(c1, c2, inter); 27 | stroke(c); 28 | line(x, i, x+w, i); 29 | } 30 | } else if (axis == X_AXIS) { // Left to right gradient 31 | for (int i = x; i <= x+w; i++) { 32 | float inter = map(i, x, x+w, 0, 1); 33 | color c = lerpColor(c1, c2, inter); 34 | stroke(c); 35 | line(i, y, i, y+h); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /js/src/Tree.js: -------------------------------------------------------------------------------- 1 | function Tree(x, y, w, h, time) { 2 | var root = new Branch(x, y, w, h, BRANCH_ROOT, 1, time); 3 | this.draw = function() { 4 | root.drawLeaves(); 5 | root.drawBranches(); 6 | }; 7 | } 8 | 9 | function Branch(x, y, w, h, side, level, time) { 10 | var branches = []; 11 | var segmentX = 0; 12 | var segmentY = 0; 13 | var leafWidth = 0; 14 | var leafHeight = 0; 15 | 16 | if (side == BRANCH_ROOT) { 17 | segmentX = x + (random(w * 0.6) - w * 0.3); 18 | segmentY = y - h * 0.75 + (random(h * 0.3) - h * 0.15); 19 | } else if (side == BRANCH_LEFT) { 20 | segmentX = x - random(w * 0.3); 21 | segmentY = y - h * 0.75 + (random(h * 0.3) - h * 0.15); 22 | } else if (side == BRANCH_RIGHT) { 23 | segmentX = x + random(w * 0.3); 24 | segmentY = y - h * 0.75 + (random(h * 0.3) - h * 0.15); 25 | } 26 | 27 | leafWidth = random(w * 0.6) + w * 0.3; 28 | leafHeight = random(h * 0.6) + h * 0.3; 29 | 30 | if (level < 3) { 31 | for (var i = 0; i < BRANCH_MAX_LEVELS; i++) { 32 | if (random(10) >= 7) { 33 | var branchY = random(segmentY, y); 34 | var branchX = x + ((segmentX - x) / (segmentY - y)) * (branchY - y); 35 | var branchDir = random(10) > 5 ? BRANCH_LEFT : BRANCH_RIGHT; 36 | branches.push(new Branch(branchX, branchY, w * 0.6, h * 0.5, branchDir, level + 1, time)); 37 | } 38 | } 39 | } 40 | 41 | this.drawLeaves = function() { 42 | var leafColor; 43 | if (time < 0.5) { 44 | leafColor = lerpColor("#FFD1D1", "#F2C975", map(time, 0, 0.5, 0, 1)); 45 | } else { 46 | leafColor = lerpColor("#D5BCE0", "#56627C", map(time, 0.5, 1, 0, 1)); 47 | } 48 | fill(leafColor); 49 | ellipse(segmentX, segmentY, leafWidth, leafHeight); 50 | 51 | branches.forEach(function(b) { 52 | b.drawLeaves(); 53 | }); 54 | }; 55 | 56 | this.drawBranches = function() { 57 | var branchColor; 58 | if (time < 0.5) { 59 | branchColor = lerpColor("#79504E", "#866A59", map(time, 0, 0.5, 0, 1)); 60 | } else { 61 | branchColor = lerpColor("#635774", "#635774", map(time, 0.5, 1, 0, 1)); 62 | } 63 | 64 | strokeWeight((y - segmentY) / 20); 65 | stroke(branchColor); 66 | line(x, y, segmentX, segmentY); 67 | 68 | branches.forEach(function(b) { 69 | b.drawBranches(); 70 | }); 71 | }; 72 | } 73 | -------------------------------------------------------------------------------- /frontier/Tree.pde: -------------------------------------------------------------------------------- 1 | final int BRANCH_ROOT = 0, 2 | BRANCH_LEFT = -1, 3 | BRANCH_RIGHT = 1, 4 | 5 | BRANCH_MAX_LEVELS = 3; 6 | 7 | class Tree { 8 | Branch root; 9 | 10 | Tree(float x, float y, float w, float h, float time) { 11 | root = new Branch(x, y, w, h, BRANCH_ROOT, 1, time); 12 | } 13 | 14 | void draw() { 15 | root.drawLeaves(); 16 | root.drawBranches(); 17 | } 18 | } 19 | 20 | class Branch { 21 | float segmentX = 0, segmentY = 0; 22 | float leafWidth = 0, leafHeight = 0; 23 | float rootX = 0, rootY = 0; 24 | float time; 25 | ArrayList branches; 26 | 27 | Branch(float x, float y, float w, float h, int side, int level, float t) { 28 | rootX = x; 29 | rootY = y; 30 | 31 | time = t; 32 | 33 | branches = new ArrayList(); 34 | 35 | if (side == BRANCH_ROOT) { 36 | segmentX = x+(random(w*0.6)-w*0.3); 37 | segmentY = y-h*0.75+(random(h*0.3)-h*0.15); 38 | } else if (side == BRANCH_LEFT) { 39 | segmentX = x-random(w*0.3); 40 | segmentY = y-h*0.75+(random(h*0.3)-h*0.15); 41 | } else if (side == BRANCH_RIGHT) { 42 | segmentX = x+random(w*0.3); 43 | segmentY = y-h*0.75+(random(h*0.3)-h*0.15); 44 | } 45 | 46 | leafWidth = random(w*0.6)+w*0.3; 47 | leafHeight = random(h*0.6)+h*0.3; 48 | 49 | if (level < 3) { 50 | for (int i=0; i= 7) { 52 | float branchY = random(segmentY, y); 53 | float branchX = x + ((segmentX - x)/(segmentY - y)) * (branchY - y); 54 | int branchDir = random(10) > 5 ? BRANCH_LEFT : BRANCH_RIGHT; 55 | branches.add(new Branch(branchX, branchY, w*0.6, h*0.5, branchDir, level+1, time)); 56 | } 57 | } 58 | } 59 | } 60 | 61 | void drawLeaves() { 62 | noStroke(); 63 | color leafColor; 64 | if (time < 0.5) { 65 | leafColor = lerpColor(#FFD1D1, #F2C975, map(time, 0, 0.5, 0, 1)); 66 | } else { 67 | leafColor = lerpColor(#D5BCE0, #56627C, map(time, 0.5, 1, 0, 1)); 68 | } 69 | fill(leafColor); 70 | ellipse(segmentX, segmentY, leafWidth, leafHeight); 71 | 72 | for (Branch b : branches) { 73 | b.drawLeaves(); 74 | } 75 | } 76 | 77 | void drawBranches() { 78 | color branchColor; 79 | if (time < 0.5) { 80 | branchColor = lerpColor(#79504E, #866A59, map(time, 0, 0.5, 0, 1)); 81 | } else { 82 | branchColor = lerpColor(#635774, #635774, map(time, 0.5, 1, 0, 1)); 83 | } 84 | 85 | strokeWeight((rootY - segmentY)/20); 86 | stroke(branchColor); 87 | line(rootX, rootY, segmentX, segmentY); 88 | 89 | for (Branch b : branches) { 90 | b.drawBranches(); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /js/src/Mountains.js: -------------------------------------------------------------------------------- 1 | function Mountains(top, bottom, time) { //#D36F6F, #AD1515 2 | var ranges = []; 3 | var start, end; 4 | if (time < 0.5) { 5 | start = lerpColor("#D36F6F", "#F2C178", map(time, 0, 0.5, 0, 1)); 6 | end = lerpColor("#AD1515", "#E85B57", map(time, 0, 0.5, 0, 1)); 7 | } else { 8 | start = lerpColor("#654183", "#2D3A5F", map(time, 0.5, 1, 0, 1)); 9 | end = lerpColor("#B198C6", "#6172A5", map(time, 0.5, 1, 0, 1)); 10 | } 11 | 12 | var layers = int(random(2, 5)); 13 | var heightUnit = (bottom - top) / (layers + 1); 14 | 15 | for (var l = 0; l < layers; l++) { 16 | var y = top + random(heightUnit * l, heightUnit * (l + 1)); 17 | var spread = random(1, 3) * heightUnit; 18 | ranges.push(new MountainRange( 19 | y, 20 | spread, 21 | lerpColor(start, end, l/layers), (l + 1) / layers, 22 | time 23 | )); 24 | } 25 | 26 | this.draw = function() { 27 | ranges.forEach(function(range) { 28 | range.draw(); 29 | }); 30 | }; 31 | } 32 | 33 | function MountainRange(y, spread, c, foreground, time) { 34 | var elevation = []; 35 | var trees = []; 36 | var buildings = []; 37 | 38 | var noiseElevation = random(1, 100); 39 | var spikiness = random(40, 90); 40 | 41 | for (var x = 0; x < 150; x++) { 42 | elevation.push(map(noise(x / spikiness, noiseElevation), 0, 1, -1, 1) * spread); 43 | } 44 | 45 | var numTrees = int(random(1, map(1.0 / foreground, 1, 100, 5, 40))); 46 | for (var i = 0; i < numTrees; i++) { 47 | var location = int(random(elevation.length)); 48 | trees.push(new Tree( 49 | (location / (elevation.length - 1)) * width, 50 | y + elevation[location], 51 | foreground * 100, 52 | foreground * 80, 53 | time 54 | )); 55 | } 56 | 57 | var numBuildings = int(random(1, map(1.0 / foreground, 1, 100, 0, 10))); 58 | for (var i = 0; i < numTrees; i++) { 59 | var location = int(random(elevation.length)); 60 | buildings.push(new Building( 61 | (location / (elevation.length - 1)) * width, 62 | y + elevation[location] + spread * 0.1, 63 | foreground * 80, 64 | foreground * 120, 65 | time 66 | )); 67 | } 68 | 69 | this.draw = function() { 70 | buildings.forEach(function(building) { 71 | building.draw(); 72 | }); 73 | 74 | fill(c); 75 | beginShape(); 76 | for (var i = 0; i < elevation.length; i++) { 77 | vertex((i / (elevation.length - 1)) * width, y + elevation[i]); 78 | } 79 | vertex(width, height); 80 | vertex(0, height); 81 | endShape(); 82 | 83 | trees.forEach(function(tree) { 84 | tree.draw(); 85 | }); 86 | }; 87 | } 88 | -------------------------------------------------------------------------------- /frontier/Mountains.pde: -------------------------------------------------------------------------------- 1 | class Mountains { 2 | int layers = 0; 3 | float heightUnit = 0; 4 | color start, end; 5 | ArrayList ranges; 6 | 7 | Mountains(float top, float bottom, float time) { //#D36F6F, #AD1515 8 | if (time < 0.5) { 9 | start = lerpColor(#D36F6F, #F2C178, map(time, 0, 0.5, 0, 1)); 10 | end = lerpColor(#AD1515, #E85B57, map(time, 0, 0.5, 0, 1)); 11 | } else { 12 | start = lerpColor(#654183, #2D3A5F, map(time, 0.5, 1, 0, 1)); 13 | end = lerpColor(#B198C6, #6172A5, map(time, 0.5, 1, 0, 1)); 14 | } 15 | 16 | layers = int(random(2, 5)); 17 | heightUnit = (bottom - top)/(layers+1); 18 | 19 | ranges = new ArrayList(); 20 | 21 | for (int l = 0; l < layers; l++) { 22 | float y = top + random(heightUnit*l, heightUnit*(l+1)); 23 | float spread = random(2, 4)*heightUnit; 24 | ranges.add(new MountainRange( 25 | y, 26 | spread, 27 | lerpColor(start, end, float(l)/layers), 28 | float(l+1)/layers, 29 | time 30 | )); 31 | } 32 | } 33 | 34 | void draw() { 35 | for (MountainRange range : ranges) { 36 | range.draw(); 37 | } 38 | } 39 | } 40 | 41 | class MountainRange { 42 | ArrayList elevation; 43 | ArrayList trees; 44 | ArrayList buildings; 45 | float rangeY = 0; 46 | color rangeColor; 47 | 48 | MountainRange(float y, float spread, color c, float foreground, float time) { 49 | rangeY = y; 50 | rangeColor = c; 51 | elevation = new ArrayList(); 52 | trees = new ArrayList(); 53 | buildings = new ArrayList(); 54 | 55 | float noiseElevation = random(1, 100); 56 | float spikiness = random(40, 90); 57 | 58 | for (int x = 0; x < 150; x++) { 59 | elevation.add(map(noise(float(x)/spikiness, noiseElevation), 0, 1, -1, 1) * spread); 60 | } 61 | 62 | int numTrees = int(random(1, map(1.0/foreground, 1, 100, 5, 40))); 63 | for (int i = 0; i < numTrees; i++) { 64 | int location = int(random(elevation.size())); 65 | trees.add(new Tree( 66 | (float(location)/(elevation.size()-1))*width, 67 | rangeY + elevation.get(location), 68 | foreground*100, 69 | foreground*80, 70 | time 71 | )); 72 | } 73 | 74 | int numBuildings = int(random(1, map(1.0/foreground, 1, 100, 0, 10))); 75 | for (int i = 0; i < numTrees; i++) { 76 | int location = int(random(elevation.size())); 77 | buildings.add(new Building( 78 | (float(location)/(elevation.size()-1))*width, 79 | rangeY + elevation.get(location) + spread*0.1, 80 | foreground*80, 81 | foreground*120, 82 | time 83 | )); 84 | } 85 | } 86 | 87 | void draw() { 88 | for (Building building : buildings) { 89 | building.draw(); 90 | } 91 | 92 | noStroke(); 93 | fill(rangeColor); 94 | beginShape(); 95 | for (int i = 0; i < elevation.size(); i++) { 96 | vertex((float(i)/(elevation.size()-1))*width, rangeY + elevation.get(i)); 97 | } 98 | vertex(width, height); 99 | vertex(0, height); 100 | endShape(CLOSE); 101 | 102 | for (Tree tree : trees) { 103 | tree.draw(); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /js/src/noise.js: -------------------------------------------------------------------------------- 1 | var ClassicalNoise = function(r) { // Classic Perlin noise in 3D, for comparison 2 | if (r == undefined) r = Math; 3 | this.grad3 = [[1,1,0],[-1,1,0],[1,-1,0],[-1,-1,0], 4 | [1,0,1],[-1,0,1],[1,0,-1],[-1,0,-1], 5 | [0,1,1],[0,-1,1],[0,1,-1],[0,-1,-1]]; 6 | this.p = []; 7 | for (var i=0; i<256; i++) { 8 | this.p[i] = Math.floor(r.random()*256); 9 | } 10 | // To remove the need for index wrapping, double the permutation table length 11 | this.perm = []; 12 | for(var i=0; i<512; i++) { 13 | this.perm[i]=this.p[i & 255]; 14 | } 15 | }; 16 | 17 | ClassicalNoise.prototype.dot = function(g, x, y, z) { 18 | return g[0]*x + g[1]*y + g[2]*z; 19 | }; 20 | 21 | ClassicalNoise.prototype.mix = function(a, b, t) { 22 | return (1.0-t)*a + t*b; 23 | }; 24 | 25 | ClassicalNoise.prototype.fade = function(t) { 26 | return t*t*t*(t*(t*6.0-15.0)+10.0); 27 | }; 28 | 29 | // Classic Perlin noise, 3D version 30 | ClassicalNoise.prototype.noise = function(x, y, z) { 31 | // Find unit grid cell containing point 32 | var X = Math.floor(x); 33 | var Y = Math.floor(y); 34 | var Z = Math.floor(z); 35 | 36 | // Get relative xyz coordinates of point within that cell 37 | x = x - X; 38 | y = y - Y; 39 | z = z - Z; 40 | 41 | // Wrap the integer cells at 255 (smaller integer period can be introduced here) 42 | X = X & 255; 43 | Y = Y & 255; 44 | Z = Z & 255; 45 | 46 | // Calculate a set of eight hashed gradient indices 47 | var gi000 = this.perm[X+this.perm[Y+this.perm[Z]]] % 12; 48 | var gi001 = this.perm[X+this.perm[Y+this.perm[Z+1]]] % 12; 49 | var gi010 = this.perm[X+this.perm[Y+1+this.perm[Z]]] % 12; 50 | var gi011 = this.perm[X+this.perm[Y+1+this.perm[Z+1]]] % 12; 51 | var gi100 = this.perm[X+1+this.perm[Y+this.perm[Z]]] % 12; 52 | var gi101 = this.perm[X+1+this.perm[Y+this.perm[Z+1]]] % 12; 53 | var gi110 = this.perm[X+1+this.perm[Y+1+this.perm[Z]]] % 12; 54 | var gi111 = this.perm[X+1+this.perm[Y+1+this.perm[Z+1]]] % 12; 55 | 56 | // The gradients of each corner are now: 57 | // g000 = grad3[gi000]; 58 | // g001 = grad3[gi001]; 59 | // g010 = grad3[gi010]; 60 | // g011 = grad3[gi011]; 61 | // g100 = grad3[gi100]; 62 | // g101 = grad3[gi101]; 63 | // g110 = grad3[gi110]; 64 | // g111 = grad3[gi111]; 65 | // Calculate noise contributions from each of the eight corners 66 | var n000= this.dot(this.grad3[gi000], x, y, z); 67 | var n100= this.dot(this.grad3[gi100], x-1, y, z); 68 | var n010= this.dot(this.grad3[gi010], x, y-1, z); 69 | var n110= this.dot(this.grad3[gi110], x-1, y-1, z); 70 | var n001= this.dot(this.grad3[gi001], x, y, z-1); 71 | var n101= this.dot(this.grad3[gi101], x-1, y, z-1); 72 | var n011= this.dot(this.grad3[gi011], x, y-1, z-1); 73 | var n111= this.dot(this.grad3[gi111], x-1, y-1, z-1); 74 | // Compute the fade curve value for each of x, y, z 75 | var u = this.fade(x); 76 | var v = this.fade(y); 77 | var w = this.fade(z); 78 | // Interpolate along x the contributions from each of the corners 79 | var nx00 = this.mix(n000, n100, u); 80 | var nx01 = this.mix(n001, n101, u); 81 | var nx10 = this.mix(n010, n110, u); 82 | var nx11 = this.mix(n011, n111, u); 83 | // Interpolate the four results along y 84 | var nxy0 = this.mix(nx00, nx10, v); 85 | var nxy1 = this.mix(nx01, nx11, v); 86 | // Interpolate the two last results along z 87 | var nxyz = this.mix(nxy0, nxy1, w); 88 | 89 | return nxyz; 90 | }; 91 | -------------------------------------------------------------------------------- /js/src/setup.js: -------------------------------------------------------------------------------- 1 | var width = 720; 2 | var height = 405; 3 | var canvas = document.getElementById("landscape"); 4 | var container = document.getElementById("header"); 5 | var overlay = document.getElementById("overlay"); 6 | var buffer, buffer_canvas; 7 | var pathCleared = true; 8 | var transitioning = false; 9 | 10 | var clearBackground = function() { 11 | buffer.clearRect(0, 0, width, height); 12 | }; 13 | 14 | var random = function(low, high) { 15 | if (high === undefined) { 16 | high = low; 17 | low = 0; 18 | } 19 | return Math.random() * (high - low) + low; 20 | }; 21 | 22 | var int = function(n) { 23 | return Math.floor(n); 24 | }; 25 | 26 | var strokeWeight = function(w) { 27 | buffer.lineWidth = w; 28 | }; 29 | 30 | var stroke = function(c) { 31 | buffer.strokeStyle = c; 32 | }; 33 | 34 | var line = function(x1, y1, x2, y2) { 35 | buffer.beginPath(); 36 | buffer.moveTo(x1, y1); 37 | buffer.lineTo(x2, y2); 38 | buffer.stroke(); 39 | }; 40 | 41 | var fill = function(c) { 42 | buffer.fillStyle = c; 43 | }; 44 | 45 | var beginShape = function() { 46 | buffer.beginPath(); 47 | pathCleared = true; 48 | }; 49 | 50 | var vertex = function(x, y) { 51 | if (pathCleared) { 52 | buffer.moveTo(x, y); 53 | } else { 54 | buffer.lineTo(x, y); 55 | } 56 | pathCleared = false; 57 | }; 58 | 59 | var endShape = function() { 60 | buffer.closePath(); 61 | buffer.fill(); 62 | }; 63 | 64 | var rect = function(x, y, w, h) { 65 | buffer.fillRect(x, y, w, h); 66 | }; 67 | 68 | var ellipse = function(x, y, w, h) { 69 | buffer.beginPath(); 70 | buffer.ellipse(x, y, w/2, h/2, 0, 0, 2 * Math.PI); 71 | buffer.fill(); 72 | }; 73 | 74 | var setGradient = function(x, y, w, h, c1, c2) { 75 | var gradient = buffer.createLinearGradient(0, 0, 0, h); 76 | gradient.addColorStop(0, c1); 77 | gradient.addColorStop(1, c2); 78 | buffer.fillStyle = gradient; 79 | buffer.fillRect(x, y, w, h); 80 | }; 81 | 82 | var hexToRGB = function(hex) { 83 | var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 84 | return result ? { 85 | r: parseInt(result[1], 16), 86 | g: parseInt(result[2], 16), 87 | b: parseInt(result[3], 16) 88 | } : null; 89 | }; 90 | 91 | var componentToHex = function(c) { 92 | var hex = int(c).toString(16); 93 | return hex.length == 1 ? "0" + hex : hex; 94 | }; 95 | 96 | var rgbToHex = function(r, g, b) { 97 | return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); 98 | }; 99 | 100 | var lerp = function(a, b, n) { 101 | return Math.abs((b - a) * n + a); 102 | }; 103 | 104 | var lerpColor = function(beginning, end, percent) { 105 | var c1 = hexToRGB(beginning); 106 | var c2 = hexToRGB(end); 107 | return rgbToHex( 108 | lerp(c1.r, c2.r, percent), 109 | lerp(c1.g, c2.g, percent), 110 | lerp(c1.b, c2.b, percent) 111 | ); 112 | }; 113 | 114 | var map = function(v, a1, b1, a2, b2) { 115 | return (((v-a1) / (b1 - a1)) * (b2 - a2) + a2); 116 | }; 117 | 118 | var perlin = new ClassicalNoise(); 119 | var noise = function(x, y) { 120 | y = y || 0; 121 | return map(perlin.noise(x, y, 0), -1, 1, 0, 1); 122 | }; 123 | 124 | var BUILDING_TOP = 0, 125 | BUILDING_LEFT = -1, 126 | BUILDING_RIGHT = 1, 127 | BUILDING_MAX_LEVELS = 2; 128 | 129 | var BRANCH_ROOT = 0, 130 | BRANCH_LEFT = -1, 131 | BRANCH_RIGHT = 1, 132 | BRANCH_MAX_LEVELS = 3; 133 | 134 | var time, sky, mountains; 135 | 136 | var drawScene = function(offsetX, offsetY) { 137 | sky.draw(); 138 | mountains.draw(); 139 | stage.drawImage(buffer_canvas, offsetX, offsetY); 140 | }; 141 | 142 | var regenScene = function() { 143 | width = canvas.width; 144 | height = canvas.height; 145 | time = random(1); 146 | sky = new Sky(time); 147 | mountains = new Mountains(height * 0.2, height * 0.8, time); 148 | }; 149 | 150 | var redrawCanvas = function(regen) { 151 | canvas.width = container.offsetWidth; 152 | canvas.height = container.offsetHeight; 153 | if (regen) regenScene(); 154 | var scale; 155 | if (canvas.width > (width/height)*canvas.height) { 156 | scale = canvas.width/width; 157 | } else { 158 | scale = canvas.height/height; 159 | } 160 | 161 | buffer_canvas.width = width*scale; 162 | buffer_canvas.height = height*scale; 163 | buffer.scale(scale, scale); 164 | drawScene( 165 | (canvas.width - buffer_canvas.width)/2, 166 | (canvas.height - buffer_canvas.height)/2 167 | ); 168 | }; 169 | 170 | var transition = function() { 171 | if (transitioning) return; 172 | overlay.classList.remove("transparent"); 173 | transitioning = true; 174 | setTimeout(function() { 175 | redrawCanvas(true); 176 | overlay.classList.add("transparent"); 177 | setTimeout(function() { 178 | transitioning = false; 179 | }, 700); 180 | }, 700); 181 | }; 182 | 183 | if (canvas.getContext) { 184 | stage = canvas.getContext("2d"); 185 | buffer_canvas = document.createElement("canvas"); 186 | buffer = buffer_canvas.getContext("2d"); 187 | if (CanvasRenderingContext2D.prototype.ellipse == undefined) { 188 | CanvasRenderingContext2D.prototype.ellipse = function(x, y, radiusX, radiusY, rotation, startAngle, endAngle, antiClockwise) { 189 | this.save(); 190 | this.translate(x, y); 191 | this.rotate(rotation); 192 | this.scale(radiusX, radiusY); 193 | this.arc(0, 0, 1, startAngle, endAngle, antiClockwise); 194 | this.restore(); 195 | }; 196 | } 197 | window.addEventListener("resize", throttle(function() { redrawCanvas() }, 200)); 198 | container.addEventListener("click", transition); 199 | setTimeout(function() { 200 | redrawCanvas(true); 201 | overlay.classList.add("transparent"); 202 | }, 200); 203 | } else { 204 | console.log("canvas not supported"); 205 | document.getElementById("header").classList.add("static"); 206 | } 207 | -------------------------------------------------------------------------------- /js/src/Building.js: -------------------------------------------------------------------------------- 1 | function BuildingUnit(x, y, w, h, side, level, time) { 2 | var WINDOW_LIGHT = "#FFE76F"; 3 | var WINDOW_DARK = "#1B5E81"; 4 | var LIGHTS_ON = 0.6; 5 | var windows = 0; 6 | var windowHeight = 0; 7 | var alignment = 0; 8 | var units = []; 9 | var lights = []; 10 | var offsetLeft = random(-0.1 * w, 0.1 * w); 11 | var offsetRight = random(-0.1 * w, 0.1 * w); 12 | var offsetRoof = random(0.1 * w, 0.3 * w); 13 | var stiltLength = 0; 14 | if (level > 1) { 15 | stiltLength = random(h * 0.2, h * 0.5); 16 | } 17 | if (level <= 2) { 18 | windows = int(random(0, 5 - level)); 19 | if (time > LIGHTS_ON) { 20 | for (var i=0; i 0.5); 22 | } 23 | } 24 | windowHeight = random((h - stiltLength) * 0.1, (h - stiltLength) * 0.5); 25 | if (random(0, 1) > 0.5) { 26 | alignment = BUILDING_LEFT; 27 | } else { 28 | alignment = BUILDING_RIGHT; 29 | } 30 | for (var i = 0; i < BUILDING_MAX_LEVELS; i++) { 31 | if (random(10) >= 5) { 32 | var branchWidth = random(0.2 * w, 0.8 * w); 33 | var branchHeight = random(0.2 * h, 0.8 * h); 34 | var branchX = 0; 35 | var branchY = 0; 36 | 37 | var branchSide = int(random(-2, 2)); 38 | if (branchSide == BUILDING_TOP) { 39 | branchX = random(x - w / 2 + branchWidth / 2, x + w / 2 - branchWidth / 2); 40 | branchY = y - h; 41 | } else if (branchSide == BUILDING_LEFT) { 42 | branchX = x - w / 2 - branchWidth / 2 + w * 0.2; 43 | branchY = random(y - 0.2 * (h - stiltLength) - stiltLength, y - 0.8 * (h - stiltLength) - stiltLength); 44 | } else if (branchSide == BUILDING_RIGHT) { 45 | branchX = x + w / 2 + branchWidth / 2 - w * 0.2; 46 | branchY = random(y - 0.2 * (h - stiltLength) - stiltLength, y - 0.8 * (h - stiltLength) - stiltLength); 47 | } 48 | 49 | units.push(new BuildingUnit(branchX, branchY, branchWidth, branchHeight, branchSide, level + 1, time)); 50 | } 51 | } 52 | } 53 | 54 | this.draw = function() { 55 | units.forEach(function(b) { 56 | b.draw(); 57 | }); 58 | 59 | var stiltColor, roofColor, houseColor, windowDark; 60 | if (time < 0.5) { 61 | stiltColor = lerpColor("#776F7E", "#4B433C", map(time, 0, 0.5, 0, 1)); 62 | houseColor = lerpColor("#AD9592", "#BFABA9", map(time, 0, 0.5, 0, 1)); 63 | roofColor = lerpColor("#776F7E", "#4B433C", map(time, 0, 0.5, 0, 1)); 64 | windowDark = lerpColor("#1B5E81", "#383E5A", map(time, 0, 0.5, 0, 1)); 65 | } else { 66 | stiltColor = lerpColor("#352B48", "#1D263E", map(time, 0.5, 1, 0, 1)); 67 | houseColor = lerpColor("#C3B7D1", "#8788A7", map(time, 0, 0.5, 0, 1)); 68 | roofColor = lerpColor("#776F7E", "#4B433C", map(time, 0, 0.5, 0, 1)); 69 | windowDark = lerpColor("#644C6F", "#3D435F", map(time, 0, 0.5, 0, 1)); 70 | } 71 | 72 | if (stiltLength > 0) { 73 | strokeWeight(w / 20); 74 | stroke(stiltColor); 75 | if (side == BUILDING_TOP) { 76 | line(x - w * 0.3, y, x - w * 0.3, y - stiltLength); 77 | line(x + w * 0.3, y, x + w * 0.3, y - stiltLength); 78 | } else if (side == BUILDING_LEFT) { 79 | line(x - w * 0.4, y - stiltLength, x + w / 2, y); 80 | } else if (side == BUILDING_RIGHT) { 81 | line(x + w * 0.4, y - stiltLength, x - w / 2, y); 82 | } 83 | } 84 | 85 | //Main building 86 | fill(level % 2 == 1 ? houseColor : lerpColor(houseColor, "#1C1443", 0.2)); 87 | beginShape(); 88 | vertex(x - w / 2, y - stiltLength); 89 | vertex(x + w / 2, y - stiltLength); 90 | vertex(x + w / 2 + offsetRight, y - h * 0.85); 91 | vertex(x - w / 2 + offsetLeft, y - h * 0.85); 92 | endShape(); 93 | 94 | //Windows 95 | for (var i = 0; i < windows; i++) { 96 | if (time > LIGHTS_ON && lights[i]) { 97 | fill(WINDOW_LIGHT); 98 | } else { 99 | fill(windowDark); 100 | } 101 | if (alignment == BUILDING_LEFT) { 102 | rect( 103 | x + offsetLeft - w / 2 + w * 0.05 + w * 0.25 * i, 104 | y - h * 0.8, 105 | w * 0.2, 106 | windowHeight 107 | ); 108 | } else if (alignment == BUILDING_RIGHT) { 109 | rect( 110 | x + offsetRight + w / 2 - w * 0.05 - w * 0.25 * (i + 1), 111 | y - h * 0.8, 112 | w * 0.2, 113 | windowHeight 114 | ); 115 | } 116 | } 117 | 118 | //Roof 119 | fill("#685D71"); 120 | beginShape(); 121 | vertex(x - w / 2 + offsetLeft - offsetRoof, y - h * 0.85); 122 | vertex(x + w / 2 + offsetRight + offsetRoof, y - h * 0.85); 123 | vertex(x + w / 2 + offsetRight, y - h); 124 | vertex(x - w / 2 + offsetLeft, y - h); 125 | endShape(); 126 | }; 127 | } 128 | 129 | function Building(x, y, w, h, time) { 130 | var units = []; 131 | var numUnits = int(random(1, 2)); 132 | for (var i = 0; i < numUnits; i++) { 133 | var unitWidth = random(0.5, 1.5) * (w / numUnits); 134 | units.push(new BuildingUnit( 135 | x - w / 2 + i * (w / numUnits) + unitWidth / 2, 136 | y, 137 | unitWidth, 138 | random(h * 0.2, h), 139 | BUILDING_TOP, 140 | 1, 141 | time 142 | )); 143 | } 144 | 145 | this.draw = function() { 146 | units.forEach(function(b) { 147 | b.draw(); 148 | }); 149 | }; 150 | } 151 | -------------------------------------------------------------------------------- /frontier/Building.pde: -------------------------------------------------------------------------------- 1 | final int BUILDING_TOP = 0, 2 | BUILDING_LEFT = -1, 3 | BUILDING_RIGHT = 1, 4 | BUILDING_MAX_LEVELS = 2; 5 | 6 | class Building { 7 | ArrayList units; 8 | 9 | Building(float x, float y, float w, float h, float time) { 10 | units = new ArrayList(); 11 | 12 | int numUnits = int(random(1, 2)); 13 | for (int i = 0; i < numUnits; i++) { 14 | float unitWidth = random(0.5, 1.5)*(w/numUnits); 15 | units.add(new BuildingUnit( 16 | x - w/2 + i*(w/numUnits) + unitWidth/2, 17 | y, 18 | unitWidth, 19 | random(h*0.2, h), 20 | BUILDING_TOP, 21 | 1, 22 | time 23 | )); 24 | } 25 | } 26 | 27 | void draw() { 28 | for (BuildingUnit b: units) { 29 | b.draw(); 30 | } 31 | } 32 | } 33 | 34 | class BuildingUnit { 35 | float unitX = 0, 36 | unitY = 0, 37 | unitWidth = 0, 38 | unitHeight = 0, 39 | offsetLeft = 0, 40 | offsetRight = 0, 41 | offsetRoof = 0, 42 | stiltLength = 0, 43 | windowHeight = 0, 44 | time = 0; 45 | int unitLevel = 0, 46 | unitSide = 0, 47 | windows = 0, 48 | alignment; 49 | ArrayList units; 50 | 51 | color WINDOW_LIGHT = #FFE76F, 52 | WINDOW_DARK = #1B5E81; 53 | 54 | BuildingUnit(float x, float y, float w, float h, int side, int level, float t) { 55 | units = new ArrayList(); 56 | unitX = x; 57 | unitY = y; 58 | unitWidth = w; 59 | unitHeight = h; 60 | unitSide = side; 61 | offsetLeft = random(-0.1*w, 0.1*w); 62 | offsetRight = random(-0.1*w, 0.1*w); 63 | offsetRoof = random(0.1*w, 0.3*w); 64 | unitLevel = level; 65 | time = t; 66 | if (level > 1) { 67 | stiltLength = random(h * 0.2, h * 0.5); 68 | } 69 | 70 | if (level <= 2) { 71 | windows = int(random(0, 5 - level)); 72 | windowHeight = random((unitHeight-stiltLength)*0.1, (unitHeight-stiltLength)*0.5); 73 | if (random(0, 1) > 0.5) { 74 | alignment = BUILDING_LEFT; 75 | } else { 76 | alignment = BUILDING_RIGHT; 77 | } 78 | } 79 | 80 | if (level < 3) { 81 | for (int i=0; i= 5) { 83 | float branchWidth = random(0.2*unitWidth, 0.8*unitWidth); 84 | float branchHeight = random(0.2*unitHeight, 0.8*unitHeight); 85 | float branchX = 0; 86 | float branchY = 0; 87 | 88 | int branchSide = int(random(-2, 2)); 89 | if (branchSide == BUILDING_TOP) { 90 | branchX = random(x - w/2 + branchWidth/2, x + w/2 - branchWidth/2); 91 | branchY = y - h; 92 | } else if (branchSide == BUILDING_LEFT) { 93 | branchX = x - w/2 - branchWidth/2 + w*0.2; 94 | branchY = random(y - 0.2*(h-stiltLength) - stiltLength, y - 0.8*(h-stiltLength) - stiltLength); 95 | } else if (branchSide == BUILDING_RIGHT) { 96 | branchX = x + w/2 + branchWidth/2 - w*0.2; 97 | branchY = random(y - 0.2*(h-stiltLength) - stiltLength, y - 0.8*(h-stiltLength) - stiltLength); 98 | } else { 99 | return; 100 | } 101 | 102 | units.add(new BuildingUnit(branchX, branchY, branchWidth, branchHeight, branchSide, level+1, time)); 103 | } 104 | } 105 | } 106 | } 107 | 108 | void draw() { 109 | for (BuildingUnit b: units) { 110 | b.draw(); 111 | } 112 | 113 | color stiltColor, roofColor, houseColor, windowDark; 114 | if (time < 0.5) { 115 | stiltColor = lerpColor(#776F7E, #4B433C, map(time, 0, 0.5, 0, 1)); 116 | houseColor = lerpColor(#AD9592, #BFABA9, map(time, 0, 0.5, 0, 1)); 117 | roofColor = lerpColor(#776F7E, #4B433C, map(time, 0, 0.5, 0, 1)); 118 | windowDark = lerpColor(#1B5E81, #383E5A, map(time, 0, 0.5, 0, 1)); 119 | } else { 120 | stiltColor = lerpColor(#352B48, #1D263E, map(time, 0.5, 1, 0, 1)); 121 | houseColor = lerpColor(#C3B7D1, #8788A7, map(time, 0, 0.5, 0, 1)); 122 | roofColor = lerpColor(#776F7E, #4B433C, map(time, 0, 0.5, 0, 1)); 123 | windowDark = lerpColor(#644C6F, #3D435F, map(time, 0, 0.5, 0, 1)); 124 | } 125 | 126 | if (stiltLength > 0) { 127 | strokeWeight(unitWidth/20); 128 | stroke(stiltColor); 129 | if (unitSide == BUILDING_TOP) { 130 | line(unitX - unitWidth*0.3, unitY, unitX - unitWidth*0.3, unitY - stiltLength); 131 | line(unitX + unitWidth*0.3, unitY, unitX + unitWidth*0.3, unitY - stiltLength); 132 | } else if (unitSide == BUILDING_LEFT) { 133 | line(unitX - unitWidth*0.4, unitY - stiltLength, unitX + unitWidth/2, unitY); 134 | } else if (unitSide == BUILDING_RIGHT) { 135 | line(unitX + unitWidth*0.4, unitY - stiltLength, unitX - unitWidth/2, unitY); 136 | } 137 | } 138 | 139 | noStroke(); 140 | 141 | //Main building 142 | fill(unitLevel % 2 == 1 ? houseColor : lerpColor(houseColor, #1C1443, 0.2)); 143 | beginShape(); 144 | vertex(unitX - unitWidth/2, unitY - stiltLength); 145 | vertex(unitX + unitWidth/2, unitY - stiltLength); 146 | vertex(unitX + unitWidth/2 + offsetRight, unitY - unitHeight * 0.85); 147 | vertex(unitX - unitWidth/2 + offsetLeft, unitY - unitHeight * 0.85); 148 | endShape(CLOSE); 149 | 150 | //Windows 151 | for (int i = 0; i < windows; i++) { 152 | if (time > 0.6 && random(1)>0.5) { 153 | fill(WINDOW_LIGHT); 154 | } else { 155 | fill(windowDark); 156 | } 157 | if (alignment == BUILDING_LEFT) { 158 | rect( 159 | unitX + offsetLeft - unitWidth/2 + unitWidth*0.05 + unitWidth*0.25*i, 160 | unitY - unitHeight*0.8, 161 | unitWidth*0.2, 162 | windowHeight 163 | ); 164 | } else if (alignment == BUILDING_RIGHT) { 165 | rect( 166 | unitX + offsetRight + unitWidth/2 - unitWidth*0.05 - unitWidth*0.25*(i+1), 167 | unitY - unitHeight*0.8, 168 | unitWidth*0.2, 169 | windowHeight 170 | ); 171 | } 172 | } 173 | 174 | //Roof 175 | fill(#685D71); 176 | beginShape(); 177 | vertex(unitX - unitWidth/2 + offsetLeft - offsetRoof, unitY - unitHeight * 0.85); 178 | vertex(unitX + unitWidth/2 + offsetRight + offsetRoof, unitY - unitHeight * 0.85); 179 | vertex(unitX + unitWidth/2 + offsetRight, unitY - unitHeight); 180 | vertex(unitX - unitWidth/2 + offsetLeft, unitY - unitHeight); 181 | endShape(CLOSE); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /js/frontier.min.js: -------------------------------------------------------------------------------- 1 | function throttle(fn, threshhold, scope) { threshhold || (threshhold = 250); var last, deferTimer; return function () { var context = scope || this; var now = +new Date, args = arguments; if (last && now < last + threshhold) { clearTimeout(deferTimer); deferTimer = setTimeout(function () { last = now; fn.apply(context, args); }, threshhold); } else { last = now; fn.apply(context, args); } }; } var ClassicalNoise = function(r) { if (r == undefined) r = Math; this.grad3 = [[1,1,0],[-1,1,0],[1,-1,0],[-1,-1,0], [1,0,1],[-1,0,1],[1,0,-1],[-1,0,-1], [0,1,1],[0,-1,1],[0,1,-1],[0,-1,-1]]; this.p = []; for (var i=0; i<256; i++) { this.p[i] = Math.floor(r.random()*256); } this.perm = []; for(var i=0; i<512; i++) { this.perm[i]=this.p[i & 255]; } }; ClassicalNoise.prototype.dot = function(g, x, y, z) { return g[0]*x + g[1]*y + g[2]*z; }; ClassicalNoise.prototype.mix = function(a, b, t) { return (1.0-t)*a + t*b; }; ClassicalNoise.prototype.fade = function(t) { return t*t*t*(t*(t*6.0-15.0)+10.0); }; ClassicalNoise.prototype.noise = function(x, y, z) { var X = Math.floor(x); var Y = Math.floor(y); var Z = Math.floor(z); x = x - X; y = y - Y; z = z - Z; X = X & 255; Y = Y & 255; Z = Z & 255; var gi000 = this.perm[X+this.perm[Y+this.perm[Z]]] % 12; var gi001 = this.perm[X+this.perm[Y+this.perm[Z+1]]] % 12; var gi010 = this.perm[X+this.perm[Y+1+this.perm[Z]]] % 12; var gi011 = this.perm[X+this.perm[Y+1+this.perm[Z+1]]] % 12; var gi100 = this.perm[X+1+this.perm[Y+this.perm[Z]]] % 12; var gi101 = this.perm[X+1+this.perm[Y+this.perm[Z+1]]] % 12; var gi110 = this.perm[X+1+this.perm[Y+1+this.perm[Z]]] % 12; var gi111 = this.perm[X+1+this.perm[Y+1+this.perm[Z+1]]] % 12; var n000= this.dot(this.grad3[gi000], x, y, z); var n100= this.dot(this.grad3[gi100], x-1, y, z); var n010= this.dot(this.grad3[gi010], x, y-1, z); var n110= this.dot(this.grad3[gi110], x-1, y-1, z); var n001= this.dot(this.grad3[gi001], x, y, z-1); var n101= this.dot(this.grad3[gi101], x-1, y, z-1); var n011= this.dot(this.grad3[gi011], x, y-1, z-1); var n111= this.dot(this.grad3[gi111], x-1, y-1, z-1); var u = this.fade(x); var v = this.fade(y); var w = this.fade(z); var nx00 = this.mix(n000, n100, u); var nx01 = this.mix(n001, n101, u); var nx10 = this.mix(n010, n110, u); var nx11 = this.mix(n011, n111, u); var nxy0 = this.mix(nx00, nx10, v); var nxy1 = this.mix(nx01, nx11, v); var nxyz = this.mix(nxy0, nxy1, w); return nxyz; }; function BuildingUnit(x, y, w, h, side, level, time) { var WINDOW_LIGHT = "#FFE76F"; var WINDOW_DARK = "#1B5E81"; var LIGHTS_ON = 0.6; var windows = 0; var windowHeight = 0; var alignment = 0; var units = []; var lights = []; var offsetLeft = random(-0.1 * w, 0.1 * w); var offsetRight = random(-0.1 * w, 0.1 * w); var offsetRoof = random(0.1 * w, 0.3 * w); var stiltLength = 0; if (level > 1) { stiltLength = random(h * 0.2, h * 0.5); } if (level <= 2) { windows = int(random(0, 5 - level)); if (time > LIGHTS_ON) { for (var i=0; i 0.5); } } windowHeight = random((h - stiltLength) * 0.1, (h - stiltLength) * 0.5); if (random(0, 1) > 0.5) { alignment = BUILDING_LEFT; } else { alignment = BUILDING_RIGHT; } for (var i = 0; i < BUILDING_MAX_LEVELS; i++) { if (random(10) >= 5) { var branchWidth = random(0.2 * w, 0.8 * w); var branchHeight = random(0.2 * h, 0.8 * h); var branchX = 0; var branchY = 0; var branchSide = int(random(-2, 2)); if (branchSide == BUILDING_TOP) { branchX = random(x - w / 2 + branchWidth / 2, x + w / 2 - branchWidth / 2); branchY = y - h; } else if (branchSide == BUILDING_LEFT) { branchX = x - w / 2 - branchWidth / 2 + w * 0.2; branchY = random(y - 0.2 * (h - stiltLength) - stiltLength, y - 0.8 * (h - stiltLength) - stiltLength); } else if (branchSide == BUILDING_RIGHT) { branchX = x + w / 2 + branchWidth / 2 - w * 0.2; branchY = random(y - 0.2 * (h - stiltLength) - stiltLength, y - 0.8 * (h - stiltLength) - stiltLength); } units.push(new BuildingUnit(branchX, branchY, branchWidth, branchHeight, branchSide, level + 1, time)); } } } this.draw = function() { units.forEach(function(b) { b.draw(); }); var stiltColor, roofColor, houseColor, windowDark; if (time < 0.5) { stiltColor = lerpColor("#776F7E", "#4B433C", map(time, 0, 0.5, 0, 1)); houseColor = lerpColor("#AD9592", "#BFABA9", map(time, 0, 0.5, 0, 1)); roofColor = lerpColor("#776F7E", "#4B433C", map(time, 0, 0.5, 0, 1)); windowDark = lerpColor("#1B5E81", "#383E5A", map(time, 0, 0.5, 0, 1)); } else { stiltColor = lerpColor("#352B48", "#1D263E", map(time, 0.5, 1, 0, 1)); houseColor = lerpColor("#C3B7D1", "#8788A7", map(time, 0, 0.5, 0, 1)); roofColor = lerpColor("#776F7E", "#4B433C", map(time, 0, 0.5, 0, 1)); windowDark = lerpColor("#644C6F", "#3D435F", map(time, 0, 0.5, 0, 1)); } if (stiltLength > 0) { strokeWeight(w / 20); stroke(stiltColor); if (side == BUILDING_TOP) { line(x - w * 0.3, y, x - w * 0.3, y - stiltLength); line(x + w * 0.3, y, x + w * 0.3, y - stiltLength); } else if (side == BUILDING_LEFT) { line(x - w * 0.4, y - stiltLength, x + w / 2, y); } else if (side == BUILDING_RIGHT) { line(x + w * 0.4, y - stiltLength, x - w / 2, y); } } fill(level % 2 == 1 ? houseColor : lerpColor(houseColor, "#1C1443", 0.2)); beginShape(); vertex(x - w / 2, y - stiltLength); vertex(x + w / 2, y - stiltLength); vertex(x + w / 2 + offsetRight, y - h * 0.85); vertex(x - w / 2 + offsetLeft, y - h * 0.85); endShape(); for (var i = 0; i < windows; i++) { if (time > LIGHTS_ON && lights[i]) { fill(WINDOW_LIGHT); } else { fill(windowDark); } if (alignment == BUILDING_LEFT) { rect( x + offsetLeft - w / 2 + w * 0.05 + w * 0.25 * i, y - h * 0.8, w * 0.2, windowHeight ); } else if (alignment == BUILDING_RIGHT) { rect( x + offsetRight + w / 2 - w * 0.05 - w * 0.25 * (i + 1), y - h * 0.8, w * 0.2, windowHeight ); } } fill("#685D71"); beginShape(); vertex(x - w / 2 + offsetLeft - offsetRoof, y - h * 0.85); vertex(x + w / 2 + offsetRight + offsetRoof, y - h * 0.85); vertex(x + w / 2 + offsetRight, y - h); vertex(x - w / 2 + offsetLeft, y - h); endShape(); }; } function Building(x, y, w, h, time) { var units = []; var numUnits = int(random(1, 2)); for (var i = 0; i < numUnits; i++) { var unitWidth = random(0.5, 1.5) * (w / numUnits); units.push(new BuildingUnit( x - w / 2 + i * (w / numUnits) + unitWidth / 2, y, unitWidth, random(h * 0.2, h), BUILDING_TOP, 1, time )); } this.draw = function() { units.forEach(function(b) { b.draw(); }); }; } function Tree(x, y, w, h, time) { var root = new Branch(x, y, w, h, BRANCH_ROOT, 1, time); this.draw = function() { root.drawLeaves(); root.drawBranches(); }; } function Branch(x, y, w, h, side, level, time) { var branches = []; var segmentX = 0; var segmentY = 0; var leafWidth = 0; var leafHeight = 0; if (side == BRANCH_ROOT) { segmentX = x + (random(w * 0.6) - w * 0.3); segmentY = y - h * 0.75 + (random(h * 0.3) - h * 0.15); } else if (side == BRANCH_LEFT) { segmentX = x - random(w * 0.3); segmentY = y - h * 0.75 + (random(h * 0.3) - h * 0.15); } else if (side == BRANCH_RIGHT) { segmentX = x + random(w * 0.3); segmentY = y - h * 0.75 + (random(h * 0.3) - h * 0.15); } leafWidth = random(w * 0.6) + w * 0.3; leafHeight = random(h * 0.6) + h * 0.3; if (level < 3) { for (var i = 0; i < BRANCH_MAX_LEVELS; i++) { if (random(10) >= 7) { var branchY = random(segmentY, y); var branchX = x + ((segmentX - x) / (segmentY - y)) * (branchY - y); var branchDir = random(10) > 5 ? BRANCH_LEFT : BRANCH_RIGHT; branches.push(new Branch(branchX, branchY, w * 0.6, h * 0.5, branchDir, level + 1, time)); } } } this.drawLeaves = function() { var leafColor; if (time < 0.5) { leafColor = lerpColor("#FFD1D1", "#F2C975", map(time, 0, 0.5, 0, 1)); } else { leafColor = lerpColor("#D5BCE0", "#56627C", map(time, 0.5, 1, 0, 1)); } fill(leafColor); ellipse(segmentX, segmentY, leafWidth, leafHeight); branches.forEach(function(b) { b.drawLeaves(); }); }; this.drawBranches = function() { var branchColor; if (time < 0.5) { branchColor = lerpColor("#79504E", "#866A59", map(time, 0, 0.5, 0, 1)); } else { branchColor = lerpColor("#635774", "#635774", map(time, 0.5, 1, 0, 1)); } strokeWeight((y - segmentY) / 20); stroke(branchColor); line(x, y, segmentX, segmentY); branches.forEach(function(b) { b.drawBranches(); }); }; } function Mountains(top, bottom, time) { var ranges = []; var start, end; if (time < 0.5) { start = lerpColor("#D36F6F", "#F2C178", map(time, 0, 0.5, 0, 1)); end = lerpColor("#AD1515", "#E85B57", map(time, 0, 0.5, 0, 1)); } else { start = lerpColor("#654183", "#2D3A5F", map(time, 0.5, 1, 0, 1)); end = lerpColor("#B198C6", "#6172A5", map(time, 0.5, 1, 0, 1)); } var layers = int(random(2, 5)); var heightUnit = (bottom - top) / (layers + 1); for (var l = 0; l < layers; l++) { var y = top + random(heightUnit * l, heightUnit * (l + 1)); var spread = random(1, 3) * heightUnit; ranges.push(new MountainRange( y, spread, lerpColor(start, end, l/layers), (l + 1) / layers, time )); } this.draw = function() { ranges.forEach(function(range) { range.draw(); }); }; } function MountainRange(y, spread, c, foreground, time) { var elevation = []; var trees = []; var buildings = []; var noiseElevation = random(1, 100); var spikiness = random(40, 90); for (var x = 0; x < 150; x++) { elevation.push(map(noise(x / spikiness, noiseElevation), 0, 1, -1, 1) * spread); } var numTrees = int(random(1, map(1.0 / foreground, 1, 100, 5, 40))); for (var i = 0; i < numTrees; i++) { var location = int(random(elevation.length)); trees.push(new Tree( (location / (elevation.length - 1)) * width, y + elevation[location], foreground * 100, foreground * 80, time )); } var numBuildings = int(random(1, map(1.0 / foreground, 1, 100, 0, 10))); for (var i = 0; i < numTrees; i++) { var location = int(random(elevation.length)); buildings.push(new Building( (location / (elevation.length - 1)) * width, y + elevation[location] + spread * 0.1, foreground * 80, foreground * 120, time )); } this.draw = function() { buildings.forEach(function(building) { building.draw(); }); fill(c); beginShape(); for (var i = 0; i < elevation.length; i++) { vertex((i / (elevation.length - 1)) * width, y + elevation[i]); } vertex(width, height); vertex(0, height); endShape(); trees.forEach(function(tree) { tree.draw(); }); }; } function Sky(time) { var sky, horizon; if (time < 0.5) { sky = lerpColor("#B9F7D5", "#57C8F0", map(time, 0, 0.5, 0, 1)); horizon = lerpColor("#FCED42", "#E365ED", map(time, 0, 0.5, 0, 1)); } else { sky = lerpColor("#C282E5", "#1A285A", map(time, 0.5, 1, 0, 1)); horizon = lerpColor("#82E3E5", "#BFE1FC", map(time, 0.5, 1, 0, 1)); } this.draw = function() { setGradient(0, 0, width, height, sky, horizon); }; } var width = 720; var height = 405; var canvas = document.getElementById("landscape"); var container = document.getElementById("header"); var overlay = document.getElementById("overlay"); var buffer, buffer_canvas; var pathCleared = true; var transitioning = false; var clearBackground = function() { buffer.clearRect(0, 0, width, height); }; var random = function(low, high) { if (high === undefined) { high = low; low = 0; } return Math.random() * (high - low) + low; }; var int = function(n) { return Math.floor(n); }; var strokeWeight = function(w) { buffer.lineWidth = w; }; var stroke = function(c) { buffer.strokeStyle = c; }; var line = function(x1, y1, x2, y2) { buffer.beginPath(); buffer.moveTo(x1, y1); buffer.lineTo(x2, y2); buffer.stroke(); }; var fill = function(c) { buffer.fillStyle = c; }; var beginShape = function() { buffer.beginPath(); pathCleared = true; }; var vertex = function(x, y) { if (pathCleared) { buffer.moveTo(x, y); } else { buffer.lineTo(x, y); } pathCleared = false; }; var endShape = function() { buffer.closePath(); buffer.fill(); }; var rect = function(x, y, w, h) { buffer.fillRect(x, y, w, h); }; var ellipse = function(x, y, w, h) { buffer.beginPath(); buffer.ellipse(x, y, w/2, h/2, 0, 0, 2 * Math.PI); buffer.fill(); }; var setGradient = function(x, y, w, h, c1, c2) { var gradient = buffer.createLinearGradient(0, 0, 0, h); gradient.addColorStop(0, c1); gradient.addColorStop(1, c2); buffer.fillStyle = gradient; buffer.fillRect(x, y, w, h); }; var hexToRGB = function(hex) { var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; }; var componentToHex = function(c) { var hex = int(c).toString(16); return hex.length == 1 ? "0" + hex : hex; }; var rgbToHex = function(r, g, b) { return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); }; var lerp = function(a, b, n) { return Math.abs((b - a) * n + a); }; var lerpColor = function(beginning, end, percent) { var c1 = hexToRGB(beginning); var c2 = hexToRGB(end); return rgbToHex( lerp(c1.r, c2.r, percent), lerp(c1.g, c2.g, percent), lerp(c1.b, c2.b, percent) ); }; var map = function(v, a1, b1, a2, b2) { return (((v-a1) / (b1 - a1)) * (b2 - a2) + a2); }; var perlin = new ClassicalNoise(); var noise = function(x, y) { y = y || 0; return map(perlin.noise(x, y, 0), -1, 1, 0, 1); }; var BUILDING_TOP = 0, BUILDING_LEFT = -1, BUILDING_RIGHT = 1, BUILDING_MAX_LEVELS = 2; var BRANCH_ROOT = 0, BRANCH_LEFT = -1, BRANCH_RIGHT = 1, BRANCH_MAX_LEVELS = 3; var time, sky, mountains; var drawScene = function(offsetX, offsetY) { sky.draw(); mountains.draw(); stage.drawImage(buffer_canvas, offsetX, offsetY); }; var regenScene = function() { width = canvas.width; height = canvas.height; time = random(1); sky = new Sky(time); mountains = new Mountains(height * 0.2, height * 0.8, time); }; var redrawCanvas = function(regen) { canvas.width = container.offsetWidth; canvas.height = container.offsetHeight; if (regen) regenScene(); var scale; if (canvas.width > (width/height)*canvas.height) { scale = canvas.width/width; } else { scale = canvas.height/height; } buffer_canvas.width = width*scale; buffer_canvas.height = height*scale; buffer.scale(scale, scale); drawScene( (canvas.width - buffer_canvas.width)/2, (canvas.height - buffer_canvas.height)/2 ); }; var transition = function() { if (transitioning) return; overlay.classList.remove("transparent"); transitioning = true; setTimeout(function() { redrawCanvas(true); overlay.classList.add("transparent"); setTimeout(function() { transitioning = false; }, 700); }, 700); }; if (canvas.getContext) { stage = canvas.getContext("2d"); buffer_canvas = document.createElement("canvas"); buffer = buffer_canvas.getContext("2d"); if (CanvasRenderingContext2D.prototype.ellipse == undefined) { CanvasRenderingContext2D.prototype.ellipse = function(x, y, radiusX, radiusY, rotation, startAngle, endAngle, antiClockwise) { this.save(); this.translate(x, y); this.rotate(rotation); this.scale(radiusX, radiusY); this.arc(0, 0, 1, startAngle, endAngle, antiClockwise); this.restore(); }; } window.addEventListener("resize", throttle(function() { redrawCanvas() }, 200)); container.addEventListener("click", transition); setTimeout(function() { redrawCanvas(true); overlay.classList.add("transparent"); }, 200); } else { console.log("canvas not supported"); document.getElementById("header").classList.add("static"); } -------------------------------------------------------------------------------- /js/frontier.js: -------------------------------------------------------------------------------- 1 | function throttle(fn, threshhold, scope) { 2 | threshhold || (threshhold = 250); 3 | var last, 4 | deferTimer; 5 | return function () { 6 | var context = scope || this; 7 | 8 | var now = +new Date, 9 | args = arguments; 10 | if (last && now < last + threshhold) { 11 | // hold on to it 12 | clearTimeout(deferTimer); 13 | deferTimer = setTimeout(function () { 14 | last = now; 15 | fn.apply(context, args); 16 | }, threshhold); 17 | } else { 18 | last = now; 19 | fn.apply(context, args); 20 | } 21 | }; 22 | } 23 | 24 | 25 | 26 | var ClassicalNoise = function(r) { // Classic Perlin noise in 3D, for comparison 27 | if (r == undefined) r = Math; 28 | this.grad3 = [[1,1,0],[-1,1,0],[1,-1,0],[-1,-1,0], 29 | [1,0,1],[-1,0,1],[1,0,-1],[-1,0,-1], 30 | [0,1,1],[0,-1,1],[0,1,-1],[0,-1,-1]]; 31 | this.p = []; 32 | for (var i=0; i<256; i++) { 33 | this.p[i] = Math.floor(r.random()*256); 34 | } 35 | // To remove the need for index wrapping, double the permutation table length 36 | this.perm = []; 37 | for(var i=0; i<512; i++) { 38 | this.perm[i]=this.p[i & 255]; 39 | } 40 | }; 41 | 42 | ClassicalNoise.prototype.dot = function(g, x, y, z) { 43 | return g[0]*x + g[1]*y + g[2]*z; 44 | }; 45 | 46 | ClassicalNoise.prototype.mix = function(a, b, t) { 47 | return (1.0-t)*a + t*b; 48 | }; 49 | 50 | ClassicalNoise.prototype.fade = function(t) { 51 | return t*t*t*(t*(t*6.0-15.0)+10.0); 52 | }; 53 | 54 | // Classic Perlin noise, 3D version 55 | ClassicalNoise.prototype.noise = function(x, y, z) { 56 | // Find unit grid cell containing point 57 | var X = Math.floor(x); 58 | var Y = Math.floor(y); 59 | var Z = Math.floor(z); 60 | 61 | // Get relative xyz coordinates of point within that cell 62 | x = x - X; 63 | y = y - Y; 64 | z = z - Z; 65 | 66 | // Wrap the integer cells at 255 (smaller integer period can be introduced here) 67 | X = X & 255; 68 | Y = Y & 255; 69 | Z = Z & 255; 70 | 71 | // Calculate a set of eight hashed gradient indices 72 | var gi000 = this.perm[X+this.perm[Y+this.perm[Z]]] % 12; 73 | var gi001 = this.perm[X+this.perm[Y+this.perm[Z+1]]] % 12; 74 | var gi010 = this.perm[X+this.perm[Y+1+this.perm[Z]]] % 12; 75 | var gi011 = this.perm[X+this.perm[Y+1+this.perm[Z+1]]] % 12; 76 | var gi100 = this.perm[X+1+this.perm[Y+this.perm[Z]]] % 12; 77 | var gi101 = this.perm[X+1+this.perm[Y+this.perm[Z+1]]] % 12; 78 | var gi110 = this.perm[X+1+this.perm[Y+1+this.perm[Z]]] % 12; 79 | var gi111 = this.perm[X+1+this.perm[Y+1+this.perm[Z+1]]] % 12; 80 | 81 | // The gradients of each corner are now: 82 | // g000 = grad3[gi000]; 83 | // g001 = grad3[gi001]; 84 | // g010 = grad3[gi010]; 85 | // g011 = grad3[gi011]; 86 | // g100 = grad3[gi100]; 87 | // g101 = grad3[gi101]; 88 | // g110 = grad3[gi110]; 89 | // g111 = grad3[gi111]; 90 | // Calculate noise contributions from each of the eight corners 91 | var n000= this.dot(this.grad3[gi000], x, y, z); 92 | var n100= this.dot(this.grad3[gi100], x-1, y, z); 93 | var n010= this.dot(this.grad3[gi010], x, y-1, z); 94 | var n110= this.dot(this.grad3[gi110], x-1, y-1, z); 95 | var n001= this.dot(this.grad3[gi001], x, y, z-1); 96 | var n101= this.dot(this.grad3[gi101], x-1, y, z-1); 97 | var n011= this.dot(this.grad3[gi011], x, y-1, z-1); 98 | var n111= this.dot(this.grad3[gi111], x-1, y-1, z-1); 99 | // Compute the fade curve value for each of x, y, z 100 | var u = this.fade(x); 101 | var v = this.fade(y); 102 | var w = this.fade(z); 103 | // Interpolate along x the contributions from each of the corners 104 | var nx00 = this.mix(n000, n100, u); 105 | var nx01 = this.mix(n001, n101, u); 106 | var nx10 = this.mix(n010, n110, u); 107 | var nx11 = this.mix(n011, n111, u); 108 | // Interpolate the four results along y 109 | var nxy0 = this.mix(nx00, nx10, v); 110 | var nxy1 = this.mix(nx01, nx11, v); 111 | // Interpolate the two last results along z 112 | var nxyz = this.mix(nxy0, nxy1, w); 113 | 114 | return nxyz; 115 | }; 116 | 117 | 118 | function BuildingUnit(x, y, w, h, side, level, time) { 119 | var WINDOW_LIGHT = "#FFE76F"; 120 | var WINDOW_DARK = "#1B5E81"; 121 | var LIGHTS_ON = 0.6; 122 | var windows = 0; 123 | var windowHeight = 0; 124 | var alignment = 0; 125 | var units = []; 126 | var lights = []; 127 | var offsetLeft = random(-0.1 * w, 0.1 * w); 128 | var offsetRight = random(-0.1 * w, 0.1 * w); 129 | var offsetRoof = random(0.1 * w, 0.3 * w); 130 | var stiltLength = 0; 131 | if (level > 1) { 132 | stiltLength = random(h * 0.2, h * 0.5); 133 | } 134 | if (level <= 2) { 135 | windows = int(random(0, 5 - level)); 136 | if (time > LIGHTS_ON) { 137 | for (var i=0; i 0.5); 139 | } 140 | } 141 | windowHeight = random((h - stiltLength) * 0.1, (h - stiltLength) * 0.5); 142 | if (random(0, 1) > 0.5) { 143 | alignment = BUILDING_LEFT; 144 | } else { 145 | alignment = BUILDING_RIGHT; 146 | } 147 | for (var i = 0; i < BUILDING_MAX_LEVELS; i++) { 148 | if (random(10) >= 5) { 149 | var branchWidth = random(0.2 * w, 0.8 * w); 150 | var branchHeight = random(0.2 * h, 0.8 * h); 151 | var branchX = 0; 152 | var branchY = 0; 153 | 154 | var branchSide = int(random(-2, 2)); 155 | if (branchSide == BUILDING_TOP) { 156 | branchX = random(x - w / 2 + branchWidth / 2, x + w / 2 - branchWidth / 2); 157 | branchY = y - h; 158 | } else if (branchSide == BUILDING_LEFT) { 159 | branchX = x - w / 2 - branchWidth / 2 + w * 0.2; 160 | branchY = random(y - 0.2 * (h - stiltLength) - stiltLength, y - 0.8 * (h - stiltLength) - stiltLength); 161 | } else if (branchSide == BUILDING_RIGHT) { 162 | branchX = x + w / 2 + branchWidth / 2 - w * 0.2; 163 | branchY = random(y - 0.2 * (h - stiltLength) - stiltLength, y - 0.8 * (h - stiltLength) - stiltLength); 164 | } 165 | 166 | units.push(new BuildingUnit(branchX, branchY, branchWidth, branchHeight, branchSide, level + 1, time)); 167 | } 168 | } 169 | } 170 | 171 | this.draw = function() { 172 | units.forEach(function(b) { 173 | b.draw(); 174 | }); 175 | 176 | var stiltColor, roofColor, houseColor, windowDark; 177 | if (time < 0.5) { 178 | stiltColor = lerpColor("#776F7E", "#4B433C", map(time, 0, 0.5, 0, 1)); 179 | houseColor = lerpColor("#AD9592", "#BFABA9", map(time, 0, 0.5, 0, 1)); 180 | roofColor = lerpColor("#776F7E", "#4B433C", map(time, 0, 0.5, 0, 1)); 181 | windowDark = lerpColor("#1B5E81", "#383E5A", map(time, 0, 0.5, 0, 1)); 182 | } else { 183 | stiltColor = lerpColor("#352B48", "#1D263E", map(time, 0.5, 1, 0, 1)); 184 | houseColor = lerpColor("#C3B7D1", "#8788A7", map(time, 0, 0.5, 0, 1)); 185 | roofColor = lerpColor("#776F7E", "#4B433C", map(time, 0, 0.5, 0, 1)); 186 | windowDark = lerpColor("#644C6F", "#3D435F", map(time, 0, 0.5, 0, 1)); 187 | } 188 | 189 | if (stiltLength > 0) { 190 | strokeWeight(w / 20); 191 | stroke(stiltColor); 192 | if (side == BUILDING_TOP) { 193 | line(x - w * 0.3, y, x - w * 0.3, y - stiltLength); 194 | line(x + w * 0.3, y, x + w * 0.3, y - stiltLength); 195 | } else if (side == BUILDING_LEFT) { 196 | line(x - w * 0.4, y - stiltLength, x + w / 2, y); 197 | } else if (side == BUILDING_RIGHT) { 198 | line(x + w * 0.4, y - stiltLength, x - w / 2, y); 199 | } 200 | } 201 | 202 | //Main building 203 | fill(level % 2 == 1 ? houseColor : lerpColor(houseColor, "#1C1443", 0.2)); 204 | beginShape(); 205 | vertex(x - w / 2, y - stiltLength); 206 | vertex(x + w / 2, y - stiltLength); 207 | vertex(x + w / 2 + offsetRight, y - h * 0.85); 208 | vertex(x - w / 2 + offsetLeft, y - h * 0.85); 209 | endShape(); 210 | 211 | //Windows 212 | for (var i = 0; i < windows; i++) { 213 | if (time > LIGHTS_ON && lights[i]) { 214 | fill(WINDOW_LIGHT); 215 | } else { 216 | fill(windowDark); 217 | } 218 | if (alignment == BUILDING_LEFT) { 219 | rect( 220 | x + offsetLeft - w / 2 + w * 0.05 + w * 0.25 * i, 221 | y - h * 0.8, 222 | w * 0.2, 223 | windowHeight 224 | ); 225 | } else if (alignment == BUILDING_RIGHT) { 226 | rect( 227 | x + offsetRight + w / 2 - w * 0.05 - w * 0.25 * (i + 1), 228 | y - h * 0.8, 229 | w * 0.2, 230 | windowHeight 231 | ); 232 | } 233 | } 234 | 235 | //Roof 236 | fill("#685D71"); 237 | beginShape(); 238 | vertex(x - w / 2 + offsetLeft - offsetRoof, y - h * 0.85); 239 | vertex(x + w / 2 + offsetRight + offsetRoof, y - h * 0.85); 240 | vertex(x + w / 2 + offsetRight, y - h); 241 | vertex(x - w / 2 + offsetLeft, y - h); 242 | endShape(); 243 | }; 244 | } 245 | 246 | function Building(x, y, w, h, time) { 247 | var units = []; 248 | var numUnits = int(random(1, 2)); 249 | for (var i = 0; i < numUnits; i++) { 250 | var unitWidth = random(0.5, 1.5) * (w / numUnits); 251 | units.push(new BuildingUnit( 252 | x - w / 2 + i * (w / numUnits) + unitWidth / 2, 253 | y, 254 | unitWidth, 255 | random(h * 0.2, h), 256 | BUILDING_TOP, 257 | 1, 258 | time 259 | )); 260 | } 261 | 262 | this.draw = function() { 263 | units.forEach(function(b) { 264 | b.draw(); 265 | }); 266 | }; 267 | } 268 | 269 | 270 | function Tree(x, y, w, h, time) { 271 | var root = new Branch(x, y, w, h, BRANCH_ROOT, 1, time); 272 | this.draw = function() { 273 | root.drawLeaves(); 274 | root.drawBranches(); 275 | }; 276 | } 277 | 278 | function Branch(x, y, w, h, side, level, time) { 279 | var branches = []; 280 | var segmentX = 0; 281 | var segmentY = 0; 282 | var leafWidth = 0; 283 | var leafHeight = 0; 284 | 285 | if (side == BRANCH_ROOT) { 286 | segmentX = x + (random(w * 0.6) - w * 0.3); 287 | segmentY = y - h * 0.75 + (random(h * 0.3) - h * 0.15); 288 | } else if (side == BRANCH_LEFT) { 289 | segmentX = x - random(w * 0.3); 290 | segmentY = y - h * 0.75 + (random(h * 0.3) - h * 0.15); 291 | } else if (side == BRANCH_RIGHT) { 292 | segmentX = x + random(w * 0.3); 293 | segmentY = y - h * 0.75 + (random(h * 0.3) - h * 0.15); 294 | } 295 | 296 | leafWidth = random(w * 0.6) + w * 0.3; 297 | leafHeight = random(h * 0.6) + h * 0.3; 298 | 299 | if (level < 3) { 300 | for (var i = 0; i < BRANCH_MAX_LEVELS; i++) { 301 | if (random(10) >= 7) { 302 | var branchY = random(segmentY, y); 303 | var branchX = x + ((segmentX - x) / (segmentY - y)) * (branchY - y); 304 | var branchDir = random(10) > 5 ? BRANCH_LEFT : BRANCH_RIGHT; 305 | branches.push(new Branch(branchX, branchY, w * 0.6, h * 0.5, branchDir, level + 1, time)); 306 | } 307 | } 308 | } 309 | 310 | this.drawLeaves = function() { 311 | var leafColor; 312 | if (time < 0.5) { 313 | leafColor = lerpColor("#FFD1D1", "#F2C975", map(time, 0, 0.5, 0, 1)); 314 | } else { 315 | leafColor = lerpColor("#D5BCE0", "#56627C", map(time, 0.5, 1, 0, 1)); 316 | } 317 | fill(leafColor); 318 | ellipse(segmentX, segmentY, leafWidth, leafHeight); 319 | 320 | branches.forEach(function(b) { 321 | b.drawLeaves(); 322 | }); 323 | }; 324 | 325 | this.drawBranches = function() { 326 | var branchColor; 327 | if (time < 0.5) { 328 | branchColor = lerpColor("#79504E", "#866A59", map(time, 0, 0.5, 0, 1)); 329 | } else { 330 | branchColor = lerpColor("#635774", "#635774", map(time, 0.5, 1, 0, 1)); 331 | } 332 | 333 | strokeWeight((y - segmentY) / 20); 334 | stroke(branchColor); 335 | line(x, y, segmentX, segmentY); 336 | 337 | branches.forEach(function(b) { 338 | b.drawBranches(); 339 | }); 340 | }; 341 | } 342 | 343 | 344 | function Mountains(top, bottom, time) { //#D36F6F, #AD1515 345 | var ranges = []; 346 | var start, end; 347 | if (time < 0.5) { 348 | start = lerpColor("#D36F6F", "#F2C178", map(time, 0, 0.5, 0, 1)); 349 | end = lerpColor("#AD1515", "#E85B57", map(time, 0, 0.5, 0, 1)); 350 | } else { 351 | start = lerpColor("#654183", "#2D3A5F", map(time, 0.5, 1, 0, 1)); 352 | end = lerpColor("#B198C6", "#6172A5", map(time, 0.5, 1, 0, 1)); 353 | } 354 | 355 | var layers = int(random(2, 5)); 356 | var heightUnit = (bottom - top) / (layers + 1); 357 | 358 | for (var l = 0; l < layers; l++) { 359 | var y = top + random(heightUnit * l, heightUnit * (l + 1)); 360 | var spread = random(1, 3) * heightUnit; 361 | ranges.push(new MountainRange( 362 | y, 363 | spread, 364 | lerpColor(start, end, l/layers), (l + 1) / layers, 365 | time 366 | )); 367 | } 368 | 369 | this.draw = function() { 370 | ranges.forEach(function(range) { 371 | range.draw(); 372 | }); 373 | }; 374 | } 375 | 376 | function MountainRange(y, spread, c, foreground, time) { 377 | var elevation = []; 378 | var trees = []; 379 | var buildings = []; 380 | 381 | var noiseElevation = random(1, 100); 382 | var spikiness = random(40, 90); 383 | 384 | for (var x = 0; x < 150; x++) { 385 | elevation.push(map(noise(x / spikiness, noiseElevation), 0, 1, -1, 1) * spread); 386 | } 387 | 388 | var numTrees = int(random(1, map(1.0 / foreground, 1, 100, 5, 40))); 389 | for (var i = 0; i < numTrees; i++) { 390 | var location = int(random(elevation.length)); 391 | trees.push(new Tree( 392 | (location / (elevation.length - 1)) * width, 393 | y + elevation[location], 394 | foreground * 100, 395 | foreground * 80, 396 | time 397 | )); 398 | } 399 | 400 | var numBuildings = int(random(1, map(1.0 / foreground, 1, 100, 0, 10))); 401 | for (var i = 0; i < numTrees; i++) { 402 | var location = int(random(elevation.length)); 403 | buildings.push(new Building( 404 | (location / (elevation.length - 1)) * width, 405 | y + elevation[location] + spread * 0.1, 406 | foreground * 80, 407 | foreground * 120, 408 | time 409 | )); 410 | } 411 | 412 | this.draw = function() { 413 | buildings.forEach(function(building) { 414 | building.draw(); 415 | }); 416 | 417 | fill(c); 418 | beginShape(); 419 | for (var i = 0; i < elevation.length; i++) { 420 | vertex((i / (elevation.length - 1)) * width, y + elevation[i]); 421 | } 422 | vertex(width, height); 423 | vertex(0, height); 424 | endShape(); 425 | 426 | trees.forEach(function(tree) { 427 | tree.draw(); 428 | }); 429 | }; 430 | } 431 | 432 | 433 | function Sky(time) { 434 | var sky, horizon; 435 | if (time < 0.5) { 436 | sky = lerpColor("#B9F7D5", "#57C8F0", map(time, 0, 0.5, 0, 1)); 437 | horizon = lerpColor("#FCED42", "#E365ED", map(time, 0, 0.5, 0, 1)); 438 | } else { 439 | sky = lerpColor("#C282E5", "#1A285A", map(time, 0.5, 1, 0, 1)); 440 | horizon = lerpColor("#82E3E5", "#BFE1FC", map(time, 0.5, 1, 0, 1)); 441 | } 442 | 443 | this.draw = function() { 444 | setGradient(0, 0, width, height, sky, horizon); 445 | }; 446 | } 447 | 448 | 449 | var width = 720; 450 | var height = 405; 451 | var canvas = document.getElementById("landscape"); 452 | var container = document.getElementById("header"); 453 | var overlay = document.getElementById("overlay"); 454 | var buffer, buffer_canvas; 455 | var pathCleared = true; 456 | var transitioning = false; 457 | 458 | var clearBackground = function() { 459 | buffer.clearRect(0, 0, width, height); 460 | }; 461 | 462 | var random = function(low, high) { 463 | if (high === undefined) { 464 | high = low; 465 | low = 0; 466 | } 467 | return Math.random() * (high - low) + low; 468 | }; 469 | 470 | var int = function(n) { 471 | return Math.floor(n); 472 | }; 473 | 474 | var strokeWeight = function(w) { 475 | buffer.lineWidth = w; 476 | }; 477 | 478 | var stroke = function(c) { 479 | buffer.strokeStyle = c; 480 | }; 481 | 482 | var line = function(x1, y1, x2, y2) { 483 | buffer.beginPath(); 484 | buffer.moveTo(x1, y1); 485 | buffer.lineTo(x2, y2); 486 | buffer.stroke(); 487 | }; 488 | 489 | var fill = function(c) { 490 | buffer.fillStyle = c; 491 | }; 492 | 493 | var beginShape = function() { 494 | buffer.beginPath(); 495 | pathCleared = true; 496 | }; 497 | 498 | var vertex = function(x, y) { 499 | if (pathCleared) { 500 | buffer.moveTo(x, y); 501 | } else { 502 | buffer.lineTo(x, y); 503 | } 504 | pathCleared = false; 505 | }; 506 | 507 | var endShape = function() { 508 | buffer.closePath(); 509 | buffer.fill(); 510 | }; 511 | 512 | var rect = function(x, y, w, h) { 513 | buffer.fillRect(x, y, w, h); 514 | }; 515 | 516 | var ellipse = function(x, y, w, h) { 517 | buffer.beginPath(); 518 | buffer.ellipse(x, y, w/2, h/2, 0, 0, 2 * Math.PI); 519 | buffer.fill(); 520 | }; 521 | 522 | var setGradient = function(x, y, w, h, c1, c2) { 523 | var gradient = buffer.createLinearGradient(0, 0, 0, h); 524 | gradient.addColorStop(0, c1); 525 | gradient.addColorStop(1, c2); 526 | buffer.fillStyle = gradient; 527 | buffer.fillRect(x, y, w, h); 528 | }; 529 | 530 | var hexToRGB = function(hex) { 531 | var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 532 | return result ? { 533 | r: parseInt(result[1], 16), 534 | g: parseInt(result[2], 16), 535 | b: parseInt(result[3], 16) 536 | } : null; 537 | }; 538 | 539 | var componentToHex = function(c) { 540 | var hex = int(c).toString(16); 541 | return hex.length == 1 ? "0" + hex : hex; 542 | }; 543 | 544 | var rgbToHex = function(r, g, b) { 545 | return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); 546 | }; 547 | 548 | var lerp = function(a, b, n) { 549 | return Math.abs((b - a) * n + a); 550 | }; 551 | 552 | var lerpColor = function(beginning, end, percent) { 553 | var c1 = hexToRGB(beginning); 554 | var c2 = hexToRGB(end); 555 | return rgbToHex( 556 | lerp(c1.r, c2.r, percent), 557 | lerp(c1.g, c2.g, percent), 558 | lerp(c1.b, c2.b, percent) 559 | ); 560 | }; 561 | 562 | var map = function(v, a1, b1, a2, b2) { 563 | return (((v-a1) / (b1 - a1)) * (b2 - a2) + a2); 564 | }; 565 | 566 | var perlin = new ClassicalNoise(); 567 | var noise = function(x, y) { 568 | y = y || 0; 569 | return map(perlin.noise(x, y, 0), -1, 1, 0, 1); 570 | }; 571 | 572 | var BUILDING_TOP = 0, 573 | BUILDING_LEFT = -1, 574 | BUILDING_RIGHT = 1, 575 | BUILDING_MAX_LEVELS = 2; 576 | 577 | var BRANCH_ROOT = 0, 578 | BRANCH_LEFT = -1, 579 | BRANCH_RIGHT = 1, 580 | BRANCH_MAX_LEVELS = 3; 581 | 582 | var time, sky, mountains; 583 | 584 | var drawScene = function(offsetX, offsetY) { 585 | sky.draw(); 586 | mountains.draw(); 587 | stage.drawImage(buffer_canvas, offsetX, offsetY); 588 | }; 589 | 590 | var regenScene = function() { 591 | width = canvas.width; 592 | height = canvas.height; 593 | time = random(1); 594 | sky = new Sky(time); 595 | mountains = new Mountains(height * 0.2, height * 0.8, time); 596 | }; 597 | 598 | var redrawCanvas = function(regen) { 599 | canvas.width = container.offsetWidth; 600 | canvas.height = container.offsetHeight; 601 | if (regen) regenScene(); 602 | var scale; 603 | if (canvas.width > (width/height)*canvas.height) { 604 | scale = canvas.width/width; 605 | } else { 606 | scale = canvas.height/height; 607 | } 608 | 609 | buffer_canvas.width = width*scale; 610 | buffer_canvas.height = height*scale; 611 | buffer.scale(scale, scale); 612 | drawScene( 613 | (canvas.width - buffer_canvas.width)/2, 614 | (canvas.height - buffer_canvas.height)/2 615 | ); 616 | }; 617 | 618 | var transition = function() { 619 | if (transitioning) return; 620 | overlay.classList.remove("transparent"); 621 | transitioning = true; 622 | setTimeout(function() { 623 | redrawCanvas(true); 624 | overlay.classList.add("transparent"); 625 | setTimeout(function() { 626 | transitioning = false; 627 | }, 700); 628 | }, 700); 629 | }; 630 | 631 | if (canvas.getContext) { 632 | stage = canvas.getContext("2d"); 633 | buffer_canvas = document.createElement("canvas"); 634 | buffer = buffer_canvas.getContext("2d"); 635 | if (CanvasRenderingContext2D.prototype.ellipse == undefined) { 636 | CanvasRenderingContext2D.prototype.ellipse = function(x, y, radiusX, radiusY, rotation, startAngle, endAngle, antiClockwise) { 637 | this.save(); 638 | this.translate(x, y); 639 | this.rotate(rotation); 640 | this.scale(radiusX, radiusY); 641 | this.arc(0, 0, 1, startAngle, endAngle, antiClockwise); 642 | this.restore(); 643 | }; 644 | } 645 | window.addEventListener("resize", throttle(function() { redrawCanvas() }, 200)); 646 | container.addEventListener("click", transition); 647 | setTimeout(function() { 648 | redrawCanvas(true); 649 | overlay.classList.add("transparent"); 650 | }, 200); 651 | } else { 652 | console.log("canvas not supported"); 653 | document.getElementById("header").classList.add("static"); 654 | } 655 | 656 | 657 | --------------------------------------------------------------------------------