Skip to content

Instantly share code, notes, and snippets.

@Michael-Kelly-Digital-Design
Forked from dribnet/.block
Last active November 2, 2017 06:06
Show Gist options
  • Save Michael-Kelly-Digital-Design/8de7e160aa265cb84cc99e1c726661f3 to your computer and use it in GitHub Desktop.
Save Michael-Kelly-Digital-Design/8de7e160aa265cb84cc99e1c726661f3 to your computer and use it in GitHub Desktop.
17.2.MDDN342 PS4
license: mit
height: 720

Hex Map Generator

An overworld terrain map generator for tabletop games. The map is aimed at people who want to create a homebrew Dugeons & Dragons campaign - they can scroll through the map a until they find a set of continents they like and use them as a basis for their lore. While this program is designed for role playing tabletop games, the hexagonal grid system means that these maps would also be suited for territorial strategy games like Risk, or Setlers of Catan.

The map is made tessellated hexagonal tiles. At deeper zoom levels, the map draws glyphs on the map at key landmarks. The tiles each have 3 different perlin noise values that determine what terrain type they are, the biome they are in, and which glyphs are drawn.

Biomes

The map is split into 4 biomes:

- Plains: Lush, grassy plains surrounding thick forests.

- Mountains: Colder, rockier, and more arid than the forests.

- Desert: Bone dry & covered in sand.

- Islands: Smaller, sandy landmasses dotted with rocks and vegetation.

Each biome has its own set of terrain types & a corresponding color pallette. These are defined by arrays in "/biomes.js" these arrays pair noise value thresholds (between 0 and 1) with color values and names of terrain types. The sketch checks the "bioNoise" of a tile and picks a corresponding biome array from that value. After the biome is determined, the sketch makes another noise value from the x,y position of the tile. This value is used to determine the terrain type of the tile - for example, in most biomes, any tile with a value below 0.5 is a sea tile. Each terrain type has a single set color, however , this color can vary slightly depending on a tile's noise value. Depending on how close to the threshold for the next terrain type in the order, the tile's color will be a blend of the color values for both types.

Zoom Scaling

As the sketch zooms, the viewport will show less tiles, and more glyphs. At it's most zoomed out, the map will render larger hex tiles with less frequency. This abstracts the composition of the map into a simplified, chunky monet painting. Scaling improves performance so the user can actively explore their worldsape without significant loading delays. Tile scaling only happens for zoom levels 0 & 1, at higher zoom levels, the sketch uses the same "native" size for tiles.

At & after zoom level 2 (starting zoom level), the tiles will stop changing size. The scaling that happens inherently when zooming will still occur but tiles will remain a constant size in the world space. This zoom level is where the map starts to focus on glyphs - symbols marking significant points. As a general rule, zooming in further will reveal more glyphs; the exact rules to this pattern is different for every glyphs though. For example - forest tiles will draw multiple tree glyphs per tile at high zoom levels, giving the appearance of a thick forest as opposed to a forest marker on a map. The bone glyphs on the other hand, are lonely anomalies in their sparse domain and do not increase in regularity at all with increased zoom.

Glyphs

Each biome has different landmarks with unique corresponding glyphs. The Plains biome is home to forests. As zoom increases, these forests become more dense. The Mountains biome has hills, mountains & snowy peaks. The snowy peaks are rare and signify very tall moutnain ranges. The Desert's glyphs are scattered widely and not clustered as in the mountains and plains. Here you will see sun-bleached skeletons in the harsh, dryer areas. The footsteps of living nomads can be found in the more forgiving parts of the desert, especially around oases. There are no glyphs for the Island biomes, as I feel the rock & vegetation tiles are enough to keep the space visually interesting.

Glyphs You Will See:

  • Plains biome

    • Forest
      • clustered in "forest" and "deep_forest" tiles.
      • drawn with randomised x/y offset for a hand-drawn feel.
  • Mountain biome

    • Hills
      • drawn over "cliff" terrain tiles.
    • Mountain
      • drawn over "mountain" tiles. -Snowy Mountain
      • drawn on "mountain" tiles with high values.
  • Desert biome

    • Bones
      • scattered widely across dry sand tiles.
    • Footsteps
      • randomly ditributed in inland tiles. Clusters in tiles with higher noise values.
  • Islands biome -none

  • Ocean (all biomes)

    • Waves

Scatter

The third type of noise in the sketch is scatter noise. This only comes into play when glyphs are being drawn. Scatter noise is drawn from a very volatile noise map and is basically a random number generator with an x & y seed. Scatternoise is used to make the positioning of the glyphs feel more hand-drawn. Drawing an identical glyph on each tile of a given type makes the map look rigidly formulated; scatter noise alleviates this by reducing the number of glyphs in an area and randomising their positioning.

I used this article extensively for information on hex grids. I cannot reccomend it enough https://www.redblobgames.com/grids/hexagons/

function getBiome(val) {
var biome;
if (val > 0.0) {
biome = biome_islands;
}
if (val > 0.375) {
biome = biome_plains;
}
if (val > 0.5) {
biome = biome_mountains;
}
if (val > 0.625) {
biome = biome_desert;
}
//biome = biome_desert;
return biome;
}
function bioNoise(p5, x, y) {
// p5.noiseDetail(10,0.5);
var n =( 1 - p5.noise(x * noiseScale/11 + 10000, y * noiseScale/11 + 10000, 8));
// if(n<0.4){console.log("island")}
return n;
}
var biome_plains = [ // lush, grassy plains surrounding deep forests
[0, "#84afcc", "deepsea"],
[0.53, "#bed6e5", "shallows"],
[0.54, "#f2efc4", "beach"],
[0.59, "#b8d8b3", "grass"],
[0.7, "#d2edc7", "plains"],
[0.76, "#82af7c", "forest"],
[0.78, "#689363", "deep_forest"],
[0.82, "#689363", "deep_forest"],
[0.91, "#3f663c", "deep_forest"],
[1, "#2d512a", "crags"]
];
var biome_mountains = [ // drier & colder than the forests,
[0, "#84afcc", "deepsea"],
[0.52, "#bed6e5", "shallows"],
[0.54, "#f7f7d7", "dunes"],
[0.58, "#b8d8b3", "grass"],
[0.65, "#cbe0be", "plains"],
[0.675, "#d8d6c3", "cliffs"],
[0.69, "#8db57c", "forest"],
[0.72, "#d8d6c3", "cliffs"],
[0.73, "#d8d6c3", "cliffs"],
[0.77, "#b1abb2", "mountain"],
[0.885, "#fafafd", "mountain"],
[1, "#ffffff", "end"]
];
var biome_desert = [
[0, "#84afcc", "deepsea"],
[0.515, "#bed6e5", "shallows"],
[0.54, "#fcf3cf", "dry_beach"],
[0.585, "#f4f4de", "dry_desert"],
[0.73, "#f9eec0", "desert"],
[0.8, "#f7e1c0", "red_desert"],
[0.845, "#f9eec0", "desert"],
[0.85, "#d4e0ba", "scrub"],
[0.85, "#d4e0ba", "scrub"],
[0.855, "#f9eec0", "desert"],
[0.865, "#bed6e5", "oasis"],
[0.95, "#bed6e5", "oasis"],
[1, "#bcedf2", "oasis"]
];
var biome_islands = [
[0, "#84afcc", "deepsea"],
[0.525, "#bed6e5", "shallows"],
[0.655, "#bcedf2", "tropical_fords"],
[0.683, "#ffffed", "beach"],
[0.684, "#ede3c2", "rocks"],
[0.685, "#e1f4be", "vegetation"],
[0.7, "#ffffed", "beach"],
[0.7675, "#fcf3cf", "bright_sand"],
[0.835, "#e1f4be", "vegetation"],
[0.8675, "#bcedf2", "tropical_fords"],
[1, "#bed6e5", "shallows"]
];
function getHexColor(p5, x, y) {
var curHexNoise = noiseVal(p5, x, y);
var biome = getBiome(bioNoise(p5, x, y));
var col1, col2;
var arraypos;
for (arraypos = 0; arraypos < biome.length; arraypos++) {
if (curHexNoise > biome[arraypos][0] && curHexNoise < biome[arraypos + 1][0]) {
col1 = p5.color(biome[arraypos][1]);
col2 = p5.color(biome[arraypos + 1][1]);
break;
}
}
var col = p5.lerpColor(col1, col2, p5.map(curHexNoise, biome[arraypos][0], biome[arraypos + 1][0], 0, 1));
return col;
}
function getHexState(p5, x, y) {
var noise = noiseVal(p5, x, y);
var biome = getBiome(bioNoise(p5, x, y));
var curHexNoise = noiseVal(p5, x, y);
var arraypos;
for (arraypos = 0; arraypos < biome.length; arraypos++) {
if (curHexNoise > biome[arraypos][0] && curHexNoise < biome[arraypos + 1][0]) {
return biome[arraypos][2];
break;
}
}
}
/*
* This is the funciton to implement to make your own abstract design.
*
* arguments:
* p5: the p5.js object - all draw commands should be prefixed with this object
* x1, x2, y1, y2: draw the pattern contained in the rectangle x1,y1 to x2, y2
* z: use this as the noise z offset (can be shifted)
* zoom: current zoom level (starts at 0), useful to decide how much detail to draw
*
* The destination drawing should be in the square 0, 0, 255, 255.
*/
function drawGrid(p5, x1, x2, y1, y2, z, zoom) {
// debug - show border
p5.noFill();
p5.stroke(255, 0, 0)
p5.rect(0, 0, 255, 255);
}
//____________[DESERT GLYPHS]____________\\
function drawSkelly(p5,xpos, ypos, rad, hex_color) {// sun-bleached, skeletal remains of a large animal buried in the sand
var w = rad*2;
var line = rad/50;
var strokeCol = p5.lerpColor(hex_color,p5.color("#dbc357"),0.5);
p5.push();
p5.fill(strokeCol);
p5.strokeWeight(line);
p5.stroke(hex_color);
p5.stroke(strokeCol);
var ypos = ypos - rad / 2
var xpos = xpos - rad / 8
// base position, tip position, and thickness of each bone
var b1 = [xpos, ypos + w / 2];
var b1_tip = [xpos - w * 7 / 32, ypos + w * 3 / 32];
var b1_mid = midpoint(b1, b1_tip);
var b1_thick = rad * 0.35;
var b1_tip_thick = b1_thick / 6;
var b1_mid_thick = p5.lerp(b1_thick, b1_tip_thick, 0.5);
var b2 = [xpos + w * 1 / 4, ypos + w * 10 / 32];
var b2_tip = [xpos + w * 1 / 32, ypos + w * 1 / 32];
var b2_mid = midpoint(b2, b2_tip);
var b2_thick = rad * 0.25;
var b2_tip_thick = b2_thick / 6;
var b2_mid_thick = p5.lerp(b2_thick, b2_tip_thick, 0.5);
var b3 = [xpos + w * 13 / 32, ypos + w * 6 / 32];
var b3_tip = [xpos + w * 8.5 / 32, ypos - 5 / 32];
var b3_mid = midpoint(b3, b3_tip);
var b3_thick = rad * 0.15;
var b3_tip_thick = b3_thick / 6;
var b3_mid_thick = p5.lerp(b3_thick, b3_tip_thick, 0.5);
//shadows
p5. ellipse( b1[0] - b1_thick*0.4 ,b1[1] - b1_thick * 0, b1_thick * 1.65 ,b1_thick * 1.2 );
p5. ellipse( b2[0] - b2_thick*0.4 ,b2[1] - b2_thick * 0, b2_thick * 1.65 ,b2_thick * 1.2 );
p5. ellipse( b3[0] - b3_thick*0.4 ,b3[1] - b3_thick * 0, b3_thick * 1.65 ,b3_thick * 1.2 );
p5.fill("white")
p5.beginShape(); //BONE 1
p5.curveVertex(b1_tip[0] - b1_tip_thick / 2, b1_tip[1]); //L tip
p5.curveVertex(b1_mid[0] + b1_mid_thick * 1 / 8, b1_mid[1] + b1_tip_thick / 2); //mid L
p5.curveVertex(b1[0] - b1_thick / 2, b1[1] + b1_thick / 4); //left
p5.curveVertex(b1[0] + b1_thick / 8, b1[1] + b1_thick *0.55); //bottom
p5.curveVertex(b1[0] + b1_thick / 2, b1[1]); //right
p5.curveVertex(b1_mid[0] + b1_mid_thick, b1_mid[1] - b1_tip_thick); // mid R
p5.curveVertex(b1_tip[0] + b1_tip_thick, b1_tip[1] - b1_tip_thick / 4); //R tip
p5.curveVertex(b1_tip[0] - b1_tip_thick / 2, b1_tip[1]); //L tip
p5.curveVertex(b1_mid[0] + b1_mid_thick * 1 / 8, b1_mid[1] + b1_tip_thick / 2); //mid L
p5.curveVertex(b1[0] - b1_thick / 2, b1[1] + b1_thick / 4); //left
p5.endShape();
p5.beginShape(); //BONE 2
p5.curveVertex(b2_tip[0] - b2_tip_thick / 2, b2_tip[1]); //L tip
p5.curveVertex(b2_mid[0] + b2_mid_thick * 1 / 8, b2_mid[1] + b2_tip_thick / 2); //mid L
p5.curveVertex(b2[0] - b2_thick / 2, b2[1] + b2_thick / 4); //left
p5.curveVertex(b2[0] + b2_thick / 8, b2[1] + b2_thick *0.55); //bottom
p5.curveVertex(b2[0] + b2_thick / 2, b2[1]); //right
p5.curveVertex(b2_mid[0] + b2_mid_thick, b2_mid[1] - b2_tip_thick); // mid R
p5.curveVertex(b2_tip[0] + b2_tip_thick, b2_tip[1] - b2_tip_thick / 4); //R tip
p5.curveVertex(b2_tip[0] - b2_tip_thick / 2, b2_tip[1]); //L tip
p5.curveVertex(b2_mid[0] + b2_mid_thick * 1 / 8, b2_mid[1] + b2_tip_thick / 2); //mid L
p5.curveVertex(b2[0] - b2_thick / 2, b2[1] + b2_thick / 4); //left
p5.endShape();
p5.strokeWeight(line*5/6); //decrease line weight as bones decrease in size
p5.beginShape(); //BONE 3
p5.curveVertex(b3_tip[0] - b3_tip_thick / 2, b3_tip[1]); //L tip
p5.curveVertex(b3_mid[0] + b3_mid_thick * 1 / 8, b3_mid[1] + b3_tip_thick / 2); //mid L
p5.curveVertex(b3[0] - b3_thick / 2, b3[1] + b3_thick / 4); //left
p5.curveVertex(b3[0] + b3_thick / 8, b3[1] + b3_thick * 0.55); //bottom
p5.curveVertex(b3[0] + b3_thick / 2, b3[1]); //right
p5.curveVertex(b3_mid[0] + b3_mid_thick, b3_mid[1] - b3_tip_thick); // mid R
p5.curveVertex(b3_tip[0] + b3_tip_thick, b3_tip[1] - b3_tip_thick / 4); //R tip
p5.curveVertex(b3_tip[0] - b3_tip_thick / 2, b3_tip[1]); //L tip
p5.curveVertex(b3_mid[0] + b3_mid_thick * 1 / 8, b3_mid[1] + b3_tip_thick / 2); //mid L
p5.curveVertex(b3[0] - b3_thick / 2, b3[1] + b3_thick / 4); //left
p5.endShape();
p5.strokeWeight(line*4/6);
p5.pop();
}
function drawSteps(p5,xpos, ypos, rad, hex_color,mode){ //footsteps drawn in one of 3 orientations
var w = rad*2;
var col = p5.color("#dbc357")
var strokeCol = p5.lerpColor(hex_color,col,0.65);
var stepSize = w/10;
var p1_index = Math.abs(mode+3)%5;
var p1 =[0,0+w/2];
var p2 =[0,0-w/2];
p5.push();
p5.stroke(strokeCol);
p5.fill(strokeCol);
p5.translate(xpos,ypos);
p5.rotate(p5.radians(mode*63.435));
for(var y = p1[1]-stepSize*7/3;y>p2[1]+stepSize*2;y-=stepSize){
stepno = Math.floor((y-p2[1])/stepSize);
if(stepno%2 == 0){
p5.ellipse(p1[0]-stepSize/3,y,w/25,w/16);
}else{
p5.ellipse(p1[0]+stepSize/3,y,w/25,w/16);
}
}
p5.pop();
}
//____________[MOUTAIN GLYPHS]____________\\
function drawHill(p5,xpos,ypos,rad,hex_color){ //rolling hill: overlaps tile to the right
var w = rad;
var line = rad/13
p5.push();
p5.fill(hex_color);
p5.stroke(hex_color);
p5.strokeWeight(1);
p5.beginShape();
p5.vertex(xpos+w*7/4,ypos+w*0.2);
p5.vertex(xpos-w*0/4,ypos+w*1-line/2);
p5.vertex(xpos-w*4/4+line/2,ypos+w*0.5);
p5.endShape(p5.CLOSE);
p5.stroke("white");
p5.strokeWeight(line);
p5.beginShape();
p5.curveVertex(xpos-w*5/4,ypos+w*0.5);
//befor midpoint
p5.curveVertex(xpos-w*4/4,ypos+w*0.5);
p5.curveVertex(xpos-w*3/4,ypos+w*0.2);
p5.curveVertex(xpos-w*2/4,ypos+w*0.2);
p5.curveVertex(xpos-w*1/4,ypos+w*0.1);
//center of hex
p5.curveVertex(xpos-w*0/4,ypos-w*0.1);
//past midpoint
p5.curveVertex(xpos+w*1/4,ypos+w*0.1);
p5.curveVertex(xpos+w*2/4,ypos+w*0.05);
p5.curveVertex(xpos+w*3/4,ypos-w*0.35);
p5.curveVertex(xpos+w*4/4,ypos-w*0.5);
//curve outside hex
p5.curveVertex(xpos+w*5/4,ypos-w*0.35);
p5.curveVertex(xpos+w*6/4,ypos+w*0.05);
p5.curveVertex(xpos+w*7/4,ypos+w*0.2);
//finalise curve
p5.curveVertex(xpos+w*7/4,ypos+w*0.2);
p5.endShape();
p5.pop();
}
function drawMountain(p5,xpos,ypos,rad,hex_color){ //moutain outline with sharp peaks;
var w = rad;
var line = rad/9
p5.noFill();
p5.push();
//p5.fill(hex_color);
p5.stroke("white");
p5.strokeWeight(line);
p5.beginShape(); //Outline
p5.vertex(xpos-w*4/4,ypos+w*0.38);
p5.vertex(xpos-w*3/4,ypos+w*0.15);
p5.vertex(xpos-w*5/8,ypos+w*0.4);
p5.vertex(xpos-w*1/4,ypos-w*0.35);
p5.vertex(xpos-w*1/8,ypos-w*0.2);
p5.vertex(xpos+w*1/8,ypos-w*1);
p5.vertex(xpos+w*2/4,ypos+w*0.05);
p5.vertex(xpos+w*5/8,ypos-w*0.3);
p5.vertex(xpos+w*4/4,ypos+w*0.38);
p5.endShape();
p5.pop();
}
function drawSnowyMountain(p5,xpos,ypos,rad,hex_color){ //at the tippy top of its domains
var w = rad;
var line = rad/7
p5.noFill();
p5.push();
p5.fill("white");
p5.stroke("white");
p5.strokeWeight(1);
p5.beginShape(); //snow 1
p5.vertex(xpos-w*1/8+line,ypos-w*0.2-line*2);
p5.vertex(xpos+w*1/8,ypos-w*1+line/2);
p5.vertex(xpos+w*2/4-line ,ypos+w*0.05-line*2);
p5.endShape(p5.CLOSE);
p5.beginShape(); //snow 2
p5.vertex(xpos+w*2/4 -line/10,ypos+w*0.05);
p5.vertex(xpos+w*5/8,ypos-w*0.3);
p5.vertex(xpos+w*4/4-line*1.75,ypos+w*0.38-line*3.5);
p5.endShape(p5.CLOSE);
p5.noFill();
p5.strokeWeight(line);
p5.beginShape(); //Outline
p5.vertex(xpos-w*4/4,ypos+w*0.38);
p5.vertex(xpos-w*3/4,ypos+w*0.15);
p5.vertex(xpos-w*5/8,ypos+w*0.4);
p5.vertex(xpos-w*1/4,ypos-w*0.35);
p5.vertex(xpos-w*1/8,ypos-w*0.2);
p5.vertex(xpos+w*1/8,ypos-w*1);
p5.vertex(xpos+w*2/4,ypos+w*0.05);
p5.vertex(xpos+w*5/8,ypos-w*0.3);
p5.vertex(xpos+w*4/4,ypos+w*0.38);
p5.endShape();
p5.pop();
}
//____________[MOUTAIN GLYPHS]____________\\
function drawForest(p5, x, y, bottom_size, hex_color) { //tree glyph to mark forests
p5.push();
p5.rectMode(p5.CENTER);
p5.fill(hex_color);
p5.strokeWeight(bottom_size/10);
p5.stroke("white");
var bs = bottom_size;
var c_b = Math.pow(bs, 2) + Math.pow(bs / 2, 2);
var h = Math.sqrt(c_b) * 0.6;
var rad = bottom_size / 3;
p5.rect(x, y + bottom_size * 0.5, bottom_size / 5, bottom_size * 0.9);
p5.triangle(x - rad, y + h, x + rad, y + h, x, y);
y -= bottom_size / 6;
rad *= 1.125;
p5.triangle(x - rad, y + h, x + rad, y + h, x, y);
p5.pop();
}
function drawWave(p5, xpos, ypos, x1, x2, y1, y2, rad) {
var w = rad * 4;
xpos += rad / 32
var res = 32;
var amp = 21; // higher val == shorter waves
var period = 18.2; // lower val == more waves
period *= w;
amp = w / amp;
var xspace = w / res;
var dx = (360 / period) * xspace; // Value for incrementing x
var theta = 0; // x1+xpos;
var store = new Array(Math.floor(w / xspace)); // y values
var x = theta;
for (var i = 0; i < store.length; i++) {
store[i] = Math.cos(x) * amp;
x += dx;
}
p5.push();
p5.noFill();
p5.stroke("white");
p5.strokeWeight(rad / 6);
p5.beginShape();
for (var x = 0; x < store.length; x++) {
p5.curveVertex(xpos + (x * xspace) - w / 2, ypos + store[x]);
}
p5.endShape();
p5.pop();
}
<!DOCTYPE html>
<html>
<head>
<title>17.2.MDDN342 PS4</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
<style>
body {
padding: 0;
margin: 0;
}
html, body, #map {
height: 540px;
width: 960px;
}
.leaflet-control-attribution {
font-size: 24px !important;
}
</style>
</head>
<body>
<div id="map"></div>
<script src="http://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.14/p5.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.14/addons/p5.dom.js"></script>
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
<script src="leaflet-hash.js"></script>
<script src="biomes.js"></script>
<script src="glyphs.js"></script>
<script src="zoomable.js"></script>
<script src="map.js"></script>
</body>
</html>
(function(window) {
var HAS_HASHCHANGE = (function() {
var doc_mode = window.documentMode;
return ('onhashchange' in window) &&
(doc_mode === undefined || doc_mode > 7);
})();
L.Hash = function(map) {
this.onHashChange = L.Util.bind(this.onHashChange, this);
if (map) {
this.init(map);
}
};
L.Hash.parseHash = function(hash) {
if(hash.indexOf('#') === 0) {
hash = hash.substr(1);
}
var args = hash.split("/");
if (args.length == 5) {
var seed = parseInt(args[0], 10),
zoom = parseInt(args[1], 10),
lat = parseFloat(args[2]),
lon = parseFloat(args[3]);
depth = parseFloat(args[4]);
if (isNaN(zoom) || isNaN(lat) || isNaN(lon)) {
return false;
} else {
return {
center: new L.LatLng(lat, lon),
zoom: zoom,
seed: seed,
depth: depth
};
}
} else {
return false;
}
};
L.Hash.formatHash = function(map) {
var seed = map._p5_seed,
depth = map._p5_depth,
center = map.getCenter(),
zoom = map.getZoom(),
precision = 12;
// precision = Math.max(0, Math.ceil(Math.log(zoom*zoom) / Math.LN2));
return "#" + [seed, zoom,
center.lat.toFixed(precision),
center.lng.toFixed(precision),
depth.toFixed(precision)
].join("/");
},
L.Hash.prototype = {
map: null,
lastHash: null,
parseHash: L.Hash.parseHash,
formatHash: L.Hash.formatHash,
init: function(map) {
this.map = map;
// reset the hash
this.lastHash = null;
this.onHashChange();
if (!this.isListening) {
this.startListening();
}
},
removeFrom: function(map) {
if (this.changeTimeout) {
clearTimeout(this.changeTimeout);
}
if (this.isListening) {
this.stopListening();
}
this.map = null;
},
onMapMove: function() {
// bail if we're moving the map (updating from a hash),
// or if the map is not yet loaded
if (this.movingMap || !this.map._loaded) {
return false;
}
var hash = this.formatHash(this.map);
if (this.lastHash != hash) {
location.replace(hash);
this.lastHash = hash;
}
},
movingMap: false,
update: function() {
var hash = location.hash;
if (hash === this.lastHash) {
return;
}
var parsed = this.parseHash(hash);
if (parsed) {
var do_reset = false;
if (!("_hash_parsed" in this.map)) {
do_reset = true;
}
this.map._hash_parsed = true;
this.map._p5_seed = parsed.seed;
this.map._p5_depth = parsed.depth;
this.movingMap = true;
this.map.setView(parsed.center, parsed.zoom, {reset: do_reset});
this.movingMap = false;
}
else if (!("_hash_parsed" in this.map)) {
this.map._hash_parsed = true;
var center = this.map.getCenter();
var zoom = this.map.getZoom();
this.map.setView(center, zoom, {reset: true});
}
else {
this.onMapMove(this.map);
}
},
// defer hash change updates every 100ms
changeDefer: 100,
changeTimeout: null,
onHashChange: function() {
// throttle calls to update() so that they only happen every
// `changeDefer` ms
if (!this.changeTimeout) {
var that = this;
this.changeTimeout = setTimeout(function() {
that.update();
that.changeTimeout = null;
}, this.changeDefer);
}
},
isListening: false,
hashChangeInterval: null,
startListening: function() {
this.map.on("moveend", this.onMapMove, this);
if (HAS_HASHCHANGE) {
L.DomEvent.addListener(window, "hashchange", this.onHashChange);
} else {
clearInterval(this.hashChangeInterval);
this.hashChangeInterval = setInterval(this.onHashChange, 50);
}
this.isListening = true;
},
stopListening: function() {
this.map.off("moveend", this.onMapMove, this);
if (HAS_HASHCHANGE) {
L.DomEvent.removeListener(window, "hashchange", this.onHashChange);
} else {
clearInterval(this.hashChangeInterval);
}
this.isListening = false;
}
};
L.hash = function(map) {
return new L.Hash(map);
};
L.Map.prototype.addHash = function() {
this._hash = L.hash(this);
};
L.Map.prototype.removeHash = function() {
this._hash.removeFrom();
};
})(window);
var myCRS = L.extend({}, L.CRS.Simple, {
transformation: new L.Transformation(1, 0,
// -1, // works like expected
1, // image travels while zooming
0)
});
if (typeof initialZoomLevel === 'undefined') {
var initialZoomLevel = 0;
}
if (typeof maxZoomLevel === 'undefined') {
var maxZoomLevel = 16;
}
var worldMap = new L.Map('map', {
continuousWorld:true,
minZoom: 0,
maxZoom: maxZoomLevel,
crs: myCRS,
attributionControl: false,
center: [512, 512],
zoom: initialZoomLevel});
worldMap._p5_seed = Math.floor(Math.random() * 1000);
worldMap._p5_depth = 0.0;
// console.log("Seed start", worldMap._p5_seed)
// Assuming your map instance is in a variable called map
var hash = new L.Hash(worldMap);
// console.log("Seed now", worldMap._p5_seed)
var s = function( p ) {
p.setup = function() {
canvas = p.createCanvas(p._L_width, p._L_height);
p.noLoop();
};
p.draw = function() {
if ("_L_size" in p && "_L_nw" in p) {
var nw = p._L_nw;
var t_size = p._L_size;
var zoom = p._L_zoom;
var m_x1 = nw.lng;
var m_y1 = nw.lat;
var m_x2 = m_x1 + t_size;
var m_y2 = m_y1 + t_size;
var depth = p._L_depth;
p.noiseSeed(p._L_seed)
drawGrid(p, m_x1, m_x2, m_y1, m_y2, depth, zoom);
}
};
};
var tiles = new L.GridLayer({continuousWorld: true});
tiles.createTile = function(coords) {
if (!("_hash_parsed" in worldMap)) {
return L.DomUtil.create('canvas', 'leaflet-tile');
}
var size = this.getTileSize();
var myp5 = new p5(s);
myp5._L_width = size.x;
myp5._L_height = size.y;
myp5._L_zoom = coords.z;
myp5._L_seed = worldMap._p5_seed;
myp5._L_depth = worldMap._p5_depth;
myp5._L_coords = coords;
// calculate projection coordinates of top left tile pixel
myp5._L_nwPoint = coords.scaleBy(size);
myp5._L_size = 256.0 / Math.pow(2, coords.z)
// calculate geographic coordinates of top left tile pixel
myp5._L_nw = worldMap.unproject(myp5._L_nwPoint, coords.z)
myp5._start();
var tile = myp5.canvas;
L.DomUtil.addClass(tile, 'leaflet-tile');
return tile;
}
tiles.addTo(worldMap)
var curLinkIndex = 0;
linkHome = "#0/0/512/512/0"
if (typeof tourPath === 'undefined') {
var tourPath = [
[2, 512, 512],
[4, 512, 512],
[6, 512, 512],
[8, 512, 512]
]
}
tourPath.unshift([initialZoomLevel, 512, 512]);
if (typeof tourSeed === 'undefined') {
var tourSeed = 0;
}
function clickHome() {
worldMap.flyTo([tourPath[0][1], tourPath[0][2]], tourPath[0][0]);
}
function clickDemo() {
if(worldMap._p5_seed != tourSeed) {
var center = worldMap.getCenter();
var zoom = worldMap.getZoom();
worldMap._p5_seed = tourSeed;
tiles.redraw();
// worldMap.setView(center, zoom, {reset: true});
curLinkIndex = 0;
}
else {
curLinkIndex = (curLinkIndex + 1) % tourPath.length
}
var curDest = tourPath[curLinkIndex]
worldMap.flyTo([curDest[1], curDest[2]], curDest[0]);
}
function clickReset() {
window.location.reload();
}
attrib = new L.Control.Attribution
attrib.setPrefix("")
attrStr = '<a href="#" onclick="javascript:clickHome();">home</a> | '
attrStr += '<a href="#" onclick="javascript:clickReset();">reset</a> | '
attrStr += '<a href="#" onclick="javascript:clickDemo();">tour</a>'
attrib.addAttribution(attrStr)
worldMap.addControl(attrib)
// Map generator with biomes, landmarks, and contextual markings
// Code adapted from Red Blob Games' guide to hexagonal grids
// https:// www.redblobgames.com/grids/hexagons/
var hex_size_static = 4 ; // width/height of each hex tile in pixels
var hex_size = hex_size_static;
var noiseScale = 1 / 100 ;
var scatterNoiseScale = 200;
var stepNoiseScale = 1 / 25;
var initialZoomLevel = 2;
var maxZoomLevel = 5;
/* TOUR VARIABLES (required)
/* the random number seed for the tour */
/* triplets of locations: zoom, x, y */
var tourSeed = 442;
var tourPath = [
[0, -1145, 2742],
[1, 520, 541],
[2, 520, 541]
];
function noiseVal(p5, x, y) { // get noise at main noiseScae;
return p5.noise(x * noiseScale + 1000000, y * noiseScale + 1000000);
}
function snap_to_grid(num, gsize) {
return (num - (num % gsize));
}
function drawGrid(p5, x1, x2, y1, y2, z, zoom) {
var hex_size = hex_size_static;
if (zoom === 0) {
hex_size *= 4;
}
if (zoom === 1) {
hex_size *= 2;
}
p5.noiseDetail(80 , 0.475); // global noise setting
var hex_sizey = 3 * hex_size / 4;
var max_shift = hex_size;
var min_x = snap_to_grid(x1 - max_shift, hex_size);
var max_x = snap_to_grid(x2 + max_shift + hex_size, hex_size);
var min_y = snap_to_grid(y1 - max_shift * 1.5, hex_sizey);
var max_y = snap_to_grid(y2 + max_shift * 1.5 + hex_sizey, hex_sizey);
p5.fill(0, 0, 128);
var rowCount = Math.floor(min_y / hex_sizey);
var offset;
// Draw Tiles
for (var y = min_y; y < max_y; y += hex_sizey) {
if (rowCount % 2 == 0) {
offset = hex_size / 2;
} else {
offset = 0;
}
for (var x = min_x; x < max_x; x += hex_size) {
var hex_pos = [x + offset, y - hex_size]; // global co-ordinates
var hex_color = getHexColor(p5, hex_pos[0], hex_pos[1]);
var x_pos = p5.map(hex_pos[0], x1, x2, 0, 256); // local co-ordinates
var y_pos = p5.map(hex_pos[1], y1, y2, 0, 256); // local co-ordinates
var rad = p5.map(hex_size / 2, 0, x2 - x1, 0, 256);
drawHex(p5, x_pos, y_pos, hex_color, rad);
}
rowCount++;
}
// Draw Features
rowCount = Math.floor(min_y / hex_sizey);
if (zoom > 0) {
for (var y = min_y - hex_sizey; y < max_y + hex_sizey; y += hex_sizey) {
if (rowCount % 2 === 0) {
offset = hex_size / 2;
} else {
offset = 0;
}
for (var x = min_x - hex_size; x < max_x + hex_size; x += hex_size) {
var hex_pos = [x + offset, y - hex_size]; // global co-ordinates
var x_pos = p5.map(hex_pos[0], x1, x2, 0, 256); // local co-ordinates
var y_pos = p5.map(hex_pos[1], y1, y2, 0, 256); // local co-ordinates
var q = Math.floor(hex_pos[0] / (hex_size)); // column index
var r = Math.floor(hex_pos[1] / (hex_sizey)); // row index
var rad = p5.map(hex_size / 2, 0, x2 - x1, 0, 256);
var hex_color = getHexColor(p5, hex_pos[0], hex_pos[1]);
var curHexState = getHexState(p5, hex_pos[0], hex_pos[1]);
var curHexNoise = noiseVal(p5, hex_pos[0], hex_pos[1]);
// scatterNoise holds the noise value from a perlin noise map with a lower period
// adjacent values have virtually no correlation. This value is used to prevent identical glyphs from being drawn too close together
var scatterNoise = p5.noise(hex_pos[0] * scatterNoiseScale, hex_pos[1] * scatterNoiseScale, 800);
//var scatterNoise = p5.noise(q * scatterNoiseScale, r * scatterNoiseScale, 800);
// glyphOffsets are used to stop glyphs from being drawn _precisely_ on the hexGrid.
// pairs with scatterNoise to give the map's legend a hand-drawn feel
var glyphOffset_x = rad *0.5 * (p5.noise(hex_pos[0], hex_pos[1], 40) - 0.5);
var glyphOffset_y = rad *0.5 * (p5.noise(hex_pos[0], hex_pos[1], 50) - 0.5);
if (curHexNoise < 0.3) { // waves in deep ocean (all biomes)
// waves are drawn on every second row and every 3rd column
if (scatterNoise > 0.45 && zoom >= 2 && q % 3 == 0 && r % 2 == 0) {
drawWave(p5, x_pos+(r % 6 * rad), y_pos + glyphOffset_y, x1, x2, y1, y2, rad);
}
if (scatterNoise > 0.35 && zoom == 1 && q % 3 == 0 && r % 2 == 0) {
drawWave(p5, x_pos+(r % 6 * rad), y_pos + glyphOffset_y, x1, x2, y1, y2, rad);
}
}
// Logic for drawing glyphs
if (curHexState == "dry_desert" || curHexState == "dry_beach") {
if (zoom >= 1) {
if (curHexNoise > 0.5675 && curHexNoise < 0.615) {
if (scatterNoise < 0.125) {
drawSkelly(p5, x_pos, y_pos, rad * 2, hex_color);
}
if (scatterNoise > 0.875) {
drawSkelly(p5, x_pos, y_pos, rad * 2, hex_color);
}
if (0.4 < scatterNoise && scatterNoise < 0.4001 ) {
drawSkelly(p5, x_pos, y_pos, rad * 2, hex_color);
}
}
}
} else if (curHexState == "red_desert" || curHexState == "desert") {
if (zoom > 1) {
var steplayer1 = 0.425;
var steplayer2 = 0.575;
var scatterThresh = 3;
var zoomDetail_mod = zoom * 0.25;
zoomDetail_mod = scatterThresh-zoomDetail_mod;
if(zoomDetail_mod < 1){
zoomDetail_mod = 1;
}
if (curHexNoise < 0.8 && scatterNoise < curHexNoise / zoomDetail_mod) {
var stepNoise = p5.noise(hex_pos[0] * stepNoiseScale, hex_pos[1] * stepNoiseScale, 320);
if (stepNoise < steplayer1) {
drawSteps(p5, x_pos, y_pos, rad * 2, hex_color, -1);
} else if (stepNoise > steplayer1 && stepNoise < steplayer2) {
drawSteps(p5, x_pos, y_pos, rad * 2, hex_color, 0);
} else {
drawSteps(p5, x_pos, y_pos, rad * 2, hex_color, 1);
}
}
if (curHexNoise > 0.8 && curHexNoise < 0.855 && scatterNoise < curHexNoise / zoomDetail_mod * 2) {
var stepNoise = p5.noise(hex_pos[0] * stepNoiseScale, hex_pos[1] * stepNoiseScale, 320);
if (stepNoise < steplayer1) {
drawSteps(p5, x_pos, y_pos, rad * 2, hex_color, -1);
} else if (stepNoise > steplayer1 && stepNoise < steplayer2) {
drawSteps(p5, x_pos, y_pos, rad * 2, hex_color, 0);
} else {
drawSteps(p5, x_pos, y_pos, rad * 2, hex_color, 1);
}
}
if (curHexNoise > 0.855 && curHexNoise < 0.8625 && scatterNoise < curHexNoise / zoomDetail_mod * 4) {
var stepNoise = p5.noise(hex_pos[0] * stepNoiseScale, hex_pos[1] * stepNoiseScale, 320);
if (stepNoise < steplayer1) {
drawSteps(p5, x_pos, y_pos, rad * 2, hex_color, -1);
} else if (stepNoise < steplayer1 && stepNoise > steplayer2) {
drawSteps(p5, x_pos, y_pos, rad * 2, hex_color, 0);
} else {
drawSteps(p5, x_pos, y_pos, rad * 2, hex_color, 1);
}
}
}
} else if (curHexState == "mountain") {
if (curHexNoise > 0.855) {
drawSnowyMountain(p5, x_pos, y_pos, rad, hex_color);
}
if (zoom >= 1) {
if (scatterNoise > 0.7) {
drawMountain(p5, x_pos, y_pos, rad, hex_color);
}
if (curHexNoise > 0.87) {
drawMountain(p5, x_pos, y_pos, rad, hex_color);
}
}
if (zoom >= 2) {
if (scatterNoise > 0.575) {
drawSnowyMountain(p5, x_pos, y_pos, rad, hex_color);
}
if (curHexNoise > 0.855 && scatterNoise > 0.5) {
drawSnowyMountain(p5, x_pos, y_pos, rad, hex_color);
}
}
} else if (curHexState == "cliffs") {
if (zoom > 1 && curHexNoise > 0.72 && curHexNoise < 0.775 && scatterNoise > 0.595) {
drawHill(p5, x_pos, y_pos, rad, hex_color);
}
} else if (curHexState == "forest" || curHexState == "deep_forest") {
if (zoom > 2) {
if (zoom > 3 && scatterNoise > 0.425 && curHexNoise < 0.79) { // tiny trees
drawForest(p5, x_pos + glyphOffset_x, y_pos - rad / 2 + glyphOffset_y, rad * 0.4, hex_color);
}
if (curHexNoise > 0.78 && scatterNoise < 0.625 && curHexNoise < 0.815) { // medium forest
glyphOffset_x = rad * (p5.noise(hex_pos[0], hex_pos[1], 60) - 0.5);
glyphOffset_y = rad * (p5.noise(hex_pos[0], hex_pos[1], 70) - 0.5);
drawForest(p5, x_pos + rad / 2 + glyphOffset_x, y_pos + rad / 3 + glyphOffset_y, rad * 0.5, hex_color);
}
}
if (curHexState == "deep_forest" && zoom > 2 && curHexNoise > 0.8 && curHexNoise < 0.82) { // big boi
glyphOffset_x = rad * (p5.noise(hex_pos[0], hex_pos[1], 80) - 0.5);
glyphOffset_y = rad * (p5.noise(hex_pos[0], hex_pos[1], 90) - 0.5);
drawForest(p5, x_pos - rad / 4 + glyphOffset_x, y_pos + glyphOffset_y, rad * 0.7, hex_color);
}
if (curHexState == "deep_forest") {
// large icons in overview
if (zoom == 1 && q % 3 == 0 && r % 5 == 0) {
drawForest(p5, x_pos + glyphOffset_x, y_pos + glyphOffset_y, rad * 2, hex_color);
}
}
}
rowCount++;
}
}
}
p5.noFill();
}
function drawWave(p5, xpos, ypos, x1, x2, y1, y2, rad) {
var w = rad * 4;
xpos += rad / 32
var res = 32;
var amp = 21; // higher val == shorter waves
var period = 18.2; // lower val == more waves
period *= w;
amp = w / amp;
var xspace = w / res;
var dx = (360 / period) * xspace; // Value for incrementing x
var theta = 0; // x1+xpos;
var store = new Array(Math.floor(w / xspace)); // y values
var x = theta;
for (var i = 0; i < store.length; i++) {
store[i] = Math.cos(x) * amp;
x += dx;
}
p5.push();
p5.noFill();
p5.stroke("white");
p5.strokeWeight(rad / 6);
p5.beginShape();
for (var x = 0; x < store.length; x++) {
p5.curveVertex(xpos + (x * xspace) - w / 2, ypos + store[x]);
}
p5.endShape();
p5.pop();
}
function drawHex(p5, x, y, hex_color, rad) { //draw hex tile
p5.push();
p5.fill(hex_color);
p5.stroke(hex_color);
// p5.stroke("white")
p5.beginShape();
for (var i = 0; i < 6; i++) {
var point = hexCorner(x, y, rad, i);
p5.vertex(point[0], point[1]);
}
p5.endShape(p5.CLOSE);
p5.pop();
}
function hexCorner(x, y, rad, i) {
// return point at a specific corner
// corners indexed from right-bottom corner on hex moving clockwise
var angle_deg = (60 * i) + 30;
var angle_rad = Math.radians(angle_deg);
angle_rad %= Math.PI * 2;
var point = [
x + rad * Math.cos(angle_rad),
y + rad * Math.sin(angle_rad)
];
var regWidth = Math.sqrt(3) / 2 * (rad * 2);
// stretch side points for a hex with equal width & height
if (i == 5 || i === 0) {
point[0] += Math.abs((rad * 2 - regWidth)) / 2;
}
if (i == 3 || i == 2) {
point[0] -= (Math.abs(rad * 2 - regWidth)) / 2;
}
return point;
}
// Converts from degrees to radians.
Math.radians = function(degrees) {
return degrees * Math.PI / 180;
};
// Converts from radians to degrees.
Math.degrees = function(radians) {
return radians * 180 / Math.PI;
};
function midpoint(p1, p2) {
return [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2];
};
// Map generator with biomes, landmarks, and contextual markings
// Code adapted from Red Blob Games' guide to hexagonal grids
// https:// www.redblobgames.com/grids/hexagons/
var hex_size_static = 4; // width/height of each hex tile in pixels
var hex_size = hex_size_static;
var noiseScale = 1 / 175;
var scatterNoiseScale = 200;
var stepNoiseScale = 1 / 25;
var initialZoomLevel = 1;
var maxZoomLevel = 5;
/* TOUR VARIABLES (required)
/* the random number seed for the tour */
var tourSeed = 306;
/* triplets of locations: zoom, x, y */
var tourPath = [
[0, 1530, 818],
[1, 1375, 552],
[2, 1375, 567],
[3, 1414, 532],
[1, 1584, 234],
[0, 1584, 234],
[2, 1832, 310],
[3, 1861, 369],
[2, 1984, 387],
[2, 2090, 510],
[3, 2088, 504],
[0, 3745, 1076],
[1, 3851, 1360],
[2, 3832, 1423],
[3, 3828, 1406],
[4, 3808, 1397],
];
function noiseVal(p5, x, y) { // get noise at main noiseScae;
return p5.noise(x * noiseScale + 1000000, y * noiseScale + 1000000);
}
function snap_to_grid(num, gsize) {
return (num - (num % gsize));
}
function drawGrid(p5, x1, x2, y1, y2, z, zoom) {
var hex_size = hex_size_static;
if (zoom === 0) {
hex_size *= 4;
}
if (zoom === 1) {
hex_size *= 4;
}
if (zoom === 2) {
hex_size *= 2;
}
if(zoom == 3){
hex_size *= 2;
}
p5.noiseDetail(40, 0.45); // global noise setting
var hex_sizey = 3 * hex_size / 4;
var max_shift = hex_size;
var min_x = snap_to_grid(x1 - max_shift, hex_size);
var max_x = snap_to_grid(x2 + max_shift + hex_size, hex_size);
var min_y = snap_to_grid(y1 - max_shift * 1.5, hex_sizey);
var max_y = snap_to_grid(y2 + max_shift * 1.5 + hex_sizey, hex_sizey);
p5.fill(0, 0, 128);
var rowCount = Math.floor(min_y / hex_sizey);
var offset;
// Draw Tiles
for (var y = min_y; y < max_y; y += hex_sizey) {
if (rowCount % 2 == 0) {
offset = hex_size / 2;
} else {
offset = 0;
}
for (var x = min_x; x < max_x; x += hex_size) {
var hex_pos = [x + offset, y - hex_size]; // global co-ordinates
var hex_color = getHexColor(p5, hex_pos[0], hex_pos[1]);
var x_pos = p5.map(hex_pos[0], x1, x2, 0, 256); // local co-ordinates
var y_pos = p5.map(hex_pos[1], y1, y2, 0, 256); // local co-ordinates
var rad = p5.map(hex_size / 2, 0, x2 - x1, 0, 256);
drawHex(p5, x_pos, y_pos, hex_color, rad);
}
rowCount++;
}
// Draw Features
rowCount = Math.floor(min_y / hex_sizey);
if (zoom > 0) {
for (var y = min_y - hex_sizey; y < max_y + hex_sizey; y += hex_sizey) {
if (rowCount % 2 === 0) {
offset = hex_size / 2;
} else {
offset = 0;
}
for (var x = min_x - hex_size; x < max_x + hex_size; x += hex_size) {
var hex_pos = [x + offset, y - hex_size]; // global co-ordinates
var x_pos = p5.map(hex_pos[0], x1, x2, 0, 256); // local co-ordinates
var y_pos = p5.map(hex_pos[1], y1, y2, 0, 256); // local co-ordinates
var q = Math.floor(hex_pos[0] / (hex_size)); // column index
var r = Math.floor(hex_pos[1] / (hex_sizey)); // row index
var rad = p5.map(hex_size / 2, 0, x2 - x1, 0, 256);
x_pos-=rad;
var hex_color = getHexColor(p5, hex_pos[0], hex_pos[1]);
var curHexState = getHexState(p5, hex_pos[0], hex_pos[1]);
var curHexNoise = noiseVal(p5, hex_pos[0], hex_pos[1]);
// scatterNoise holds the noise value from a perlin noise map with a lower period
// adjacent values have virtually no correlation. This value is used to prevent identical glyphs from being drawn too close together
var scatterNoise = p5.noise(hex_pos[0] * scatterNoiseScale, hex_pos[1] * scatterNoiseScale, 800);
//var scatterNoise = p5.noise(q * scatterNoiseScale, r * scatterNoiseScale, 800);
// glyphOffsets are used to stop glyphs from being drawn _precisely_ on the hexGrid.
// pairs with scatterNoise to give the map's legend a hand-drawn feel
var glyphOffset_x = rad *0.5 * (p5.noise(hex_pos[0], hex_pos[1], 40) - 0.5);
var glyphOffset_y = rad *0.5 * (p5.noise(hex_pos[0], hex_pos[1], 50) - 0.5);
if (curHexNoise < 0.3) { // waves in deep ocean (all biomes)
// waves are drawn on every second row and every 3rd column
if (scatterNoise > 0.45 && zoom >= 2 && q % 3 == 0 && r % 2 == 0) {
drawWave(p5, x_pos+(r % 6 * rad), y_pos + glyphOffset_y, x1, x2, y1, y2, rad);
}
if (scatterNoise > 0.35 && zoom == 1 && q % 3 == 0 && r % 2 == 0) {
drawWave(p5, x_pos+(r % 6 * rad), y_pos + glyphOffset_y, x1, x2, y1, y2, rad);
}
}
if (curHexState == "dry_desert" || curHexState == "dry_beach") {
if (zoom > 1) {
if (curHexNoise > 0.5675 && curHexNoise < 0.615) {
if (scatterNoise < 0.125) {
drawSkelly(p5, x_pos, y_pos, rad * 2, hex_color);
}
if (scatterNoise > 0.875) {
drawSkelly(p5, x_pos, y_pos, rad * 2, hex_color);
}
if (0.4 < scatterNoise && scatterNoise < 0.4001 ) {
drawSkelly(p5, x_pos, y_pos, rad * 2, hex_color);
}
}
}
} else if (curHexState == "red_desert" || curHexState == "desert") {
if (zoom > 1) {
var steplayer1 = 0.425;
var steplayer2 = 0.575;
var scatterThresh = 4;
var zoomDetail_mod = zoom * 0.45;
zoomDetail_mod = scatterThresh-zoomDetail_mod;
if(zoomDetail_mod < 1){
zoomDetail_mod = 1;
}
if (curHexNoise < 0.8 && scatterNoise < curHexNoise / zoomDetail_mod) {
var stepNoise = p5.noise(hex_pos[0] * stepNoiseScale, hex_pos[1] * stepNoiseScale, 320);
if (stepNoise < steplayer1) {
drawSteps(p5, x_pos, y_pos, rad * 2, hex_color, -1);
} else if (stepNoise > steplayer1 && stepNoise < steplayer2) {
drawSteps(p5, x_pos, y_pos, rad * 2, hex_color, 0);
} else {
drawSteps(p5, x_pos, y_pos, rad * 2, hex_color, 1);
}
}
if (curHexNoise > 0.8 && curHexNoise < 0.855 && scatterNoise < curHexNoise / zoomDetail_mod * 2) {
var stepNoise = p5.noise(hex_pos[0] * stepNoiseScale, hex_pos[1] * stepNoiseScale, 320);
if (stepNoise < steplayer1) {
drawSteps(p5, x_pos, y_pos, rad * 2, hex_color, -1);
} else if (stepNoise > steplayer1 && stepNoise < steplayer2) {
drawSteps(p5, x_pos, y_pos, rad * 2, hex_color, 0);
} else {
drawSteps(p5, x_pos, y_pos, rad * 2, hex_color, 1);
}
}
if (curHexNoise > 0.855 && curHexNoise < 0.8625 && scatterNoise < curHexNoise / zoomDetail_mod * 4) {
var stepNoise = p5.noise(hex_pos[0] * stepNoiseScale, hex_pos[1] * stepNoiseScale, 320);
if (stepNoise < steplayer1) {
drawSteps(p5, x_pos, y_pos, rad * 2, hex_color, -1);
} else if (stepNoise < steplayer1 && stepNoise > steplayer2) {
drawSteps(p5, x_pos, y_pos, rad * 2, hex_color, 0);
} else {
drawSteps(p5, x_pos, y_pos, rad * 2, hex_color, 1);
}
}
}
} else if (curHexState == "mountain") {
if (curHexNoise > 0.855) {
drawSnowyMountain(p5, x_pos, y_pos, rad, hex_color);
}
if (zoom >= 1) {
if (scatterNoise > 0.725) {
drawMountain(p5, x_pos, y_pos, rad, hex_color);
}
if (curHexNoise > 0.87) {
drawMountain(p5, x_pos, y_pos, rad, hex_color);
}
}
if (zoom >= 2) {
if (scatterNoise > 0.65) {
drawMountain(p5, x_pos, y_pos, rad, hex_color);
}
}
} else if (curHexState == "cliffs") {
if (zoom > 1 && curHexNoise > 0.72 && curHexNoise < 0.775 && scatterNoise > 0.795) {
drawHill(p5, x_pos, y_pos, rad, hex_color);
}
if (zoom > 2 && curHexNoise > 0.72 && curHexNoise < 0.775 && scatterNoise > 0.695) {
drawHill(p5, x_pos, y_pos, rad, hex_color);
}
if (zoom > 2 && curHexNoise > 0.72 && curHexNoise < 0.775 && scatterNoise > 0.625) {
drawHill(p5, x_pos, y_pos, rad, hex_color);
}
} else if (curHexState == "forest" || curHexState == "deep_forest") {
if (zoom >= 2) {
if (zoom >=3 && scatterNoise > 0.425 ) { // tiny trees
drawForest(p5, x_pos + glyphOffset_x, y_pos - rad / 2 + glyphOffset_y, rad * 0.4, hex_color);
}
if (curHexNoise > 0.78 && scatterNoise < 0.625 && curHexNoise < 0.815) { // medium forest
glyphOffset_x = rad * (p5.noise(hex_pos[0], hex_pos[1], 60) - 0.5);
glyphOffset_y = rad * (p5.noise(hex_pos[0], hex_pos[1], 70) - 0.5);
drawForest(p5, x_pos + rad / 2 + glyphOffset_x, y_pos + rad / 3 + glyphOffset_y, rad * 0.5, hex_color);
}
}
if (curHexState == "deep_forest" && zoom > 2 && curHexNoise > 0.8 && curHexNoise < 0.82) { // big boi
glyphOffset_x = rad * (p5.noise(hex_pos[0], hex_pos[1], 80) - 0.5);
glyphOffset_y = rad * (p5.noise(hex_pos[0], hex_pos[1], 90) - 0.5);
drawForest(p5, x_pos - rad / 4 + glyphOffset_x, y_pos + glyphOffset_y, rad * 0.7, hex_color);
}
if (curHexState == "deep_forest") {
// large icons in overview
if (zoom >= 3 && q % 3 == 0 && r % 4 == 0 && scatterNoise >0.45) {
//drawForest(p5, x_pos + glyphOffset_x, y_pos + glyphOffset_y, rad * 2.333, hex_color);
}
if (zoom === 2 && q % 3 == 0 && r % 4 == 0 && scatterNoise >0.45) {
drawForest(p5, x_pos + glyphOffset_x, y_pos + glyphOffset_y, rad * 2.666, hex_color);
}
if (zoom === 1 && q % 3 == 0 && r % 4 == 0 ) {
drawForest(p5, x_pos + glyphOffset_x, y_pos + glyphOffset_y, rad * 2, hex_color);
}
}
}
rowCount++;
}
}
}
p5.noFill();
}
function drawHex(p5, x, y, hex_color, rad) { //draw hex tile
p5.push();
p5.fill(hex_color);
p5.stroke(hex_color);
// p5.stroke("white")
p5.beginShape();
for (var i = 0; i < 6; i++) {
var point = hexCorner(x, y, rad, i);
p5.vertex(point[0], point[1]);
}
p5.endShape(p5.CLOSE);
p5.pop();
}
function hexCorner(x, y, rad, i) {
// return point at a specific corner
// corners indexed from right-bottom corner on hex moving clockwise
var angle_deg = (60 * i) + 30;
var angle_rad = Math.radians(angle_deg);
angle_rad %= Math.PI * 2;
var point = [
x + rad * Math.cos(angle_rad),
y + rad * Math.sin(angle_rad)
];
var regWidth = Math.sqrt(3) / 2 * (rad * 2);
// stretch side points for a hex with equal width & height
if (i == 5 || i === 0) {
point[0] += Math.abs((rad * 2 - regWidth)) / 2;
}
if (i == 3 || i == 2) {
point[0] -= (Math.abs(rad * 2 - regWidth)) / 2;
}
return point;
}
// Converts from degrees to radians.
Math.radians = function(degrees) {
return degrees * Math.PI / 180;
};
// Converts from radians to degrees.
Math.degrees = function(radians) {
return radians * 180 / Math.PI;
};
function midpoint(p1, p2) {
return [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2];
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment