Fantasy-Map-Generator/WIP version/script.js
2018-02-26 21:45:08 +03:00

2639 lines
No EOL
105 KiB
JavaScript

// Fantasy Map Generator (WIP) main script
fantasyMapGenerator();
function fantasyMapGenerator() {
console.clear();
console.time(" TimeTotal");
$(".container").hide();
// Define variables
var svg = d3.select("svg"),
mapWidth = +svg.attr("width"),
mapHeight = +svg.attr("height"),
defs = svg.select("#deftemp"),
viewbox = svg.select(".viewbox").on("touchmove mousemove", moved).on("click", clicked),
container = viewbox.select(".container"), //.attr("transform", "translate(80 25)"),
ocean = container.append("g").attr("class", "ocean"),
rose = container.append("use").attr("xlink:href","#rose"),
islandBack = container.append("g").attr("class", "islandBack"),
hCells = container.append("g").attr("class", "hCells"),
grid = container.append("g").attr("class", "grid"),
mapCells = container.append("g").attr("class", "mapCells"),
mapContours = container.append("g").attr("class", "mapContours"),
hatching = container.append("g").attr("class", "hatching"),
rivers = container.append("g").attr("class", "rivers"),
riversShade = rivers.append("g").attr("class", "riversShade"),
coasts = container.append("g").attr("class", "coasts"),
coastline = coasts.append("g").attr("class", "coastline"),
lakecoast = coasts.append("g").attr("class", "lakecoast"),
debug = container.append("g").attr("class", "debug"),
selected = debug.append("g").attr("class", "selected"),
highlighted = debug.append("g").attr("class", "highlighted")
cursored = debug.append("g").attr("class", "cursored");
var base = ocean.append("rect").attr("x", 0).attr("y", 0).attr("width", mapWidth).attr("height", mapHeight).attr("class", "base");
var mottling = container.append("rect").attr("x", 0).attr("y", 0).attr("width", mapWidth).attr("height", mapHeight).attr("class", "mottling");
// Define basic data for Voronoi. Poisson-disc sampling for a points
// Source: bl.ocks.org/mbostock/99049112373e12709381
console.time('poissonDiscSampler');
var voronoi = d3.voronoi().extent([[0, 0], [mapWidth, mapHeight]]);
var diagram, polygons;
var sampler = poissonDiscSampler(mapWidth, mapHeight, 5.9);
var samples = [], sample;
while (sample = sampler()) {samples.push([Math.ceil(sample[0]), Math.ceil(sample[1])]);}
console.timeEnd('poissonDiscSampler');
// Add D3 drag and zoom behavior
var zoom = d3.zoom()
.translateExtent([[0, 0], [mapWidth, mapHeight]])
.scaleExtent([1, 40]); // 40x is default max zoom;
svg.call(zoom);
var drag = d3.drag()
.container(function() {return this;})
.subject(function() {var p=[d3.event.x, d3.event.y]; return [p, p];})
.on("start", dragstarted);
function zoomed() {
scale = d3.event.transform.k;
viewX = d3.event.transform.x;
viewY = d3.event.transform.y;
if (mapStyle.value == "map_contours" && mapType.value == "heightmap") {
var x = 0.5 / scale;
var y = 0.6 / scale;
container.selectAll(".contoursShade")
.attr("transform", "translate("+x+" "+y+")")
.attr("opacity", 4 / scale);
mapContours.attr("opacity", 4 / scale);
}
viewbox.attr("transform", d3.event.transform);
}
// manually update viewbox
function zoomUpdate() {
var transform = d3.zoomIdentity.translate(viewX, viewY).scale(scale);
svg.call(zoom.transform, transform);
}
var dragCloud = d3.drag()
.container(function() {return this;})
.subject(function() {var p=[d3.event.x, d3.event.y]; return [p, p];})
.on("start", dragCloudstarted);
cloud = debug.append("g").attr("class", "cloud").call(dragCloud);
cloud.append("text").text("☁").attr("x", 96).attr("y", mapHeight*0.5);
cloud.append("text").text("⇶").attr("x", 96).attr("y", mapHeight*0.5)
.attr("dx", "2").attr("dy", "-17").attr("id", "cloudArrow");
// Common variables
var queue = [], riversData = [], selection = [], highlighting = [],
scale = 1, viewX = 0, viewY = 0, simplex,
noiseApplied, pointsHeights = [], pointsCells = [], pointsBiomes = [], pointsCellHeights = [],
biomNames = [], biomColors = [], biomIDs = "", biomGrad = "", adjectives = [],
mapTemplate = "Undefined", boids = [], animation, animated = false,
// D3 colors
bright = d3.scaleSequential(d3.interpolateSpectral), // 1-
light = d3.scaleSequential(d3.interpolateRdYlGn), // 1.2-
green = d3.scaleSequential(d3.interpolateGreens), // 0
blue = d3.scaleSequential(d3.interpolateBlues), // 0
monochrome = d3.scaleSequential(d3.interpolateGreys), // 0.8-
sepia = d3.scaleLinear().domain([0, 1]).interpolate(d3.interpolateHcl).range([d3.rgb("#8e5e2a"), d3.rgb("#faf6ea")]),
// Journey data
journeyStep = -1,
stepName = ["", "1. Define Landmass", "2. Finalize Heighmap", "3. Define Climate"],
stepHint = ["", "Select a mode and mock up the Heighmap", "Fine-tune the Heighmap and overlay with noise", "Define precipitation and set up Biomes",];
// D3 Line generator
var scX = d3.scaleLinear().domain([0, mapWidth]).range([0, mapWidth]);
var scY = d3.scaleLinear().domain([0, mapHeight]).range([0, mapHeight]);
var lineGen = d3.line().x(function(d) {return scX(d.scX);}).y(function(d) {return scY(d.scY);});
// Prepare voronoi graph on-load
loadData();
newRandomMap();
$("#initial, .container").fadeIn("slow");
console.timeEnd(" TimeTotal");
// Assing long strings and arrays (will be a separate json file)
function loadData() {
adjectives = ["Ablaze", "Ablazing", "Accented", "Ashen", "Ashy", "Beaming", "Bi-Color", "Blazing", "Bleached", "Bleak", "Blended", "Blotchy", "Bold", "Brash", "Bright", "Brilliant", "Burnt", "Checkered", "Chromatic", "Classic", "Clean", "Colored", "Colorful", "Colorless", "Complementing", "Contrasting", "Cool", "Coordinating", "Crisp", "Dappled", "Dark", "Dayglo", "Deep", "Delicate", "Digital", "Dim", "Dirty", "Discolored", "Dotted", "Drab", "Dreary", "Dull", "Dusty", "Earth", "Electric", "Eye-Catching", "Faded", "Faint", "Festive", "Fiery", "Flashy", "Flattering", "Flecked", "Florescent", "Frosty", "Full-Toned", "Glistening", "Glittering", "Glowing", "Harsh", "Hazy", "Hot", "Hued", "Icy", "Illuminated", "Incandescent", "Intense", "Interwoven", "Iridescent", "Kaleidoscopic", "Lambent", "Light", "Loud", "Luminous", "Lusterless", "Lustrous", "Majestic", "Marbled", "Matte", "Medium", "Mellow", "Milky", "Mingled", "Mixed", "Monochromatic", "Motley", "Mottled", "Muddy", "Multicolored", "Multihued", "Murky", "Natural", "Neutral", "Opalescent", "Opaque", "Pale", "Pastel", "Patchwork", "Patchy", "Patterned", "Perfect", "Picturesque", "Plain", "Primary", "Prismatic", "Psychedelic", "Pure", "Radiant", "Reflective", "Rich", "Royal", "Ruddy", "Rustic", "Satiny", "Saturated", "Secondary", "Shaded", "Sheer", "Shining", "Shiny", "Shocking", "Showy", "Smoky", "Soft", "Solid", "Somber", "Soothing", "Sooty", "Sparkling", "Speckled", "Stained", "Streaked", "Streaky", "Striking", "Strong Neutral", "Subtle", "Sunny", "Swirling", "Tinged", "Tinted", "Tonal", "Toned", "Translucent", "Transparent", "Two-Tone", "Undiluted", "Uneven", "Uniform", "Vibrant", "Vivid", "Wan", "Warm", "Washed-Out", "Waxen", "Wild"];
biomNames = ["Hot desert","Savanna","Tropical dry forest","Tropical wet forest","Xeric srubland","Temperate dry grassland","Temperate wet grassland","Temperate deciduous forest","Subtropical rain forest","Cold desert","Temperate rain forest","Coniferous wet forest","Temperate coniferous forest","Subtaiga","Boreal wet forest","Boreal dry forest","Subpolar scrub","Subpolar desert","Tundra","Rocky desert ","Polar desert","Glacier"];
biomColors = ["#fbfaae","#eef586","#b6d95d","#7dcb35","#d6dd7f","#bdde82","#a1d77a","#29bc56","#76bd32","#e1df9b","#45b348","#52a444","#6fb252","#567c2c","#618a38","#a4b36d","#acb076","#b5ad8b","#d5d59d","#bfbfbf","#f2f2f2","#fafeff"];
biomIDs = "2,3,3,3,3,8,8,8,8,8,8,8,8,8,8,8,8,8,8,10,10,10,10,10,10,10,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,13,13,13,13,13,14,14,14,14,14,14,14,14,18,18,18,18,18,20,20,20,20,20,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21||2,2,3,3,3,3,8,8,8,8,8,8,8,8,8,8,8,8,8,8,10,10,10,10,10,10,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,13,13,13,13,13,14,14,14,14,14,14,14,18,18,18,18,18,20,20,20,20,20,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21||1,2,2,3,3,3,8,8,8,8,8,8,8,8,8,8,8,8,8,8,10,10,10,10,10,10,10,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,13,13,13,13,13,14,14,14,14,14,14,14,18,18,18,18,18,20,20,20,20,20,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21||1,1,2,2,2,2,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,13,13,13,13,13,13,14,14,14,14,14,14,14,18,18,18,18,20,20,20,20,20,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21||1,1,2,2,2,2,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,12,12,12,12,12,12,12,12,12,14,14,14,14,14,14,14,14,14,14,14,14,14,14,18,18,18,20,20,20,20,20,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21||1,1,1,2,2,2,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,12,12,12,12,12,12,12,12,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,18,18,20,20,20,20,20,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21||0,1,1,1,1,2,2,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,18,20,20,20,20,20,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21||0,0,1,1,1,1,1,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,20,20,20,20,20,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21||0,0,0,1,1,1,1,1,5,5,5,5,5,5,5,5,5,5,5,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,16,16,16,16,16,16,16,16,16,16,16,16,16,16,20,20,20,20,20,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21||0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,17,17,17,17,17,17,17,17,17,17,17,17,17,17,19,19,19,19,19,19,19,19,19,19,19,19,20,20,20,20,21,21,21,21,21";
biomGrad = "#b6d95d,#aad453,#9fcf4b,#96cb44,#8ec73f,#88c53b,#83c438,#7fc236,#7cc034,#7abf33,#78be32,#77be32,#76be33,#75be34,#73be35,#6fbd37,#6bbc3a,#66bb3d,#61ba40,#5cb942,#57b844,#53b745,#50b646,#4eb347,#4eb047,#4eae46,#4fac45,#50aa44,#51a844,#52a644,#52a544,#52a444,#52a444,#52a444,#52a444,#52a444,#52a444,#52a444,#52a444,#52a444,#52a444,#52a444,#52a444,#52a444,#52a444,#52a444,#52a444,#52a444,#52a444,#52a444,#52a444,#52a444,#52a444,#53a343,#54a242,#559f40,#569b3e,#57973b,#589238,#598e35,#5a8a33,#5b8732,#5c8532,#5d8432,#5d8533,#5e8534,#608535,#648737,#6b8b3c,#769244,#839a4f,#90a35b,#9dad6a,#a8b87b,#b1c38d,#b9cd9f,#c1d6b1,#c9dec1,#cfe5d0,#d6ebdc,#ddf0e6,#e3f4ed,#e8f7f2,#ecfaf6,#effcf9,#f2fdfb,#f4fdfd,#f6fdfe,#f8fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#fafeff||#c5df67,#b8d95d,#abd455,#9fd04e,#95cc49,#8bc944,#84c841,#7ec63f,#79c43d,#76c33c,#73c23b,#71c13b,#6fc13c,#6dc03d,#6bc03d,#68c03f,#65bf41,#61be44,#5dbd46,#59bc47,#55bb49,#51ba4a,#4eb94b,#4db64b,#4db34a,#4db149,#4eaf48,#4fae48,#50ac48,#50ab48,#50aa48,#50a948,#50a948,#50a948,#51a948,#51a847,#51a847,#52a847,#52a847,#52a847,#52a847,#52a847,#52a847,#52a847,#53a847,#53a847,#54a847,#54a847,#55a847,#55a847,#57a847,#58a847,#58a747,#59a746,#5aa645,#5ba343,#5c9f41,#5d9b3e,#5d963b,#5e9238,#5e8e36,#5f8a35,#5f8835,#608736,#608736,#618737,#628737,#658838,#6b8b3c,#759143,#81984c,#8ca057,#99a965,#a4b476,#adbf88,#b5ca9b,#bed3ae,#c6dcbe,#cce3ce,#d4e9da,#dbefe5,#e2f4ec,#e7f7f2,#ecfaf6,#effcf9,#f2fdfb,#f4fdfd,#f6fdfe,#f8fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#fafeff||#d2e572,#c5de68,#b7d95f,#aad458,#9ed153,#93ce4e,#8acc4b,#82ca48,#7cc846,#78c745,#74c645,#71c545,#6fc445,#6dc446,#6bc346,#68c348,#66c349,#62c24b,#5fc04d,#5cbf4e,#5abf4f,#57be50,#54bc50,#54ba4f,#53b84f,#54b64e,#54b44d,#55b34d,#56b24d,#56b14d,#56b04d,#55b04d,#55b04d,#55b04d,#57af4d,#57ae4c,#57ae4c,#58ae4c,#58ae4c,#59ae4c,#59ae4c,#59ae4c,#59ae4c,#59ae4c,#5aae4c,#59af4c,#5baf4c,#5cae4c,#5eae4c,#5fae4c,#61ad4c,#62ad4c,#63ac4c,#64ac4b,#65ab4a,#66a949,#67a547,#67a144,#679c41,#68983e,#68943c,#68903b,#678d3a,#688c3b,#678c3b,#688b3b,#688b3b,#6b8b3c,#708e3f,#789245,#82984c,#8c9f56,#97a763,#a1b273,#aabd85,#b2c798,#bbd1ab,#c3dabc,#cae1cc,#d3e8d9,#daeee4,#e2f3eb,#e7f7f1,#ebf9f5,#effcf9,#f2fdfb,#f4fdfd,#f6fdfe,#f8fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#fafeff||#ddea7d,#d1e373,#c3de6a,#b6d962,#a9d55c,#9ed258,#94d055,#8cce52,#85cc50,#80cb4f,#7cca4f,#79c94f,#76c84f,#74c84f,#72c750,#70c751,#6ec752,#6cc653,#6ac454,#68c454,#66c455,#64c356,#63c155,#63c055,#62be55,#62bc54,#62bb53,#63ba53,#63b953,#63b853,#63b853,#63b853,#63b853,#63b853,#64b753,#65b653,#65b653,#65b653,#65b653,#66b653,#67b653,#67b653,#67b653,#67b653,#68b653,#68b752,#69b652,#6ab652,#6cb552,#6eb552,#6fb452,#71b452,#72b352,#73b352,#74b251,#75b050,#76ad4e,#76a94c,#75a549,#75a047,#759c45,#749843,#739542,#739343,#719242,#719142,#719042,#739042,#779244,#7d9549,#859a4e,#8ea057,#97a763,#a0b172,#a9bc84,#b1c696,#b9cfa9,#c1d9bb,#c9e0cb,#d2e7d8,#d9ede3,#e1f2ea,#e6f6f1,#eaf9f5,#eefbf9,#f2fcfb,#f4fcfd,#f6fcfe,#f8fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#f9fdfe,#fafeff||#e6ee88,#dbe87e,#cfe375,#c3de6d,#b7da67,#acd762,#a2d55f,#9ad35c,#93d15a,#8ed059,#8acf59,#86ce59,#84cd59,#82cd59,#81cc59,#80cc5a,#7ecc5a,#7dcb5b,#7bca5b,#7aca5b,#78ca5c,#77c95c,#76c75b,#77c65b,#76c55b,#76c35b,#76c25a,#77c25a,#77c15a,#77c15a,#76c15a,#76c05a,#76c05a,#77c05a,#78bf5a,#78be5a,#78be5a,#78be5a,#78be5a,#79be5a,#7abe5a,#7abe5a,#7abe5a,#7abe5a,#7abf5a,#7bbf5a,#7cbe5a,#7dbe5a,#7ebd5a,#80bd5a,#81bc5a,#83bc5a,#84bb5a,#85bb5a,#86ba59,#87b858,#87b657,#87b255,#86ae53,#85aa51,#84a54f,#83a14d,#819e4c,#809b4c,#7e994b,#7d974b,#7d964b,#7e964a,#81974c,#86994f,#8c9c53,#93a15a,#9aa865,#a2b173,#aabb83,#b1c495,#b8cda7,#bfd7b9,#c6dec9,#cfe5d6,#d6ebe1,#def0e8,#e4f4ef,#e9f7f3,#edf9f7,#f0faf9,#f2fbfb,#f4fbfc,#f6fcfd,#f7fcfd,#f7fcfd,#f7fcfd,#f7fdfd,#f8fcfe,#f8fdfe,#f8fdfe,#f8fdfe,#f9fdff,#f9fdff,#fafeff||#edf191,#e4ec88,#dae77f,#cfe378,#c5df73,#bcdc6e,#b3db6b,#acd968,#a6d866,#a1d765,#9dd665,#9ad465,#98d365,#97d365,#96d265,#95d165,#94d164,#93d064,#92d064,#91cf64,#90cf64,#8fce64,#8ecc63,#8ecc63,#8dcb63,#8dca63,#8dc962,#8dc962,#8dc862,#8dc862,#8cc862,#8cc762,#8cc762,#8dc762,#8ec662,#8ec663,#8ec663,#8ec663,#8ec663,#8fc663,#90c663,#90c663,#90c663,#90c663,#90c663,#91c663,#92c563,#93c563,#94c563,#96c563,#96c363,#98c363,#98c364,#98c364,#99c263,#9ac062,#9abe61,#9abb60,#99b85e,#97b45c,#96af5a,#94ab59,#92a758,#90a457,#8ea157,#8c9e56,#8b9d56,#8c9c55,#8d9c56,#919d58,#959f5b,#9aa360,#9fa969,#a5b175,#acb984,#b2c294,#b8caa5,#bed3b6,#c4dac5,#cbe0d2,#d3e6dc,#d9eae3,#dfeee9,#e3f1ed,#e7f3f0,#eaf4f3,#ecf5f5,#eff6f6,#f1f7f7,#f2f8f8,#f3f9f9,#f4fafa,#f4fbfb,#f5fbfc,#f6fcfd,#f7fcfe,#f7fdfe,#f8fdff,#f9fdff,#fafeff||#f2f399,#ebef91,#e4eb8a,#dbe884,#d4e57f,#cde37b,#c6e178,#c0e076,#bbdf74,#b7de73,#b4dd73,#b1dc73,#b0db73,#b0db73,#afda72,#add972,#add871,#abd671,#aad570,#a9d470,#a8d46f,#a7d36e,#a6d16e,#a6d16d,#a5d06d,#a5cf6d,#a5ce6d,#a5ce6d,#a5ce6d,#a5ce6d,#a4ce6d,#a4cd6d,#a4cd6d,#a4cd6d,#a4cd6d,#a5cc6e,#a5cc6e,#a5cc6e,#a5cc6e,#a5cc6e,#a6cc6e,#a6cc6e,#a6cc6e,#a6cc6e,#a6cc6e,#a7cc6e,#a8cb6e,#a9cb6e,#aacb6e,#abcb6e,#abca6e,#acca6e,#acca6f,#acca6f,#adc96e,#adc86e,#adc66d,#adc46c,#acc16b,#aabe69,#a8b968,#a6b566,#a3b065,#a1ad64,#9ea964,#9ca663,#9aa462,#9aa261,#9ba162,#9ca263,#9fa365,#a2a569,#a5aa6f,#a9b179,#aeb786,#b3bf94,#b7c6a3,#bccdb1,#c1d3bf,#c6d9ca,#ccded3,#d2e1da,#d7e5df,#dbe7e3,#dee9e6,#e1eae9,#e4eceb,#e7eded,#e9efef,#ebf0f0,#edf2f2,#eff5f4,#f1f7f6,#f2f9f9,#f4fafb,#f5fbfc,#f5fcfd,#f7fdfe,#f8fdff,#fafeff||#f6f5a1,#f1f29a,#ecf095,#e7ee91,#e2ec8e,#deea8b,#d9e988,#d5e886,#d2e885,#cfe784,#cde684,#cbe684,#cae584,#cae584,#c9e383,#c7e283,#c6e081,#c4de80,#c3dc7f,#c1db7e,#c0da7c,#bfd97c,#bdd77c,#bcd67b,#bcd57a,#bbd47a,#bbd37a,#bbd37a,#bbd37a,#bbd37a,#bbd37a,#bbd37a,#bbd37a,#bbd37a,#bbd37a,#bbd27b,#bbd27b,#bbd27b,#bbd27b,#bbd27b,#bcd27b,#bcd27b,#bcd27b,#bcd27b,#bcd27b,#bdd27b,#bdd17b,#bdd17b,#bed17b,#bed17b,#bed17b,#bfd17b,#bfd17c,#bfd17c,#c0d07c,#c0cf7c,#c0ce7c,#bfcd7b,#beca7a,#bdc878,#bbc377,#b8bf75,#b5ba74,#b2b673,#aeb272,#abae71,#a9ab70,#a8a86f,#a8a770,#a8a770,#a9a771,#aaa873,#acac77,#aeb07e,#b0b588,#b3bb94,#b5c0a0,#b9c6ac,#bccbb7,#c0d0c1,#c4d4c8,#c9d6ce,#cdd9d3,#d0dad6,#d3dcd9,#d6dddc,#d9dfde,#dce1e1,#e0e4e3,#e3e6e6,#e6eae9,#eaedec,#edf1f0,#eff4f3,#f1f6f6,#f3f8f8,#f4fafa,#f6fcfc,#f7fdfe,#fafeff||#f9f7a8,#f6f6a4,#f4f5a1,#f1f49f,#eff39e,#edf29c,#ebf19b,#e9f199,#e8f199,#e6f098,#e5f098,#e4f098,#e4ef98,#e3ef98,#e2ed97,#e0ec96,#dfea94,#dde792,#dae491,#d8e28f,#d6e08d,#d4de8c,#d2dc8c,#d1db8b,#d0da8a,#cfd98a,#cfd98a,#cfd98a,#cfd98a,#cfd98a,#cfd98a,#cfd98a,#cfd98a,#cfd98a,#cfd98a,#cfd88a,#cfd88a,#cfd88a,#cfd88a,#cfd88a,#d0d88a,#d0d88a,#d0d88a,#d0d88a,#d0d88a,#d0d88a,#d0d88a,#d0d88a,#d0d88a,#d0d88a,#d0d88a,#d1d88a,#d1d88b,#d1d88b,#d1d78b,#d1d78b,#d1d68b,#d1d68b,#d0d48a,#cfd288,#cdce87,#cac986,#c6c484,#c2bf83,#beba81,#bbb680,#b8b27f,#b5af7e,#b3ad7e,#b2ac7e,#b2ab7e,#b2ac7f,#b2ae81,#b2b085,#b2b38b,#b3b794,#b3bb9d,#b5bfa7,#b7c2af,#bac6b7,#bcc9bd,#bfcac2,#c3ccc6,#c5ccc8,#c7ceca,#c9cfcd,#cdd1d0,#d1d3d3,#d6d7d7,#dadbdb,#dfe0e0,#e4e5e4,#e8eae9,#eceeed,#eff2f1,#f2f4f4,#f3f7f7,#f5f9f9,#f7fcfc,#fafeff||#fbfaae,#fbfaae,#fbfaae,#fbfaae,#fbfaae,#fbfaae,#fbfaae,#fbfaae,#fbfaae,#fbfaae,#fbfaae,#fbfaae,#fbfaae,#faf9ad,#f9f8ac,#f7f6ab,#f5f4a9,#f3f1a6,#efeda4,#eceaa2,#e9e7a0,#e6e49e,#e4e29d,#e2e09c,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9b,#e1df9a,#e0dd99,#ded998,#dbd497,#d7cf95,#d2c993,#cdc391,#c9be8f,#c5b98f,#c0b68e,#bdb38d,#bab28c,#b9b08b,#b8b08b,#b7b08b,#b5b08c,#b3b18f,#b2b394,#b1b69b,#b1b8a2,#b2baa8,#b3bcae,#b4beb3,#b6bfb6,#b8bfb9,#babfba,#bbc0bc,#bdc1bf,#c1c3c2,#c6c6c6,#cbcbcb,#d1d1d1,#d7d7d7,#dddddd,#e3e3e3,#e8e8e8,#ededed,#f0f1f1,#f2f4f4,#f4f7f7,#f7fbfb,#fafeff";
biomIDs = biomIDs.split("||");
biomIDs = biomIDs.map(function(m) {return m.split(",");})
biomGrad = biomGrad.split("||");
biomGrad = biomGrad.map(function(m) {return m.split(",");})
}
// Calculate Voronoi Diagram
function calculateVoronoi(points) {
diagram = voronoi(points),
polygons = diagram.polygons();
}
// Find cells for every x/y point
function findCells(points) {
console.time('findCells');
pointsCells = [];
points.map(function(i, d) {
if (!pointsCells[i[1]]) {pointsCells[i[1]] = [];}
pointsCells[i[1]][i[0]] = d;
neighborCells(i[1], i[0], 2);
});
d3.range(0, mapHeight).forEach(function(y) {
d3.range(0, mapWidth).forEach(function(x) {
if (!pointsCells[y]) {pointsCells[y] = [];}
if (!pointsCells[y][x]) {
pointsCells[y][x] = diagram.find(x, y).index;
neighborCells(y, x, 1);
}
});
});
console.timeEnd('findCells');
}
function neighborCells(y, x, l) {
if (y > l && x > l && y + l < mapHeight && x + l < mapWidth) {
var v = pointsCells[y][x];
for (c = l * -1; c < l; c ++) {
if (!pointsCells[y+c]) {pointsCells[y+c] = [];}
if (!pointsCells[y-c]) {pointsCells[y-c] = [];}
pointsCells[y+c][x] = v;
pointsCells[y+c][x+c] = v;
pointsCells[y][x+c] = v;
pointsCells[y][x-c] = v;
if (c > 1 || c < -1) {
var p = c+1;
if (!pointsCells[y+p]) {pointsCells[y+p] = [];}
if (!pointsCells[y-p]) {pointsCells[y-p] = [];}
pointsCells[y+c][x+p] = v;
pointsCells[y+c][x-p] = v;
pointsCells[y+p][x+c] = v;
pointsCells[y+p][x-c] = v;
}
}
}
}
function detectNeighbors() {
console.time("detectNeighbors");
// define neighbors for each polygon
polygons.map(function(i, d) {
i.index = d;
if (!i.height) {i.height = 0;}
var neighbors = [];
diagram.cells[d].halfedges.forEach(function(e) {
var edge = diagram.edges[e], ea;
if (edge.left && edge.right) {
ea = edge.left.index;
if (ea === d) {
ea = edge.right.index;
}
neighbors.push(ea);
} else {
if (edge.left) {
ea = edge.left.index;
} else {
ea = edge.right.index;
}
polygons[ea].type = -99; // map border
}
})
i.neighbors = neighbors;
});
console.timeEnd("detectNeighbors");
}
function addMountain() {
var x = Math.floor(Math.random() * mapWidth / 2 + mapWidth / 4);
var y = Math.floor(Math.random() * mapHeight / 3 + mapHeight / 3);
var rnd = diagram.find(x, y).index;
var height = Math.random() * 0.1 + 0.9;
add(rnd, "mountain", height);
}
function addHill(count, shift) {
// shift from 0 to 0.5
for (c = 0; c < count; c++) {
var limit = 0;
do {
var height = Math.random() * 0.4 + 0.1;
var x = Math.floor(Math.random() * mapWidth * (1-shift*2) + mapWidth * shift);
var y = Math.floor(Math.random() * mapHeight * (1-shift*2) + mapHeight * shift);
var rnd = diagram.find(x, y).index;
limit ++;
} while (polygons[rnd].height + height > 0.9 && limit < 100)
add(rnd, "hill", height);
}
}
function add(start, type, height) {
var sharpness = 0.2;
var radius = 0.99;
if (type === "mountain") {radius = 0.9;}
var queue = []; // polygons to check
var used = []; // used polygons
polygons[start].height += height;
polygons[start].feature = undefined;
queue.push(start);
used.push(start);
for (i = 0; i < queue.length && height > 0.01; i++) {
if (type == "mountain") {
height = polygons[queue[i]].height * radius - height/100;
} else {
height = height * radius;
}
polygons[queue[i]].neighbors.forEach(function(e) {
if (used.indexOf(e) < 0) {
var mod = Math.random() * sharpness + 1.1 - sharpness;
if (sharpness == 0) {mod = 1;}
polygons[e].height += height * mod;
if (polygons[e].height > 1) {
polygons[e].height = 1;
}
polygons[e].feature = undefined;
queue.push(e);
used.push(e);
}
});
}
}
function addRange(mod) {
var count = Math.abs(mod);
for (c = 0; c < count; c++) {
var from, to, diff = 0;
do {
var xf = Math.floor(Math.random() * (mapWidth*0.7)) + mapWidth*0.15;
var yf = Math.floor(Math.random() * (mapHeight*0.6)) + mapHeight*0.2;
from = diagram.find(xf, yf).index;
var xt = Math.floor(Math.random() * (mapWidth*0.7)) + mapWidth*0.15;
var yt = Math.floor(Math.random() * (mapHeight*0.6)) + mapHeight*0.2;
to = diagram.find(xt, yt).index;
diff = Math.hypot(xt - xf, yt - yf);
} while (diff < 180 || diff > 400)
var range = [];
if (from && to) {
for (var l = 0; from != to && l < 1000; l++) {
var min = 10000;
polygons[from].neighbors.forEach(function(e) {
diff = Math.hypot(polygons[to].data[0] - polygons[e].data[0], polygons[to].data[1] - polygons[e].data[1]);
if (Math.random() > 0.5) {diff = diff/2}
if (diff < min) {
min = diff;
from = e;
}
});
range.push(from);
}
}
if (range.length > 0) {
var change = Math.random() * 0.4 + 0.1;
var query = [];
var used = [];
for (var i = 1; change > 0.01; i++) {
var rnd = Math.random() * 0.4 + 0.8;
change -= i / 30 * rnd;
range.map(function(r) {
polygons[r].neighbors.forEach(function(e) {
if (used.indexOf(e) == -1 && Math.random() > 0.2 && change > 0) {
query.push(e);
used.push(e);
if (mod > 0) {
polygons[e].height += change;
if (polygons[e].height > 1) {polygons[e].height = 1;}
} else if (polygons[e].height >= 0.2) {
polygons[e].height -= change;
if (polygons[e].height < 0.1) {
polygons[e].height = 0.13 + i/100;
if (polygons[e].height >= 0.2) {polygons[e].height = 0.19;}
}
}
}
});
range = query.slice();
});
}
}
}
}
function addPit(count) {
for (c = 0; c < count; c++) {
var change = Math.random() * 0.3 + 0.2;
var limit = 0; // iterations limit
do {
rnd = Math.floor(Math.random() * polygons.length);
limit++;
} while (polygons[rnd].height < 0.17 && limit < 100)
var query = [rnd], used = [];
for (var i = 1; change > 0.01; i++) {
var rnd = Math.random() * 0.4 + 0.8;
change -= i / 60 * rnd;
query.map(function(p) {
polygons[p].neighbors.forEach(function(e) {
if (used.indexOf(e) == -1 && change > 0) {
query.push(e);
used.push(e);
polygons[e].height -= change;
if (polygons[e].height < 0.1) {
polygons[e].height = 0.1 + i/100;
if (polygons[e].height >= 0.2) {polygons[e].height = 0.19;}
}
}
});
});
}
}
}
function getNoise(nx, ny) {
return simplex.noise2D(nx, ny) / 2 + 0.5;
}
function addNoise() {
console.time("addNoise");
pointsHeights = [], pointsCellHeights = [], pointsCells = [];
// SimplexNoise by Jonas Wagner
simplex = new SimplexNoise();
d3.range(0, mapHeight).forEach(function(y) {
d3.range(0, mapWidth).forEach(function(x) {
var cell = diagram.find(x, y).index;
if (!pointsCells[y]) {pointsCells[y] = [];}
pointsCells[y][x] = cell;
var cellHeight = polygons[cell].height;
if (cellHeight >= 0.2) {
var nx = x / mapWidth - 0.5;
var ny = y / mapHeight - 0.5;
var noise = getNoise(2 * nx, 2 * ny) / 2;
noise += getNoise(4 * nx, 4 * ny) / 4;
noise += getNoise(8 * nx, 8 * ny) / 8;
var height = (cellHeight * 2 + noise) / 3;
if (height < 0.2) {height = 0.2;}
pointsCellHeights.push(height);
height += (Math.floor(Math.random() * 3) - 1) / 100;
pointsHeights.push(height);
} else {
pointsCellHeights.push(cellHeight);
pointsHeights.push(cellHeight);
}
});
});
console.timeEnd("addNoise");
}
function showNoise() {
coastline.selectAll("*").remove();
hCells.selectAll("*").remove();
polygons.forEach(function(i) {
x = Math.floor(i.data[0]);
y = Math.floor(i.data[1]);
var height = pointsCellHeights[x + y * mapWidth];
if (height >= 0.2) {
var clr = bright(1 - height);
hCells.append("path")
.attr("d", "M" + i.join("L") + "Z")
.attr("stroke", clr)
.attr("fill", clr);
}
});
}
function applyNoise() {
polygons.forEach(function(i) {
if (i.height >= 0.2) {
x = i.data[0];
y = i.data[1];
i.height = pointsCellHeights[x + y * mapWidth];
}
});
}
function drawMapBase(style) {
console.time("drawMapBase");
// remove map base elements to redraw
mapCells.selectAll("*").remove();
mapContours.selectAll("*").remove();
// detect color scheme with Evil
var color = eval(mapColor.value), clr;
// set backgroud color for islands
if (mapStyle.value === "map_flat") {
clr = "#f9f9eb";
} else if (mapStyle.value === "map_shaded") {
clr = sepia(0.2);
} else if (mapType.value === "heightmap") {
clr = color(0.75);
} else {
var temp = Math.floor(20 - (temperatureInput.value - 12) / 0.2);
if (temp > 99) {temp = 99;} // max value
if (temp < 0) {temp = 0;} // min value
clr = biomGrad[1][temp];
}
d3.selectAll(".islandBack").attr("fill", clr);
// "polygonal" map style
if (mapStyle.value === "map_polygonal") {
polygons.map(function(i) {
if (i.height >= 0.2) {
var clr;
if (mapType.value === "heightmap") {clr = color(1 - i.height);}
if (mapType.value === "biomes") {clr = biomColors[i.biom];}
mapCells.append("path")
.attr("shape-rendering", "geometricPrecision")
.attr("d", "M" + i.join("L") + "Z")
.attr("fill", clr)
.attr("stroke", clr)
.attr("stroke-width", 0.7);
}
});
}
// 'triangled' map style
if (mapStyle.value === "map_triangled") {
diagram.edges.forEach(function(e) {
if (e.left && e.right) {
var clrR, clrL, hDelta = 0;
var hLeft = polygons[e.left.index].height;
var hRight = polygons[e.right.index].height;
if (hLeft >= 0.2 || hRight >= 0.2) {
var dR = e[0][0] + " " + e[0][1] + " L" + e.right[0] + " " + e.right[1] + " L" + e[1][0] + " " + e[1][1];
var dL = e[0][0] + " " + e[0][1] + " L" + e.left[0] + " " + e.left[1] + " L" + e[1][0] + " " + e[1][1];
hDelta = hRight - hLeft;
if (mapType.value === "heightmap") {
clrR = d3.hsl(color(1 - hRight + hDelta / 3));
clrL = d3.hsl(color(1 - hLeft - hDelta / 3));
}
if (mapType.value === "biomes") {
var cR = polygons[e.right.index].biomColor;
var cL = polygons[e.left.index].biomColor;
clrR = d3.hsl(d3.interpolateLab(cR, cL)(0.33));
clrL = d3.hsl(d3.interpolateLab(cL, cR)(0.33));
}
if (hLeft >= 0.2 && hRight >= 0.2) {
clrR = clrR.darker(hDelta * 2 * hRight);
if (hDelta > 0.02) {
clrR.l -= hDelta / 4;
}
clrL = clrL.darker(hDelta);
}
mapCells.append("path")
.attr("d", "M" + dR + "Z")
.attr("fill", clrR);
mapCells.append("path")
.attr("d", "M" + dL + "Z")
.attr("fill", clrL);
}
}
})
}
// 'noisy' map style
if (mapStyle.value === "map_noisy") {
if (mapType.value === "heightmap") {
mapCells.selectAll("path")
.data(d3.contours()
.size([mapWidth, mapHeight])
.thresholds(d3.range(0.2, 0.9, 0.04))
(pointsHeights))
.enter().append("path")
.attr("d", d3.geoPath())
.attr("fill", function(d) { return color(0.95-d.value);});
}
if (mapType.value === "biomes") {
mapCells.selectAll("path")
.data(d3.contours()
.size([mapWidth, mapHeight])
.thresholds(d3.range(0, 22))
.smooth(false)
(pointsBiomes))
.enter().append("path")
.attr("d", d3.geoPath())
.attr("fill", function(d) {return biomColors[d.value]});
}
}
// 'contours' map style
if (mapStyle.value === "map_contours") {
mapCells.selectAll("path")
.data(d3.contours()
.size([mapWidth, mapHeight])
.thresholds(d3.range(0.2, 0.9, 0.04))
(pointsHeights))
.enter().append("path")
.attr("d", d3.geoPath())
.attr("fill", function(d) {return color(0.95-d.value);});
var data = d3.contours()
.size([mapWidth, mapHeight])
.thresholds(d3.range(0.22, 0.9, 0.04))
(pointsCellHeights);
var contours = mapContours.selectAll("p")
.data(data).enter().append("g")
.attr("shape-rendering", "geometricPrecision");
contours.data(data).append("path")
.attr("d", d3.geoPath())
.attr("class", "contoursShade")
.attr("transform", "translate("+0.5/scale+" "+0.6/scale+")")
.attr("opacity", 4/scale)
.attr("fill", function(d) { return d3.hsl(color(0.95-d.value)).darker(2)});
contours.data(data).append("path")
.attr("d", d3.geoPath())
.attr("fill", function(d) { return color(0.95-d.value)});
}
// 'relaxed' map style
if (mapStyle.value === "map_relaxed") {
var cont = [];
lineGen.curve(d3.curveBasisClosed);
if (mapType.value === "biomes") {
console.time("range")
var range = [...new Set(pointsBiomes)];
range.shift();
range.sort(sortNumber);
console.timeEnd("range")
var data = d3.contours()
.size([mapWidth, mapHeight])
.thresholds(d3.range(0, 22))
.smooth(false)
(pointsBiomes)
range.forEach(function(d) {
cont[d] = [data[d]];
var el = defs.data(cont[d]).append("path").attr("d", d3.geoPath());
var path = el.node().getPathData();
var elements = [{scX:path[0].values[0], scY:path[0].values[1]}];
var p = "";
for (var s = 1; s < path.length; s++) {
if (path[s].type == "M") {
if (elements.length > 8) {
p += lineGen(elements);
}
elements = [];
}
if (path[s].type != "Z" && s % 4 == 1) {
elements.push({scX:path[s].values[0], scY:path[s].values[1]});
}
}
mapCells.append("path")
.attr("id", d).attr("d", p)
.attr("fill", biomColors[d])
.attr("shape-rendering", "geometricPrecision");
});
} else {
var range = d3.range(0.22, 0.9, 0.04);
var data = d3.contours()
.size([mapWidth, mapHeight])
.thresholds(range)
(pointsCellHeights);
range.forEach(function(d, i) {
cont[i] = [data[i]];
var clr = color(0.95-d.toFixed(2));
var el = defs.data(cont[i]).append("path").attr("d", d3.geoPath());
var path = el.node().getPathData();
var p = "";
if (path.length > 0) {
var elements = [{scX:path[0].values[0], scY:path[0].values[1]}];
}
for (var s = 1; s < path.length; s++) {
if (path[s].type == "M") {
if (elements.length > 8) {p += lineGen(elements);}
elements = [];
}
if (path[s].type != "Z" && s % 4 == 1) {
elements.push({scX:path[s].values[0], scY:path[s].values[1]});
}
}
mapCells.append("path").attr("d", p).attr("fill", clr)
.attr("shape-rendering", "geometricPrecision");
});
}
}
// 'shaded' map style
if (mapStyle.value === "map_shaded") {
mapCells.selectAll("path")
.data(d3.contours()
.size([mapWidth, mapHeight])
.thresholds(d3.range(0.2, 0.9, 0.04))
(pointsCellHeights))
.enter().append("path")
.attr("d", d3.geoPath())
.attr("fill", function(d) {return sepia(d.value);})
.attr("stroke", "none")
.attr("filter", "url(#blurFilter)");
}
console.timeEnd("drawMapBase");
}
function drawHeightmap(change) {
hCells.selectAll("*").remove();
var nonzero = $.grep(polygons, function(e) {return (e.height);});
nonzero.map(function(i) {
if (change) {i.height += change;}
if (i.height > 1) {i.height = 1;}
if (i.height < 0) {i.height = 0;}
var clr = bright(1 - i.height);
hCells.append("path")
.attr("d", "M" + i.join("L") + "Z")
.attr("stroke", clr)
.attr("fill", clr);
});
}
// Draw edgy coastline for the Journey
function mockCoastline() {
coastline.selectAll("*").remove();
var edges = [];
for (var i = 0; i < polygons.length; i++) {
if (polygons[i].height >= 0.2) {
var cell = diagram.cells[i];
cell.halfedges.forEach(function(e) {
var edge = diagram.edges[e];
if (edge.left && edge.right) {
var ea = edge.left.index;
if (ea === i) {ea = edge.right.index;}
if (polygons[ea].height < 0.2) {
var start = edge[0].join(" ");
var end = edge[1].join(" ");
edges.push({start, end});
polygons[ea].type = -1;
}
}
})
}
}
lineGen.curve(d3.curveLinear);
var line = getContinuousLine(edges, 0, 0);
coastline.append("path").attr("d", line);
}
// Draw selection
function drawSelection() {
selected.selectAll("*").remove();
selection.map(function(i) {
selected.append("path")
.attr("d", "M" + polygons[i].join("L") + "Z");
});
}
function getPrecipitation(prec) {
if (prec > 0.9) {return 0;}
if (prec > 0.75) {return 1;}
if (prec > 0.6) {return 2;}
if (prec > 0.5) {return 3;}
if (prec > 0.4) {return 4;}
if (prec > 0.3) {return 5;}
if (prec > 0.2) {return 6;}
if (prec > 0.05) {return 7;}
if (prec > 0.02) {return 8;}
return 9;
}
// Mark and name features (ocean, lakes, isles)
function markFeatures() {
console.time("markFeatures");
var queue = [], island = 0, lake = 0, number = 0, type, name, greater = 0, less = 0;
var start = diagram.find(0, 0).index; // start for top left corner to define Ocean first
var unmarked = [polygons[start]];
while (unmarked.length > 0) {
if (unmarked[0].height >= 0.2) {
type = "Island";
number = island;
island += 1;
greater = 0.2;
less = 100; // just to omit exclusion
} else {
type = "Lake";
number = lake;
lake += 1;
greater = -100; // just to omit exclusion
less = 0.2;
}
if (type == "Lake" && number == 0) {type = "Ocean";}
start = unmarked[0].index;
queue.push(start);
polygons[start].feature = type;
polygons[start].featureNumber = number;
while (queue.length > 0) {
var i = queue[0];
queue.shift();
polygons[i].neighbors.forEach(function(e) {
if (!polygons[e].feature && polygons[e].height >= greater && polygons[e].height < less) {
polygons[e].feature = type;
polygons[e].featureNumber = number;
queue.push(e);
}
if (type == "Island" && polygons[e].height < 0.2) {
polygons[i].type = 1;
polygons[e].type = -1;
}
});
}
unmarked = $.grep(polygons, function(e) {return (!e.feature);});
}
console.timeEnd("markFeatures");
}
function drawOcean(limits, colors) {
console.time("drawOcean");
// Mark distances
var frontier = $.grep(polygons, function(e) {return (e.type === -1);});
for (var c = -2; frontier.length > 0 && c > limits[0]; c--) {
frontier.map(function(i) {
i.neighbors.forEach(function(e) {
if (!polygons[e].type) {polygons[e].type = c;}
});
});
frontier = $.grep(polygons, function(e) {return (e.type === c);});
}
// Define area edges
for (var c = 0; c < limits.length; c++) {
var edges = [];
for (var i = 0; i < polygons.length; i++) {
if (polygons[i].feature === "Ocean" && polygons[i].type >= limits[c]) {
var cell = diagram.cells[i];
cell.halfedges.forEach(function(e) {
var edge = diagram.edges[e];
if (edge.left && edge.right) {
var ea = edge.left.index;
if (ea === i) {ea = edge.right.index;}
var type = polygons[ea].type;
if (type < limits[c] || type == undefined) {
var start = edge[0].join(" ");
var end = edge[1].join(" ");
edges.push({start, end});
}
} else {
var start = edge[0].join(" ");
var end = edge[1].join(" ");
edges.push({start, end});
}
})
}
}
lineGen.curve(d3.curveBasisClosed);
var relax = 0.8-c/10;
if (relax < 0.2) {relax = 0.2};
var line = getContinuousLine(edges, 0, relax);
ocean.append("path").attr("d", line).attr("fill", colors[c]);
}
// Define ocean fill-rule
if (mapTemplate === "Atoll") {
ocean.attr("fill-rule", "nonzero");
} else {
ocean.attr("fill-rule", "evenodd");
}
console.timeEnd("drawOcean");
}
// Detect and draw the coasline
function drawCoastline() {
console.time('drawCoastline');
var oceanEdges = [], lakeEdges = [], seashore = [];
for (var i = 0; i < polygons.length; i++) {
if (polygons[i].height >= 0.2) {
var cell = diagram.cells[i];
cell.halfedges.forEach(function(e) {
var edge = diagram.edges[e];
if (edge.left && edge.right) {
var ea = edge.left.index;
if (ea === i) {ea = edge.right.index;}
if (polygons[ea].height < 0.2) {
var start = edge[0].join(" ");
var end = edge[1].join(" ");
if (polygons[ea].feature === "Lake") {
lakeEdges.push({start, end});
} else {
oceanEdges.push({start, end});
}
var x = (edge[0][0] + edge[1][0]) / 2;
var y = (edge[0][1] + edge[1][1]) / 2;
// Add costline edge's centers to array to use later to place manors
seashore.push({cell: i, x, y});
}
}
})
}
}
lineGen.curve(d3.curveCatmullRom); //curveBasis, curveStep, curveCatmullRom;
var line = getContinuousLine(oceanEdges, 1.5, 0);
defs.select("#shape").append("path").attr("d", line);
islandBack.append("path").attr("d", line); // draw the landmass
coastline.append("path").attr("d", line).attr("class", "coastShade");; // draw the coastline
line = getContinuousLine(lakeEdges, 1.5, 0);
lakecoast.append("path").attr("d", line); // draw the lakes
console.timeEnd('drawCoastline');
}
function getContinuousLine(edges, indention, relax) {
var line = "";
while (edges.length > 2) {
var edgesOrdered = []; // to store points in a correct order
var start = edges[0].start;
var end = edges[0].end;
edges.shift();
var spl = start.split(" ");
edgesOrdered.push({scX: spl[0], scY: spl[1]});
spl = end.split(" ");
edgesOrdered.push({scX: spl[0], scY: spl[1]});
var x0 = spl[0];
var y0 = spl[1];
for (var i = 0; end !== start && i < 2000; i++) {
var next = $.grep(edges, function(e) {return (e.start == end || e.end == end);});
if (next.length > 0) {
if (next[0].start == end) {
end = next[0].end;
} else if (next[0].end == end) {
end = next[0].start;
}
spl = end.split(" ");
var dist = Math.hypot(spl[0] - x0, spl[1] - y0);
if (dist >= indention && Math.random() > relax) {
edgesOrdered.push({scX: spl[0], scY: spl[1]});
x0 = spl[0];
y0 = spl[1];
}
}
var rem = edges.indexOf(next[0]);
edges.splice(rem, 1);
}
line += lineGen(edgesOrdered) + "Z ";
}
return line;
}
// Onclick actions
function clicked() {
if (journeyStep == 1) {
var point = d3.mouse(this),
cell = diagram.find(point[0], point[1]).index,
status = map_mode.getAttribute("status");
if (status == 1) {
var power = +$("#change_power").text();
if ($("#draw_increase").attr("status") == 1) {polygons[c2].height += power;}
if ($("#draw_decrease").attr("status") == 1) {polygons[c2].height -= power;}
if ($("#draw_erase").attr("status") == 1) {polygons[c2].height = 0;}
if ($("#draw_smooth").attr("status") == 1) {
var heights = [polygons[cell].height];
polygons[cell].neighbors.forEach(function(e) {heights.push(polygons[e].height);});
polygons[cell].height = d3.mean(heights);
}
drawHeightmap();
mockCoastline();
} else if (status == 2) {
var index = selection.indexOf(cell);
if (index == -1) {
if (cell_line.getAttribute("start") == "") {
selection.push(cell);
} else { // cell_line - add highlighted to selection
addHighlighted();
cell_line.setAttribute("start", cell);
}
} else {
if (cell_line.getAttribute("start") == "") {
selection.splice(index, 1);
} else { // cell_line - add highlighted to selection
addHighlighted();
cell_line.setAttribute("start", "");
}
}
drawSelection();
}
}
}
// Drag actions
function dragstarted() {
var redraw = 0;
var x0 = d3.event.x,
y0 = d3.event.y,
c0 = diagram.find(x0, y0).index,
c1 = c0;
var mode = selection.indexOf(c0);
d3.event.on("drag", function() {
var x1 = d3.event.x,
y1 = d3.event.y,
c2 = diagram.find(x1, y1).index;
if (c2 !== c1 && journeyStep == 1) {
c1 = c2;
status = map_mode.getAttribute("status");
if (status == 2) {
var state = selection.indexOf(c2);
if (mode == -1 && state == -1) {
selection.push(c2);
drawSelection();
} else if (mode != -1 && state != -1) {
selection.splice(state, 1);
drawSelection();
}
} else if (status == 1) {
var power = +$("#change_power").text();
if ($("#draw_increase").attr("status") == 1) {polygons[c2].height += power;}
if ($("#draw_decrease").attr("status") == 1) {polygons[c2].height -= power;}
if ($("#draw_align").attr("status") == 1) {polygons[c2].height = polygons[c0].height;}
if ($("#draw_erase").attr("status") == 1) {polygons[c2].height = 0;}
if ($("#draw_smooth").attr("status") == 1) {
var heights = [polygons[c2].height];
polygons[c2].neighbors.forEach(function(e) {heights.push(polygons[e].height);});
polygons[c2].height = d3.mean(heights);
}
;
highlighting.push(c2);
highlighting.map(function(h) {
highlighted.append("path").attr("d", "M" + polygons[h].join("L") + "Z");
});
redraw = 1;
}
}
}).on("end", function() {
if (redraw == 1) {
drawHeightmap();
mockCoastline();
highlighting = [];
highlighted.selectAll("*").remove();
}
});
}
function dragCloudstarted() {
var angle, winds;
d3.event.on("drag", function() {
var x = d3.event.x;
var y = d3.event.y + 20;
x = Math.max(40, Math.min(mapWidth-40, x));
y = Math.max(40, Math.min(mapHeight, y));
angle = Math.atan2(mapHeight / 2 - y, mapWidth / 2 - x);
cloud.selectAll("text").attr("x", x).attr("y", y);
var arrow = cloud.selectAll("#cloudArrow").node().getBBox();
var arrowX = arrow.x + arrow.width / 2;
var arrowY = arrow.y + arrow.height / 2;
degree = angle * 180 / Math.PI;
var azimuth = degree;
if (degree < 0) {azimuth = 360 + degree;}
if (azimuth >= 90) {azimuth -= 90;} else {azimuth += 270;}
winds = parseWinds(degree);
hintbar.innerHTML = "Prevailing winds: " + winds + " (" + Math.floor(azimuth) + "º)";
cloud.selectAll("#cloudArrow").attr("transform", "rotate("+degree+" "+arrowX+" "+arrowY+")");
}).on("end", function() {
simulateWind(angle, winds);
});
}
function parseWinds(d) {
if (d > 40 && d <= 140) {return "North";}
if (d > 140 && d < 160) {return "North-East";}
if (Math.abs(d) >= 160) {return "East";}
if (d > -160 && d < -140) {return "South-East";}
if (d > -140 && d <= -40) {return "South";}
if (d > -40 && d < -20) {return "South-West";}
if (Math.abs(d) <= 20) {return "West";}
if (d > 20 && d < 40) {return "North-West";}
}
function addHighlighted() {
highlighting.map(function(i) {
if (selection.indexOf(i) == -1) {selection.push(i);}
});
highlighted.selectAll("*").remove();
highlighting = [];
}
function clear() {
console.log("----------");
svg.transition().duration(3000).call(zoom.transform, d3.zoomIdentity);
// Remove all on regenerate
container.selectAll("path").remove();
debug.selectAll("circle").remove();
//debug.selectAll("text").remove();
defs.selectAll("path").remove();
}
// Random map
$("#new_random").click(function() {
clear();
newRandomMap();
});
// Random map Routine
function newRandomMap() {
console.time(" RandomMap");
base.transition().duration(3000).attr("fill", "#5167a9");
$("#explore").text("Explore the Map");
calculateVoronoi(samples);
detectNeighbors();
generateHeightmap();
markFeatures();
drawOcean([-6,-3,-1], ["#5E78B6","#6d8cc5","#7EABD5"]);
reGraph();
collectStats();
addNoise();
applyNoise();
//randomizePrecipitation();
//calculatePrecipitation();
unifyPrecipitation(); // temp
resolveDepressions();
//downcutRivers();
drawCoastline();
//defineBiomes();
drawMapBase();
console.timeEnd(" RandomMap");
}
// Select Heighmap Template
function generateHeightmap() {
console.time("generateHeightmap");
var rnd = Math.random();
if (rnd > 0.8) {templateHighIsland();}
if (rnd > 0.55 && rnd <= 0.8) {templateLowIsland();}
if (rnd > 0.3 && rnd <= 0.55) {templateIsles();}
if (rnd > 0.05 && rnd <= 0.3) {templateArchipelago();}
if (rnd <= 0.05) {templateAtoll();}
downgradeBorders();
smoothOcean();
console.timeEnd("generateHeightmap");
}
// Heighmap Template: High Island
function templateHighIsland() {
mapTemplate = "High Island";
addMountain();
modifyHeights("all",0.04,0.9);
addHill(8, 0.4);
addHill(8, 0.33);
addRange(-2);
addPit(3);
modifyHeights("land",0,0.6);
addRange(2);
}
// Heighmap Template: Low Island
function templateLowIsland() {
mapTemplate = "Low Island";
addMountain();
modifyHeights("all",0.03,0.9);
addHill(8, 0.4);
addHill(8, 0.33);
addRange(-2);
addPit(3);
modifyHeights("land",0,0.3);
}
// Heighmap Template: Continental Islands
function templateIsles() {
mapTemplate = "Continental Islands";
addMountain();
addHill(14, 0.25);
addRange(-5);
modifyHeights("land",0,0.9);
}
// Heighmap Template: Archipelago
function templateArchipelago() {
mapTemplate = "Archipelago";
addMountain();
modifyHeights("land",-0.2,1);
addHill(15, 0.15);
addRange(-2);
addPit(8);
modifyHeights("land",-0.05,0.9);
}
// Heighmap Template: Atoll
function templateAtoll() {
mapTemplate = "Atoll";
addMountain();
addHill(8, 0.4);
modifyHeights("land",0.1,1);
var frontier = [];
polygons.map(function(i) {
if (i.height) {
var heights = [i.height];
i.neighbors.forEach(function(e) {heights.push(polygons[e].height);});
i.height = d3.mean(heights);
}
});
polygons.map(function(i) {
if (i.height < 0.18) {i.height -= 0.06;}
if (i.height > 0.21) {i.height = 0.17;}
if (i.height >= 0.18 && Math.random() > 0.2) {
i.height = 0.20 * (Math.random() * 0.1 + 1);
frontier.push(i.index);
}
});
var change = 0.05;
var used = frontier.slice();
while (change > 0.01) {
var queue = [];
change -= 0.01 * (Math.random() * 0.4 + 0.8);
frontier.map(function(i) {
polygons[i].neighbors.forEach(function(e) {
if (used.indexOf(e) == -1) {
used.push(e);
queue.push(e);
polygons[e].height += change * (Math.random() * 0.2 + 0.9);
}
});
frontier = queue.slice();
});
}
}
// recalculate Voronoi Graph to relax Ocean and pack coast cells
function reGraph() {
console.time("reGraph");
var points = [], data = [];
var coastal = $.grep(polygons, function(e) {return (e.type == 1 || e.type == -1);});
console.log("coastal polygons: " + coastal.length);
polygons.map(function(i) {
//debug.append("text").attr("x", i.data[0]).attr("y", i.data[1]).attr("font-size", 2).attr("fill", "black").text(i.type);
if (i.type == 1 || i.type == -1) {
var x0 = i.data[0];
var y0 = i.data[1];
points.push([x0, y0]);
data.push({height:i.height, type:i.type, feature:i.feature, number:i.featureNumber});
//if (i.type == -1) {debug.append("circle").attr("cx", x0).attr("cy", y0).attr("r", 0.3).attr("fill", "white");}
// add new points
i.neighbors.forEach(function(e) {
if (polygons[e].type == i.type) {
var x1 = Math.ceil((x0 * 2 + polygons[e].data[0]) / 3 - 0.5 + Math.random());
var y1 = Math.ceil((y0 * 2 + polygons[e].data[1]) / 3 - 0.5 + Math.random());
var copy = $.grep(points, function(e) {return (e[0] == x1 && e[1] == y1);});
if (!copy.length) {
points.push([x1, y1]);
data.push({height:i.height, type:i.type, feature:i.feature, number:i.featureNumber});
//if (i.type == -1) {debug.append("circle").attr("cx", x1).attr("cy", y1).attr("r", 0.3).attr("fill", "white");}
}
};
});
} else if (i.height >= 0.20) {
points.push([i.data[0], i.data[1]]);
data.push({height:i.height, type:i.type, feature:i.feature, number:i.featureNumber});
}
});
calculateVoronoi(points);
polygons.map(function(i, d) {
i.index = d;
i.height = data[d].height;
i.type = data[d].type;
i.feature = data[d].feature;
i.featureNumber = data[d].number;
i.precipitation = 0;
var neighbors = []; // re-detect neighbors
diagram.cells[d].halfedges.forEach(function(e) {
var edge = diagram.edges[e], ea;
if (edge.left && edge.right) {
ea = edge.left.index;
if (ea === d) {ea = edge.right.index;}
neighbors.push(ea);
}
})
i.neighbors = neighbors;
});
//findCells(points);
console.timeEnd("reGraph");
}
// collect and display map stats
function collectStats() {
console.log(" Map stats:");
var land = $.grep(polygons, function(e,d) {return (e.height >= 0.2);});
var landmass = Math.floor(land.length / polygons.length * 100);
var heights = [];
land.map(function(i) {heights.push(i.height);});
var avElevation = d3.mean(heights);
console.log(" Heighmat template: "+mapTemplate)
console.log(" Polygons initial: "+samples.length);
console.log(" Polygons repacked: "+polygons.length);
console.log(" Land / Ocean polygons: "+land.length+"/"+(polygons.length-land.length));
console.log(" Average land elevation: "+avElevation.toFixed(2));
}
// Downgrade near-border cells (prevent non-island formation errors)
function downgradeBorders() {
var borderCells = $.grep(polygons, function(e) {return (e.type == -99);});
borderCells.map(function(i) {
i.neighbors.forEach(function(e) {polygons[e].height = 0;});
});
}
// Smooth Ocean
function smoothOcean() {
var frontier = $.grep(polygons, function(e) {return (e.type === -1);});
for (var c = -2; frontier.length > 0 && c > -6; c--) {
frontier.map(function(i) {
polygons[i.index].height = 0.2 + (c+1)/50;
i.neighbors.forEach(function(e) {
if (!polygons[e].type && polygons[e].height < 0.2) {polygons[e].type = c;}
});
});
frontier = $.grep(polygons, function(e) {return (e.type === c);});
}
}
// Modify heights multiplying/adding by value
function modifyHeights(type, add, mult) {
polygons.map(function(i) {
if (type === "land") {
if (i.height >= 0.2) {
i.height += add;
var dif = i.height - 0.2;
var factor = mult;
if (mult == "^2") {factor = dif}
if (mult == "^3") {factor = dif * dif;}
i.height = 0.2 + dif * factor;
}
}
if (type === "ocean") {
if (i.height < 0.2 && i.height > 0) {
i.height += add;
i.height *= mult;
}
}
if (type === "all") {
if (i.height > 0) {
i.height += add;
i.height *= mult;
}
}
});
}
// Explore map
$("#explore").click(function() {
zoom.on("zoom", zoomed);
container.transition().duration(3000).attr("transform", "translate(0 0)");
svg.select("#rose").transition().duration(3000).attr("transform", "translate(75 75) scale(0.2)");
if (journeyStep == 0) {
$("#initial, #rose").fadeOut();
$("#toolbar, #hintbar").fadeIn();
journeyStep = 1;
$("#toolbar_step1").fadeIn();
hintbar.innerHTML = stepHint[journeyStep];
} else if (journeyStep == -1) {
$("#initial").fadeOut();
}
});
// New Journey
$("#new_journey").click(function() {
zoom.on("zoom", zoomed);
journeyStep = 1;
d3.select(".mottling").attr("display", "none");
$("#initial, #rose").fadeOut();
$("#toolbar, #hintbar").fadeIn();
$("#toolbar_step1").fadeIn();
base.transition().duration(3000).attr("fill", "#5e4fa2");
svg.append("text").text(stepName[journeyStep]).attr("class", "step")
.attr("x", mapWidth/6).attr("y", mapHeight/3).transition()
.duration(3000).style("font-size", "50px").style("fill-opacity", 1)
.transition().delay(1000)
.duration(2500).style("font-size", "0px").style("fill-opacity", 0)
.remove();
clear();
calculateVoronoi(samples);
detectNeighbors();
hintbar.innerHTML = stepHint[journeyStep];
container.attr("transform", "translate(0 0)");
noiseApplied = false;
$("#precN").attr("prec", 0);
$("#precE").attr("prec", 0);
$("#precS").attr("prec", 0);
$("#precW").attr("prec", 0);
//toggleGrid();
});
// Journey Move back
function back() {
$(".step_buttons").fadeOut();
if (journeyStep == -1) {
$("#initial").fadeIn();
} else if (journeyStep == 0) {
$("#initial, #rose").fadeOut();
$("#toolbar, #hintbar").fadeIn();
hintbar.innerHTML = stepHint[journeyStep];
} else if (journeyStep == 1) {
$("#new_journey").text("New Journey");
$("#explore").text("Back to Journey");
$("#initial").fadeIn();
$("#toolbar, #hintbar").fadeOut();
}
journeyStep--;
$("#toolbar_step"+journeyStep).toggle();
}
// Journey Move forward
function next() {
if (hCells.selectAll("*").size()) {
journeyStep++;
$(".step_buttons").hide();
$("#toolbar_step"+journeyStep).toggle();
svg.append("text").text(stepName[journeyStep]).attr("class", "step")
.attr("x", mapWidth/6).attr("y", mapHeight/3).transition()
.duration(3000).style("font-size", "50px").style("fill-opacity", 1)
.transition().delay(1000).duration(2500).style("font-size", "0px")
.style("fill-opacity", 0).remove();
if (journeyStep == 2) {
viewbox.on(".drag", null);
$(".container").fadeOut("slow");
coastline.selectAll("*").remove();
hCells.selectAll("*").remove();
markFeatures();
drawOcean([-6,-3,-1], ["#5E78B6","#6d8cc5","#7EABD5"]);
reGraph();
if (noiseApplied == false) {addNoise();}
drawCoastline();
drawMapBase();
$(".container").fadeIn("slow");
}
} else {
hintbar.innerHTML = "You have to define landmass first!";
}
}
// Toolbar buttons handler
$(".toolbar_button").click(function() {
var button = $(this).attr('id');
var parentButton = $(this).parent().attr('id');
if (button == "map_mode") {
var status = map_mode.getAttribute("status");
if (status == -1) {
map_mode.setAttribute("status", 0);
$("#map_mode").text("♒");
$("#map_mode_rand").show();
hintbar.innerHTML = "Random mode: place a feature at a random point";
} else if (status == 0) {
map_mode.setAttribute("status", 1);
$("#map_mode").text("✏");
$("#map_mode_rand").hide();
$("#map_mode_draw").show();
viewbox.call(drag);
hintbar.innerHTML = "Free Draw mode: click or drag to directly change cell heights";
} else if (status == 1) {
map_mode.setAttribute("status", 2);
$("#map_mode").text("➚");
$("#map_mode_draw").hide();
$("#map_mode_selection").show();
hintbar.innerHTML = "Selection mode: click or drag to select cells";
} else if (status == 2) {
map_mode.setAttribute("status", -1);
$("#map_mode").text("☸");
$("#map_mode_selection").hide();
hintbar.innerHTML = "Click to change the mode of operation";
viewbox.on(".drag", null);
}
}
if (parentButton == "map_mode_draw") {$("#map_mode_draw > div").attr("status", 0)}
if (button == "draw_increase") {$("#draw_increase").attr("status", 1);}
if (button == "draw_decrease") {$("#draw_decrease").attr("status", 1);}
if (button == "draw_align") {$("#draw_align").attr("status", 1);}
if (button == "draw_erase") {$("#draw_erase").attr("status", 1);}
if (button == "draw_smooth") {$("#draw_smooth").attr("status", 1);}
if (button == "map_clear") {
clear();
polygons.map(function(i) {i.height = 0;});
cell_cancel.click();
}
if (button == "cell_expand") {
var list = selection.slice();
list.map(function(i) {
polygons[i].neighbors.forEach(function(e) {
if (selection.indexOf(e) == -1) {
selection.push(e);
}
});
});
drawSelection();
}
if (button == "cell_line") {
highlighted.selectAll("*").remove();
highlighting = [];
if (cell_line.getAttribute("start") == "") {
var cell = +selection[selection.length-1];
cell_line.setAttribute("start", cell);
hintbar.innerHTML = "Click to set selection end cell";
} else {
cell_line.setAttribute("start", "");
hintbar.innerHTML = "Select line of cells";
}
drawSelection();
}
if (button == "cell_cancel") {
selection = [];
$("#cellMenu").hide();
cell_line.setAttribute("start", "");
selected.selectAll("*").remove();
highlighted.selectAll("*").remove();
}
if (parentButton == "map_mode_rand") {
if (button == "rand_hill") {addHill(1, 0.25);}
if (button == "rand_range") {addRange(1);}
if (button == "rand_pit") {addPit(1);}
if (button == "rand_trough") {addRange(-1);}
drawHeightmap();
mockCoastline();
}
if (button == "map_up" || button == "map_down") {
var change = +$("#change_power").text();
var mod = 1;
if (button == "map_down") {mod = -1;}
if (selection.length > 0) {
var used = selection.slice();
var frontier = selection.slice();
frontier.map(function(i) {polygons[i].height += change * mod * (Math.random() * 0.2 + 0.9);});
while (change > 0.01) {
var queue = [];
change -= 0.01 * (Math.random() * 0.4 + 0.8);
frontier.map(function(i) {
polygons[i].neighbors.forEach(function(e) {
if (used.indexOf(e) == -1) {
used.push(e);
queue.push(e);
polygons[e].height += change * mod * (Math.random() * 0.2 + 0.9);
}
});
frontier = queue.slice();
});
}
drawHeightmap();
} else {
drawHeightmap(change * mod);
}
mockCoastline();
}
if (button == "change_power") {
if ($("#change_power").text() == ".01") {
$("#change_power").text(".05");
} else if ($("#change_power").text() == ".05") {
$("#change_power").text(".10");
} else {
$("#change_power").text(".01");
}
}
if (button == "map_relax") {
if (selection.length > 0) {
selection.map(function(i) {
var heights = [polygons[i].height];
polygons[i].neighbors.forEach(function(e) {heights.push(polygons[e].height);});
var mean = d3.mean(heights);
polygons[i].height = (polygons[i].height * 3 + mean) / 4;
});
} else {
polygons.map(function(i) {
if (i.height > 0) {
var heights = [i.height];
i.neighbors.forEach(function(e) {heights.push(polygons[e].height);});
var mean = d3.mean(heights);
i.height = (i.height * 3 + mean) / 4;
}
});
}
drawHeightmap();
mockCoastline();
}
if (button == "map_random") {
if (selection.length > 0) {
selection.map(function(i) {
if (polygons[i].height > 0) {
var power = +$("#change_power").text();
var value = Math.random() * power;
var mod = 1;
if (Math.random() > 0.5) {mod = -1;}
if (polygons[i].height < 0.2) {value /= 10;}
polygons[i].height += value * mod;
}
});
} else {
polygons.map(function(i) {
if (i.height > 0) {
var power = +$("#change_power").text();
var value = Math.random() * power;
var mod = 1;
if (Math.random() > 0.5) {mod = -1;}
if (i.height < 0.2) {value /= 10;}
i.height += value * mod;
}
});
}
drawHeightmap();
mockCoastline();
}
if (button == "map_template") {
$("#map_template_buttons").toggle();
if ($("#map_template_buttons").is(':visible')) {
$("#toolbar").css("border-radius", "0 0 4px 0");
} else {
$("#toolbar").css("border-radius", "0 4px 4px 0");
}
}
if (button == "map_noise") {$("#map_noise_buttons").toggle();}
if (button == "map_style") {$("#map_style_buttons").toggle();}
if (button == "map_rand") {$("#map_rand_buttons").toggle();}
if (parentButton == "map_template_buttons") {
clear();
polygons.map(function(i) {i.height = 0; i.type = undefined;});
cell_cancel.click();
if (button == "temp_high_island") {templateHighIsland();}
if (button == "temp_low_island") {templateLowIsland();}
if (button == "temp_isles") {templateIsles();}
if (button == "temp_archipelago") {templateArchipelago();}
if (button == "temp_atoll") {templateAtoll();}
polygons.map(function(i) {if (i.height < 0.2) {i.height = 0}});
downgradeBorders();
mockCoastline();
smoothOcean();
drawHeightmap();
}
if (button == "generate_noise") {
addNoise();
showNoise();
}
if (button == "apply_noise") {
applyNoise();
noiseApplied = true;
drawHeightmap();
mockCoastline();
}
if (button == "cancel_noise") {
drawHeightmap();
mockCoastline();
}
if (parentButton == "map_style_buttons") {
mapStyle.value = button;
drawMapBase();
}
if (button == "map_wind") {
cancelAnimationFrame(animation);
animated = false;
if ($("#main").length) {$("#canvasContainer").empty();}
$("#map_wind_buttons, .cloud").toggle();
}
if (button == "resetZoom") {svg.transition().duration(1000).call(zoom.transform, d3.zoomIdentity);}
if (button == "back") {back();}
if (button == "next") {next();}
});
// Change hint on button mouseover
$(".toolbar_button, .cellMenu_button").mouseover(function() {
var button = $(this).attr("id"), hint;
// step 1
if (button == "map_mode") {
var status = map_mode.getAttribute("status");
if (status == 1) {
hint = "Free Draw mode: click or drag to directly change cell heights";
} else if (status == 2) {
hint = "Selection mode: click or drag to select cells";
} else {
hint = "Random mode: place a feature at a random point";
}
}
if (button == "map_template") {hint = "Apply formation template (current heightmap will be lost)";}
if (button == "temp_high_island") {hint = "Apply High (Volcanic) Island formation template (overwrite current map)";}
if (button == "temp_low_island") {hint = "Apply Low Island formation template (overwrite current map)";}
if (button == "temp_isles") {hint = "Apply Continental Islands formation template (overwrite current map)";}
if (button == "temp_archipelago") {hint = "Apply Archipelago formation template (overwrite current map)";}
if (button == "temp_atoll") {hint = "Apply Atoll formation template (overwrite current map)";}
if (button == "change_power") {hint = "Change brush power";}
if (button == "draw_increase") {hint = "Increase height by brush power";}
if (button == "draw_decrease") {hint = "Decrease height by brush power";}
if (button == "draw_align") {hint = "Spread the clicked cell height";}
if (button == "draw_erase") {hint = "Set cell height to zero";}
if (button == "draw_smooth") {hint = "Smooth by taking neigbours cells mean height";}
if (button == "map_clear") {hint = "Clear the map (all the data will be lost!)";}
if (button == "map_up") {hint = "Decrease selection height";}
if (button == "map_down") {hint = "Increase selection height";}
if (button == "map_relax") {hint = "Smooth the selection";}
if (button == "map_random") {hint = "Randomize the selection";}
if (button == "map_noise") {hint = "Overlay with random noise layer";}
if (button == "map_rand") {hint = "Randomly Place a feature";}
if (button == "rand_hill") {hint = "Place a random Hill";}
if (button == "rand_range") {hint = "Place a random Range";}
if (button == "rand_pit") {hint = "Place a random Pit";}
if (button == "rand_trough") {hint = "Place a random Trough";}
// step 2
if (button == "map_temp") {hint = "Set average annual temperature. Temperature depends this value and land altitude";}
if (button == "map_wind") {hint = "Toggle wind map. Drag the Cloud to set a prevailing winds";}
if (button == "map_style") {hint = "Select base map rendering style";}
if (button == "map_polygonal") {hint = "Polygonal map rendering style";}
if (button == "map_noisy") {hint = "Noisy map rendering style";}
if (button == "map_relaxed") {hint = "Relaxed map rendering style";}
if (button == "map_contours") {hint = "Contours map rendering style";}
if (button == "map_triangled") {hint = "Triangled map rendering style";}
if (button == "map_shaded") {hint = "Shaded map rendering style";}
if (button == "map_flat") {hint = "Flat map rendering style";}
// general buttons
if (button == "resetZoom") {hint = "Restore default zoom";}
if (button == "download") {hint = "Save map in SVG";}
if (button == "back") {hint = "Back to the previous step";}
if (button == "next") {hint = "Go to the next step";}
// cell buttons
if (button == "cell_expand") {hint = "Expand the selection";}
if (button == "cell_line") {hint = "Select line of cells";}
if (button == "cell_cancel") {hint = "Deselect all cells";}
hintbar.innerHTML = hint;
}).mouseout(function() {
hintbar.innerHTML = stepHint[journeyStep];
});
// redraw map on options change
$("#mapStyle, #mapColor, #mapType").change(function() {
drawMapBase();
});
// recalculate Temperature and redraw map on option change
$("#temperatureInput").change(function() {
defineBiomes();
drawMapBase();
});
// Change Initial Precipitation on cloud click (journey step 2)
function changePrec() {
var prec = +$(this).attr("prec");
prec++;
if (prec>5) {prec=0;}
$(this).attr("prec", prec);
$(this).attr("fill", blue(prec/6));
var text = "Click to change the fainfall power. Current value: ";
hintbar.innerHTML = text + prec;
updatePrec();
}
// Show hint on Cloud mousemove (journey step 2)
function changePrecHint() {
var prec = +$(this).attr("prec");
var text = "Click to change the fainfall power. Current value: ";
hintbar.innerHTML = text + prec;
}
// recalculate Precipitation and redraw map on option change
function updatePrec() {
riversShade.selectAll("path").remove();
rivers.selectAll("path").remove();
polygons.map(function(i) {
i.precipitation = 0;
i.flux = undefined;
i.river = undefined;
})
calculatePrecipitation();
//defineBiomes();
}
// redraw coastline on coast style change
$("#coastStyle").change(function() {
drawCoastline();
});
// Draw of remove blur polygons on input change
$("#blurInput").change(function() {
if (blurInput.checked == true) {
d3.selectAll(".mapCells")
.attr("filter", "url(#blurFilter)");
} else {
d3.selectAll(".mapCells")
.attr("filter", "");
}
});
// Switch map color scheme
function switchColor() {
d3.selectAll(".blur").remove();
if (blurInput.valueAsNumber > 0) {
var limit = 0.2;
if (seaInput.checked == true) {
limit = 0;
}
polygons.map(function(i) {
if (i.height >= limit) {
mapCells.append("path")
.attr("d", "M" + i.join("L") + "Z")
.attr("class", "blur")
.attr("stroke-width", blurInput.valueAsNumber)
.attr("stroke", bright(1 - i.height));
}
});
}
}
// Toggle polygons strokes on input change
$("#strokesInput").change(function() {
toggleGrid();
});
function toggleGrid() {
if (grid.selectAll("*").empty()) {
var d = "";
polygons.map(function(i) {
d += "M" + i.join("L") + "Z";
});
grid.append("path").attr("d", d);
} else {
grid.selectAll("*").remove();
}
}
// Toggle precipitation map
$("#fluxInput").change(function() {
if (fluxInput.checked == true) {
polygons.map(function(i) {
if (i.height >= 0.2) {
mapCells.append("path")
.attr("d", "M" + i.join("L") + "Z")
.attr("stroke", blue(i.precipitation))
.attr("fill", blue(i.precipitation))
.attr("class", "flux");
}
});
} else {
d3.selectAll(".flux").remove();
}
});
// Toggle slope hatching
$("#hatchingInput").change(function() {
if (hatchingInput.checked == true) {
console.time("slopeHatching");
d3.range(0, mapHeight).forEach(function(y) {
d3.range(0, mapWidth).forEach(function(x) {
var point = x + y * mapWidth;
var height = pointsHeights[point];
if (height >= 0.2) {
var delta = pointsHeights[point] - pointsHeights[point-1];
if (Math.abs(delta) > 0.05) {
var dx = x - 0.4 + Math.random() * 0.8;
var dy = y - 0.4 + Math.random() * 0.8;
var l = Math.abs(delta) * 50;
var h = delta * -10 * height;
if (l > 1) {l =1;}
if (l < 0.5) {l = 0.5;}
if (h > 0.4) {h = 0.4;}
if (h < -0.4) {h = -0.4;}
var d = dx+" "+dy+" l"+l+" "+h;
hatching.append("path").attr("d", "M" + d + "Z");
}
}
});
});
console.timeEnd("slopeHatching");
} else {
hatching.selectAll("path").remove();
}
});
// Descrease or increase land
function elevateLand(mod) {
polygons.map(function(i) {
if (i.height >= 0.2) {i.height += mod;}
});
}
function downcutRivers() {
console.time("downcutRivers");
var downcut = downcuttingInput.valueAsNumber;
polygons.map(function(i) {
if (i.flux >= 0.85 && i.height >= 0.21) {
i.height -= downcut / 10;
}
});
console.timeEnd("downcutRivers");
}
function randomizePrecipitation() {
var total = 0;
function rndPrec() {
if (Math.random() > 0.2) {
var prec = Math.floor(Math.random() * 12) + 6;
} else {
var prec = Math.floor(Math.random() * 18) + 2;
}
return prec;
}
Math.random() >= 0.75 ? prec = rndPrec() : prec = 0;
$("#precN").attr("prec", prec);
total = prec;
Math.random() >= 0.75 ? prec = rndPrec() : prec = 0;
$("#precE").attr("prec", prec);
total += prec;
Math.random() >= 0.75 ? prec = rndPrec() : prec = 0;
$("#precS").attr("prec", prec);
total += prec;
Math.random() >= 0.75 ? prec = rndPrec() : prec = 0;
$("#precW").attr("prec", prec);
total += prec;
if (total == 0) {$("#precW").attr("prec", 12)}
}
function calculatePrecipitation() {
console.time("calculatePrecipitation");
var precN = +$("#precN").attr("prec");
var precE = +$("#precE").attr("prec");
var precS = +$("#precS").attr("prec");
var precW = +$("#precW").attr("prec");
if (precN > 0) {
var frontier = $.grep(polygons, function(e) {
return (e.data[1] < precN * 10 && e.data[0] > mapWidth*0.1 && e.data[0] < mapWidth*0.9);
});
frontier.map(function(i) {
var x = i.data[0], y = i.data[1];
var precipitation = precN;
while (y < mapHeight * 0.9 && precipitation > 0) {
y += 10;
x += Math.floor(Math.random() * 20 - 10);
precipitation = rainfall(x, y, precipitation);
}
});
}
if (precE > 0) {
var frontier = $.grep(polygons, function(e) {
return (e.data[0] * 10 > mapWidth-precE && e.data[1] > mapHeight*0.1 && e.data[1] < mapHeight*0.9);
});
frontier.map(function(i) {
var x = i.data[0], y = i.data[1];
var precipitation = precE;
while (x > mapWidth * 0.1 && precipitation > 0) {
x -= 10;
y += Math.floor(Math.random() * 20 - 10);
precipitation = rainfall(x, y, precipitation);
}
});
}
if (precS > 0) {
var frontier = $.grep(polygons, function(e) {
return (e.data[1] * 10 > mapHeight-precS && e.data[0] > mapWidth*0.1 && e.data[0] < mapWidth*0.9);
});
frontier.map(function(i) {
var x = i.data[0], y = i.data[1];
var precipitation = precS;
while (y > mapHeight * 0.1 && precipitation > 0) {
y -= 10;
x += Math.floor(Math.random() * 20 - 10);
precipitation = rainfall(x, y, precipitation);
}
});
}
if (precW > 0) {
var frontier = $.grep(polygons, function(e) {
return (e.data[0] * 10 < precW && e.data[1] > mapHeight*0.1 && e.data[1] < mapHeight*0.9);
});
frontier.map(function(i) {
var x = i.data[0], y = i.data[1];
var precipitation = precW;
while (x < mapWidth * 0.9 && precipitation > 0) {
x += 10;
y += Math.floor(Math.random() * 20 - 10);
precipitation = rainfall(x, y, precipitation);
}
});
}
// Smooth precipitation by taking average values of all neighbors
polygons.map(function(i) {
if (i.height >= 0.2) {
var prec = [i.precipitation];
i.neighbors.forEach(function(e) {
prec.push(polygons[e].precipitation);
});
var mean = d3.mean(prec);
if (i.precipitation < mean) {
i.precipitation = mean;
}
i.flux = i.precipitation;
if (i.precipitation < 0.01) {
i.precipitation = 0.01;
}
}
});
console.timeEnd("calculatePrecipitation");
}
function rainfall(x, y, precipitation) {
if (x < 0) {x = 0;}
if (y < 0) {y = 0;}
if (x >= mapWidth) {y = mapWidth-1;}
if (y >= mapHeight) {y = mapHeight-1;}
var height = pointsHeights[x + y * mapWidth];
if (height >= 0.2) {
var cell = pointsCells[y][x];
if (height < 0.6) {
var rain = Math.random() * height;
precipitation -= rain;
polygons[cell].precipitation += rain;
} else {
precipitation = 0;
polygons[cell].precipitation += precipitation;
}
}
return precipitation;
}
function unifyPrecipitation() {
polygons.map(function(i) {
if (i.height >= 0.2) {
i.flux = 0.1;
i.precipitation = 0.3;
}
});
}
function resolveDepressions() {
console.time('resolveDepressions');
land = $.grep(polygons, function(e) {
return (e.height >= 0.2);
});
var depression = 1, minCell, minHigh;
while (depression > 0) {
depression = 0;
for (var i = 0; i < land.length; i++) {
minHigh = 10;
land[i].neighbors.forEach(function(e) {
if (polygons[e].height < minHigh) {
minHigh = polygons[e].height;
minCell = e;
}
});
if (land[i].height <= polygons[minCell].height) {
depression += 1;
land[i].height = polygons[minCell].height + 0.01;
}
}
}
land.sort(compareHeight);
console.timeEnd('resolveDepressions');
flux();
}
function compareHeight(a, b) {
if (a.height < b.height) return 1;
if (a.height > b.height) return -1;
return 0;
}
function compareOrder(a, b) {
if (a.order < b.order) return 1;
if (a.order > b.order) return -1;
return 0;
}
function sortNumber(a, b) {
return a - b;
}
function flux() {
console.time('flux');
riversData = [];
var riversOrder = [], confluence = [];
var oposite, riverNext = 0;
for (var i = 0; i < land.length; i++) {
var pour = [], id = land[i].index;
var min, minHeight = 1;
diagram.cells[id].halfedges.forEach(function(e) {
var edge = diagram.edges[e];
var ea = edge.left.index;
if (ea === id || !ea) {
ea = edge.right.index;
}
if (ea) {
if (polygons[ea].height < minHeight) {
min = ea;
minHeight = polygons[ea].height;
}
// Define neighbour ocean cells for deltas
if (polygons[ea].height < 0.2) {
var xDiff = (edge[0][0] + edge[1][0]) / 2;
var yDiff = (edge[0][1] + edge[1][1]) / 2;
pour.push({x:xDiff, y:yDiff, cell:ea});
}
}
})
// Define river number
if (!land[i]) {console.log("- Land cell is undefined!")}
if (land[i].flux > 0.85) {
if (!land[i].river) {
// State new River
land[i].river = riverNext;
var rnd = Math.random() / 1000;
riversOrder.push({r: riverNext, order: rnd});
riversData.push({river: riverNext,
cell: id, x: land[i].data[0],
y: land[i].data[1], type: "source"});
riverNext += 1;
}
// Assing existing River to the downhill cell
if (!polygons[min].river) {
polygons[min].river = land[i].river;
} else {
var riverTo = polygons[min].river;
var iRiver = $.grep(riversData, function(e) {
return (e.river == land[i].river);
});
var minRiver = $.grep(riversData, function(e) {
return (e.river == riverTo);
});
var iRiverL = iRiver.length;
var minRiverL = minRiver.length;
// re-assing river nunber if new part is greater
if (iRiverL >= minRiverL) {
riversOrder[land[i].river].order += iRiverL;
polygons[min].river = land[i].river;
iRiverL += 1;
minRiverL -= 1;
} else {
if (!riversOrder[riverTo]) {
console.log("- Order Error!");
riversOrder[riverTo] = [];
riversOrder[riverTo].order = minRiverL;
} else {
riversOrder[riverTo].order += minRiverL;
}
}
// mark confluences
if (polygons[min].height >= 0.2 && iRiverL > 1 && minRiverL > 1) {
if (iRiverL >= minRiverL) {
confluence.push({id: min, s: id, l: iRiverL, r: land[i].river})
}
if (!polygons[min].confluence) {
polygons[min].confluence = 2;
var cellTo = minRiver[minRiverL-1].cell;
if (cellTo == min) {
cellTo = minRiver[minRiverL-2].cell;
}
confluence.push({id: min, s: cellTo, l: minRiverL-1, r: riverTo})
} else {
polygons[min].confluence += 1;
}
if (iRiverL < minRiverL) {
confluence.push({id: min, s: id, l: iRiverL, r: land[i].river})
}
}
}
}
polygons[min].flux += land[i].flux;
if (land[i].precipitation * 0.97> polygons[min].precipitation) {
polygons[min].precipitation = land[i].precipitation * 0.97
}
if (land[i].river) {
if (polygons[min].height < 0.2) {
// pour water to the Ocean
if (land[i].flux > 14 && pour.length > 1 && !land[i].confluence) {
// River Delta
for (var c = 0; c < pour.length; c++) {
if (c == 0) {
riversData.push({river: land[i].river,
cell: id, x: pour[0].x,
y: pour[0].y, type: "delta",
pour: pour[0].cell});
} else {
riversData.push({river: riverNext,
cell: id, x: land[i].data[0],
y: land[i].data[1], type: "course"});
riversData.push({river: riverNext,
cell: id, x: pour[c].x,
y: pour[c].y, type: "delta",
pour: pour[0].cell});
riverNext += 1;
}
}
} else {
// River Estuary
var x = pour[0].x+(pour[0].x-land[i].data[0])/10;
var y = pour[0].y+(pour[0].y-land[i].data[1])/10;
riversData.push({river: land[i].river,
cell: id, x: x, y: y,
type: "estuary", pour: pour[0].cell});
}
}
else {
// add next River segment
riversData.push({river: land[i].river,
cell: min, x: polygons[min].data[0],
y: polygons[min].data[1], type: "course"});
}
}
}
console.timeEnd('flux');
riversOrder.sort(compareOrder);
drawRiverLines(riversOrder, confluence);
}
function drawRiverLines(riversOrder, confluence) {
console.time('drawRiverLines');
var dataRiver, x, y, line, side = 1, confAngles = [];
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
for (var i = 0; i < riversOrder.length; i++) {
dataRiver = $.grep(riversData, function(e) {
return (e.river == riversOrder[i].r);
});
var order = riversOrder[i].r;
var riverAmended = [];
if (dataRiver.length > 1) {
if (dataRiver.length > 2 || dataRiver[1].type == "delta") {
// add more river points on 1/3 and 2/3 of length
for (var r = 0; r < dataRiver.length; r++) {
var dX = dataRiver[r].x;
var dY = dataRiver[r].y;
riverAmended.push({scX:dX, scY:dY});
if (r+1 < dataRiver.length) {
var eX = dataRiver[r+1].x;
var eY = dataRiver[r+1].y;
var angle = Math.atan2(eY - dY, eX - dX);
if (dataRiver[r+1].type == "course") {
var meandr = 0.4 + Math.random() * 0.3;
var stX = (dX * 2 + eX) / 3;
var stY = (dY * 2 + eY) / 3;
var enX = (dX + eX * 2) / 3;
var enY = (dY + eY * 2) / 3;
if (Math.random() > 0.5) {side *= -1};
stX += -Math.sin(angle) * meandr * side;
stY += Math.cos(angle) * meandr * side;
if (Math.random() > 0.6) {side *= -1};
enX += Math.sin(angle) * meandr * side;
enY += -Math.cos(angle) * meandr * side;
riverAmended.push({scX:stX, scY:stY});
riverAmended.push({scX:enX, scY:enY});
} else {
var meandr = 0.2 + Math.random() * 0.1;
var mX = (dX + eX) / 2;
var mY = (dY + eY) / 2;
mX += -Math.sin(angle) * meandr * side;
mY += Math.cos(angle) * meandr * side;
riverAmended.push({scX:mX, scY:mY});
}
}
}
}
var d = lineGen(riverAmended);
if (dataRiver[1].type == "delta") {
riversShade.append("path").attr("d", d).attr("stroke-width", 0.2);
rivers.append("path").attr("d", d).attr("stroke-width", 0.6);
} else {
var river = defs.append("path").attr("d", d);
var path = river.node().getPathData();
var count = 1, width = 0;
for (var s = 1; s < path.length; s++) {
var segment = "";
if (s == 1) {
var sX = path[0].values[0];
var sY = path[0].values[1];
} else {
var sX = path[s-1].values[4];
var sY = path[s-1].values[5];
}
var eX = path[s].values[4];
var eY = path[s].values[5];
var xn = eX, yn = eY;
var to = diagram.find(eX, eY, 0.01);
var riverWidth = (count + width * 3) / 50;
var curve = " C"+ path[s].values[0]+"," +path[s].values[1] + ", " + path[s].values[2]+"," +path[s].values[3];
count += 1;
if (to) {
if (polygons[to.index].confluence) {
var confData = $.grep(confluence, function(e) {
return (e.id == to.index);
});
if (s+1 !== path.length) {
var angle = Math.atan2(eY - path[s].values[3], eX - path[s].values[2]);
confAngles[to.index] = angle;
// Tributaries use Main Stem's angle and amended curve
var angle = confAngles[to.index];
var midX = (path[s].values[0] + path[s].values[2]) / 2;
var midY = (path[s].values[1] + path[s].values[3]) / 2;
var curve = " C"+ path[s].values[0]+"," +path[s].values[1] + ", " + midX + "," + midY;
if (angle == undefined) {
// if tributary rendered before main stem only
var angle = Math.atan2(eY - path[s].values[3], eX - path[s].values[2]);
}
}
var flux = polygons[to.index].flux;
count = 0, width = Math.pow(flux, 0.9);
var df = (width * 3 / 50 - riverWidth) / 2;
var c1 = confData[0].s;
var c2 = confData[1].s;
var bX = (polygons[c1].data[0] + polygons[c2].data[0])/2;
var bY = (polygons[c1].data[1] + polygons[c2].data[1])/2;
var xl = -Math.sin(angle) * df + eX;
var yl = Math.cos(angle) * df + eY;
var xr = Math.sin(angle) * df + eX;
var yr = -Math.cos(angle) * df + eY;
var cross = ((bX-eX)*(sY-eY) - (bY-eY)*(sX-eX));
if (cross > 0) {
xn = xr;
yn = yr;
} else {
xn = xl;
yn = yl;
}
}
}
segment += sX +","+sY + curve + "," + xn+"," +yn;
var shadowWidth = riverWidth/3;
if (shadowWidth < 0.1) {shadowWidth = 0.1;}
riversShade.append("path").attr("d", "M"+segment)
.attr("stroke-width", shadowWidth);
rivers.append("path").attr("d", "M"+segment)
.attr("stroke-width", riverWidth);
}
}
}
}
console.timeEnd('drawRiverLines');
}
function defineBiomes() {
console.time('defineBiomes');
var temperature, temp, prec;
pointsBiomes = [];
if (randomTemp.checked) {
var rand = Math.random();
if (rand > 0.5) {
temperature = Math.floor(Math.random() * 6) + 7;
} else if (rand > 0.2) {
temperature = Math.floor(Math.random() * 10) + 5;
} else {
temperature = Math.floor(Math.random() * 20);
}
temperatureInput.value = temperature;
temperatureOutput.value = temperature;
} else {
temperature = temperatureInput.value;
}
d3.range(0, mapHeight).forEach(function(y) {
d3.range(0, mapWidth).forEach(function(x) {
var height = pointsHeights[x + y * mapWidth];
if (height >= 0.2) {
var cell = pointsCells[y][x];
prec = getPrecipitation(polygons[cell].precipitation);
temp = Math.floor(polygons[cell].height * 100 - (temperature - 12) / 0.2);
if (temp > 99) {temp = 99;} // max value
if (temp < 0) {temp = 0;} // min value
pointsBiomes.push(biomIDs[prec][temp]);
} else {
pointsBiomes.push(-1);
}
});
});
polygons.map(function(i) {
if (i.height >= 0.2) {
var prec = getPrecipitation(i.precipitation);
var temp = Math.floor(i.height * 100 - (temperature - 12) / 0.2);
if (temp > 99) {temp = 99;} // max value
if (temp < 0) {temp = 0;} // min value
i.biom = biomIDs[prec][temp];
i.biomColor = biomGrad[prec][temp];
}
});
console.timeEnd('defineBiomes');
}
// genearal update on mousemove
function moved() {
var point = d3.mouse(this),
x = Math.floor(point[0]),
y = Math.floor(point[1]),
i = diagram.find(x, y).index;
if (journeyStep == 1 && $("#map_mode").attr("status") > 0) {
cursored.selectAll("path").transition().duration(50).style("opacity", 0).remove();
var d = "M" + polygons[i].join("L") + "Z";
cursored.append("path").attr("d", d).attr("stroke", "white").attr("stroke-width", 0.3);
cursored.append("path").attr("d", d).attr("stroke", "black").attr("stroke-width", 0.1);
}
$("#cell").text(i);
$("#heightPoint").text(pointsHeights[x + y * mapWidth]);
if (polygons[i].feature) {
$("#feature").text(polygons[i].feature);
} else {
$("#feature").text("n/a");
}
if (polygons[i].precipitation) {
var prec = Math.floor(polygons[i].precipitation * 500);
$("#precipitation").text(prec);
} else {
$("#precipitation").text("n/a");
var prec = "ocean";
}
if (polygons[i].biom) {
$("#biom").text(biomNames[polygons[i].biom]);
} else {
$("#biom").text("no");
}
var height = (polygons[i].height).toFixed(2);
$("#height").text(height);
if (height >= 0.2) {
var temp = Math.floor(temperatureInput.value - (polygons[i].height - 0.2) * 20);
$("#temperature").text(temp);
} else {
var temp = temperatureInput.value;
$("#temperature").text(temperatureInput.value);
}
if (height >= 0.2) {
var elev = Math.ceil((height-0.195) * 4000 * height);
} else if (height < 0.2 && height > 0) {
var elev = Math.ceil((height-0.2) * 70 / height);
} else {
var elev = "-1000";
}
$("#elevation").text(elev);
if (journeyStep > 0) {
if (height > 0) {
if (journeyStep === 1) {
var hint = "Height: " + height + " (" + elev + " m)";
} else if (journeyStep === 2) {
if (prec !== "ocean") {
var hint = "Height: " + elev + "m " + "Temperature: " + temp + "°C " + "Precipitation: " + prec + "cm";
} else {
var hint = "Height: " + elev + "m " + "Temperature: " + temp + "°C";
}
}
}
$("#hintbar").text(hint);
}
if (polygons[i].river) {
$("#river").text(polygons[i].river);
} else {
$("#river").text("no");
}
// cell_line highlighting
if (cell_line.getAttribute("start") != "") {
var next = +cell_line.getAttribute("start");
highlighted.selectAll("*").remove();
highlighting = [];
// from to i
while (next != i) {
var min = 1000;
polygons[next].neighbors.forEach(function(e) {
var x = polygons[i].data[0] - polygons[e].data[0];
var y = polygons[i].data[1] - polygons[e].data[1];
var diff = Math.hypot(x, y);
if (diff < min) {
min = diff;
next = e;
}
});
highlighting.push(next);
}
highlighting.map(function(h) {
highlighted.append("path")
.attr("d", "M" + polygons[h].join("L") + "Z");
});
}
}
function simulateWind(trend, winds) {
if ($("#main").length == 0) {
$("#canvasContainer").append("<canvas id='main' width='960' height='540'></canvas>");
$("#canvasContainer").append("<canvas class='off' width='960' height='540' style='display: none;'></canvas>");
}
cancelAnimationFrame(animation);
boids = []; // remove all elements
var init = new Vec2(Math.cos(trend) * 0.25, Math.sin(trend) * 0.25); // initial change
var persistence = init.clone().scale(persistenceInput.value); // persistence of change (1% of initial change)
var waterPolygons = $.grep(polygons, function(e) {return (e.height < 0.2);});
var canvas = document.getElementById("main"),
context = canvas.getContext("2d"),
offscreen = document.querySelector(".off"),
offscreenContext = offscreen.getContext("2d"),
elements = 800,
wave = 7,
maxVelocity = 2;
offscreenContext.globalAlpha = 0.95;
offscreenContext.clearRect(0, 0, mapWidth, mapHeight);
context.clearRect(0, 0, mapWidth, mapHeight);
animation = requestAnimationFrame(tick);
animated = true;
$("#map_wind_animation").click(function() {
if (animated) {
cancelAnimationFrame(animation);
animated = false;
} else {
animation = requestAnimationFrame(tick);
animated = true;
}
});
function tick() {
offscreenContext.clearRect(0, 0, mapWidth, mapHeight);
offscreenContext.drawImage(canvas, 0, 0, mapWidth, mapHeight);
context.clearRect(0, 0, mapWidth, mapHeight);
context.drawImage(offscreen, 0, 0, mapWidth, mapHeight);
var angleMod = angleInput.value;
if (elements - boids.length >= wave) {
if (boids.length > 200) {addInternalBlow(Math.floor(wave*0.2));}
addExternalBlows(Math.floor(wave*0.8));
}
boids.forEach(function(b, i){
var x = Math.ceil(b.pos.x);
var y = Math.ceil(b.pos.y);
var hCur = pointsCellHeights[x + y * mapWidth];
if (!hCur) {hCur = 0.2;}
var fPos = b.pos.clone().add(b.change);
var x = Math.ceil(fPos.x);
var y = Math.ceil(fPos.y);
var hNew = pointsHeights[x + y * mapWidth];
if (!hNew) {boids.splice(i, 1); return;}
var alignment = new Vec2();
var separation = new Vec2();
boids.forEach(function(b2){
if (b === b2) {return;}
var diff = b2.pos.clone().subtract(b.pos);
var distance = diff.length();
if (distance) {
if (distance < 5 && hNew < 0.2) {
boids.splice(i, 1); // remove element;
return;
}
if (distance < 5) {separation.add(diff.clone().scaleTo(-1 / distance));}
if (distance < 20) {alignment.add(b2.change.clone().scale(1 / distance));}
}
});
if (hCur >= 0.2 && hNew >= 0.2) {
var dif = (hCur - hNew) * 3;
var cliff = hNew - 0.25;
if (cliff < 0) {cliff = 0.01;}
var angle = dif * cliff * angleMod;
var lc = b.change.clone().turn(angle).scaleTo(10);
var rc = b.change.clone().turn(angle * -1).scaleTo(10);
var lp = b.pos.clone().add(lc);
var rp = b.pos.clone().add(rc);
var lh = pointsHeights[Math.ceil(lp.x) + Math.ceil(lp.y) * mapWidth];
var rh = pointsHeights[Math.ceil(rp.x) + Math.ceil(rp.y) * mapWidth];
if (lh < rh) {b.change.turn(angle);} else {b.change.turn(angle * -1);}
b.change.scale(0.8 + dif);
} else {
b.change.scale(1.01).scaleTo(maxVelocity);
}
b.change.add(alignment.scaleTo(alignmentInput.value));
b.change.add(separation.scale(separationInput.value));
b.change.add(persistence).truncate(maxVelocity);
b.pos.add(b.change);
if (b.change.length() > 0.1) {
context.beginPath();
context.fillStyle = $(".jscolor").css("background-color");
context.arc(b.pos.x, b.pos.y, 1, 1, Math.PI);
context.fill();
} else {
boids.splice(i, 1); // remove element;
return;
}
});
$("#info").text(boids.length);
animation = requestAnimationFrame(tick);
}
function addExternalBlows(count) {
for (var i = 0; i < count; i++) {
var x, y;
if (Math.random() > 0.5) {
if (winds === "North-East" || winds === "South-East") {x = mapWidth;}
if (winds === "North-West" || winds === "South-West") {x = 0;}
y = Math.floor(Math.random() * mapHeight);
} else {
if (winds === "South-West" || winds === "South-East") {y = mapHeight-1;}
if (winds === "North-West" || winds === "North-East") {y = 0;}
x = Math.floor(Math.random() * mapWidth);
}
if (winds === "North") {y = 0; x = Math.floor(Math.random() * mapWidth);}
if (winds === "South") {y = mapHeight-1; x = Math.floor(Math.random() * mapWidth);}
if (winds === "West") {x = 0; y = Math.floor(Math.random() * mapHeight);}
if (winds === "East") {x = mapWidth; y = Math.floor(Math.random() * mapHeight);}
boids.push({pos: new Vec2(x, y), change: init.clone()});
}
}
function addInternalBlow(count) {
for (var i = 0; i < count; i++) {
var p = Math.floor(Math.random() * waterPolygons.length);
var x = waterPolygons[p].data[0];
var y = waterPolygons[p].data[1];
boids.push({pos: new Vec2(x, y), change: init.clone()});
}
}
}
// Based on https://www.jasondavies.com/poisson-disc
function poissonDiscSampler(width, height, radius) {
var k = 5, // maximum number of samples before rejection
radius2 = radius * radius,
R = 3 * radius2,
cellSize = radius * Math.SQRT1_2,
gridWidth = Math.ceil(width / cellSize),
gridHeight = Math.ceil(height / cellSize),
grid = new Array(gridWidth * gridHeight),
queue = [],
queueSize = 0,
sampleSize = 0;
return function() {
if (!sampleSize) return sample(Math.random() * width, Math.random() * height);
// Pick a random existing sample and remove it from the queue
while (queueSize) {
var i = Math.random() * queueSize | 0,
s = queue[i];
// Make a new candidate between [radius, 2 * radius] from the existing sample.
for (var j = 0; j < k; ++j) {
var a = 2 * Math.PI * Math.random(),
r = Math.sqrt(Math.random() * R + radius2),
x = s[0] + r * Math.cos(a),
y = s[1] + r * Math.sin(a);
// Reject candidates that are outside the allowed extent, or closer than 2 * radius to any existing sample
if (0 <= x && x < width && 0 <= y && y < height && far(x, y)) return sample(x, y);
}
queue[i] = queue[--queueSize];
queue.length = queueSize;
}
};
function far(x, y) {
var i = x / cellSize | 0,
j = y / cellSize | 0,
i0 = Math.max(i - 2, 0),
j0 = Math.max(j - 2, 0),
i1 = Math.min(i + 3, gridWidth),
j1 = Math.min(j + 3, gridHeight);
for (j = j0; j < j1; ++j) {
var o = j * gridWidth;
for (i = i0; i < i1; ++i) {
if (s = grid[o + i]) {
var s,
dx = s[0] - x,
dy = s[1] - y;
if (dx * dx + dy * dy < radius2) return false;
}
}
}
return true;
}
function sample(x, y) {
var s = [x, y];
queue.push(s);
grid[gridWidth * (y / cellSize | 0) + (x / cellSize | 0)] = s;
++sampleSize;
++queueSize;
return s;
}
}
// Hotkeys
d3.select("body").on("keydown", function() {
switch(d3.event.keyCode) {
case 27: // Escape for back
back();
break;
case 13: // Enter for next
next();
break;
case 32: // Space for next
next();
break;
case 37: // Left
if (viewX + 10 <= 0) {
viewX += 10;
zoomUpdate();
}
break;
case 39: // Right
if (viewX - 10 >= (mapWidth * (scale-1) * -1)) {
viewX -= 10;
zoomUpdate();
}
break;
case 38: // Up
if (viewY + 10 <= 0) {
viewY += 10;
zoomUpdate();
}
break;
case 40: // Down
if (viewY - 10 >= (mapHeight * (scale-1) * -1)) {
viewY -= 10;
zoomUpdate();
}
break;
case 107: // Plus
if (scale < 40) {
var dx = mapWidth / 2 * (scale-1) + viewX;
var dy = mapHeight / 2 * (scale-1) + viewY;
viewX = dx - mapWidth / 2 * scale;
viewY = dy - mapHeight / 2 * scale;
scale += 1;
if (scale > 40) {scale = 40;}
zoomUpdate();
}
break;
case 109: // Minus
if (scale > 1) {
var dx = mapWidth / 2 * (scale-1) + viewX;
var dy = mapHeight / 2 * (scale-1) + viewY;
viewX += mapWidth / 2 - dx;
viewY += mapHeight / 2 - dy;
scale -= 1;
if (scale < 1) {
scale = 1;
viewX = 0;
viewY = 0;
}
zoomUpdate();
}
break;
}
});
// Downoad map in SVG
d3.select("#download").on("click", function() {
var config = {filename: 'map_dowloaded',}
d3_save_svg.save(d3.select('svg').node(), config);
svg.selectAll("*").attr("style", null);
});
}