mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-16 17:31:24 +01:00
2639 lines
No EOL
105 KiB
JavaScript
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);
|
|
});
|
|
} |